vcert 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2cf972208fd705b59387e15029d570634fc0c31417c8e675fe876b8cbd4f158
4
- data.tar.gz: 65dc71e3ccc17f3bd5dc210198dcf7e0f7d6b8ab3c7e5e65f6545d3fe66da4bb
3
+ metadata.gz: 64e42e07f9d888bfb7ce44fb88012806021ddc027d153f85c8d525862f974b30
4
+ data.tar.gz: '08cce53508dac0bd413f4b5d9ce5279995cdf9c4f09f85be1936e9f6255ab529'
5
5
  SHA512:
6
- metadata.gz: ee6b93da05a44810c9034ad1aa85a5122bcf8d520d98c2055acbb2185c4a53460a7399e89d51fa9977cb91fb3dfa36ff2f3db7dafd45d46665ca1bc7345f39bc
7
- data.tar.gz: 83b5964f8300fb15704dc9bf06ebf5afbe95a287f7671fd4f128843a5280a6b9a19680f70479207fc87bc6b5e7b19d626aa491da5b68756c4a3b612885bb134b
6
+ metadata.gz: 11ac60359d93852be73e6d55440f9453525258f9ee88727fbdf98e8bc899e01264b385589a6b0f77979af8af497689ba152bc94fdc66589a7d12190659c1644c
7
+ data.tar.gz: 02e16121c702f97653271b3e8b1c172ec1cce57b8e3fbfec2b011a3c87cb5a87f7583b3c6ffdbb8ffb6b162e8379acc1cb2afa94d888da8e9ca9578554e29f98
@@ -2,9 +2,15 @@ require 'json'
2
2
  require 'utils/utils'
3
3
 
4
4
  class Vcert::CloudConnection
5
- def initialize(url, token)
6
- @url = url
7
- @token = token
5
+ CLOUD_PREFIX = '<Cloud>'.freeze
6
+
7
+ def initialize(url, apikey)
8
+ @url = if url.nil?
9
+ 'https://api.venafi.cloud/v1'.freeze
10
+ else
11
+ url
12
+ end
13
+ @apikey = apikey
8
14
  end
9
15
 
10
16
 
@@ -174,10 +180,11 @@ class Vcert::CloudConnection
174
180
  url = uri.path + "/" + url
175
181
 
176
182
 
177
- response = request.get(url, {TOKEN_HEADER_NAME => @token})
183
+ LOG.info("#{CLOUD_PREFIX} GET #{url}")
184
+ response = request.get(url, { TOKEN_HEADER_NAME => @apikey })
178
185
  case response.code.to_i
179
186
  when 200, 201, 202, 409
180
- LOG.info(("HTTP status OK"))
187
+ LOG.info("#{CLOUD_PREFIX} GET HTTP status OK")
181
188
  when 403
182
189
  raise Vcert::AuthenticationError
183
190
  else
@@ -206,10 +213,11 @@ class Vcert::CloudConnection
206
213
  request.use_ssl = true
207
214
  url = uri.path + "/" + url
208
215
  encoded_data = JSON.generate(data)
209
- response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token, "Content-Type" => "application/json", "Accept" => "application/json"})
216
+ LOG.info("#{CLOUD_PREFIX} POST #{url}")
217
+ response = request.post(url, encoded_data, { TOKEN_HEADER_NAME => @apikey, "Content-Type" => "application/json", "Accept" => "application/json" })
210
218
  case response.code.to_i
211
219
  when 200, 201, 202, 409
212
- LOG.info(("HTTP status OK"))
220
+ LOG.info("#{CLOUD_PREFIX} POST HTTP status OK")
213
221
  when 403
214
222
  raise Vcert::AuthenticationError
215
223
  else
@@ -58,7 +58,7 @@ module Vcert
58
58
  subject_attrs.push(['L', @locality])
59
59
  end
60
60
 
61
- LOG.info("Making request from subject array #{subject_attrs.inspect}")
61
+ LOG.info("#{VCERT_PREFIX} Making request from subject array #{subject_attrs.inspect}")
62
62
  subject = OpenSSL::X509::Name.new subject_attrs
63
63
  csr = OpenSSL::X509::Request.new
64
64
  csr.version = 0
@@ -55,43 +55,36 @@ class Vcert::TPPConnection
55
55
  end
56
56
 
57
57
  def renew(request, generate_new_key: true)
58
- if request.id == nil && request.thumbprint == nil
59
- raise("Either request ID or certificate thumbprint is required to renew the certificate")
58
+ if request.id.nil? && request.thumbprint.nil?
59
+ raise('Either request ID or certificate thumbprint is required to renew the certificate')
60
60
  end
61
61
 
62
- if request.thumbprint != nil
63
- request.id = search_by_thumbprint(request.thumbprint)
64
- end
62
+ request.id = search_by_thumbprint(request.thumbprint) unless request.thumbprint.nil?
65
63
  renew_req_data = {"CertificateDN": request.id}
