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 +4 -4
- data/lib/cloud/cloud.rb +15 -7
- data/lib/objects/objects.rb +1 -1
- data/lib/tpp/tpp.rb +23 -28
- data/lib/tpp/tpp_token.rb +430 -0
- data/lib/utils/utils.rb +52 -4
- data/lib/vcert.rb +67 -13
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64e42e07f9d888bfb7ce44fb88012806021ddc027d153f85c8d525862f974b30
|
4
|
+
data.tar.gz: '08cce53508dac0bd413f4b5d9ce5279995cdf9c4f09f85be1936e9f6255ab529'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11ac60359d93852be73e6d55440f9453525258f9ee88727fbdf98e8bc899e01264b385589a6b0f77979af8af497689ba152bc94fdc66589a7d12190659c1644c
|
7
|
+
data.tar.gz: 02e16121c702f97653271b3e8b1c172ec1cce57b8e3fbfec2b011a3c87cb5a87f7583b3c6ffdbb8ffb6b162e8379acc1cb2afa94d888da8e9ca9578554e29f98
|
data/lib/cloud/cloud.rb
CHANGED
@@ -2,9 +2,15 @@ require 'json'
|
|
2
2
|
require 'utils/utils'
|
3
3
|
|
4
4
|
class Vcert::CloudConnection
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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(
|
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
|
-
|
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(
|
220
|
+
LOG.info("#{CLOUD_PREFIX} POST HTTP status OK")
|
213
221
|
when 403
|
214
222
|
raise Vcert::AuthenticationError
|
215
223
|
else
|
data/lib/objects/objects.rb
CHANGED
@@ -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
|
data/lib/tpp/tpp.rb
CHANGED
@@ -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
|
59
|
-
raise(
|
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
|
-
|
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
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
79
|
+
LOG.info("Trying to renew certificate #{request.id}")
|
84
80
|
_, d = post(URL_CERTIFICATE_RENEW, renew_req_data)
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
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
|
-
|
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
|
+
|
data/lib/utils/utils.rb
CHANGED
@@ -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
|
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
|
+
|
data/lib/vcert.rb
CHANGED
@@ -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
|
29
|
+
elsif !cloud_token.nil?
|
18
30
|
@conn = CloudConnection.new url, cloud_token
|
19
|
-
elsif user
|
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,
|
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
|
-
|
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
|
82
|
+
t = Time.new + timeout
|
71
83
|
loop do
|
72
|
-
if Time.new
|
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
|
-
|
78
|
-
return certificate
|
79
|
-
end
|
89
|
+
return certificate unless certificate.nil?
|
80
90
|
sleep 10
|
81
91
|
end
|
82
|
-
|
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 '
|
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.
|
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:
|
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
|
-
|
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
|