├── spec ├── fixtures │ ├── .digicertrc │ ├── rsa4096.csr │ └── rsa4096.key ├── support │ ├── disable-logging.rb │ └── console_helper.rb ├── digicert │ ├── cli_spec.rb │ └── cli │ │ ├── rcfile_spec.rb │ │ ├── order_retriever_spec.rb │ │ ├── filter_builder_spec.rb │ │ ├── certificate_downloader_spec.rb │ │ ├── order_spec.rb │ │ ├── csr_spec.rb │ │ ├── order_creator_spec.rb │ │ ├── order_reissuer_spec.rb │ │ ├── order_duplicator_spec.rb │ │ └── certificate_spec.rb ├── acceptance │ ├── config_spec.rb │ ├── duplicating_order_spec.rb │ ├── reissuing_order_spec.rb │ ├── csr_spec.rb │ ├── order_spec.rb │ └── certificate_spec.rb └── spec_helper.rb ├── .hound.yml ├── Rakefile ├── bin ├── setup ├── rspec └── digicert ├── Gemfile ├── .gitignore ├── lib └── digicert │ ├── cli │ ├── auth.rb │ ├── commands │ │ ├── config.rb │ │ ├── csr.rb │ │ ├── certificate.rb │ │ └── order.rb │ ├── base.rb │ ├── command.rb │ ├── util.rb │ ├── rcfile.rb │ ├── version.rb │ ├── order_retriever.rb │ ├── order.rb │ ├── filter_builder.rb │ ├── certificate_downloader.rb │ ├── csr.rb │ ├── order_creator.rb │ ├── order_duplicator.rb │ ├── order_reissuer.rb │ └── certificate.rb │ └── cli.rb ├── .rubocop.yml ├── .github └── workflows │ └── test.yml ├── CHANGELOG.md ├── LICENSE.txt ├── digicert-cli.gemspec └── README.md /spec/fixtures/.digicertrc: -------------------------------------------------------------------------------- 1 | --- 2 | :api_key: super_secret_key 3 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | Enabled: true 3 | config_file: .rubocop.yml 4 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rspec/core/rake_task" 3 | 4 | RSpec::Core::RakeTask.new(:spec) 5 | 6 | task :default => :spec 7 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in digicert-cli.gemspec 4 | gemspec 5 | 6 | # Temporary until there is a new release 7 | # gem "digicert", github: "riboseinc/digicert", ref: "9d2915d" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | source_credentials.sh 2 | .DS_Store 3 | /.bundle/ 4 | /.yardoc 5 | /.pryrc 6 | /Gemfile.lock 7 | /_yardoc/ 8 | /coverage/ 9 | /doc/ 10 | /pkg/ 11 | /spec/reports/ 12 | /tmp/ 13 | 14 | # rspec failure tracking 15 | .rspec_status 16 | -------------------------------------------------------------------------------- /lib/digicert/cli/auth.rb: -------------------------------------------------------------------------------- 1 | require "digicert" 2 | require "digicert/cli/rcfile" 3 | 4 | unless Digicert.configuration.api_key 5 | Digicert.configure do |config| 6 | config.api_key = Digicert::CLI::RCFile.api_key || ENV["DIGICERT_API_KEY"] 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Auto-generated by Cimas: Do not edit it manually! 2 | # See https://github.com/metanorma/cimas 3 | inherit_from: 4 | - https://raw.githubusercontent.com/riboseinc/oss-guides/master/ci/rubocop.yml 5 | 6 | # local repo-specific modifications 7 | # ... 8 | 9 | AllCops: 10 | TargetRubyVersion: 2.5 11 | -------------------------------------------------------------------------------- /spec/support/disable-logging.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | module Util 4 | # In the test console, printing process related messages creates 5 | # some unnecessary noices, this module method will temporarily 6 | # disabled those extra noices from the log. 7 | # 8 | def self.say(_message) 9 | end 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/digicert/cli/commands/config.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/rcfile" 2 | 3 | module Digicert 4 | module CLI 5 | module Commands 6 | class Config < Thor 7 | desc "api-key API_KEY", "Configure Your Digicert API Key" 8 | def api_key(api_key) 9 | Digicert::CLI::RCFile.set_key(api_key) 10 | end 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/digicert/cli/base.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class Base 4 | attr_reader :order_id, :options 5 | 6 | def initialize(attributes = {}) 7 | @options = attributes 8 | @order_id = options.delete(:order_id) 9 | 10 | extract_local_attributes(options) 11 | end 12 | 13 | private 14 | 15 | def extract_local_attributes(options) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | # 4 | # This file was generated by Bundler. 5 | # 6 | # The application 'rspec' is installed as part of a gem, and 7 | # this file is here to facilitate running it. 8 | # 9 | 10 | require "pathname" 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", 12 | Pathname.new(__FILE__).realpath) 13 | 14 | require "rubygems" 15 | require "bundler/setup" 16 | 17 | load Gem.bin_path("rspec-core", "rspec") 18 | -------------------------------------------------------------------------------- /spec/digicert/cli_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI do 4 | describe ".start" do 5 | it "sends start message to command handler" do 6 | shell_command = %w(order find -n order_id) 7 | allow(Digicert::CLI::Command).to receive(:start) 8 | 9 | Digicert::CLI.start(shell_command) 10 | 11 | expect( 12 | Digicert::CLI::Command, 13 | ).to have_received(:start).with(shell_command) 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/acceptance/config_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Config" do 4 | describe "configuring key" do 5 | it "stores the provided api key" do 6 | command = %w(config api-key DIGICERT_SECRET_KEY) 7 | 8 | allow(Digicert::CLI::RCFile).to receive(:set_key) 9 | _output = capture_stdout { Digicert::CLI.start(command) } 10 | 11 | expect( 12 | Digicert::CLI::RCFile, 13 | ).to have_received(:set_key).with("DIGICERT_SECRET_KEY") 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /bin/digicert: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # encoding: UTF-8 3 | 4 | # resolve bin path, ignoring symlinks 5 | require "pathname" 6 | bin_file = Pathname.new(__FILE__).realpath 7 | 8 | # add self to libpath 9 | $:.unshift File.expand_path("../../lib", bin_file) 10 | 11 | # Fixes https://github.com/rubygems/rubygems/issues/1420 12 | require "rubygems/specification" 13 | 14 | class Gem::Specification 15 | def this; self; end 16 | end 17 | 18 | # start up the CLI 19 | require "digicert/cli" 20 | 21 | Digicert::CLI.start(ARGV) 22 | -------------------------------------------------------------------------------- /spec/digicert/cli/rcfile_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::RCFile do 4 | describe ".set_key" do 5 | it "writes the api key to the configuration file" do 6 | secret_api_key = "super_secret_key" 7 | allow(File).to receive(:expand_path).and_return(fixtures_path) 8 | 9 | Digicert::CLI::RCFile.set_key(secret_api_key) 10 | 11 | expect(Digicert::CLI::RCFile.api_key).to eq(secret_api_key) 12 | end 13 | end 14 | 15 | def fixtures_path 16 | File.expand_path("../../../fixtures", __FILE__) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/console_helper.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module ConsoleHelper 3 | def capture_stdout(&_block) 4 | original_stdout = $stdout 5 | $stdout = fake = StringIO.new 6 | 7 | begin 8 | yield 9 | ensure 10 | $stdout = original_stdout 11 | end 12 | 13 | fake.string 14 | end 15 | 16 | def capture_stderr(&_block) 17 | original_stderr = $stderr 18 | $stderr = fake = StringIO.new 19 | 20 | begin 21 | yield 22 | ensure 23 | $stderr = original_stderr 24 | end 25 | 26 | fake.string 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ v* ] 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | name: RSpec 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | ruby: ["2.6", "2.7", "3.0", "3.1"] 17 | os: [ ubuntu-latest, windows-latest, macos-latest ] 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | 22 | - uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: ${{ matrix.ruby }} 25 | bundler-cache: true 26 | 27 | - run: bundle exec rake 28 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "webmock/rspec" 2 | require "digicert/cli" 3 | require "digicert/rspec" 4 | 5 | Dir["./spec/support/**/*.rb"].sort.each { |file| require file } 6 | 7 | RSpec.configure do |config| 8 | # Enable flags like --only-failures and --next-failure 9 | config.example_status_persistence_file_path = ".rspec_status" 10 | config.include Digicert::ConsoleHelper 11 | 12 | config.expect_with :rspec do |c| 13 | c.syntax = :expect 14 | end 15 | 16 | config.before :all do 17 | Digicert.configure do |digicert_config| 18 | digicert_config.api_key = ENV["DIGICERT_API_KEY"] || "SECRET_KEY" 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.2 (2020-03-03) 2 | 3 | * Fix certificate download related issue 4 | 5 | ## 0.5.1 (2019-06-29) 6 | 7 | * Fix invalid table width argument issue 8 | * Gracefully handle invalid API requests 9 | 10 | ## 0.5.0 (2019-03-14) 11 | 12 | * Change CSR's Order Id to be optional 13 | * Add `--debug` flag with CLI commands 14 | * Add order duplication support in CLI 15 | * Add `order create` interface in the gem 16 | 17 | ## 0.4.1 (2018-07-18) 18 | 19 | * Upgrade `digicert` gem to latest version 20 | 21 | ## 0.4.0 (2017-12-11) 22 | 23 | * Upgrade `digicert` gem with `rubocop` config 24 | 25 | ## 0.1.0 - 0.3.0 (2017-10-16) 26 | 27 | * Initial fully functional release 28 | -------------------------------------------------------------------------------- /lib/digicert/cli/command.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/commands/csr" 2 | require "digicert/cli/commands/order" 3 | require "digicert/cli/commands/config" 4 | require "digicert/cli/commands/certificate" 5 | 6 | module Digicert 7 | module CLI 8 | class Command < Thor 9 | desc "csr", "Fetch/generate Certificate CSR" 10 | subcommand :csr, Digicert::CLI::Commands::Csr 11 | 12 | desc "order", "Manage Digicert Orders" 13 | subcommand :order, Digicert::CLI::Commands::Order 14 | 15 | desc "certificate", "Manage Digicert Certificates" 16 | subcommand :certificate, Digicert::CLI::Commands::Certificate 17 | 18 | desc "config", "Configure The CLI Client" 19 | subcommand :config, Digicert::CLI::Commands::Config 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/digicert/cli/order_retriever_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "digicert/cli/order_retriever" 3 | 4 | RSpec.describe Digicert::CLI::OrderRetriever do 5 | describe ".fetch" do 6 | context "with number_of_times option specfied" do 7 | it "tries to retrieve the order by specfied number of times" do 8 | allow(Digicert::Order).to receive(:fetch).and_return(order) 9 | 10 | Digicert::CLI::OrderRetriever.fetch( 11 | order.id, number_of_times: 2, wait_time: 1 12 | ) 13 | 14 | expect(Digicert::Order).to have_received(:fetch).twice 15 | end 16 | end 17 | end 18 | 19 | def order(order_id = 123_456) 20 | stub_digicert_order_fetch_api(order_id) 21 | @order ||= Digicert::Order.fetch(order_id) 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/acceptance/duplicating_order_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Order duplicating" do 4 | describe "duplicate an order" do 5 | context "duplicate with new csr" do 6 | it "duplicate an order with provided csr" do 7 | mock_digicert_order_duplication_message_chain 8 | command = %w(order duplicate 123456 --csr ./spec/fixtures/rsa4096.csr) 9 | 10 | _output = capture_stdout { Digicert::CLI.start(command) } 11 | 12 | expect(Digicert::CLI::OrderDuplicator).to have_received(:new). 13 | with(order_id: "123456", csr: "./spec/fixtures/rsa4096.csr") 14 | end 15 | end 16 | end 17 | 18 | def mock_digicert_order_duplication_message_chain 19 | allow(Digicert::CLI::OrderDuplicator). 20 | to receive_message_chain(:new, :create) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/digicert/cli/util.rb: -------------------------------------------------------------------------------- 1 | require "digicert" 2 | require "terminal-table" 3 | 4 | module Digicert 5 | module CLI 6 | module Util 7 | def self.make_it_pretty(headings:, rows:) 8 | Terminal::Table.new do |table| 9 | table.headings = headings 10 | table.rows = rows 11 | end 12 | end 13 | 14 | def self.say(message, color = nil) 15 | Thor::Shell::Basic.new.say(message, color) 16 | end 17 | 18 | def self.run(arguments) 19 | if arguments.include?("--debug") 20 | arguments.delete("--debug") 21 | 22 | Digicert.configuration.debug_mode = true 23 | Digicert::CLI::Command.start(arguments) 24 | Digicert.configuration.debug_mode = true 25 | else 26 | 27 | Digicert::CLI::Command.start(arguments) 28 | end 29 | end 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /spec/digicert/cli/filter_builder_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::FilterBuilder do 4 | describe ".build" do 5 | context "normal filter options" do 6 | it "builds filters only with valid attributes" do 7 | options = { status: "pending", invalid: "invalid", search: "" } 8 | filters = Digicert::CLI::FilterBuilder.build(options) 9 | 10 | expect(filters).to eq("status" => "pending") 11 | end 12 | end 13 | 14 | context "filter values as an array" do 15 | it "builds the valid filters properly" do 16 | options = { status: "pending,active", search: "s" } 17 | filters = Digicert::CLI::FilterBuilder.build(options) 18 | 19 | expect(filters).to eq( 20 | "status" => { "0" => "pending", "1" => "active" }, "search" => "s", 21 | ) 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/digicert/cli/certificate_downloader_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::CertificateDownloader do 4 | describe ".download" do 5 | it "downloads the certificate to the specified path" do 6 | certificate_id = 123_456_789 7 | allow(File).to receive(:open) 8 | mock_digicert_certificate_download_api(certificate_id) 9 | 10 | Digicert::CLI::CertificateDownloader.download( 11 | print_enable: false, 12 | path: download_path, 13 | certificate_id: certificate_id, 14 | ) 15 | 16 | expect(File).to have_received(:open).thrice 17 | end 18 | end 19 | 20 | def download_path 21 | File.expand_path("../../tmp", __FILE__).to_s 22 | end 23 | 24 | def mock_digicert_certificate_download_api(certificate_id) 25 | stub_digicert_certificate_download_by_format( 26 | certificate_id, "pem_all", "pem" 27 | ) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/digicert/cli/commands/csr.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/csr" 2 | 3 | module Digicert 4 | module CLI 5 | module Commands 6 | class Csr < Thor 7 | class_option :debug, type: "boolean", desc: "Enable debug mode" 8 | 9 | desc "fetch ORDER_ID", "Fetch an existing CSR" 10 | def fetch(order_id) 11 | say(csr_instance(order_id: order_id).fetch) 12 | end 13 | 14 | desc "generate", "Generate certificate CSR" 15 | option :order_id, aliases: "-o", desc: "An Order ID" 16 | option :common_name, aliases: "-c", desc: "The common name" 17 | option :organization_id, desc: "Your digicert's organization ID" 18 | option :san, type: :array, desc: "The subject alternative names" 19 | option :key, aliases: "-k", desc: "Complete path to the rsa key file" 20 | 21 | def generate 22 | say(csr_instance.generate) 23 | end 24 | 25 | private 26 | 27 | def csr_instance(id_attribute = {}) 28 | Digicert::CLI::CSR.new(options.merge(id_attribute)) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Ribose Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/digicert/cli/rcfile.rb: -------------------------------------------------------------------------------- 1 | require "yaml" 2 | require "singleton" 3 | 4 | module Digicert 5 | module CLI 6 | class RCFile 7 | include Singleton 8 | attr_reader :path, :data 9 | FILE_NAME = ".digicertrc".freeze 10 | 11 | def initialize 12 | @path = File.join(File.expand_path("~"), FILE_NAME) 13 | @data = load_configuration 14 | end 15 | 16 | def set_key(api_key) 17 | data[:api_key] = api_key 18 | write_api_key_to_file 19 | end 20 | 21 | def self.api_key 22 | new.data[:api_key] 23 | end 24 | 25 | def self.set_key(api_key) 26 | new.set_key(api_key) 27 | end 28 | 29 | private 30 | 31 | def load_configuration 32 | YAML.load_file(path) 33 | rescue Errno::ENOENT 34 | default_configuration 35 | end 36 | 37 | def default_configuration 38 | { api_key: nil } 39 | end 40 | 41 | def write_api_key_to_file 42 | File.open(path, File::RDWR | File::TRUNC | File::CREAT, 0o0600) do |rc| 43 | rc.write(data.to_yaml) 44 | end 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/digicert/cli/version.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Copyright (c) 2018 Ribose Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | # ++ 22 | 23 | module Digicert 24 | module CLI 25 | VERSION = "1.0.0".freeze 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/acceptance/reissuing_order_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Order reissuing" do 4 | describe "reissue an order" do 5 | context "reissue with new csr" do 6 | it "reissues an order with the provided csr" do 7 | mock_digicert_order_reissuer_create_message_chain 8 | command = %w(order reissue 123456 --csr ./spec/fixtures/rsa4096.csr) 9 | 10 | _output = capture_stdout { Digicert::CLI.start(command) } 11 | 12 | expect(Digicert::CLI::OrderReissuer).to have_received(:new). 13 | with(order_id: "123456", csr: "./spec/fixtures/rsa4096.csr") 14 | end 15 | end 16 | 17 | context "reissue and download certificate" do 18 | it "reissues an order and download the certificate" do 19 | mock_digicert_order_reissuer_create_message_chain 20 | command = %w(order reissue 123456 --output /tmp/downloads) 21 | 22 | _output = capture_stdout { Digicert::CLI.start(command) } 23 | 24 | expect(Digicert::CLI::OrderReissuer).to have_received(:new). 25 | with(order_id: "123456", output: "/tmp/downloads") 26 | end 27 | end 28 | end 29 | 30 | def mock_digicert_order_reissuer_create_message_chain 31 | allow(Digicert::CLI::OrderReissuer).to receive_message_chain(:new, :create) 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /spec/digicert/cli/order_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::Order do 4 | describe "#list" do 5 | context "without order_id attributes" do 6 | it "sends all message to digicert order interface" do 7 | allow(Digicert::Order).to receive(:all).and_return([]) 8 | 9 | order = Digicert::CLI::Order.new 10 | order.list 11 | 12 | expect(Digicert::Order).to have_received(:all) 13 | end 14 | end 15 | end 16 | 17 | describe "#find" do 18 | context "without any option" do 19 | it "returns the first filtered order from digicert" do 20 | common_name = "digicert.com" 21 | stub_digicert_order_list_api 22 | 23 | order = Digicert::CLI::Order.new(common_name: common_name).find 24 | 25 | expect(order.id).not_to be_nil 26 | expect(order.certificate.common_name).to eq(common_name) 27 | end 28 | end 29 | 30 | context "with quiet option" do 31 | it "only returns the id from the order" do 32 | common_name = "digicert.com" 33 | stub_digicert_order_list_api 34 | 35 | order_id = Digicert::CLI::Order.new( 36 | common_name: common_name, quiet: true, 37 | ).find 38 | 39 | expect(order_id).to be_a(Integer) 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/digicert/cli/order_retriever.rb: -------------------------------------------------------------------------------- 1 | require "date" 2 | 3 | module Digicert 4 | module CLI 5 | class OrderRetriever 6 | def initialize(order_id, attributes) 7 | @order_id = order_id 8 | @wait_time = attributes[:wait_time] || 10 9 | @number_of_times = attributes[:number_of_times] || 5 10 | end 11 | 12 | def fetch 13 | fetch_order_in_interval 14 | reissued_order 15 | end 16 | 17 | def self.fetch(order_id, attributes) 18 | new(order_id, **attributes).fetch 19 | end 20 | 21 | private 22 | 23 | attr_reader :order_id, :number_of_times, :wait_time, :reissued_order 24 | 25 | def fetch_order_in_interval 26 | number_of_times.to_i.times do |number| 27 | sleep wait_time.to_i 28 | say("Fetch attempt #{number + 1}..") 29 | order = Digicert::Order.fetch(order_id) 30 | 31 | if recently_reissued?(order.last_reissued_date) 32 | break @reissued_order = order 33 | end 34 | end 35 | end 36 | 37 | def recently_reissued?(datetime) 38 | if datetime 39 | ((Time.now - DateTime.parse(datetime).to_time) / 60).ceil < 3 40 | end 41 | end 42 | 43 | def say(message) 44 | Digicert::CLI::Util.say(message) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /digicert-cli.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path("../lib", __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require "digicert/cli/version" 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "digicert-cli" 8 | spec.version = Digicert::CLI::VERSION 9 | spec.authors = ["Ribose Inc."] 10 | spec.email = ["operations@ribose.com"] 11 | 12 | spec.summary = %q{The CLI for digicert API} 13 | spec.description = %q{The CLI for digicert API} 14 | spec.homepage = "https://www.ribose.com" 15 | 16 | spec.files = `git ls-files`.split("\n") 17 | spec.test_files = `git ls-files -- {spec}/*`.split("\n") 18 | spec.required_ruby_version = Gem::Requirement.new(">= 2.6") 19 | 20 | spec.require_paths = ["lib"] 21 | spec.bindir = "bin" 22 | spec.executables = "digicert" 23 | 24 | spec.add_dependency "thor", "~> 0.19.4" 25 | spec.add_dependency "digicert", "~> 1.0" 26 | spec.add_dependency "openssl", ">= 2.0.3" 27 | spec.add_dependency "terminal-table" 28 | 29 | spec.add_development_dependency "bundler", "~> 2.1" 30 | spec.add_development_dependency "rake", "~> 10.0" 31 | spec.add_development_dependency "rspec", "~> 3.0" 32 | spec.add_development_dependency "webmock", "~> 2.0" 33 | spec.add_development_dependency "pry", "~> 0.11.3" 34 | end 35 | -------------------------------------------------------------------------------- /lib/digicert/cli/order.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/filter_builder" 2 | 3 | module Digicert 4 | module CLI 5 | class Order < Digicert::CLI::Base 6 | def list 7 | display_orders_in_table(orders) 8 | end 9 | 10 | def find 11 | apply_ouput_flag(orders.first) 12 | end 13 | 14 | private 15 | 16 | def orders 17 | @orders ||= order_api.all(filter_options) 18 | end 19 | 20 | def order_api 21 | Digicert::Order 22 | end 23 | 24 | def filter_options 25 | if options[:filter] 26 | { filters: Digicert::CLI::FilterBuilder.build(options[:filter]) } 27 | end 28 | end 29 | 30 | def apply_ouput_flag(order) 31 | options[:quiet] ? order.id : order 32 | end 33 | 34 | def display_orders_in_table(orders) 35 | orders_attribtues = orders.map do |order| 36 | [ 37 | order.id, 38 | order.product_name_id, 39 | order.certificate.common_name, 40 | order.status, 41 | order.certificate.valid_till, 42 | ] 43 | end 44 | 45 | Digicert::CLI::Util.make_it_pretty( 46 | rows: orders_attribtues, 47 | headings: ["Id", "Product Type", "Common Name", "Status", "Expiry"], 48 | ) 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/digicert/cli/filter_builder.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class FilterBuilder 4 | attr_reader :options, :filters_hash 5 | 6 | def initialize(options) 7 | @options = options 8 | @filters_hash = {} 9 | end 10 | 11 | def build 12 | build_filters 13 | filters_hash 14 | end 15 | 16 | def self.build(options) 17 | new(options).build 18 | end 19 | 20 | private 21 | 22 | def build_filters 23 | options.each do |key, value| 24 | add_to_filters(key.to_s, value) 25 | end 26 | end 27 | 28 | def add_to_filters(key, value) 29 | if supported_filters.include?(key) && !value.empty? 30 | @filters_hash[key] = prepare_filter_value(value) 31 | end 32 | end 33 | 34 | def prepare_filter_value(value) 35 | values = value.split(",") 36 | build_nested_values(values) || values.first 37 | end 38 | 39 | def build_nested_values(values) 40 | if values.length > 1 41 | Hash.new.tap do |value_hash| 42 | values.length.times do |num| 43 | value_hash[num.to_s] = values[num].strip 44 | end 45 | end 46 | end 47 | end 48 | 49 | def supported_filters 50 | %w(date_created valid_till status search common_name product_name_id) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/digicert/cli/certificate_downloader.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class CertificateDownloader 4 | def initialize(certificate_id:, path:, **options) 5 | @path = path 6 | @options = options 7 | @certificate_id = certificate_id 8 | @filename = options.fetch(:filename, certificate_id) 9 | end 10 | 11 | def download 12 | if certificate_contents 13 | write_certificate_contents(certificate_contents) 14 | end 15 | end 16 | 17 | def self.download(attributes) 18 | new(**attributes).download 19 | end 20 | 21 | private 22 | 23 | attr_reader :certificate_id, :path, :filename, :options 24 | 25 | def certificate_contents 26 | @certificate_contents ||= 27 | Digicert::CertificateDownloader.fetch_content(certificate_id) 28 | end 29 | 30 | def write_certificate_contents(contents) 31 | say("Downloaded certificate to:") 32 | 33 | write_to_path("root", contents[:root_certificate]) 34 | write_to_path("certificate", contents[:certificate]) 35 | write_to_path("intermediate", contents[:intermediate_certificate]) 36 | end 37 | 38 | def write_to_path(key, content) 39 | certificate_name = [filename, key, "crt"].join(".") 40 | certificate_path = [path, certificate_name].join("/") 41 | 42 | say(certificate_path) 43 | File.open(certificate_path, "w") { |file| file.write(content) } 44 | end 45 | 46 | def say(message) 47 | Digicert::CLI::Util.say(message) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/digicert/cli/csr.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class CSR < Digicert::CLI::Base 4 | def fetch 5 | if order 6 | order.certificate.csr 7 | end 8 | end 9 | 10 | def generate 11 | if order || valid_data? 12 | generate_new_csr 13 | end 14 | end 15 | 16 | private 17 | 18 | attr_reader :rsa_key, :organization_id, :common_name 19 | 20 | def extract_local_attributes(options) 21 | @rsa_key = options.fetch(:key, nil) 22 | @common_name = options.fetch(:common_name, nil) 23 | @organization_id = options.fetch(:organization_id, nil) 24 | end 25 | 26 | def valid_data? 27 | !organization.nil? && !options[:common_name].nil? 28 | end 29 | 30 | def order 31 | if order_id 32 | @order ||= Digicert::Order.fetch(order_id) 33 | end 34 | end 35 | 36 | def organization 37 | if organization_id 38 | @organization ||= Digicert::Organization.fetch(organization_id) 39 | end 40 | end 41 | 42 | def generate_new_csr 43 | if rsa_key && File.exists?(rsa_key) 44 | Digicert::CSRGenerator.generate(csr_attributes(order)) 45 | end 46 | end 47 | 48 | def csr_attributes(order) 49 | Hash.new.tap do |csr| 50 | csr[:rsa_key] = File.read(rsa_key) 51 | csr[:organization] = organization || order.organization 52 | csr[:common_name] = common_name || order.certificate.common_name 53 | 54 | if options[:san] 55 | csr[:san_names] = options[:san] 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /spec/digicert/cli/csr_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::CSR do 4 | describe "#fetch" do 5 | it "fetches the csr for an existing order" do 6 | order_id = 123456 7 | stub_digicert_order_fetch_api(order_id) 8 | 9 | csr = Digicert::CLI::CSR.new(order_id: order_id).fetch 10 | 11 | expect(csr).not_to be_nil 12 | expect(csr).to eq("------ [CSR HERE] ------") 13 | end 14 | end 15 | 16 | describe "#generate" do 17 | context "with existing order details" do 18 | it "generates a new csr for an existing order" do 19 | order_id = 123456 20 | key_file = "./spec/fixtures/rsa4096.key" 21 | stub_digicert_order_fetch_api(order_id) 22 | 23 | csr = Digicert::CLI::CSR.new(order_id: order_id, key: key_file).generate 24 | 25 | expect(csr.start_with?("-----BEGIN CERTIFICATE REQUEST")).to be_truthy 26 | expect(csr.end_with?("--END CERTIFICATE REQUEST-----\n")).to be_truthy 27 | end 28 | end 29 | 30 | context "with custom details" do 31 | it "generates a new csr using the provided details" do 32 | organization_id = 123456 33 | common_name = "ribosetest.com" 34 | key_file = "./spec/fixtures/rsa4096.key" 35 | san = ["site1.ribosetest.com", "site2.ribosetest.com"] 36 | stub_digicert_organization_fetch_api(organization_id) 37 | 38 | csr = Digicert::CLI::CSR.new( 39 | san: san, 40 | key: key_file, 41 | common_name: common_name, 42 | organization_id: organization_id, 43 | ).generate 44 | 45 | expect(csr.start_with?("-----BEGIN CERTIFICATE REQUEST")).to be_truthy 46 | expect(csr.end_with?("--END CERTIFICATE REQUEST-----\n")).to be_truthy 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/digicert/cli/commands/certificate.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/certificate" 2 | 3 | module Digicert 4 | module CLI 5 | module Commands 6 | class Certificate < Thor 7 | class_option :debug, type: "boolean", desc: "Enable debug mode" 8 | 9 | desc "fetch ORDER_ID", "Find an order's certificate" 10 | option :quiet, type: :boolean, aliases: "-q", desc: "Retrieve only id" 11 | option :output, aliases: "-o", desc: "Path to download the certificate" 12 | 13 | def fetch(order_id) 14 | say(certificate_instance(order_id: order_id).fetch) 15 | end 16 | 17 | desc "download [RSOURCE_OPTION]", "Download a certificate" 18 | option :order_id, aliases: "-i", desc: "Digicert order ID" 19 | option :certificate_id, aliases: "-c", desc: "The certificate ID" 20 | 21 | option( 22 | :output, 23 | aliases: "-o", 24 | default: Dir.pwd, 25 | desc: "Path to download the certificate", 26 | ) 27 | 28 | def download 29 | required_option_exists? || say(certificate_instance.download) 30 | rescue Digicert::Errors::RequestError 31 | say("Invalid Resource ID") 32 | end 33 | 34 | desc "duplicates ORDER_ID", "List duplicate certificates" 35 | def duplicates(order_id) 36 | say(certificate_instance(order_id: order_id).duplicates) 37 | end 38 | 39 | private 40 | 41 | def certificate_instance(id_attribute = {}) 42 | Digicert::CLI::Certificate.new(options.merge(id_attribute)) 43 | end 44 | 45 | def required_option_exists? 46 | unless options[:order_id] || options[:certificate_id] 47 | say("You must provide either `--order_id` or `--certificate_id`.") 48 | end 49 | end 50 | end 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /spec/acceptance/csr_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "CSR" do 4 | describe "fetching a CSR" do 5 | it "fetches the CSR for specified order" do 6 | command = %w(csr fetch 123456) 7 | allow(Digicert::CLI::CSR).to receive_message_chain(:new, :fetch) 8 | 9 | _output = capture_stdout { Digicert::CLI.start(command) } 10 | 11 | expect(Digicert::CLI::CSR).to have_received(:new).with(order_id: "123456") 12 | end 13 | end 14 | 15 | describe "generating CSR" do 16 | context "with existing order" do 17 | it "generates a new CSR for an existing order" do 18 | allow(Digicert::CLI::CSR).to receive_message_chain(:new, :generate) 19 | command = %w(csr generate -o 123456 --key ./spec/fixtures/rsa4096.key) 20 | 21 | _output = capture_stdout { Digicert::CLI.start(command) } 22 | 23 | expect(Digicert::CLI::CSR).to have_received(:new).with( 24 | order_id: "123456", key: "./spec/fixtures/rsa4096.key", 25 | ) 26 | end 27 | end 28 | 29 | context "with provided details" do 30 | it "generates a new CSR with the details" do 31 | command = %w( 32 | csr generate 33 | --organization_id 1234 34 | --common_name ribosetest.com 35 | --key ./spec/fixtures/rsa4096.key 36 | --san site1.ribosetest.com site2.ribosetest.com 37 | ) 38 | 39 | allow(Digicert::CLI::CSR).to receive_message_chain(:new, :generate) 40 | _output = capture_stdout { Digicert::CLI.start(command) } 41 | 42 | expect(Digicert::CLI::CSR).to have_received(:new).with( 43 | organization_id: "1234", 44 | common_name: "ribosetest.com", 45 | key: "./spec/fixtures/rsa4096.key", 46 | san: ["site1.ribosetest.com", "site2.ribosetest.com"], 47 | ) 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/digicert/cli/order_creator.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class OrderCreator < Digicert::CLI::Base 4 | def initialize(name_id, attributes) 5 | @name_id = name_id 6 | @attributes = attributes 7 | end 8 | 9 | def create 10 | create_order 11 | end 12 | 13 | def self.create(name_id, attributes) 14 | new(name_id, attributes).create 15 | end 16 | 17 | private 18 | 19 | attr_reader :name_id, :attributes 20 | 21 | def create_order 22 | Digicert::Order.create(name_id, order_attributes) 23 | end 24 | 25 | def csr_file_content(csr_file) 26 | if csr_file && File.exists?(csr_file) 27 | File.read(csr_file) 28 | end 29 | end 30 | 31 | def organization 32 | Digicert::Organization.all.last 33 | end 34 | 35 | def order_attributes 36 | { 37 | certificate: { 38 | common_name: attributes[:common_name], 39 | csr: csr_file_content(attributes[:csr]), 40 | signature_hash: attributes[:signature_hash], 41 | organization_units: attributes[:organization_units], 42 | server_platform: { id: attributes[:server_platform_id] }, 43 | profile_option: attributes[:profile_option], 44 | }, 45 | 46 | organization: { id: attributes[:organization_id] || organization.id }, 47 | validity_years: attributes[:validity_years] || 3, 48 | custom_expiration_date: attributes[:custom_expiration_date], 49 | comments: attributes[:comments], 50 | disable_renewal_notifications: attributes[:disable_renewal_notifications], 51 | renewal_of_order_id: attributes[:renewal_of_order_id], 52 | payment_method: attributes[:payment_method], 53 | disable_ct: attributes[:disable_ct], 54 | } 55 | end 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/digicert/cli/order_duplicator.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class OrderDuplicator < Digicert::CLI::Base 4 | def create 5 | apply_output_options(duplicate_an_order) 6 | end 7 | 8 | private 9 | 10 | attr_reader :csr_file, :output_path 11 | 12 | def extract_local_attributes(options) 13 | @csr_file = options.fetch(:csr, nil) 14 | @output_path = options.fetch(:output, "/tmp") 15 | end 16 | 17 | def duplicate_an_order 18 | Digicert::OrderDuplicator.create(**order_params) 19 | end 20 | 21 | def order_params 22 | Hash.new.tap do |order_params| 23 | order_params[:order_id] = order_id 24 | 25 | if csr_file && File.exists?(csr_file) 26 | order_params[:csr] = File.read(csr_file) 27 | end 28 | end 29 | end 30 | 31 | def apply_output_options(duplicate) 32 | if duplicate 33 | print_request_details(duplicate.requests.first) 34 | fetch_and_download_certificate(duplicate.requests.first.id) 35 | end 36 | end 37 | 38 | def print_request_details(request) 39 | Digicert::CLI::Util.say( 40 | "Duplication request #{request.id} created for order - #{order_id}", 41 | ) 42 | end 43 | 44 | def fetch_and_download_certificate(request_id) 45 | if options[:output] 46 | certificate = fetch_certificate(request_id) 47 | download_certificate_order(certificate.id) 48 | end 49 | end 50 | 51 | def fetch_certificate(request_id) 52 | Digicert::DuplicateCertificateFinder.find_by(request_id: request_id) 53 | end 54 | 55 | def download_certificate_order(certificate_id) 56 | Digicert::CLI::CertificateDownloader.download( 57 | filename: order_id, path: output_path, certificate_id: certificate_id, 58 | ) 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /lib/digicert/cli.rb: -------------------------------------------------------------------------------- 1 | #-- 2 | # Copyright (c) 2017 Ribose Inc. 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | # ++ 22 | 23 | require "thor" 24 | require "openssl" 25 | require "digicert" 26 | 27 | require "digicert/cli/util" 28 | require "digicert/cli/auth" 29 | require "digicert/cli/base" 30 | require "digicert/cli/command" 31 | 32 | module Digicert 33 | module CLI 34 | def self.start(arguments) 35 | Digicert::CLI::Util.run(arguments) 36 | 37 | rescue Digicert::Errors::RequestError => error 38 | Digicert::CLI::Util.say( 39 | "A request to Digicert API failed\nError: #{error}", 40 | ) 41 | rescue Digicert::Errors::Forbidden, NoMethodError 42 | Digicert::CLI::Util.say( 43 | "Invalid: Missing API KEY\n\n" \ 44 | "A valid Digicert API key is required for any of the CLI operation\n" \ 45 | "You can set your API Key using `digicert config api-key YOUR_API_KEY`", 46 | ) 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/acceptance/order_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Order" do 4 | describe "listing orders" do 5 | it "retrieves the list of the orders" do 6 | command = %w(order list --filter common_name:*.ribostetest.com) 7 | 8 | allow(Digicert::CLI::Order).to receive_message_chain(:new, :list) 9 | _output = capture_stdout { Digicert::CLI.start(command) } 10 | 11 | expect(Digicert::CLI::Order.new).to have_received(:list) 12 | end 13 | end 14 | 15 | describe "finding an order" do 16 | it "finds a specific order based on the filters params" do 17 | command = %w(order find --filter common_name:ribosetest.com) 18 | 19 | allow(Digicert::CLI::Order).to receive_message_chain(:new, :find) 20 | _output = capture_stdout { Digicert::CLI.start(command) } 21 | 22 | expect(Digicert::CLI::Order.new).to have_received(:find) 23 | end 24 | end 25 | 26 | describe "creating an order" do 27 | context "with valid information" do 28 | it "creates a new certificate order" do 29 | allow( 30 | Digicert::CLI::OrderCreator, 31 | ).to receive(:create).and_return(double("order", id: 123_456)) 32 | 33 | command = %w( 34 | order create ssl_plus 35 | --csr ./spec/fixtures/rsa4096.csr 36 | --common-name ribosetest.com 37 | --signature-hash sha512 38 | --organization-id 123456 39 | --validity-years 3 40 | --payment-method card 41 | ) 42 | 43 | output = capture_stdout { Digicert::CLI.start(command) } 44 | 45 | expect(output).to include("New Order Created! Oder Id") 46 | expect(Digicert::CLI::OrderCreator).to have_received(:create). 47 | with( 48 | "ssl_plus", 49 | "csr" => "./spec/fixtures/rsa4096.csr", 50 | "common_name" => "ribosetest.com", 51 | "signature_hash" => "sha512", 52 | "organization_id" => "123456", 53 | "validity_years" => "3", 54 | "payment_method" => "card", 55 | ) 56 | end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/digicert/cli/order_reissuer.rb: -------------------------------------------------------------------------------- 1 | module Digicert 2 | module CLI 3 | class OrderReissuer < Digicert::CLI::Base 4 | def create 5 | apply_output_options(reissue_an_order) 6 | end 7 | 8 | def self.create(attributes) 9 | new(attributes).create 10 | end 11 | 12 | private 13 | 14 | attr_reader :csr_file, :output_path 15 | 16 | def extract_local_attributes(options) 17 | @csr_file = options.fetch(:csr, nil) 18 | @output_path = options.fetch(:output, "/tmp") 19 | end 20 | 21 | def reissue_an_order 22 | Digicert::OrderReissuer.create(**order_params) 23 | end 24 | 25 | def order_params 26 | Hash.new.tap do |order_params| 27 | order_params[:order_id] = order_id 28 | 29 | if csr_file && File.exists?(csr_file) 30 | order_params[:csr] = File.read(csr_file) 31 | end 32 | end 33 | end 34 | 35 | def apply_output_options(reissue) 36 | if reissue 37 | print_request_details(reissue.requests.first) 38 | fetch_and_download_certificate(reissue.id) 39 | end 40 | end 41 | 42 | def print_request_details(request) 43 | Digicert::CLI::Util.say( 44 | "Reissue request #{request.id} created for order - #{order_id}", 45 | ) 46 | end 47 | 48 | def fetch_and_download_certificate(reissued_order_id) 49 | if options[:output] 50 | order = fetch_reissued_order(reissued_order_id) 51 | download_certificate_order(order.certificate.id) 52 | end 53 | end 54 | 55 | def fetch_reissued_order(reissued_order_id) 56 | Digicert::CLI::OrderRetriever.fetch( 57 | reissued_order_id, 58 | wait_time: options[:wait_time], 59 | number_of_times: options[:number_of_times] 60 | ) 61 | end 62 | 63 | def download_certificate_order(certificate_id) 64 | Digicert::CLI::CertificateDownloader.download( 65 | filename: order_id, path: output_path, certificate_id: certificate_id 66 | ) 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /spec/acceptance/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe "Certificate" do 4 | describe "fetch a certificate" do 5 | it "returns certificate details for an order" do 6 | command = %w(certificate fetch 123456 --quiet) 7 | allow(certificate_klass).to receive_message_chain(:new, :fetch) 8 | 9 | _output = capture_stdout { Digicert::CLI.start(command) } 10 | 11 | expect(certificate_klass.new).to have_received(:fetch) 12 | expect(certificate_klass).to have_received(:new).with( 13 | order_id: "123456", quiet: true, 14 | ) 15 | end 16 | end 17 | 18 | describe "downloading a certificate" do 19 | it "downloads the certificate to provided path" do 20 | command = %w(certificate fetch 123456 --output /tmp/downloads) 21 | allow(Digicert::CLI::Certificate).to receive_message_chain(:new, :fetch) 22 | 23 | _output = capture_stdout { Digicert::CLI.start(command) } 24 | 25 | expect(Digicert::CLI::Certificate).to have_received(:new).with( 26 | order_id: "123456", output: "/tmp/downloads" 27 | ) 28 | end 29 | end 30 | 31 | describe "listing duplicate certificates" do 32 | it "returns the list of duplicate certificates" do 33 | command = %w(certificate duplicates 123456) 34 | 35 | allow( 36 | Digicert::CLI::Certificate, 37 | ).to receive_message_chain(:new, :duplicates) 38 | 39 | _output = capture_stdout { Digicert::CLI.start(command) } 40 | 41 | expect( 42 | Digicert::CLI::Certificate, 43 | ).to have_received(:new).with(order_id: "123456") 44 | end 45 | end 46 | 47 | describe "downloading a certificate" do 48 | it "downloads the certificate to output path" do 49 | command = %w(certificate download --certificate_id 123 --output /tmp) 50 | 51 | allow( 52 | Digicert::CLI::Certificate, 53 | ).to receive_message_chain(:new, :download) 54 | 55 | _output = capture_stdout { Digicert::CLI.start(command) } 56 | 57 | expect( 58 | Digicert::CLI::Certificate, 59 | ).to have_received(:new).with(certificate_id: "123", output: "/tmp") 60 | end 61 | end 62 | 63 | def certificate_klass 64 | Digicert::CLI::Certificate 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /spec/digicert/cli/order_creator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::OrderCreator do 4 | describe ".create" do 5 | context "with valid order information" do 6 | it "creates a new digicert certificate order" do 7 | name_id = "ssl_plus" 8 | stub_digicert_order_create_api( 9 | name_id, rest_order_attributes(order_attributes) 10 | ) 11 | 12 | order = Digicert::CLI::OrderCreator.create(name_id, order_attributes) 13 | 14 | expect(order.id).not_to be_nil 15 | expect(order.requests.first.status).to eq("pending") 16 | end 17 | end 18 | end 19 | 20 | def order_attributes 21 | @order_attributes ||= { 22 | common_name: "ribosetest.com", 23 | csr: "./spec/fixtures/rsa4096.csr", 24 | signature_hash: "sha512", 25 | organization_units: "Developer Units", 26 | server_platform_id: "platform_id_101", 27 | profile_option: "certificate-profile", 28 | organization_id: "organization-id", 29 | validity_years: 3, 30 | custom_expiration_date: "11-11-2019", 31 | comments: "Ordered using digicert CLI", 32 | disable_renewal_notifications: false, 33 | renewal_of_order_id: 123456, 34 | payment_method: "balanace", 35 | disable_ct: false, 36 | } 37 | end 38 | 39 | def rest_order_attributes(attributes) 40 | { 41 | certificate: { 42 | organization_units: attributes[:organization_units], 43 | server_platform: { id: attributes[:server_platform_id] }, 44 | profile_option: attributes[:profile_option], 45 | csr: File.read(attributes[:csr]), 46 | common_name: attributes[:common_name], 47 | signature_hash: attributes[:signature_hash], 48 | }, 49 | 50 | organization: { id: attributes[:organization_id] }, 51 | validity_years: attributes[:validity_years], 52 | custom_expiration_date: attributes[:custom_expiration_date], 53 | comments: attributes[:comments], 54 | disable_renewal_notifications: attributes[:disable_renewal_notifications], 55 | renewal_of_order_id: attributes[:renewal_of_order_id], 56 | payment_method: attributes[:payment_method], 57 | disable_ct: attributes[:disable_ct], 58 | } 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/digicert/cli/certificate.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/certificate_downloader" 2 | 3 | module Digicert 4 | module CLI 5 | class Certificate < Digicert::CLI::Base 6 | def fetch 7 | apply_option_flags(order.certificate) 8 | end 9 | 10 | def duplicates 11 | if order_id && duplicate_certificates 12 | display_in_table(duplicate_certificates) 13 | end 14 | end 15 | 16 | def download 17 | download_certificate(certificate_id) 18 | end 19 | 20 | private 21 | 22 | attr_reader :output_path 23 | 24 | def extract_local_attributes(options) 25 | @output_path = options.fetch(:output, nil) 26 | end 27 | 28 | def order 29 | @order ||= Digicert::Order.fetch(order_id) 30 | end 31 | 32 | def certificate_id 33 | @certificate_id ||= options[:certificate_id] || order.certificate.id 34 | end 35 | 36 | def duplicate_certificates 37 | @certificates ||= Digicert::DuplicateCertificate.all(order_id: order_id) 38 | end 39 | 40 | def apply_option_flags(certificate) 41 | download_certificate(certificate.id) || apply_output_flag(certificate) 42 | end 43 | 44 | def download_certificate(certificate_id) 45 | if output_path 46 | Digicert::CLI::CertificateDownloader.download( 47 | path: output_path, 48 | certificate_id: certificate_id, 49 | filename: order_id || certificate_id, 50 | ) 51 | end 52 | end 53 | 54 | def apply_output_flag(certificate) 55 | options[:quiet] ? certificate.id : certificate 56 | end 57 | 58 | def display_in_table(certificates) 59 | certificates_attributes = certificates.map do |certificate| 60 | [ 61 | certificate.id, 62 | certificate.common_name, 63 | certificate.dns_names.join("\n"), 64 | certificate.status, 65 | [certificate.valid_from, certificate.valid_till].join(" - "), 66 | ] 67 | end 68 | 69 | Digicert::CLI::Util.make_it_pretty( 70 | rows: certificates_attributes, 71 | headings: ["Id", "Common Name", "SAN Names", "Status", "Validity"], 72 | ) 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /spec/digicert/cli/order_reissuer_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::OrderReissuer do 4 | describe "#create" do 5 | context "with only order id passed" do 6 | it "sends create message to digicert order reissuer" do 7 | order_id = 123_456 8 | allow(Digicert::OrderReissuer).to receive(:create) 9 | 10 | Digicert::CLI::OrderReissuer.new(order_id: order_id).create 11 | 12 | expect( 13 | Digicert::OrderReissuer, 14 | ).to have_received(:create).with(order_id: order_id) 15 | end 16 | end 17 | 18 | context "with order id and new csr" do 19 | it "sends create message to reissuer with new details" do 20 | order_id = 123_456 21 | csr_file = "./spec/fixtures/rsa4096.csr" 22 | allow(Digicert::OrderReissuer).to receive(:create) 23 | 24 | Digicert::CLI::OrderReissuer.new( 25 | order_id: order_id, csr: csr_file, 26 | ).create 27 | 28 | expect(Digicert::OrderReissuer).to have_received(:create).with( 29 | order_id: order_id, csr: File.read(csr_file), 30 | ) 31 | end 32 | end 33 | 34 | context "with order id and --fetch option" do 35 | it "reissue order and fetch the updated order" do 36 | order_id = 456_789 37 | 38 | mock_order_fetch_and_download_requests(order) 39 | stub_digicert_order_reissue_api(order_id, order_attributes(order)) 40 | allow(Digicert::CLI::CertificateDownloader).to receive(:download) 41 | 42 | Digicert::CLI::OrderReissuer.create( 43 | order_id: order_id, output: "/tmp", number_of_times: 1, wait_time: 1, 44 | ) 45 | 46 | expect(Digicert::CLI::CertificateDownloader). 47 | to have_received(:download). 48 | with(hash_including(certificate_id: order.certificate.id)) 49 | end 50 | end 51 | end 52 | 53 | def mock_order_fetch_and_download_requests(order) 54 | allow(Digicert::Order).to receive(:fetch).and_return(order) 55 | allow(Digicert::CLI::OrderRetriever).to receive(:fetch).and_return(order) 56 | end 57 | 58 | def order(order_id = 123_456) 59 | stub_digicert_order_fetch_api(order_id) 60 | @order ||= Digicert::Order.fetch(order_id) 61 | end 62 | 63 | def order_attributes(order) 64 | { 65 | common_name: order.certificate.common_name, 66 | dns_names: order.certificate.dns_names, 67 | csr: order.certificate.csr, 68 | signature_hash: order.certificate.signature_hash, 69 | server_platform: { id: 45 }, 70 | } 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/digicert/cli/order_duplicator_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::OrderDuplicator do 4 | describe "#create" do 5 | context "with a valid order id" do 6 | it "sends create message to digicert order duplicator" do 7 | order_id = 123_456 8 | allow(Digicert::OrderDuplicator).to receive(:create) 9 | 10 | Digicert::CLI::OrderDuplicator.new(order_id: order_id).create 11 | 12 | expect( 13 | Digicert::OrderDuplicator, 14 | ).to have_received(:create).with(order_id: order_id) 15 | end 16 | end 17 | 18 | context "with order id and new csr" do 19 | it "sends create message to duplicator with new csr" do 20 | order_id = 123_456 21 | csr_file = "./spec/fixtures/rsa4096.csr" 22 | allow(Digicert::OrderDuplicator).to receive(:create) 23 | 24 | Digicert::CLI::OrderDuplicator.new( 25 | order_id: order_id, csr: csr_file, 26 | ).create 27 | 28 | expect(Digicert::OrderDuplicator).to have_received(:create).with( 29 | order_id: order_id, csr: File.read(csr_file), 30 | ) 31 | end 32 | end 33 | 34 | context "with order id and --fetch option" do 35 | it "duplicates order and fetch the updated order" do 36 | order_id = 456_789 37 | 38 | mock_order_fetch_and_download_requests(order) 39 | stub_digicert_order_duplicate_api(order_id, order_attributes(order)) 40 | allow(Digicert::CLI::CertificateDownloader).to receive(:download) 41 | 42 | Digicert::CLI::OrderDuplicator.new( 43 | order_id: order_id, output: "/tmp", number_of_times: 1, wait_time: 1, 44 | ).create 45 | 46 | expect(Digicert::CLI::CertificateDownloader). 47 | to have_received(:download). 48 | with(hash_including(certificate_id: order.certificate.id)) 49 | end 50 | end 51 | 52 | def order(order_id = 123_456) 53 | stub_digicert_order_fetch_api(order_id) 54 | @order ||= Digicert::Order.fetch(order_id) 55 | end 56 | 57 | def order_attributes(order) 58 | { 59 | common_name: order.certificate.common_name, 60 | dns_names: order.certificate.dns_names, 61 | csr: order.certificate.csr, 62 | signature_hash: order.certificate.signature_hash, 63 | server_platform: { id: 45 }, 64 | } 65 | end 66 | 67 | def mock_order_fetch_and_download_requests(order) 68 | allow(Digicert::Order).to receive(:fetch).and_return(order) 69 | 70 | allow( 71 | Digicert::DuplicateCertificateFinder, 72 | ).to receive(:find_by).and_return(order.certificate) 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /spec/fixtures/rsa4096.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | 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 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /spec/fixtures/rsa4096.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIJKgIBAAKCAgEA519E1L4i5vF3bKwrZGDYcqAn9KGJLdazcuh4Sj98Wss1Viuz 3 | g+K+9wgcvpskre6Z2lSFK3Qb1sm58DF+Be+acqTnj/Tuc6p0AxgrvlE29/pxaMmC 4 | huBO8wJMJz9EjCFOecFyIrC4S+VnT+Qh72r+GpvQvful+RwLsOpXEL7HQ+ADO4TW 5 | Zf+jPz7u4foR+b6njOfu9CTBw7w7HopSST1nMUga4cPJnNRJ9ZJt+bQR0lWM74hs 6 | dUmxWKjxJI73i1fJV9HSjwcsDU8xvTNRsDejJPpCgvF49PnjVvioYr6kEHwd+khT 7 | ouxfXbxG7AzNH8CJJ8PdXvLGZ9qDmENX68Vt4fyAGiZQxQzeqvlffoDXIdDEAAVP 8 | ZT34dIpnZmCKDLjbKTi93hv066gtEkDpqXqdvjPhjGgGkd9L0JQOn9t+J8gS92KA 9 | DsoacNfYfw76Nnc/pZkgmi97NHxfCWWzqEa8PiUzhpdSiMvMEHtPgUpqwICP5dzw 10 | +2TFvMwUkHKSkheW+nzSjETawONdUF/TrfLNFHpBxhUKHaLvbRUdAMYSq5IYZmtN 11 | 3Usu/1kuQfDSnN6AX27oRhNNx99FjDzgj/fG6E/iSHKRzMUadFVoQqRLKsjT9AIV 12 | R84q4fMTd+yuSqsTTi1vCQjR4lNikpDWvvE3Tr3QR2in9JJ/FDJZN4RqF4MCAwEA 13 | AQKCAgEA0KhTK8T5Nurmt8OhMlpAeUdEIVMYopUwql1KNjOA02TVigvJThRMAf53 14 | 5dGGR7GZYJO+sUx52r98B0irDXFjCSb8ig/qh7dd/nhq4qzddM+QPV8VbsuVh4Q3 15 | 52EgUXusCRPS+cQDwLZ28E6d6AvGc3q3ys3KhZisVnVP5ZMXo3e/koqey8e6kkwQ 16 | JQ1f7qno8qMsFVOcxwfXDRjTUqeki4YqcBYgmWW9+VCAC6RAOj7a5h5TKYc2/+0D 17 | 4+NnDWwy8RcR29ks+ifEhItmjRPv9mYXW32nhs5hHssLGFozHYbBhjh57MFc0+z6 18 | zOBSkOMTDiCOYJVzJq+i48s/3CnliCQKhpCPQTRvI8uR5T7bVnq/+HJuqqxZpTkd 19 | 1w3DeDReXPps27MVZiUwLbrnnnCYNFdf9wsLgz5vc3450NkaRUixGplKTZSe2RF4 20 | axFY0NlwTtPXpCmlfJgaQmZ0JvIBZ7vBn4t0uRTK1djGu0y3Fb+PxH626v7NC34w 21 | bVYOOSdKqIjzn/Qkmn4pgczCWE0VK2EeNQr5yoetnzhY0ErztQu39DKuMtUI9eqa 22 | 3YgTQw404Syr4hx7MdTPSdErcHlq2sAHcOtrtOIMEOUTRaMw/i8oN/JJNBZr7utX 23 | RedPamVXLXj8zQXMl5NRmnQoWOdk714b9wSr9AH5tOJxjurS75ECggEBAPozPvze 24 | Nj1OQgPMOT2EyTBSZJkz8NF2htp0mMrghVJb1o2jA5RwePqwD3/9WwvutDewKqyQ 25 | 6XD34xJpICYtCvc325cZaXerrpa38Qu9kGSn7xKMlUxTGjbY1dKnTsJ2SHSUrekj 26 | fEeBYN/uIqoilPSJWWSaIZGFY3YjGuoHsdabzvUfdAbSIPAA9ktJcCuFYdiXBorT 27 | 3QUF0aCX3NGrWwDcVri+Z5qaIXM5NaiH/t7vRZsHu5SoQxuCKkrLQUAljMG5weA0 28 | lJXIAnbBYliuZ3gS5OYHTSqfgLuqIZCu5jrYH9B138bDIgeqffSAknvuPZbxzrXp 29 | c/6qps0bA7iS5ZkCggEBAOy8StNZKM++mWqInbYwPTeVG0nXQ8cQLJfzvOfxp+dw 30 | +cr4zKkQVaVdkb8X/L2mxCoYkc+dmd5zzHWGnHXpuWR+uyioKdZ8t0tmMWhqQxcC 31 | jzEAEhnrmshE0QS7kAbmFbhXhQOq5Rgh1oAexhEyMd44gdAMe0wndrOlKuIxGzuJ 32 | j9ssjowk5cdK+XDUN6gCbpi8xULKta4dn6yRivloB4dv21herDZzBo4MMcryA1fG 33 | x06uazG8jCQel0j83efzQWPC8yF4uh2VySFDmel+iEcLsXvVrXjMLSSWFqnNM3s9 34 | OfHWtpyDKkcCkaa093A2yhg8bZkuIyqU8lW2gYjyX3sCggEAPuGYSAc1DI1ZjAjM 35 | rghsZAehHtvt/0bRt5+sMvjgqQVJ1AkPQkROM3sCOkGbm1Ef3AsbfolhEjJK0Hq5 36 | SL7zTZStTLlnR1tPorOSEkhPPOzz6e6JK0iLgxNWEf5YjgkaRqqDVt/DQVlj1oPM 37 | FIRieV73p5ARNbiXeb5y6jSK3owEJJkGGRzAiHFFdUB8v4NjRwMV8tgyaSvANqNU 38 | LSHq2jmGViIMec+Y7pOHR9b+GFt8W+1CmKb9TrGVHX0d5hhJ2vprnoS4fzhoXh5W 39 | MEGM4aGmA6X8H+U8fm3Qx8MdO9bLkCG/3v111QVlaIjTx+/lbMVTFWcZ7vxGta+/ 40 | bKkGqQKCAQEAzvwu3C2njkRS9R+v7TyuOavoKR7LBwCwTMdyksXqjWRtMzdoEiXT 41 | DHwMU62QcO2ftEK5MnLUtvg+ez+QC1SooSJhV8H4mq1+wbD/YBEQycyWEDzElt81 42 | /QaWTnIEEtQXh48WIMfJ+NiVKH4/pYdirK5xacuP/ly+34F5Rj2zVtIG8pY5qHUW 43 | ZrK5+BnE8+P0eR0LyENeqHcERikW/swjURrPCKv2HMFjqM0muA/0Nkn5t2SvGtSF 44 | H4uTsOBO0WAR+zzXwZtB914gdjIaH1pfouapbuG8A9NZYRTNifd9nLJCuJ2IGr5g 45 | N6gaW0z8z6NH/frPxM/fNXr3i1PAXFG2gwKCAQEA4sDQgMNHpTkWu4MZgDU7oX86 46 | tkdVtM8SIoBodlwyfG+bOWYebyNi3044y8VEK7Jrt+koIS4tz9rP7u2dDGfGSGTq 47 | DwKue6Iyv2zbozL+a0Ojf5hK+nErqB9map3qVljf1RgR72WYcQORNVeYk0eyUc6x 48 | jTzepiWrA5KjyiGIRoRtABmHiT2L//sJ/4PiZ0Rq42rlo0lylBCB8wVLqC64RR8l 49 | /Yi8yfHggUtNnIiXzWJYAJtgn5DC5LZEcyjLmI0EUOIv66MuZmlyvl+nt6U/+Z/B 50 | x28HsgOJm/ybHlEZ6sUzLX/T2qeLWbII05IqFs4YLMKqPUpvL9JOC2D2hI3ylA== 51 | -----END RSA PRIVATE KEY----- 52 | -------------------------------------------------------------------------------- /spec/digicert/cli/certificate_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | 3 | RSpec.describe Digicert::CLI::Certificate do 4 | describe "#fetch" do 5 | context "with only order id" do 6 | it "returns the details for the certificate" do 7 | order_id = 123_456_789 8 | stub_digicert_order_fetch_api(order_id) 9 | 10 | certificate = Digicert::CLI::Certificate.new( 11 | order_id: order_id 12 | ).fetch 13 | 14 | expect(certificate.id).not_to be_nil 15 | expect(certificate.organization.id).not_to be_nil 16 | expect(certificate.common_name).to eq("digicert.com") 17 | end 18 | end 19 | 20 | context "with option attributes" do 21 | it "returns the option attribute specified details" do 22 | order_id = 112_358 23 | stub_digicert_order_fetch_api(order_id) 24 | 25 | certificate = Digicert::CLI::Certificate.new( 26 | order_id: order_id, quiet: true 27 | ).fetch 28 | 29 | expect(certificate).to eq(order_id) 30 | end 31 | end 32 | 33 | context "output attribute specified" do 34 | it "sends download message to certificate downloader" do 35 | order_id = 112_358 36 | 37 | stub_digicert_order_fetch_api(order_id) 38 | allow(Digicert::CLI::CertificateDownloader).to receive(:download) 39 | 40 | Digicert::CLI::Certificate.new( 41 | order_id: order_id, output: "/tmp/downloads", 42 | ).fetch 43 | 44 | expect(Digicert::CLI::CertificateDownloader). 45 | to have_received(:download).with({ 46 | certificate_id: 112358, filename: order_id, path: "/tmp/downloads", 47 | }) 48 | end 49 | end 50 | end 51 | 52 | describe "#duplicates" do 53 | it "lists duplicate certificates" do 54 | order_id = 112_358 55 | allow(Digicert::DuplicateCertificate).to receive(:all) 56 | 57 | Digicert::CLI::Certificate.new(order_id: order_id).duplicates 58 | 59 | expect( 60 | Digicert::DuplicateCertificate, 61 | ).to have_received(:all).with(order_id: order_id) 62 | end 63 | end 64 | 65 | describe "#download" do 66 | context "with certificate_id" do 67 | it "sends downloader a download message" do 68 | certificate_id = 123_456_789 69 | downloader = Digicert::CLI::CertificateDownloader 70 | allow(downloader).to receive(:download) 71 | 72 | Digicert::CLI::Certificate.new( 73 | certificate_id: certificate_id, output: "/tmp/downloads", 74 | ).download 75 | 76 | expect(downloader).to have_received(:download).with( 77 | hash_including( 78 | certificate_id: certificate_id, filename: certificate_id, 79 | ), 80 | ) 81 | end 82 | end 83 | 84 | context "with order_id" do 85 | it "fetch order and sends downloader a download message" do 86 | order_id = 123_456_789 87 | downloader = Digicert::CLI::CertificateDownloader 88 | 89 | allow(downloader).to receive(:download) 90 | stub_digicert_order_fetch_api(order_id) 91 | 92 | Digicert::CLI::Certificate.new( 93 | order_id: order_id, output: "/tmp/downloads", 94 | ).download 95 | 96 | expect(downloader).to have_received(:download).with( 97 | hash_including( 98 | filename: order_id, path: "/tmp/downloads", certificate_id: 112358, 99 | ), 100 | ) 101 | end 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /lib/digicert/cli/commands/order.rb: -------------------------------------------------------------------------------- 1 | require "digicert/cli/order" 2 | require "digicert/cli/order_reissuer" 3 | require "digicert/cli/order_creator" 4 | require "digicert/cli/order_duplicator" 5 | 6 | module Digicert 7 | module CLI 8 | module Commands 9 | class Order < Thor 10 | class_option :debug, type: "boolean", desc: "Enable debug mode" 11 | 12 | desc "list", "List digicert orders" 13 | method_option :filter, type: :hash, desc: "Specify filter options" 14 | 15 | def list 16 | say(order_instance.list) 17 | end 18 | 19 | desc "find", "Find a digicert order" 20 | method_option :filter, type: :hash, desc: "Specify filter options" 21 | option :quiet, type: :boolean, aliases: "-q", desc: "Retrieve only id" 22 | 23 | def find 24 | say(order_instance.find) 25 | end 26 | 27 | desc "reissue ORDER_ID", "Reissue digicert order" 28 | option :csr, desc: "The CSR content from a file" 29 | method_option :common_name, desc: "Certificate Common Name" 30 | method_option :signature_hash, desc: "Certificate signature hash" 31 | option :output, aliases: "-o", desc: "Path to download certificates" 32 | 33 | def reissue(order_id) 34 | say(reissue_an_order(order_id)) 35 | end 36 | 37 | desc "create", "Create a new order" 38 | method_option :csr, desc: "The CSR content from a file" 39 | method_option :common_name, desc: "Certificate Common Name" 40 | method_option :signature_hash, desc: "Certificate signature hash" 41 | method_option :organization_id, desc: "The Organization ID" 42 | method_option :validity_years, desc: "Validity years for certificate" 43 | method_option :comments, desc: "Comments about the certificate order" 44 | method_option :payment_method, desc: "Speicfy the payment method" 45 | 46 | method_option :disable_renewal_notifications, type: :boolean 47 | method_option :server_platform_id, desc: "Server Platform Id" 48 | method_option :profile_option, desc: "Specify certificate profile" 49 | method_option :organization_units, type: :array, desc: "organization_units" 50 | method_option :custom_expiration_date, desc: "Expiry Date in YYY-MM-DD" 51 | method_option :renewal_of_order_id, desc: "Id for renewalable Order" 52 | method_option :disable_ct, type: :boolean, desc: "Disable CT logging" 53 | 54 | def create(name_id) 55 | order = create_new_order(name_id, options) 56 | say("New Order Created! Oder Id: #{order.id}.") 57 | 58 | rescue Digicert::Errors::RequestError => error 59 | say("Request Error: #{error}.") 60 | end 61 | 62 | desc "duplicate ORDER_ID", "Duplicate digicert order" 63 | option :csr, desc: "The CSR content from a file" 64 | method_option :common_name, desc: "Certificate Common Name" 65 | method_option :signature_hash, desc: "Certificate signature hash" 66 | option :output, aliases: "-o", desc: "Path to download certificate" 67 | 68 | def duplicate(order_id) 69 | say(duplicate_an_order(order_id)) 70 | end 71 | 72 | private 73 | 74 | def order_instance 75 | Digicert::CLI::Order.new(options) 76 | end 77 | 78 | def reissue_an_order(order_id) 79 | Digicert::CLI::OrderReissuer.new( 80 | options.merge(order_id: order_id), 81 | ).create 82 | end 83 | 84 | def create_new_order(name_id, options) 85 | Digicert::CLI::OrderCreator.create(name_id, options) 86 | end 87 | 88 | def duplicate_an_order(order_id) 89 | Digicert::CLI::OrderDuplicator.new( 90 | options.merge(order_id: order_id), 91 | ).create 92 | end 93 | end 94 | end 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Digicert CLI 2 | 3 | [![Build Status](https://github.com/riboseinc/digicert-cli/actions/workflows/test.yml/badge.svg)](https://github.com/riboseinc/digicert-cli/actions/workflows/test.yml) 4 | [![Code 5 | Climate](https://codeclimate.com/github/riboseinc/digicert-cli/badges/gpa.svg)](https://codeclimate.com/github/riboseinc/digicert-cli) 6 | [![Gem 7 | Version](https://badge.fury.io/rb/digicert-cli.svg)](https://badge.fury.io/rb/digicert-cli) 8 | 9 | The [Digicert CLI] is a tool that allows us to manage Digicert orders, 10 | certificates and etc using [Digicert Ruby Client]. 11 | 12 | ## Configure 13 | 14 | The `CLI` commands are heavily dependent on the Digicert API. Please 15 | [follow the instruction here] to request an API Key from Digicert, Once you have 16 | your API key then you can configure it using the `config` command. 17 | 18 | ```sh 19 | $ digicert config api-key YOUR_API_KEY 20 | ``` 21 | 22 | ## Usages 23 | 24 | ### Getting Help 25 | 26 | We have been trying to simplify the `CLI` with proper `help` documentation. Each 27 | of the `command` and `subcommand` should provide you the basic usages guide with 28 | the list of supported options. 29 | 30 | The parent command should fire up the `help` documentation, but if it does not 31 | then you can explicitly call the `help` command or pass `-h` flags with any of 32 | the command and that should fire up the documentation. For example 33 | 34 | ```sh 35 | $ digicert help 36 | ``` 37 | 38 | ```sh 39 | Commands: 40 | digicert certificate # Manage Digicert Certificates 41 | digicert config # Configure The CLI Client 42 | digicert csr # Fetch/generate Certificate CSR 43 | digicert help [COMMAND] # Describe available / One specific command 44 | digicert order # Manage Digicert Orders 45 | ``` 46 | 47 | The above command lists the available commands with a basic description. As you 48 | might have noticed, it also ships with a `help` command which can be used to 49 | fire up the usages guide and options for it's nested command. 50 | 51 | ```sh 52 | # digicert order -h 53 | $ digicert help order 54 | ``` 55 | 56 | ```sh 57 | Commands: 58 | digicert order find # Find a digicert order 59 | digicert order help [COMMAND] # Describe subcommands or one specific 60 | digicert order list # List digicert orders 61 | digicert order reissue ORDER_ID # Reissue digicert order 62 | ``` 63 | 64 | Hopefully you get the idea, we are trying our best to keep this guide up to date 65 | but whenever you need some more information please add the `-h` flags with any 66 | commands or subcommands and you should see more accurate help documentation. 67 | 68 | ### Orders 69 | 70 | #### Listing Orders 71 | 72 | The `CLI` made listing Digicert orders pretty simple, once we have our API key 73 | configured then we can list all of our orders using the `order list` command. 74 | 75 | ```sh 76 | $ digicert order list 77 | ``` 78 | 79 | ```sh 80 | +---------------+---------------+------------------+-------------+-------------+ 81 | | Id | Product Type | Common Name | Status | Expiry | 82 | +---------------+---------------+------------------+-------------+-------------+ 83 | | xxxxx65 | ssl_wildcard | *.ribosetest.com | expired | 2018-06-25 | 84 | | xxxxx20 | ssl_wildcard | *.ribosetest.com | issued | 2018-06-15 | 85 | | xxxxx06 | ssl_wildcard | *.ribosetest.com | revoked | 2018-05-09 | 86 | +---------------+---------------+------------------+-------------+-------------+ 87 | ``` 88 | 89 | The above command without any option will list out all of our Digicert orders, 90 | but if we need to filter those orders then we can do that by passing `--filter` 91 | option and the expected values as in `key:value` pair. 92 | 93 | For example, to list all of the orders that has product type of `ssl_wildcard` 94 | we can use the following and it will list only the filtered orders. 95 | 96 | ```sh 97 | $ digicert order list --filter 'product_name_id:ssl_wildcard' 98 | ``` 99 | 100 | Supported filters options are `date_created`, `valid_till`, `status`, `search`, 101 | `common_name` and `product_name_id`. Please [check the wiki] for more uptodate 102 | filter options list. 103 | 104 | #### Find an order 105 | 106 | To find an order we can use `order find` command, by default it will print the 107 | order details in the console but this command also supports the normal filter 108 | options as described on the listing order section. 109 | 110 | One important thing to remember, it will only retrieve one single entry, so if 111 | you have multiple orders in your specified terms then it will only retrieve the 112 | most recent one from that list. 113 | 114 | ```sh 115 | $ digicert order find --filter 'common_name:ribosetest.com' 'product_name_id:ssl_plus' 116 | ``` 117 | 118 | ```sh 119 | # 121 | ``` 122 | 123 | Lots of information? Well, if you don't need that much details and only need the 124 | `ID` then you can pass the `--quiet` flags and it will only print the order id. 125 | 126 | #### Reissue an order 127 | 128 | To reissue a non-expired order we can use the `order reissue` command and pass 129 | the order id. By default it will reissue the order using the existing details 130 | but we can update that by passing the certificate CSR as`--csr` 131 | 132 | ```sh 133 | $ digicert order reissue 12345 --csr path_to_the_new_csr.csr 134 | ``` 135 | 136 | ```sh 137 | Reissue request xxxxx8 created for order - 123456 138 | ``` 139 | 140 | Pretty cool right? The above command also support `--output` option that we 141 | can use to download the reissued certificates. To download we need to provide a 142 | valid path and it will automatically download the certificates to it 143 | 144 | ```sh 145 | $ digicert order reissue 123456 --output /path/to/downloads 146 | ``` 147 | 148 | ```sh 149 | Reissue request 1xxxxx created for order - 123456 150 | 151 | Fetch attempt 1.. 152 | Downloaded certificate to: 153 | 154 | /path/to/downloads/123456.root.crt 155 | /path/to/downloads/123456.certificate.crt 156 | /path/to/downloads/123456.intermediate.crt 157 | ``` 158 | 159 | ### Certificate 160 | 161 | #### Fetch a certificate 162 | 163 | The `certificate fetch` command retrieves the certificate for any specific order, 164 | by default it will print out the certificate detail in the console but if we can 165 | change it by passing additional option to it. Like the `--quiet` flags will only 166 | return the certificate id instead of all the details 167 | 168 | ```sh 169 | $ digicert certificate fetch 123456789 --quiet 170 | ``` 171 | 172 | #### Download a certificate 173 | 174 | To download a certificate we can use the same `certificate fetch` command but 175 | with the `--output` option. Based on the `--output` option this command will 176 | fetch and download the certificates to the provided path. 177 | 178 | ```sh 179 | $ digicert certificate fetch 123456 --output /path/to/downloads 180 | ``` 181 | 182 | The `fetch` command only works with the `order_id` but what if we have the 183 | certificate id? Well, we have another command `certificate download` which 184 | supports both the `--order-id` and the `certificate-id`. 185 | 186 | ```sh 187 | $ digicert certificate download --order-id 654321 --output /downloads 188 | $ digicert certificate download --certificate-id 123456 --output /downloads 189 | ``` 190 | 191 | #### List duplicate certificates 192 | 193 | Digicert allows us to duplicate a certificate and if we want to list all of the 194 | duplicates then we can use the `certificate duplicates` command. It expects us 195 | to provide the `order-id` to list all the duplicates 196 | 197 | ```sh 198 | $ digicert certificate duplicates 123456 199 | ``` 200 | 201 | ```sh 202 | +----------+-------------------+------------------+----------+--------------+ 203 | | Id | Common Name | SAN Names | Status | Validity | 204 | +----------+-------------------+------------------+----------+--------------+ 205 | | xxxxx19 | *.ribosetest.com | *.ribosetest.com | approved | xxxxx-xxxxxx | 206 | | | | ribosetest.com | | | 207 | +----------+-------------------+------------------+----------+--------------+ 208 | ``` 209 | 210 | ### CSR 211 | 212 | #### Fetch an order's CSR 213 | 214 | Retrieving a `CSR` is pretty easy, if we have an order id and we want retrieve 215 | it's `CSR` then we can use the `csr fetch` command and it will print out the 216 | details in the console. 217 | 218 | ```sh 219 | $ digicert csr fetch 123456 220 | ``` 221 | 222 | #### Generate a new CSR 223 | 224 | Digicert gem usages a third party library to generate a CSR, and this CLI 225 | included that to simply the `CSR` generation, so if we need to generate a new 226 | `CSR` then we can use the `csr generate` command and pass the order id with a 227 | key file and it will generate a new CSR. 228 | 229 | ```sh 230 | $ digicert csr generate --oreder-id 12345 --key /path/to/the/key-file.key 231 | ``` 232 | 233 | This command also supports custom details like `common-name` and `san`. We can 234 | pass those as `--common-name` and the `--san` and it will use those to generate 235 | the `CSR` 236 | 237 | ```sh 238 | $ digicert csr generate --common-name ribosetest.com --order-id 1234 \ 239 | --san test1.ribosetest.com test2.ribosetest.com --key path_to_key_file 240 | ``` 241 | 242 | ## Development 243 | 244 | We are following Sandi Metz's Rules for this gem, you can read the 245 | [description of the rules here][sandi-metz] All new code should follow these 246 | rules. If you make changes in a pre-existing file that violates these rules you 247 | should fix the violations as part of your contribution. 248 | 249 | ### Setup 250 | 251 | Clone the repository. 252 | 253 | ```sh 254 | git clone https://github.com/riboseinc/digicert-cli 255 | ``` 256 | 257 | Setup your environment. 258 | 259 | ```sh 260 | bin/setup 261 | ``` 262 | 263 | Run the test suite 264 | 265 | ```sh 266 | bin/rspec 267 | ``` 268 | 269 | ## Contributing 270 | 271 | First, thank you for contributing! We love pull requests from everyone. By 272 | participating in this project, you hereby grant [Ribose Inc.][riboseinc] the 273 | right to grant or transfer an unlimited number of non exclusive licenses or 274 | sub-licenses to third parties, under the copyright covering the contribution 275 | to use the contribution by all means. 276 | 277 | Here are a few technical guidelines to follow: 278 | 279 | 1. Open an [issue][issues] to discuss a new feature. 280 | 1. Write tests to support your new feature. 281 | 1. Make sure the entire test suite passes locally and on CI. 282 | 1. Open a Pull Request. 283 | 1. [Squash your commits][squash] after receiving feedback. 284 | 1. Party! 285 | 286 | 287 | ## Credits 288 | 289 | This gem is developed, maintained and funded by [Ribose Inc.][riboseinc] 290 | 291 | [riboseinc]: https://www.ribose.com 292 | [issues]: https://github.com/riboseinc/digicert-cli/issues 293 | [squash]: https://github.com/thoughtbot/guides/tree/master/protocol/git#write-a-feature 294 | [sandi-metz]: http://robots.thoughtbot.com/post/50655960596/sandi-metz-rules-for-developers 295 | [Digicert CLI]: https://github.com/riboseinc/digicert-cli 296 | [Digicert Ruby Client]: https://github.com/riboseinc/digicert 297 | [check the wiki]: https://github.com/riboseinc/digicert-cli/wiki 298 | [follow the instruction here]: https://www.digicert.com/rest-api 299 | --------------------------------------------------------------------------------