66
64
  if generate_new_key
67
- _, r = post(URL_SECRET_STORE_SEARCH, d = {"Namespace": "config", "Owner": request.id, "VaultType": 512})
68
- vaultId = r["VaultIDs"][0]
69
- _, r = post(URL_SECRET_STORE_RETRIEVE, d = {"VaultID": vaultId})
70
- csr_base64_data = r['Base64Data']
71
- csr_pem = "-----BEGIN CERTIFICATE REQUEST-----\n#{csr_base64_data}\n-----END CERTIFICATE REQUEST-----\n"
72
- parsed_csr = parse_csr_fields(csr_pem)
65
+ csr_base64_data = retrieve request
66
+ LOG.info("Retrieved certificate:\n#{csr_base64_data.cert}")
67
+ parsed_csr = parse_csr_fields_tpp(csr_base64_data.cert)
73
68
  renew_request = Vcert::Request.new(
74
- common_name: parsed_csr.fetch(:CN, nil),
75
- san_dns: parsed_csr.fetch(:DNS, nil),
76
- country: parsed_csr.fetch(:C, nil),
77
- province: parsed_csr.fetch(:ST, nil),
78
- locality: parsed_csr.fetch(:L, nil),
79
- organization: parsed_csr.fetch(:O, nil),
80
- organizational_unit: parsed_csr.fetch(:OU, nil))
69
+ common_name: parsed_csr.fetch(:CN, nil),
70
+ san_dns: parsed_csr.fetch(:DNS, nil),
71
+ country: parsed_csr.fetch(:C, nil),
72
+ province: parsed_csr.fetch(:ST, nil),
73
+ locality: parsed_csr.fetch(:L, nil),
74
+ organization: parsed_csr.fetch(:O, nil),
75
+ organizational_unit: parsed_csr.fetch(:OU, nil)
76
+ )
81
77
  renew_req_data.merge!(PKCS10: renew_request.csr)
82
78
  end
83
- LOG.info("Trying to renew certificate %s" % request.id)
79
+ LOG.info("Trying to renew certificate #{request.id}")
84
80
  _, d = post(URL_CERTIFICATE_RENEW, renew_req_data)
85
- if d.key?('Success')
86
- if generate_new_key
87
- return request.id, renew_request.private_key
88
- else
89
- return request.id, nil
90
- end
81
+ raise 'Certificate renew error' unless d.key?('Success')
82
+
83
+ if generate_new_key
84
+ [request.id, renew_request.private_key]
91
85
  else
92
- raise "Certificate renew error"
86
+ [request.id, nil]
93
87
  end
94
-
95
88
  end
96
89
 
97
90
  private
@@ -140,6 +133,7 @@ class Vcert::TPPConnection
140
133
  end
141
134
  url = uri.path + url
142
135
  encoded_data = JSON.generate(data)
136
+ LOG.info("#{Vcert::VCERT_PREFIX} POST request: #{request.inspect}\n\tpath: #{url}\n\tdata: #{encoded_data}")
143
137
  response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token[0], "Content-Type" => "application/json"})
144
138
  data = JSON.parse(response.body)
145
139
  return response.code.to_i, data
@@ -156,7 +150,8 @@ class Vcert::TPPConnection
156
150
  request.ca_file = @trust_bundle
157
151
  end
158
152
  url = uri.path + url
159
- response = request.get(url, {TOKEN_HEADER_NAME => @token[0]})
153
+ LOG.info("#{Vcert::VCERT_PREFIX} GET request: #{request.inspect}\n\tpath: #{url}")
154
+ response = request.get(url, { TOKEN_HEADER_NAME => @token[0] })
160
155
  # TODO: check valid json
161
156
  data = JSON.parse(response.body)
162
157
  return response.code.to_i, data
