varanus 0.1.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c3f1e3b8a3c5d8b1999ab00642afaef76bfa69be3872b9af2ab91af9aa4731bc
4
- data.tar.gz: bf2cd9f9449d290133f6a8f21a5c64fb5541d42c6349b0179eaa85af153bf3e9
3
+ metadata.gz: 72820b52f9184bfc3c35816e39b1c721d0e4eabf6c660b6c4da95d5cdb1bf025
4
+ data.tar.gz: 3cc5f9ac737e5c375027db08860443b6ece64dac58ad7cf7e515a9f6009e825a
5
5
  SHA512:
6
- metadata.gz: 6890038fac5dfa28e212cdb119d16ff35bfa5344359df100076ddd533f5226b3b4c00f8302442d24c27ecdd233d9e23991d37ac3ca6400066ff050f168937d2a
7
- data.tar.gz: df2e85079624160c161e8d8849c97c0e7287f5e89585dd3a1f9fa2c6fddd100876b72b6fff6e0dc3c028090c7830835c047d8bd22ffbb477c6935ed19d857496
6
+ metadata.gz: 614b412e36992ee4a7c99f06a26e7e5f3768960200e2d962e8ffd9d0aaf64502dc81643be9c038b151ca44de0dea84a952f8a58b1d67b1379f1cf0806a3fdbd5
7
+ data.tar.gz: 484a02bacc17c1b26ad7749d10142bf48570a6260b5557267318d25bb3d051ac3a19030f7522ef9684274424d4a7f25d2e863faedf6d64c75932eb1a9fd63af8
data/.gitignore CHANGED
@@ -6,3 +6,4 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
+ /test.rb
@@ -1,9 +1,15 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.3
2
+ NewCops: disable
3
+ TargetRubyVersion: 2.5
3
4
 
4
5
  Bundler/OrderedGems:
5
6
  AutoCorrect: false
6
7
 
8
+ Layout/LineLength:
9
+ Max: 90
10
+ Exclude:
11
+ - 'test/**/*'
12
+
7
13
  Metrics/AbcSize:
8
14
  Max: 25
9
15
  Exclude:
@@ -14,11 +20,6 @@ Metrics/ClassLength:
14
20
  Exclude:
15
21
  - 'test/**/*'
16
22
 
17
- Metrics/LineLength:
18
- Max: 90
19
- Exclude:
20
- - 'test/**/*'
21
-
22
23
  Metrics/MethodLength:
23
24
  Max: 20
24
25
  Exclude:
@@ -28,23 +29,12 @@ Naming/FileName:
28
29
  Exclude:
29
30
  - Gemfile
30
31
 
31
- Naming/UncommunicativeMethodParamName:
32
- AllowedNames:
33
- - gb
34
- - id
35
- - ip
36
- - os
37
- - vm
38
-
39
32
  Style/ClassAndModuleChildren:
40
- Enabled: false
33
+ EnforcedStyle: compact
41
34
 
42
35
  Style/ConditionalAssignment:
43
36
  Enabled: false
44
37
 
45
- Style/FrozenStringLiteralComment:
46
- Enabled: false
47
-
48
38
  Style/MethodDefParentheses:
49
39
  EnforcedStyle: require_no_parentheses_except_multiline
50
40
 
@@ -57,14 +47,5 @@ Style/RescueModifier:
57
47
  Style/SymbolArray:
58
48
  EnforcedStyle: brackets
59
49
 
60
- Style/TrailingCommaInArguments:
61
- EnforcedStyleForMultiline: no_comma
62
-
63
- Style/TrailingCommaInArrayLiteral:
64
- EnforcedStyleForMultiline: no_comma
65
-
66
- Style/TrailingCommaInHashLiteral:
67
- EnforcedStyleForMultiline: no_comma
68
-
69
50
  Style/WordArray:
70
51
  EnforcedStyle: brackets
@@ -6,10 +6,9 @@ sudo: false
6
6
  language: ruby
7
7
  cache: bundler
8
8
  rvm:
9
- - 2.3
10
- - 2.4
11
9
  - 2.5
12
10
  - 2.6
11
+ - 2.7
13
12
  before_install: gem install bundler -v 1.16.5
14
13
  before_script:
15
14
  - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