@@ -0,0 +1,430 @@
1
+ require 'json'
2
+ require 'date'
3
+ require 'base64'
4
+ require 'utils/utils'
5
+
6
+
7
+ class Vcert::TokenConnection
8
+
9
+ # @param [String] url
10
+ # @param [String] access_token
11
+ # @param [String] refresh_token
12
+ # @param [String] user
13
+ # @param [String] password
14
+ # @param [String] trust_bundle
15
+ def initialize(url, access_token: nil, refresh_token: nil, user: nil, password: nil , trust_bundle: nil)
16
+ @url = normalize_url url
17
+ @auth = Vcert::Authentication.new access_token: access_token, refresh_token: refresh_token, user: user, password: password
18
+ @trust_bundle = trust_bundle
19
+ end
20
+
21
+ # @param [String] zone_tag
22
+ # @param [Vcert::Request] request
23
+ def request(zone_tag, request)
24
+ data = { PolicyDN: policy_dn(zone_tag),
25
+ PKCS10: request.csr,
26
+ ObjectName: request.friendly_name,
27
+ DisableAutomaticRenewal: 'true' }
28
+ code, response = post URL_CERTIFICATE_REQUESTS, data
29
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200
30
+
31
+ request.id = response['CertificateDN']
32
+ end
33
+
34
+ # @param [Request] request
35
+ # @return [Vcert::Certificate]
36
+ def retrieve(request)
37
+ retrieve_request = { CertificateDN: request.id, Format: 'base64', IncludeChain: 'true', RootFirstOrder: 'false' }
38
+ code, response = post URL_CERTIFICATE_RETRIEVE, retrieve_request
39
+ return nil if code != 200
40
+
41
+ full_chain = Base64.decode64(response['CertificateData'])
42
+ LOG.info("#{Vcert::VCERT_PREFIX} cert data decoded: #{full_chain}")
43
+ cert = parse_full_chain full_chain
44
+ cert.private_key = request.private_key if cert.private_key == nil
45
+ cert
46
+ end
47
+
48
+ # @param [String] zone_tag
49
+ # @return [Vcert::Policy]
50
+ def policy(zone_tag)
51
+ code, response = post URL_ZONE_CONFIG, { PolicyDN: policy_dn(zone_tag) }
52
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200
53
+
54
+ parse_policy_response response, zone_tag
55
+ end
56
+
57
+ # @param [String] zone_tag
58
+ # @return [Vcert::ZoneConfiguration]
59
+ def zone_configuration(zone_tag)
60
+ LOG.info("#{Vcert::VCERT_PREFIX} Reading zone configuration: #{zone_tag}")
61
+ code, response = post URL_ZONE_CONFIG, { PolicyDN: policy_dn(zone_tag) }
62
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if code != 200
63
+
64
+ parse_zone_configuration response
65
+ end
66
+
67
+ def renew(request, generate_new_key: true)
68
+ if request.id.nil? && request.thumbprint.nil?
69
+ raise('Either request ID or certificate thumbprint is required to renew the certificate')
70
+ end
71
+
72
+ request.id = search_by_thumbprint(request.thumbprint) unless request.thumbprint.nil?
73
+ renew_req_data = { CertificateDN: request.id }
74
+ if generate_new_key
75
+ csr_base64_data = retrieve request
76
+ LOG.info("#{Vcert::VCERT_PREFIX} Retrieved certificate:\n#{csr_base64_data.cert}")
77
+ parsed_csr = parse_csr_fields_tpp(csr_base64_data.cert)
78
+ renew_request = Vcert::Request.new(
79
+ common_name: parsed_csr.fetch(:CN, nil),
80
+ san_dns: parsed_csr.fetch(:DNS, nil),
81
+ country: parsed_csr.fetch(:C, nil),
82
+ province: parsed_csr.fetch(:ST, nil),
83
+ locality: parsed_csr.fetch(:L, nil),
84
+ organization: parsed_csr.fetch(:O, nil),
85
+ organizational_unit: parsed_csr.fetch(:OU, nil)
86
+ )
87
+ renew_req_data.merge!(PKCS10: renew_request.csr)
88
+ end
89
+ LOG.info("#{Vcert::VCERT_PREFIX} Trying to renew certificate #{request.id}")
90
+ _, d = post(URL_CERTIFICATE_RENEW, renew_req_data)
91
+ raise 'Certificate renew error' unless d.key?('Success')
92
+
93
+ if generate_new_key
94
+ [request.id, renew_request.private_key]
95
+ else
96
+ [request.id, nil]
97
+ end
98
+ end
99
+
100
+ # @param [Vcert::Authentication] authentication
101
+ # @return [Vcert::TokenInfo]
102
+ def get_access_token(authentication: nil)
103
+ @auth = authentication unless authentication.nil?
104
+ return refresh_access_token unless @auth.refresh_token.nil?
105
+
106
+ return nil if @auth.user.nil? || @auth.password.nil?
107
+
108
+ request_data = {
109
+ username: @auth.user,
110
+ password: @auth.password,
111
+ client_id: @auth.client_id,
112
+ scope: @auth.scope,
113
+ state: ''
114
+ }
115
+ status, response = post(URL_AUTHORIZE_TOKEN, request_data, check_token: false, include_headers: false)
116
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}" if status != 200
117
+
118
+ token_info = parse_access_token_data response
119
+ update_authentication(token_info)
120
+ token_info
121
+ end
122
+
123
+ # @return [Vcert::TokenInfo]
124
+ def refresh_access_token
125
+ request_data = {
126
+ refresh_token: @auth.refresh_token,
127
+ client_id: @auth.client_id
128
+ }
129
+
130
+ status, response = post(URL_REFRESH_TOKEN, request_data, check_token: false, include_headers: false)
131
+ if status != 200
132
+ raise Vcert::ServerUnexpectedBehaviorError, "Server returns #{code} status on refreshing access token"
133
+ end
134
+
135
+ token_info = parse_access_token_data(response)
136
+ update_authentication(token_info)
137
+ token_info
138
+ end
139
+
140
+ # @return []
141
+ def revoke_access_token
142
+ status, response = get(URL_REVOKE_TOKEN, check_token: false)
143
+ if status != 200
144
+ raise Vcert::ServerUnexpectedBehaviorError, "Server returns #{status} status on revoking access token"
145
+ end
146
+
147
+ response
148
+ end
149
+
150
+ private
151
+
152
+ API_TOKEN_URL = 'vedauth/'.freeze
153
+ API_BASE_URL = 'vedsdk/'.freeze
154
+
155
+ URL_AUTHORIZE_TOKEN = "#{API_TOKEN_URL}authorize/oauth".freeze
156
+ URL_REFRESH_TOKEN = "#{API_TOKEN_URL}authorize/token".freeze
157
+ URL_REVOKE_TOKEN = "#{API_TOKEN_URL}revoke/token".freeze
158
+
159
+ URL_AUTHORIZE = "#{API_BASE_URL}authorize/".freeze
160
+ URL_CERTIFICATE_REQUESTS = "#{API_BASE_URL}certificates/request".freeze
161
+ URL_ZONE_CONFIG = "#{API_BASE_URL}certificates/checkpolicy".freeze
162
+ URL_CERTIFICATE_RETRIEVE = "#{API_BASE_URL}certificates/retrieve".freeze
163
+ URL_CERTIFICATE_SEARCH = "#{API_BASE_URL}certificates/".freeze
164
+ URL_CERTIFICATE_RENEW = "#{API_BASE_URL}certificates/renew".freeze
165
+ URL_SECRET_STORE_SEARCH = "#{API_BASE_URL}SecretStore/LookupByOwner".freeze
166
+ URL_SECRET_STORE_RETRIEVE = "#{API_BASE_URL}SecretStore/Retrieve".freeze
167
+ HEADER_NAME_AUTHORIZATION = 'Authorization'.freeze
168
+ ALL_ALLOWED_REGEX = '.*'.freeze
169
+
170
+ # @param [String] url
171
+ # @param [Hash] data
172
+ # @param [boolean] check_token
173
+ # @param [boolean] include_headers
174
+ # @return [Integer, Array]
175
+ def post(url, data, check_token: true, include_headers: true)
176
+ validate_token if check_token
177
+
178
+ uri = URI.parse(@url)
179
+ request = Net::HTTP.new(uri.host, uri.port)
180
+ request.use_ssl = true
181
+ request.ca_file = @trust_bundle unless @trust_bundle.nil?
182
+ url = uri.path + url
183
+ encoded_data = JSON.generate(data)
184
+ headers = {
185
+ 'Content-Type' => 'application/json'
186
+ }
187
+ headers.merge!(HEADER_NAME_AUTHORIZATION => build_authorization_header_value) if include_headers
188
+ LOG.info("#{Vcert::VCERT_PREFIX} POST request: #{request.inspect}\n\tpath: #{url}\n\tdata: #{encoded_data}\n\theaders: #{headers}")
189
+ response = request.post(url, encoded_data, headers)
190
+ LOG.info("#{Vcert::VCERT_PREFIX} POST response: [#{response.body}]")
191
+ data = JSON.parse(response.body)
192
+ [response.code.to_i, data]
193
+ end
194
+
195
+ # @param [String] url
196
+ # @param [boolean] check_token
197
+ # @param [boolean] include_headers
198
+ # @return [Integer, Array]
199
+ def get(url, check_token: true, include_headers: true)
200
+ validate_token if check_token
201
+
202
+ uri = URI.parse(@url)
203
+ request = Net::HTTP.new(uri.host, uri.port)
204
+ request.use_ssl = true
205
+ request.ca_file = @trust_bundle unless @trust_bundle.nil?
206
+
207
+ url = uri.path + url
208
+
209
+ headers = {}
210
+ headers = { HEADER_NAME_AUTHORIZATION => build_authorization_header_value } if include_headers
211
+ LOG.info("#{Vcert::VCERT_PREFIX} GET request: #{request.inspect}\n\tpath: #{url}\n\theaders: [#{headers}]")
212
+ response = request.get(url, headers)
213
+ LOG.info("#{Vcert::VCERT_PREFIX} GET response with status [#{response.code}]. #{response.inspect} ")
214
+ # TODO: check valid json
215
+ data = JSON.parse(response.body) unless response.body.nil? || response.body.eql?('')
216
+ [response.code.to_i, data]
217
+ end
218
+
219
+ # @param [String] zone
220
+ # @return [String]
221
+ def policy_dn(zone)
222
+ raise Vcert::ClientBadDataError, 'Zone should not be empty' if zone.nil? || zone == ''
223
+ return zone if zone =~ /^\\\\VED\\\\Policy/
224
+
225
+ if zone =~ /^\\\\/
226
+ "\\VED\\Policy#{zone}"
227
+ else
228
+ "\\VED\\Policy\\#{zone}"
229
+ end
230
+ end
231
+
232
+ # @param [String] url
233
+ # @return [String]
234
+ def normalize_url(url)
235
+ if url.index('http://') == 0
236
+ url = "https://#{url[7..-1]}"
237
+ elsif url.index('https://') != 0
238
+ url = "https://#{url}"
239
+ end
240
+ url += '/' unless url.end_with?('/')
241
+ raise Vcert::ClientBadDataError, 'Invalid URL for TPP' unless url =~ %r{^https://[a-z\d]+[-a-z\d.]+[a-z\d][:\d]*/$}
242
+
243
+ url
244
+ end
245
+
246
+ # @param [String] full_chain
247
+ # @return [Vcert::Certificate]
248
+ def parse_full_chain(full_chain)
249
+ pem_list = parse_pem_list(full_chain)
250
+ Vcert::Certificate.new cert: pem_list[0], chain: pem_list[1..-1], private_key: nil
251
+ end
252
+
253
+ # @param [String] thumbprint
254
+ # @return [String]
255
+ def search_by_thumbprint(thumbprint)
256
+ # thumbprint = re.sub(r'[^\dabcdefABCDEF]', "", thumbprint)
257
+ thumbprint = thumbprint.upcase
258
+ status, data = get(URL_CERTIFICATE_SEARCH + "?Thumbprint=#{thumbprint}")
259
+ # TODO: check that data have valid certificate in it
260
+ raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status}. Message:\n #{data.body.to_s}" if status != 200
261
+
262
+ # TODO: check valid data
263
+ data['Certificates'][0]['DN']
264
+ end
265
+
266
+ # @param [Hash] data
267
+ # @return [Vcert::ZoneConfiguration]
268
+ def parse_zone_configuration(data)
269
+ LOG.info("#{Vcert::VCERT_PREFIX} Parsing Zone configuration: #{data}")
270
+ s = data['Policy']['Subject']
271
+ country = Vcert::CertField.new s['Country']['Value'], locked: s['Country']['Locked']
272
+ state = Vcert::CertField.new s['State']['Value'], locked: s['State']['Locked']
273
+ city = Vcert::CertField.new s['City']['Value'], locked: s['City']['Locked']
274
+ organization = Vcert::CertField.new s['Organization']['Value'], locked: s['Organization']['Locked']
275
+ organizational_unit = Vcert::CertField.new s['OrganizationalUnit']['Values'], locked: s['OrganizationalUnit']['Locked']
276
+ key_type = Vcert::KeyType.new data['Policy']['KeyPair']['KeyAlgorithm']['Value'], data['Policy']['KeyPair']['KeySize']['Value']
277
+ Vcert::ZoneConfiguration.new country: country, province: state, locality: city, organization: organization,
278
+ organizational_unit: organizational_unit, key_type: Vcert::CertField.new(key_type)
279
+ end
280
+
281
+ # @param [Hash] response
282
+ # @param [String] zone_tag
283
+ # @return [Vcert::Policy]
284
+ def parse_policy_response(response, zone_tag)
285
+ def addStartEnd(s)
286
+ s = '^' + s unless s.index('^') == 0
287
+ s = s + '$' unless s.end_with?('$')
288
+ s
289
+ end
290
+
291
+ def escape(value)
292
+ if value.kind_of? Array
293
+ return value.map { |v| addStartEnd(Regexp.escape(v)) }
294
+ else
295
+ return addStartEnd(Regexp.escape(value))
296
+ end
297
+ end
298
+
299
+ policy = response['Policy']
300
+ s = policy['Subject']
301
+ if policy['WhitelistedDomains'].empty?
302
+ subjectCNRegex = [ALL_ALLOWED_REGEX]
303
+ else
304
+ if policy['WildcardsAllowed']
305
+ subjectCNRegex = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w\-*]+' + Regexp.escape('.' + d)) }
306
+ else
307
+ subjectCNRegex = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w\-]+' + Regexp.escape('.' + d)) }
308
+ end
309
+ end
310
+ if s['OrganizationalUnit']['Locked']
311
+ subjectOURegexes = escape(s['OrganizationalUnit']['Values'])
312
+ else
313
+ subjectOURegexes = [ALL_ALLOWED_REGEX]
314
+ end
315
+ if s['Organization']['Locked']
316
+ subjectORegexes = [escape(s['Organization']['Value'])]
317
+ else
318
+ subjectORegexes = [ALL_ALLOWED_REGEX]
319
+ end
320
+ if s['City']['Locked']
321
+ subjectLRegexes = [escape(s['City']['Value'])]
322
+ else
323
+ subjectLRegexes = [ALL_ALLOWED_REGEX]
324
+ end
325
+ if s['State']['Locked']
326
+ subjectSTRegexes = [escape(s['State']['Value'])]
327
+ else
328
+ subjectSTRegexes = [ALL_ALLOWED_REGEX]
329
+ end
330
+ if s['Country']['Locked']
331
+ subjectCRegexes = [escape(s['Country']['Value'])]
332
+ else
333
+ subjectCRegexes = [ALL_ALLOWED_REGEX]
334
+ end
335
+ if policy['SubjAltNameDnsAllowed']
336
+ if policy['WhitelistedDomains'].length == 0
337
+ dnsSanRegExs = [ALL_ALLOWED_REGEX]
338
+ else
339
+ dnsSanRegExs = policy['WhitelistedDomains'].map { |d| addStartEnd('[\w-]+' + Regexp.escape('.' + d)) }
340
+ end
341
+ else
342
+ dnsSanRegExs = []
343
+ end
344
+ if policy['SubjAltNameIpAllowed']
345
+ ipSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
346
+ else
347
+ ipSanRegExs = []
348
+ end
349
+ if policy['SubjAltNameEmailAllowed']
350
+ emailSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
351
+ else
352
+ emailSanRegExs = []
353
+ end
354
+ if policy['SubjAltNameUriAllowed']
355
+ uriSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
356
+ else
357
+ uriSanRegExs = []
358
+ end
359
+
360
+ if policy['SubjAltNameUpnAllowed']
361
+ upnSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
362
+ else
363
+ upnSanRegExs = []
364
+ end
365
+ unless policy['KeyPair']['KeyAlgorithm']['Locked']
366
+ key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new('rsa', s) } + Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new('ecdsa', c) }
367
+ else
368
+ if policy['KeyPair']['KeyAlgorithm']['Value'] == 'RSA'
369
+ if policy['KeyPair']['KeySize']['Locked']
370
+ key_types = [Vcert::KeyType.new('rsa', policy['KeyPair']['KeySize']['Value'])]
371
+ else
372
+ key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new('rsa', s) }
373
+ end
374
+ elsif policy['KeyPair']['KeyAlgorithm']['Value'] == 'EC'
375
+ if policy['KeyPair']['EllipticCurve']['Locked']
376
+ curve = { 'p224' => 'secp224r1', 'p256' => 'prime256v1', 'p521' => 'secp521r1' }[policy['KeyPair']['EllipticCurve']['Value'].downcase]
377
+ key_types = [Vcert::KeyType.new('ecdsa', curve)]
378
+ else
379
+ key_types = Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new('ecdsa', c) }
380
+ end
381
+ end
382
+ end
383
+
384
+ Vcert::Policy.new(policy_id: policy_dn(zone_tag), name: zone_tag, system_generated: false, creation_date: nil,
385
+ subject_cn_regexes: subjectCNRegex, subject_o_regexes: subjectORegexes,
386
+ subject_ou_regexes: subjectOURegexes, subject_st_regexes: subjectSTRegexes,
387
+ subject_l_regexes: subjectLRegexes, subject_c_regexes: subjectCRegexes, san_regexes: dnsSanRegExs,
388
+ key_types: key_types)
389
+ end
390
+
391
+ # @param [Hash] response_data
392
+ # @return [Vcert::TokenInfo]
393
+ def parse_access_token_data(response_data)
394
+ Vcert::TokenInfo.new response_data['access_token'],
395
+ response_data['expires'],
396
+ response_data['identity'],
397
+ response_data['refresh_token'],
398
+ response_data['refresh_until'],
399
+ response_data['scope'],
400
+ response_data['token_type']
401
+ end
402
+
403
+ # @param [Vcert::TokenInfo] token_info
404
+ def update_authentication(token_info)
405
+ return unless token_info.instance_of?(Vcert::TokenInfo)
406
+
407
+ @auth.access_token = token_info.access_token
408
+ @auth.refresh_token = token_info.refresh_token
409
+ @auth.token_expiration_date = token_info.expires
410
+ end
411
+
412
+ def validate_token
413
+ if @auth.access_token.nil?
414
+ LOG.info("#{Vcert::VCERT_PREFIX} Requesting new Access Token with credentials.")
415
+ get_access_token
416
+ elsif !@auth.token_expiration_date.nil? && @auth.token_expiration_date < Time.now.to_i
417
+ raise Vcert::AuthenticationError, 'Access Token expired. No refresh token provided.' if @auth.refresh_token.nil?
418
+
419
+ LOG.info("#{Vcert::VCERT_PREFIX} Requesting new Access Token with refresh token.")
420
+ refresh_access_token
421
+ end
422
+ end
423
+
424
+ # @return [String]
425
+ def build_authorization_header_value
426
+ return "Bearer #{@auth.access_token}" unless @auth.access_token.nil?
427
+ end
428
+ end
429
+
430
+
@@ -19,16 +19,16 @@ def parse_pem_list(multiline)
19
19
  end
20
20
 
21
21
  def parse_csr_fields(csr)
22
- LOG.info("Trying to parse CSR:\n#{csr}")
22
+ LOG.info("#{Vcert::VCERT_PREFIX} Trying to parse CSR:\n#{csr}")
23
23
  csr_obj = OpenSSL::X509::Request.new(csr)
24
24
  result = Hash.new
25
25
 
26
26
  subject_array = csr_obj.subject.to_a
27
- subject_array.map { |x|
27
+ subject_array.map do |x|
28
28
  if x[1] != ""
29
29
  result[x[0].to_sym] = x[1]
30
30
  end
31
- }
31
+ end
32
32
 
33
33
  attributes = csr_obj.attributes
34
34
 
@@ -93,6 +93,54 @@ def parse_csr_fields(csr)
93
93
  end
94
94
 
95
95
 
96
- LOG.info("Parsed CSR fields:\n #{result.inspect}")
96
+ LOG.info("#{Vcert::VCERT_PREFIX} Parsed CSR fields:\n #{result.inspect}")
97
97
  return result
98
98
  end
99
+
100
+ def parse_csr_fields_tpp(csr)
101
+ LOG.info("#{Vcert::VCERT_PREFIX} Trying to parse CSR:\n#{csr}")
102
+ csr_obj = OpenSSL::X509::Certificate.new(csr)
103
+ result = Hash.new
104
+
105
+ subject_array = csr_obj.subject.to_a
106
+ subject_array.map do |x|
107
+ result[x[0].to_sym] = x[1] unless x[1] == ''
108
+ end
109
+
110
+ LOG.info("#{Vcert::VCERT_PREFIX} Parsed CSR fields:\n #{result.inspect}")
111
+ result
112
+ end
113
+
114
+ CLIENT_ID = 'vcert-sdk'.freeze
115
+ SCOPE = 'certificate:manage,revoke'.freeze
116
+
117
+ module Vcert
118
+ class Authentication
119
+ attr_accessor :access_token, :refresh_token, :user, :password, :token_expiration_date, :client_id, :scope
120
+
121
+ def initialize (access_token: nil, refresh_token: nil, user: nil, password: nil, expiration_date: nil, client_id: CLIENT_ID, scope: SCOPE)
122
+ @access_token = access_token
123
+ @refresh_token = refresh_token
124
+ @user = user
125
+ @password = password
126
+ @token_expiration_date = expiration_date
127
+ @client_id = client_id
128
+ @scope = scope
129
+ end
130
+ end
131
+
132
+ class TokenInfo
133
+ attr_reader :access_token, :refresh_token, :refresh_until, :expires, :identity, :scope, :token_type
134
+
135
+ def initialize (access_token, expires, identity, refresh_token, refresh_until, scope, token_type)
136
+ @access_token = access_token
137
+ @refresh_token = refresh_token
138
+ @refresh_until = refresh_until
139
+ @expires = expires
140
+ @identity = identity
141
+ @scope = scope
142
+ @token_type = token_type
143
+ end
144
+ end
145
+ end
146
+
@@ -10,16 +10,28 @@ module Vcert
10
10
  class ClientBadDataError < VcertError; end