@@ -0,0 +1,20 @@
1
+ ### 0.4.0 (2021-01-06)
2
+ * Add Varanus::DCV
3
+
4
+ ### 0.3.1 (2020-10-14)
5
+ * Fix issue when Sectigo reports two identical 'Short Life' certs
6
+
7
+ ### 0.3.0 (2020-08-24)
8
+ * Add support for new 'Short Life' certs
9
+
10
+ ### 0.2.1 (2018-11-13)
11
+ * Increase timeout value for SSL requests
12
+
13
+ ### 0.2.0 (2018-11-09)
14
+ * Added Varanus::SSL::CSR.generate
15
+ * Added Reports
16
+ * Varanus::Reports#ssl - list of SSL/TLS certs
17
+ * Varanus::Reports#domains - list of domains validated with DCV
18
+
19
+ ### 0.1.0 (2018-11-07)
20
+ * Initial release
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
@@ -1,61 +1,93 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- varanus (0.1.0)
4
+ varanus (0.4.0)
5
5
  faraday
6
6
  faraday_middleware
7
+ savon (~> 2.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- addressable (2.5.2)
12
- public_suffix (>= 2.0.2, < 4.0)
13
- ast (2.4.0)
14
- crack (0.4.3)
15
- safe_yaml (~> 1.0.0)
16
- docile (1.3.1)
17
- faraday (0.15.3)
12
+ addressable (2.7.0)
13
+ public_suffix (>= 2.0.2, < 5.0)
14
+ akami (1.3.1)
15
+ gyoku (>= 0.4.0)
16
+ nokogiri
17
+ ast (2.4.1)
18
+ builder (3.2.4)
19
+ crack (0.4.5)
20
+ rexml
21
+ docile (1.3.4)
22
+ faraday (1.3.0)
23
+ faraday-net_http (~> 1.0)
18
24
  multipart-post (>= 1.2, < 3)
19
- faraday_middleware (0.12.2)
20
- faraday (>= 0.7.4, < 1.0)
21
- hashdiff (0.3.7)
22
- jaro_winkler (1.5.1)
23
- json (2.1.0)
24
- metaclass (0.0.4)
25
- minitest (5.11.3)
25
+ ruby2_keywords
26
+ faraday-net_http (1.0.0)
27
+ faraday_middleware (1.0.0)
28
+ faraday (~> 1.0)
29
+ gyoku (1.3.1)
30
+ builder (>= 2.1.2)
31
+ hashdiff (1.0.1)
32
+ httpi (2.4.5)
33
+ rack
34
+ socksify
35
+ minitest (5.14.3)
26
36
  minitest-rg (5.2.0)
27
37
  minitest (~> 5.0)
28
- mocha (1.7.0)
29
- metaclass (~> 0.0.1)
30
- multipart-post (2.0.0)
31
- parallel (1.12.1)
32
- parser (2.5.3.0)
33
- ast (~> 2.4.0)
34
- powerpack (0.1.2)
35
- public_suffix (3.0.3)
38
+ mocha (1.12.0)
39
+ multipart-post (2.1.1)
40
+ nokogiri (1.11.1-x86_64-linux)
41
+ racc (~> 1.4)
42
+ nori (2.6.0)
43
+ parallel (1.20.1)
44
+ parser (3.0.0.0)
45
+ ast (~> 2.4.1)
46
+ public_suffix (4.0.6)
47
+ racc (1.5.2)
48
+ rack (2.2.3)
36
49
  rainbow (3.0.0)
37
50
  rake (10.5.0)
38
- rubocop (0.60.0)
39
- jaro_winkler (~> 1.5.1)
51
+ regexp_parser (2.0.3)
52
+ rexml (3.2.4)
53
+ rubocop (1.7.0)
40
54
  parallel (~> 1.10)
41
- parser (>= 2.5, != 2.5.1.1)
42
- powerpack (~> 0.1)
55
+ parser (>= 2.7.1.5)
43
56
  rainbow (>= 2.2.2, < 4.0)
57
+ regexp_parser (>= 1.8, < 3.0)
58
+ rexml
59
+ rubocop-ast (>= 1.2.0, < 2.0)
44
60
  ruby-progressbar (~> 1.7)
45
- unicode-display_width (~> 1.4.0)
46
- ruby-progressbar (1.10.0)
47
- safe_yaml (1.0.4)
48
- simplecov (0.16.1)
61
+ unicode-display_width (>= 1.4.0, < 2.0)
62
+ rubocop-ast (1.4.0)
63
+ parser (>= 2.7.1.5)
64
+ ruby-progressbar (1.11.0)
65
+ ruby2_keywords (0.0.2)
66
+ savon (2.12.1)
67
+ akami (~> 1.2)
68
+ builder (>= 2.1.2)
69
+ gyoku (~> 1.2)
70
+ httpi (~> 2.3)
71
+ nokogiri (>= 1.8.1)
72
+ nori (~> 2.4)
73
+ wasabi (~> 3.4)
74
+ simplecov (0.21.1)
49
75
  docile (~> 1.1)
50
- json (>= 1.8, < 3)
51
- simplecov-html (~> 0.10.0)
52
- simplecov-html (0.10.2)
53
- unicode-display_width (1.4.0)
54
- webmock (3.4.2)
76
+ simplecov-html (~> 0.11)
77
+ simplecov_json_formatter (~> 0.1)
78
+ simplecov-html (0.12.3)
79
+ simplecov_json_formatter (0.1.2)
80
+ socksify (1.7.1)
81
+ unicode-display_width (1.7.0)
82
+ wasabi (3.6.1)
83
+ addressable
84
+ httpi (~> 2.0)
85
+ nokogiri (>= 1.4.2)
86
+ webmock (3.11.0)
55
87
  addressable (>= 2.3.6)
56
88
  crack (>= 0.3.2)
57
- hashdiff
58
- yard (0.9.16)
89
+ hashdiff (>= 0.4.0, < 2.0.0)
90
+ yard (0.9.26)
59
91
 
60
92
  PLATFORMS
61
93
  ruby
@@ -73,4 +105,4 @@ DEPENDENCIES
73
105
  yard
74
106
 
75
107
  BUNDLED WITH
76
- 1.17.1
108
+ 1.17.3
data/README.md CHANGED
@@ -7,24 +7,29 @@ Support for Sectigo's other APIs (S/MIME, code signing, device certificates, etc
7
7
  be added at a later date. Merge requests to add some of this functionality would be
8
8
  greatly appreciated.
9
9
 
10
- ## Installation
10
+ [![Build Status](https://travis-ci.org/duke-automation/varanus.svg?branch=master)](https://travis-ci.org/duke-automation/varanus)
11
+ [![Gem Version](https://badge.fury.io/rb/varanus.svg)](http://badge.fury.io/rb/varanus)
12
+ [![Maintainability](https://api.codeclimate.com/v1/badges/593ef1aa2ba757b5374f/maintainability)](https://codeclimate.com/github/duke-automation/varanus/maintainability)
13
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/593ef1aa2ba757b5374f/test_coverage)](https://codeclimate.com/github/duke-automation/varanus/test_coverage)
11
14
 
12
- Add this line to your application's Gemfile:
15
+ ## Usage
16
+
17
+ #### Generate and sign SSL cert
13
18
 
14
19
  ```ruby
15
- gem 'varanus'
20
+ key, csr = Varanus::SSL::CSR.generate(['example.com'])
21
+ varanus = Varanus.new(customer_uri, username, password)
22
+ id = varanus.ssl.sign csr, org_id
23
+ begin
24
+ cert = varanus.ssl.collect id
25
+ rescue Varanus::Error::StillProcessing
26
+ sleep 1
27
+ retry
28
+ end
29
+ puts key
30
+ puts cert
16
31
  ```
17
32
 
18
- And then execute:
19
-
20
- $ bundle
21
-
22
- Or install it yourself as:
23
-
24
- $ gem install varanus
25
-
26
- ## Usage
27
-
28
33
  #### Sign SSL cert from CSR
29
34
 
30
35
  ```ruby
@@ -46,6 +51,18 @@ puts cert
46
51
  Varanus.new(customer_uri, username, password).ssl.revoke(id)
47
52
  ```
48
53
 
54
+ #### Reports
55
+
56
+ Report on all SSL certs
57
+ ```ruby
58
+ pp Varanus.new(customer_uri, usernams, password).reports.ssl
59
+ ```
60
+
61
+ Report on all domains (DCV status)
62
+ ```ruby
63
+ pp Varanus.new(customer_uri, usernams, password).reports.domains
64
+ ```
65
+
49
66
  #### Authentication
50
67
 
51
68
  Authentication requires the same credentials you use to login to cert-manager.com as well as the ```customer_uri```. If your URL to log into cert-manager.com is https://cert-manager.com/customer/MyCompany then your ```customer_uri``` will be ```'MyCompany'```
@@ -56,6 +73,22 @@ Signing a cert requires specifying an ```org_id```. Each department in cert-man
56
73
 
57
74
  To find the ```org_id```, log into cert-manager.com, go to **Settings** -> **Departments**, then click to edit the department you are interested in. The value you want is in the **OrgID** field.
58
75
 
76
+ ## Installation
77
+
78
+ Add this line to your application's Gemfile:
79
+
80
+ ```ruby
81
+ gem 'varanus'
82
+ ```
83
+
84
+ And then execute:
85
+
86
+ $ bundle
87
+
88
+ Or install it yourself as:
89
+
90
+ $ gem install varanus
91
+
59
92
  ## Development
60
93
 
61
94
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'varanus'
@@ -1,7 +1,7 @@
1
1
  version: '3'
2
2
  services:
3
3
  console:
4
- image: ruby:2.3
4
+ image: ruby:2.5
5
5
  volumes:
6
6
  - .:/app:z
7
7
  hostname: varanus-dev
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Interface for Sectigo's (formerly Comodo CA) API.
2
4
  class Varanus
3
5
  attr_reader :customer_uri, :username, :password
@@ -12,6 +14,33 @@ class Varanus
12
14
  @password = password
13
15
  end
14
16
 
17
+ # :nodoc:
18
+ def connection
19
+ @connection ||= Faraday.new(url: 'https://cert-manager.com/api',
20
+ request: { timeout: 300 }) do |conn|
21
+ conn.request :json
22
+ conn.response :json, content_type: /\bjson$/
23
+
24
+ conn.headers['login'] = @username
25
+ conn.headers['password'] = @password
26
+ conn.headers['customerUri'] = @customer_uri
27
+
28
+ conn.adapter Faraday.default_adapter
29
+ end
30
+ end
31
+
32
+ # Retrive DCV instance
33
+ # @return [Varanus::DCV]
34
+ def dcv
35
+ @dcv ||= DCV.new(self)
36
+ end
37
+
38
+ # Retrieve Reports instance
39
+ # @return [Varanus::Reports]
40
+ def reports
41
+ @reports ||= Reports.new(self)
42
+ end
43
+
15
44
  # Retrive SSL instance
16
45
  # @return [Varanus::SSL]
17
46
  def ssl
@@ -23,9 +52,13 @@ end
23
52
  require 'faraday'
24
53
  require 'faraday_middleware'
25
54
  require 'openssl'
55
+ require 'savon'
26
56
 
27
57
  # Require other files in this gem
28
58
  require 'varanus/error'
59
+ require 'varanus/rest_resource'
60
+ require 'varanus/dcv'
61
+ require 'varanus/reports'
29
62
  require 'varanus/ssl'
30
63
  require 'varanus/ssl/csr'
31
64
  require 'varanus/version'
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An connection to the DCV API. This should not be initialized directly. Instead,
4
+ # use Varanus#dcv
5
+ class Varanus::DCV < Varanus::RestResource
6
+ # Returns an Array of DCV information about searched for domains.
7
+ # This method will automatically page through all results
8
+ # @param opts [Hash] - all opts are optional
9
+ # @option opts [String] :domain Domain to search for
10
+ # @option opts [Integer] :org ID of organization
11
+ # @option opts [Integer] :department ID of department
12
+ # @option opts [String] :dcvStatus
13
+ # @option opts [String] :orderStatus
14
+ # @option opts [Integer] :expiresIn Expires in (days)
15
+ #
16
+ # Results will included an extra 'expiration_date_obj' if 'expirationDate' is in the
17
+ # response
18
+ def search opts = {}
19
+ get_with_size_and_position('dcv/v2/validation', opts).map(&method(:_format_status))
20
+ end
21
+
22
+ # Start domain validation process. This must be called before #submit is called
23
+ # @option domain [String] domain to validate
24
+ # @option type [String] Type of validation. Must be one of 'http', 'https', 'cname',
25
+ # or 'email'
26
+ def start domain, type
27
+ post("dcv/v1/validation/start/domain/#{type}", domain: domain)
28
+ end
29
+
30
+ # Retrieve DCV status for a single domain
31
+ # Result will included an extra 'expiration_date_obj' if 'expirationDate' is in the
32
+ # response
33
+ def status domain
34
+ _format_status(post('dcv/v2/validation/status', domain: domain))
35
+ end
36
+
37
+ # Submit domain validation for verficiation. This must be called after #start
38
+ # @option domain [String] domain to validate
39
+ # @option type [String] Type of validation. Must be one of 'http', 'https', 'cname',
40
+ # or 'email'
41
+ # @option email_address [String] This is required of +type+ is 'email'. Otherwise, it is
42
+ # ignored.
43
+ def submit domain, type, email_address = nil
44
+ if type.to_s == 'email'
45
+ raise ArgumentError, 'email_address must be specified' if email_address.nil?
46
+
47
+ post('dcv/v1/validation/submit/domain/email', domain: domain,
48
+ email: email_address)
49
+ else
50
+ post("dcv/v1/validation/submit/domain/#{type}", domain: domain)
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def _format_status status
57
+ return status unless status['expirationDate']
58
+
59
+ status.merge('expiration_date_obj' =>
60
+ Date.strptime(status['expirationDate'], '%Y-%m-%d'))
61
+ end
62
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Error returned from the Sectigo API
2
4
  class Varanus::Error < StandardError
3
5
  # @return [Integer] Code associated with error
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An connection to the Reports API. This should not be initialized directly. Instead,
4
+ # use Varanus#reports
5
+ class Varanus::Reports
6
+ SSL_CERT_STATUSES = {
7
+ any: 0,
8
+ requested: 1,
9
+ downloaded: 2,
10
+ revoked: 3,
11
+ expired: 4,
12
+ pending_download: 5,
13
+ not_enrolled: 6
14
+ }.freeze
15
+
16
+ # @note Do not call this directly. Use {Varanus#reports} to initialize
17
+ def initialize varanus
18
+ @varanus = varanus
19
+ end
20
+
21
+ def domains
22
+ r = soap_call :get_domain_report, {}
23
+ format_results r[:report_row_domains]
24
+ end
25
+
26
+ # Return report on SSL request
27
+ # @param [opts] [Hash]
28
+ # @option opts [String, Array] :orgs Name(s) of organizations (departments) to limit
29
+ # the report to. If this is unset, results from all departments are returned.
30
+ # @option opts [Symbol] :status (:any) One of :any, :requested, :downloaded, :revoked,
31
+ # :expired, :pending_download, :not_enrolled. :downloaded and :pending_download
32
+ # mean the cert has been enrolled/signed.
33
+ # @return [Array<Hash>]
34
+ def ssl opts = {}
35
+ msg = { organizationNames: nil, certificateStatus: 0 }
36
+
37
+ msg[:organizationNames] = Array(opts[:orgs]).join(',') if opts.include? :orgs
38
+ if opts.include? :status
39
+ msg[:certificateStatus] = SSL_CERT_STATUSES[opts[:status]]
40
+ raise ArgumentError, 'Invalid status' if msg[:certificateStatus].nil?
41
+ end
42
+
43
+ r = soap_call :get_SSL_report, msg
44
+ format_results r[:reports]
45
+ end
46
+
47
+ private
48
+
49
+ def format_results results
50
+ if results.is_a? Hash
51
+ [results]
52
+ else
53
+ results.to_a
54
+ end
55
+ end
56
+
57
+ def savon
58
+ @savon ||= Savon.client(
59
+ namespace: 'http://report.ws.epki.comodo.com/',
60
+ endpoint: 'https://cert-manager.com:443/ws/ReportService',
61
+ log: false
62
+ )
63
+ end
64
+
65
+ def soap_call func, opts = {}
66
+ msg = opts.dup
67
+ msg[:authData] = { customerLoginUri: @varanus.customer_uri, login: @varanus.username,
68
+ password: @varanus.password }
69
+
70
+ result = savon.call func, message: msg
71
+ result.body[(func.to_s.downcase + '_response').to_sym][:return]
72
+ end
73
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ # An abstract class for rest resources
4
+ # Rest resources should not be initialized directly. They should be created by methods
5
+ # on Varanus
6
+ class Varanus::RestResource
7
+ # :nodoc:
8
+ def initialize varanus
9
+ @varanus = varanus
10
+ end
11
+
12
+ private
13
+
14
+ def check_result result
15
+ body = result.body
16
+ return unless body.is_a?(Hash)
17
+ return if body['code'].nil?
18
+
19
+ klass = Varanus::Error
20
+ if body['code'] == 0 && body['description'] =~ /process/
21
+ klass = Varanus::Error::StillProcessing
22
+ end
23
+
24
+ raise klass.new(body['code'], body['description'])
25
+ end
26
+
27
+ def get path, *args
28
+ result = @varanus.connection.get(path, *args)
29
+ check_result result
30
+ result.body
31
+ end
32
+
33
+ # Performs multiple GETs with varying positions to ensure all results are returned.
34
+ def get_with_size_and_position path, opts = {}
35
+ size = opts[:size] || 200
36
+ position = opts[:position] || 0
37
+
38
+ results = []
39
+ loop do
40
+ params = { size: size, position: position }.merge(opts)
41
+ new_results = get(path, params)
42
+ results += new_results
43
+ break if new_results.length < size
44
+
45
+ position += size
46
+ end
47
+
48
+ results
49
+ end
50
+
51
+ def post path, *args
52
+ result = @varanus.connection.post(path, *args)
53
+ check_result result
54
+ result.body
55
+ end
56
+ end
@@ -1,34 +1,40 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # An connection to the SSL/TSL API. This should not be initialized directly. Instead,
2
4
  # use Varanus#ssl
3
- class Varanus::SSL
4
- # @note Do not call this directly. Use {Varanus#ssl} to initialize
5
- def initialize varanus
6
- @varanus = varanus
7
- end
8
-
5
+ class Varanus::SSL < Varanus::RestResource
9
6
  # Returns the option from #certificate_types that best matches the csr.
10
7
  # @param csr [Varanus::SSL::CSR]
11
8
  # @return [Hash] The option from {#certificate_types} that best matches the csr
12
- def certificate_type_from_csr csr
13
- # first exclude certificate types we don't want
14
- types = certificate_types.reject do |ct|
15
- ct['name'] =~ /\b(?:EV|ECC|AMT|Elite)\b/
16
- end
17
- if csr.all_names.any? { |n| n.start_with?('*.') }
18
- types.find { |ct| ct['name'] =~ /Wildcard.+SSL/i }
19
- elsif csr.subject_alt_names.any?
20
- types.find { |ct| ct['name'] =~ /Multi.?Domain.+SSL/i }
21
- else
22
- types.find do |ct|
23
- ct['name'] =~ /\bSSL\b/ && ct['name'] !~ /(?:Multi.?Domain|Wildcard)/i
24
- end
9
+ def certificate_type_from_csr csr, days = nil
10
+ types = certificate_types_standard(days)
11
+ return types.first if types.length <= 1
12
+
13
+ regexp = cert_type_regexp(csr)
14
+ typ = types.find { |ct| ct['name'] =~ regexp } if regexp
15
+ return typ unless typ.nil?
16
+
17
+ types.find do |ct|
18
+ ct['name'] =~ /\bSSL\b/ && ct['name'] !~ /(?:Multi.?Domain|Wildcard)/i
25
19
  end
26
20
  end
27
21
 
28
22
  # Certificate types that can be used to sign a cert
29
23
  # @return [Array<Hash>]
30
24
  def certificate_types
31
- @certificate_types ||= get('types')
25
+ @certificate_types ||= get('ssl/v1/types')
26
+ end
27
+
28
+ # Return Array of certificate types based on standard sorting.
29
+ # @param days [Integer] if present, only include types that support the given day count
30
+ # @return [Array<Hash>]
31
+ def certificate_types_standard days = nil
32
+ types = certificate_types.reject do |ct|
33
+ ct['name'] =~ /\b(?:EV|ECC|AMT|Elite)\b/
34
+ end
35
+ types = types.select! { |t| t['terms'].include? days } unless days.nil?
36
+
37
+ types
32
38
  end
33
39
 
34
40
  # Retrieves the cert.
@@ -46,7 +52,7 @@ class Varanus::SSL
46
52
  # @raise [Varanus::Error::StillProcessing] Cert is still being signed
47
53
  # @return [String] Certificate
48
54
  def collect id, type = 'x509'
49
- get("collect/#{id}/#{type}")
55
+ get("ssl/v1/collect/#{id}/#{type}")
50
56
  end
51
57
 
52
58
  # Revoke an ssl cert
@@ -54,7 +60,7 @@ class Varanus::SSL
54
60
  # @param reason [String] Reason for revoking. Sectigo's API will return an error if it
55
61
  # is blank.
56
62
  def revoke id, reason
57
- post("revoke/#{id}", reason: reason)
63
+ post("ssl/v1/revoke/#{id}", reason: reason)
58
64
  nil
59
65
  end
60
66
 
@@ -73,6 +79,7 @@ class Varanus::SSL
73
79
  # specified, lowest allowed for the cert type will be used)
74
80
  # @return [Integer] Id of SSL cert.
75
81
  def sign csr, org_id, opts = {}
82
+ opts[:days] ||= opts[:years] * 365 unless opts[:years].nil?
76
83
  csr = Varanus::SSL::CSR.new(csr) unless csr.is_a?(Varanus::SSL::CSR)
77
84
  cert_type_id = opts_to_cert_type_id opts, csr
78
85
  args = {
@@ -85,41 +92,17 @@ class Varanus::SSL
85
92
  comments: opts[:comments].to_s[0, 1024],
86
93
  externalRequester: opts[:external_requester].to_s[0, 512]
87
94
  }
88
- post('enroll', args)['sslId']
95
+ post('ssl/v1/enroll', args)['sslId']
89
96
  end
90
97
 
91
98
  private
92
99
 
93
- def check_result result
94
- body = result.body
95
- return unless body.is_a?(Hash)
96
- return if body['code'].nil?
97
-
98
- klass = Varanus::Error
99
- if body['code'] == 0 && body['description'] =~ /process/
100
- klass = Varanus::Error::StillProcessing
101
- end
102
-
103
- raise klass.new(body['code'], body['description'])
104
- end
105
-
106
- def connection
107
- @connection ||= Faraday.new(url: 'https://cert-manager.com/api/ssl/v1') do |conn|
108
- conn.request :json
109
- conn.response :json, content_type: /\bjson$/
100
+ def cert_type_regexp csr
101
+ return /Wildcard.+SSL/i if csr.all_names.any? { |n| n.start_with?('*.') }
110
102
 
111
- conn.headers['login'] = @varanus.username
112
- conn.headers['password'] = @varanus.password
113
- conn.headers['customerUri'] = @varanus.customer_uri
103
+ return /Multi.?Domain.+SSL/i if csr.subject_alt_names.any?
114
104
 
115
- conn.adapter Faraday.default_adapter
116
- end
117
- end
118
-
119
- def get path
120
- result = connection.get(path)
121
- check_result result
122
- result.body
105
+ nil
123
106
  end
124
107
 
125
108
  def opts_to_cert_type_id opts, csr
@@ -129,19 +112,12 @@ class Varanus::SSL
129
112
  when String
130
113
  certificate_types.find { |ct| ct['name'] == opts[:cert_type] }['id']
131
114
  else
132
- certificate_type_from_csr(csr)['id']
115
+ certificate_type_from_csr(csr, opts[:days])['id']
133
116
  end
134
117
  end
135
118
 
136
- def post path, *args
137
- result = connection.post(path, *args)
138
- check_result result
139
- result.body
140
- end
141
-
142
119
  def opts_to_term opts, cert_type_id
143
120
  term = opts[:days]
144
- term ||= opts[:years] * 365 unless opts[:years].nil?
145
121
  term ||= certificate_types.find { |ct| ct['id'] == cert_type_id }['terms'].min
146
122
  term
147
123
  end
@@ -1,23 +1,70 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Wrapper class around a OpenSSL::X509::Request
2
4
  # Provides helper functions to make reading information from the CSR easier
3
5
  class Varanus::SSL::CSR
6
+ # Key size used when calling {.generate}
7
+ DEFAULT_KEY_SIZE = 4096
8
+
9
+ # Generate a CSR
10
+ # @param names [Array<String>] List of DNS names. The first one will be the CN
11
+ # @param key [OpenSSL::PKey::RSA, OpenSSL::PKey::DSA, nil] Secret key for the cert.
12
+ # A DSA key will be generated if +nil+ is passed in.
13
+ # @param subject [Hash] Options for the subject of the cert. By default only CN will
14
+ # be set
15
+ # @return [Array(OpenSSL::PKey::PKey, Varanus::SSL::CSR)] The private key for the cert
16
+ # and CSR
17
+ def self.generate names, key = nil, subject = {}
18
+ raise ArgumentError, 'names cannot be empty' if names.empty?
19
+
20
+ subject = subject.dup
21
+ subject['CN'] = names.first
22
+
23
+ key ||= OpenSSL::PKey::DSA.new(DEFAULT_KEY_SIZE)
24
+
25
+ request = OpenSSL::X509::Request.new
26
+ request.version = 0
27
+ request.subject = OpenSSL::X509::Name.parse subject.map { |k, v| "/#{k}=#{v}" }.join
28
+ request.add_attribute names_to_san_attribute(names)
29
+ request.public_key = key.public_key
30
+
31
+ request.sign(key, OpenSSL::Digest::SHA256.new)
32
+
33
+ [key, Varanus::SSL::CSR.new(request)]
34
+ end
35
+
36
+ # :nodoc:
37
+ # Create a Subject Alternate Names attribute from an Array of dns names
38
+ def self.names_to_san_attribute names
39
+ ef = OpenSSL::X509::ExtensionFactory.new
40
+ name_str = names.map { |n| "DNS:#{n}" }.join(', ')
41
+ ext = ef.create_extension('subjectAltName', name_str, false)
42
+ seq = OpenSSL::ASN1::Sequence([ext])
43
+ ext_req = OpenSSL::ASN1::Set([seq])
44
+ OpenSSL::X509::Attribute.new('extReq', ext_req)
45
+ end
46
+
4
47
  # Common Name (CN) for cert.
5
48
  # @return [String]
6
49
  attr_reader :cn
7
50
 
51
+ # OpenSSL::X509::Request representation of CSR
52
+ # @return [OpenSSL::X509::Request]
53
+ attr_reader :request
54
+
8
55
  # @param csr [String, OpenSSL::X509::Request]
9
56
  def initialize csr
10
57
  if csr.is_a? OpenSSL::X509::Request
11
- @req = csr
58
+ @request = csr
12
59
  @text = csr.to_s
13
60
  else
14
61
  @text = csr.to_s
15
- @req = OpenSSL::X509::Request.new @text
62
+ @request = OpenSSL::X509::Request.new @text
16
63
  end
17
64
 
18
- raise 'Improperly signed CSR' unless @req.verify @req.public_key
65
+ raise 'Improperly signed CSR' unless @request.verify @request.public_key
19
66
 
20
- cn_ref = @req.subject.to_a.find { |a| a[0] == 'CN' }
67
+ cn_ref = @request.subject.to_a.find { |a| a[0] == 'CN' }
21
68
  @cn = cn_ref && cn_ref[1].downcase
22
69
 
23
70
  _parse_sans
@@ -35,13 +82,13 @@ class Varanus::SSL::CSR
35
82
  # Key size for the cert
36
83
  # @return [Integer]
37
84
  def key_size
38
- case @req.public_key
85
+ case @request.public_key
39
86
  when OpenSSL::PKey::RSA
40
- @req.public_key.n.num_bytes * 8
87
+ @request.public_key.n.num_bytes * 8
41
88
  when OpenSSL::PKey::DSA
42
- @req.public_key.p.num_bytes * 8
89
+ @request.public_key.p.num_bytes * 8
43
90
  else
44
- raise "Unknown public key type: #{@req.public_key.class}"
91
+ raise "Unknown public key type: #{@request.public_key.class}"
45
92
  end
46
93
  end
47
94
 
@@ -59,7 +106,7 @@ class Varanus::SSL::CSR
59
106
  private
60
107
 
61
108
  def _parse_sans
62
- extensions = @req.attributes.select { |at| at.oid == 'extReq' }
109
+ extensions = @request.attributes.select { |at| at.oid == 'extReq' }
63
110
  sans_extensions = extensions.flat_map do |extension|
64
111
  extension.value.value[0].value
65
112
  .select { |ext| ext.first.value == 'subjectAltName' }
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Varanus
2
- VERSION = '0.1.0'.freeze
4
+ VERSION = '0.4.0'
3
5
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  lib = File.expand_path('lib', __dir__)
2
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
5
  require 'varanus/version'
4
6
 
5
- # rubocop:disable Metrics/BlockLength
6
7
  Gem::Specification.new do |spec|
7
8
  spec.name = 'varanus'
8
9
  spec.version = Varanus::VERSION
@@ -26,7 +27,7 @@ Gem::Specification.new do |spec|
26
27
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
28
  end
28
29
  spec.require_paths = ['lib']
29
- spec.required_ruby_version = '>= 2.3.0'
30
+ spec.required_ruby_version = '>= 2.5.0'
30
31
 
31
32
  spec.add_development_dependency 'bundler', '~> 1.16'
32
33
  spec.add_development_dependency 'minitest', '~> 5.0'
@@ -40,5 +41,5 @@ Gem::Specification.new do |spec|
40
41
 
41
42
  spec.add_runtime_dependency 'faraday'
42
43
  spec.add_runtime_dependency 'faraday_middleware'
44
+ spec.add_runtime_dependency 'savon', '~> 2.0'
43
45
  end
44
- # rubocop:enable Metrics/BlockLength
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: varanus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Dilda
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-07 00:00:00.000000000 Z
11
+ date: 2021-01-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: savon
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.0'
167
181
  description: |
168
182
  This gem provides an interface to Sectigo's (formerly Comodo CA) APIs for working
169
183
  with SSL/TLS certificates as well as its reporting API.
@@ -180,6 +194,7 @@ files:
180
194
  - ".gitignore"
181
195
  - ".rubocop.yml"
182
196
  - ".travis.yml"
197
+ - CHANGELOG.md
183
198
  - Gemfile
184
199
  - Gemfile.lock
185
200
  - LICENSE.txt
@@ -189,7 +204,10 @@ files:
189
204
  - bin/setup
190
205
  - docker-compose.yml
191
206
  - lib/varanus.rb
207
+ - lib/varanus/dcv.rb
192
208
  - lib/varanus/error.rb
209
+ - lib/varanus/reports.rb
210
+ - lib/varanus/rest_resource.rb
193
211
  - lib/varanus/ssl.rb
194
212
  - lib/varanus/ssl/csr.rb
195
213
  - lib/varanus/version.rb
@@ -206,15 +224,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
206
224
  requirements:
207
225
  - - ">="
208
226
  - !ruby/object:Gem::Version
209
- version: 2.3.0
227
+ version: 2.5.0
210
228
  required_rubygems_version: !ruby/object:Gem::Requirement
211
229
  requirements:
212
230
  - - ">="
213
231
  - !ruby/object:Gem::Version
214
232
  version: '0'
215
233
  requirements: []
216
- rubyforge_project:
217
- rubygems_version: 2.7.8
234
+ rubygems_version: 3.0.3
218
235
  signing_key:
219
236
  specification_version: 4
220
237
  summary: Interface for Sectigo's (formerly Comodo CA) API.