11
11
  class ValidationError < VcertError; end
12
12
 
13
+ VCERT_PREFIX = '<Vcert>'.freeze
14
+
15
+ # <b>DEPRECATED</b>
16
+ # Please use <tt>VenafiConnection<tt> instead.
17
+ #
18
+ # This class provides an easy way to configure and retrieve a connector for a Venafi platform.
19
+ # Usage:
20
+ # TPP:
21
+ # Connection.new url: TPP_URL, user: TPP_USER, password: TPP_PASSWORD, trust_bundle: TRUST_BUNDLE
22
+ # CLoud:
23
+ # Connection.new token: CLOUD_API_KEY
24
+ #
13
25
  class Connection
14
26
  def initialize(url: nil, user: nil, password: nil, cloud_token: nil, trust_bundle:nil, fake: false)
15
27
  if fake
16
28
  @conn = FakeConnection.new
17
- elsif cloud_token != nil
29
+ elsif !cloud_token.nil?
18
30
  @conn = CloudConnection.new url, cloud_token
19
- elsif user != nil && password != nil && url != nil then
31
+ elsif !user.nil? && !password.nil? && !url.nil?
20
32
  @conn = TPPConnection.new url, user, password, trust_bundle:trust_bundle
21
33
  else
22
- raise ClientBadDataError, "Invalid credentials list"
34
+ raise ClientBadDataError, 'Invalid credentials list'
23
35
  end
24
36
  end
25
37
 
@@ -61,31 +73,73 @@ module Vcert
61
73
  # @param [Integer] timeout
62
74
  # @return [Certificate]
63
75
  def request_and_retrieve(req, zone, timeout: TIMEOUT)
76
+ LOG.info("#{VCERT_PREFIX} Requesting and retrieving Certificate: [#{req}], [#{zone}]")
64
77
  request zone, req
65
- cert = retrieve_loop(req, timeout: timeout)
66
- return cert
78
+ retrieve_loop(req, timeout: timeout)
67
79
  end
68
80
 
69
81
  def retrieve_loop(req, timeout: TIMEOUT)
70
- t = Time.new() + timeout
82
+ t = Time.new + timeout
71
83
  loop do
72
- if Time.new() > t
73
- LOG.info("Waiting certificate #{req.id}")
84
+ if Time.new > t
85
+ LOG.info("#{VCERT_PREFIX} Waiting certificate #{req.id}")
74
86
  break
75
87
  end
76
88
  certificate = @conn.retrieve(req)
77
- if certificate != nil
78
- return certificate
79
- end
89
+ return certificate unless certificate.nil?
80
90
  sleep 10
81
91
  end
82
- return nil
92
+ nil
83
93
  end
84
94
 
85
95
  end
96
+
97
+ # This class provides an easy way to configure and retrieve a connector for a Venafi platform.
98
+ # It supports the use of token authentication for TPP, and drops the use of user/password credentials.
99
+ # Usage:
100
+ # TPP:
101
+ # VenafiConnection.new url: TPP_TOKEN_URL, user: TPPUSER, password: TPPPASSWORD, trust_bundle: TRUST_BUNDLE
102
+ # VenafiConnection.new url: TPP_TOKEN_URL, access_token: TPP_ACCESS_TOKEN, trust_bundle: TRUST_BUNDLE
103
+ # VenafiConnection.new url: TPP_TOKEN_URL, refresh_token: TPP_REFRESH_TOKEN, trust_bundle: TRUST_BUNDLE
104
+ # CLoud:
105
+ # VenafiConnection.new token: CLOUD_API_KEY
106
+ #
107
+ class VenafiConnection < Connection
108
+
109
+ def initialize(url: nil, access_token: nil, refresh_token: nil, user: nil, password: nil, apikey: nil, trust_bundle:nil, fake: false)
110
+ if fake
111
+ @conn = FakeConnection.new
112
+ elsif !apikey.nil?
113
+ @conn = CloudConnection.new url, apikey
114
+ elsif (!access_token.nil? || !refresh_token.nil? || (!user.nil? && !password.nil?)) && !url.nil?
115
+ @conn = TokenConnection.new url, access_token: access_token, refresh_token: refresh_token, user: user,
116
+ password: password, trust_bundle: trust_bundle
117
+ else
118
+ raise ClientBadDataError, 'Invalid credentials list'
119
+ end
120
+ end
121
+
122
+ # @param [Vcert::Authentication] authentication
123
+ # @return [Vcert::TokenInfo]
124
+ def get_access_token(authentication: nil)
125
+ @conn.get_access_token authentication: authentication if @conn.is_a?(Vcert::TokenConnection)
126
+ end
127
+
128
+ # @return [Vcert::TokenInfo]
129
+ def refresh_access_token
130
+ @conn.refresh_access_token if @conn.is_a?(Vcert::TokenConnection)
131
+ end
132
+
133
+ # @return []
134
+ def revoke_access_token
135
+ @conn.revoke_access_token if @conn.is_a?(Vcert::TokenConnection)
136
+ end
137
+ end
86
138
  end
87
139
 
88
140
  require 'fake/fake'
89
141
  require 'cloud/cloud'
90
142
  require 'tpp/tpp'
91
- require 'objects/objects'
143
+ require 'tpp/tpp_token'
144
+ require 'objects/objects'
145
+
metadata CHANGED
@@ -1,15 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vcert
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Subbotin
8
8
  - Alexander Rykalin
9
+ - Russel Vela
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2019-09-18 00:00:00.000000000 Z
13
+ date: 2021-01-05 00:00:00.000000000 Z
13
14
  dependencies: []
14
15
  description: Ruby client for Venafi Cloud and Trust Protection Platform
15
16
  email: opensource@venafi.com
@@ -21,6 +22,7 @@ files:
21
22
  - lib/fake/fake.rb
22
23
  - lib/objects/objects.rb
23
24
  - lib/tpp/tpp.rb
25
+ - lib/tpp/tpp_token.rb
24
26
  - lib/utils/utils.rb
25
27
  - lib/vcert.rb
26
28
  homepage: https://rubygems.org/gems/vcert
@@ -42,8 +44,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
44
  - !ruby/object:Gem::Version
43
45
  version: '0'
44
46
  requirements: []
45
- rubyforge_project:
46
- rubygems_version: 2.7.6
47
+ rubygems_version: 3.0.3
47
48
  signing_key:
48
49
  specification_version: 4
49
50
  summary: Library for Venafi products