vcert 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: aca37924c54553b285f4a71cb5dd665a67899ee035024acb0fdae853d0fda053
4
+ data.tar.gz: 16683bfaaf3d43b2b6b2f899fee72a13860121cf8be85975b4808fd687b0b165
5
+ SHA512:
6
+ metadata.gz: 32b44cb26e66b63fc39dd4066470e79363b3de5af723d6ad5e9a63eaf6447568041bcacbd71ebfebc5beff36174204c201d0346e47cfc25994da33f37b63cf11
7
+ data.tar.gz: 29c0bdf783fd33860c32685d9f7465b1a788acb3a60371b9e998f809c46182da36e2315f7567a07b5a073221678f411796e53db71e7d00534f57ff11d98747ce
@@ -0,0 +1,281 @@
1
+ require 'json'
2
+ require 'utils/utils'
3
+
4
+ class Vcert::CloudConnection
5
+ def initialize(url, token)
6
+ @url = url
7
+ @token = token
8
+ end
9
+
10
+
11
+ def request(zone_tag, request)
12
+ zone_id = get_zoneId_by_tag(zone_tag)
13
+ _, data = post(URL_CERTIFICATE_REQUESTS, {:zoneId => zone_id, :certificateSigningRequest => request.csr})
14
+ LOG.debug("Raw response to certificate request:")
15
+ LOG.debug(JSON.pretty_generate(data))
16
+ request.id = data['certificateRequests'][0]["id"]
17
+ request
18
+ end
19
+
20
+ def retrieve(request)
21
+ LOG.info(("Getting certificate status for ID %s" % request.id))
22
+ status, data = get(URL_CERTIFICATE_STATUS % request.id)
23
+ if [200, 409].include? status
24
+ case data['status']
25
+ when CERT_STATUS_PENDING, CERT_STATUS_REQUESTED
26
+ LOG.info(("Certificate status is: %s" % data['status']))
27
+ return nil
28
+ when CERT_STATUS_FAILED
29
+ raise Vcert::ServerUnexpectedBehaviorError, "Certificate issue status is FAILED"
30
+ when CERT_STATUS_ISSUED
31
+ status, full_chain = get(URL_CERTIFICATE_RETRIEVE % request.id + "?chainOrder=#{CHAIN_OPTION_ROOT_LAST}&format=PEM")
32
+ if status == 200
33
+ cert = parse_full_chain full_chain
34
+ if cert.private_key == nil
35
+ cert.private_key = request.private_key
36
+ end
37
+ return cert
38
+ else
39
+ LOG.error("Can't issue certificate: #{full_chain}")
40
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{status}"
41
+ end
42
+ else
43
+ raise Vcert::ServerUnexpectedBehaviorError, "Unknown certificate status #{data['status']}"
44
+ end
45
+ end
46
+ end
47
+
48
+ def renew(request, generate_new_key: true)
49
+ puts("Trying to renew certificate")
50
+ if request.id == nil && request.thumbprint == nil
51
+ raise Vcert::ClientBadDataError, "Either request ID or certificate thumbprint is required to renew the certificate"
52
+ end
53
+ if request.thumbprint != nil
54
+ manage_id = search_by_thumbprint(request.thumbprint)
55
+ end
56
+ if request.id != nil
57
+ prev_request = get_cert_status(request)
58
+ manage_id = prev_request[:manage_id]
59
+ zone = prev_request[:zoneId]
60
+ end
61
+ if manage_id == nil
62
+ raise Vcert::VcertError, "Can't find the existing certificate"
63
+ end
64
+
65
+ status, data = get(URL_MANAGED_CERTIFICATE_BY_ID % manage_id)
66
+ if status == 200
67
+ request.id = data['latestCertificateRequestId']
68
+ else
69
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{status}"
70
+ end
71
+
72
+ if zone == nil
73
+ prev_request = get_cert_status(request)
74
+ zone = prev_request[:zoneId]
75
+ end
76
+
77
+ d = {existingManagedCertificateId: manage_id, zoneId: zone}
78
+ if request.csr?
79
+ d.merge!(certificateSigningRequest: request.csr)
80
+ d.merge!(reuseCSR: false)
81
+ elsif generate_new_key
82
+ parsed_csr = parse_csr_fields(prev_request[:csr])
83
+ renew_request = Vcert::Request.new(
84
+ common_name: parsed_csr[:CN],
85
+ san_dns: parsed_csr[:DNS],
86
+ country: parsed_csr[:C],
87
+ province: parsed_csr[:ST],
88
+ locality: parsed_csr[:L],
89
+ organization: parsed_csr[:O],
90
+ organizational_unit: parsed_csr[:OU])
91
+ d.merge!(certificateSigningRequest: renew_request.csr)
92
+ else
93
+ d.merge!(reuseCSR: true)
94
+ end
95
+
96
+ status, data = post(URL_CERTIFICATE_REQUESTS, data = d)
97
+ if status == 201
98
+ if generate_new_key
99
+ return data['certificateRequests'][0]['id'], renew_request.private_key
100
+ else
101
+ return data['certificateRequests'][0]['id'], nil
102
+ end
103
+
104
+ else
105
+ raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status} Message: #{data}"
106
+ end
107
+
108
+ end
109
+
110
+ def zone_configuration(tag)
111
+ if tag.to_s.strip.empty?
112
+ raise Vcert::ClientBadDataError, "Zone should not be empty"
113
+ end
114
+ LOG.info("Getting configuration for zone #{tag}")
115
+ _, data = get(URL_ZONE_BY_TAG % tag)
116
+ template_id = data['certificateIssuingTemplateId']
117
+ _, data = get(URL_TEMPLATE_BY_ID % template_id)
118
+ kt = Vcert::KeyType.new data['keyTypes'][0]["keyType"], data['keyTypes'][0]["keyLengths"][0].to_i
119
+ z = Vcert::ZoneConfiguration.new(
120
+ country: Vcert::CertField.new(""),
121
+ province: Vcert::CertField.new(""),
122
+ locality: Vcert::CertField.new(""),
123
+ organization: Vcert::CertField.new(""),
124
+ organizational_unit: Vcert::CertField.new(""),
125
+ key_type: Vcert::CertField.new(kt, locked: true),
126
+ )
127
+ return z
128
+ end
129
+
130
+ def policy(zone_id)
131
+ unless zone_id
132
+ raise Vcert::ClientBadDataError, "Zone should be not nil"
133
+ end
134
+ status, data = get(URL_PROJECT_ZONE_DETAILS % zone_id)
135
+ if status != 200
136
+ raise Vcert::ServerUnexpectedBehaviorError, "Invalid status getting issuing template: %s for zone %s" % status, zone_id
137
+ end
138
+ template_id = data['certificateIssuingTemplateId']
139
+ status, data = get(URL_TEMPLATE_BY_ID % template_id)
140
+ if status != 200
141
+ raise Vcert::ServerUnexpectedBehaviorError, "Invalid status getting policy: %s for issuing template %s" % status, template_id
142
+ end
143
+ parse_policy_responce_to_object(data)
144
+ end
145
+
146
+ private
147
+
148
+ TOKEN_HEADER_NAME = "tppl-api-key"
149
+ CHAIN_OPTION_ROOT_FIRST = "ROOT_FIRST"
150
+ CHAIN_OPTION_ROOT_LAST = "EE_FIRST"
151
+ CERT_STATUS_REQUESTED = 'REQUESTED'
152
+ CERT_STATUS_PENDING = 'PENDING'
153
+ CERT_STATUS_FAILED = 'FAILED'
154
+ CERT_STATUS_ISSUED = 'ISSUED'
155
+ URL_ZONE_BY_TAG = "zones/tag/%s"
156
+ URL_PROJECT_ZONE_DETAILS = "projectzones/%s"
157
+ URL_TEMPLATE_BY_ID = "certificateissuingtemplates/%s"
158
+ URL_CERTIFICATE_REQUESTS = "certificaterequests"
159
+ URL_CERTIFICATE_STATUS = URL_CERTIFICATE_REQUESTS + "/%s"
160
+ URL_CERTIFICATE_RETRIEVE = URL_CERTIFICATE_REQUESTS + "/%s/certificate"
161
+ URL_CERTIFICATE_SEARCH = "certificatesearch"
162
+ URL_MANAGED_CERTIFICATES = "managedcertificates"
163
+ URL_MANAGED_CERTIFICATE_BY_ID = URL_MANAGED_CERTIFICATES + "/%s"
164
+
165
+ def get_zoneId_by_tag(tag)
166
+ _, data = get(URL_ZONE_BY_TAG % tag)
167
+ data['id']
168
+ end
169
+
170
+ def get(url)
171
+ uri = URI.parse(@url)
172
+ request = Net::HTTP.new(uri.host, uri.port)
173
+ request.use_ssl = true
174
+ url = uri.path + "/" + url
175
+
176
+
177
+ response = request.get(url, {TOKEN_HEADER_NAME => @token})
178
+ case response.code.to_i
179
+ when 200, 201, 202, 409
180
+ LOG.info(("HTTP status OK"))
181
+ when 403
182
+ raise Vcert::AuthenticationError
183
+ else
184
+ raise Vcert::ServerUnexpectedBehaviorError, "Unexpected code #{response.code} for URL #{url}. Message: #{response.body}"
185
+ end
186
+ case response.header['content-type']
187
+ when "application/json"
188
+ begin
189
+ data = JSON.parse(response.body)
190
+ rescue JSON::ParserError
191
+ raise Vcert::ServerUnexpectedBehaviorError, "Invalid JSON"
192
+ end
193
+ when "text/plain"
194
+ data = response.body
195
+ else
196
+ raise Vcert::ServerUnexpectedBehaviorError, "Unexpected content-type #{response.header['content-type']}"
197
+ end
198
+ # rescue *ALL_NET_HTTP_ERRORS
199
+ return response.code.to_i, data
200
+ # end
201
+ end
202
+
203
+ def post(url, data)
204
+ uri = URI.parse(@url)
205
+ request = Net::HTTP.new(uri.host, uri.port)
206
+ request.use_ssl = true
207
+ url = uri.path + "/" + url
208
+ encoded_data = JSON.generate(data)
209
+ response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token, "Content-Type" => "application/json", "Accept" => "application/json"})
210
+ case response.code.to_i
211
+ when 200, 201, 202, 409
212
+ LOG.info(("HTTP status OK"))
213
+ when 403
214
+ raise Vcert::AuthenticationError
215
+ else
216
+ raise Vcert::ServerUnexpectedBehaviorError, "Unexpected code #{response.code} for URL #{url}. Message: #{response.body}"
217
+ end
218
+ data = JSON.parse(response.body)
219
+ return response.code.to_i, data
220
+ end
221
+
222
+ def parse_full_chain(full_chain)
223
+ pems = parse_pem_list(full_chain)
224
+ Vcert::Certificate.new(
225
+ cert: pems[0],
226
+ chain: pems[1..-1]
227
+ )
228
+ end
229
+
230
+ def parse_policy_responce_to_object(d)
231
+ key_types = []
232
+ d['keyTypes'].each { |kt| key_types.push(['keyType']) }
233
+ Vcert::Policy.new(policy_id: d['id'],
234
+ name: d['name'],
235
+ system_generated: d['systemGenerated'],
236
+ creation_date: d['creationDate'],
237
+ subject_cn_regexes: d['subjectCNRegexes'],
238
+ subject_o_regexes: d['subjectORegexes'],
239
+ subject_ou_regexes: d['subjectOURegexes'],
240
+ subject_st_regexes: d['subjectSTRegexes'],
241
+ subject_l_regexes: d['subjectLRegexes'],
242
+ subject_c_regexes: d['subjectCValues'],
243
+ san_regexes: d['sanRegexes'],
244
+ key_types: key_types)
245
+ end
246
+
247
+ def search_by_thumbprint(thumbprint)
248
+ # thumbprint = re.sub(r'[^\dabcdefABCDEF]', "", thumbprint)
249
+ thumbprint = thumbprint.upcase
250
+ status, data = post(URL_CERTIFICATE_SEARCH, data = {"expression": {operands: [
251
+ {field: "fingerprint", operator: "MATCH", value: thumbprint}]}})
252
+ # TODO: check that data have valid certificate in it
253
+ if status != 200
254
+ raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status}. Message: #{data.body.to_s}"
255
+ end
256
+ # TODO: check data
257
+ manageId = data['certificates'][0]['managedCertificateId']
258
+ LOG.info("Found existing certificate with ID #{manageId}")
259
+ return manageId
260
+ end
261
+
262
+ def get_cert_status(request)
263
+ status, d = get(URL_CERTIFICATE_STATUS % request.id)
264
+ if status == 200
265
+ request_status = Hash.new
266
+ request_status[:status] = d['status']
267
+ request_status[:subject] = d['subjectDN'] or d['subjectCN'][0]
268
+ request_status[:subject_alt_names] = d['subjectAlternativeNamesByType']
269
+ request_status[:zoneId] = d['zoneId']
270
+ request_status[:manage_id] = d['managedCertificateId']
271
+ request_status[:csr] = d['certificateSigningRequest']
272
+ request_status[:key_lenght] = d['keyLength']
273
+ request_status[:key_type] = d['keyType']
274
+ return request_status
275
+ else
276
+ raise Vcert::ServerUnexpectedBehaviorError, "status: #{status}"
277
+ end
278
+ end
279
+
280
+ end
281
+
@@ -0,0 +1,130 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+
5
+
6
+ class Vcert::FakeConnection
7
+ def initialize()
8
+ @cert_cache = {}
9
+ end
10
+
11
+ def request(zone_tag, request)
12
+ request.id = Base64.encode64(request.csr)
13
+ end
14
+
15
+ def retrieve(request)
16
+ csrpem = Base64.decode64(request.id)
17
+ csr = OpenSSL::X509::Request.new(csrpem)
18
+ root_ca = OpenSSL::X509::Certificate.new ROOT_CA
19
+ root_key = OpenSSL::PKey::RSA.new ROOT_KEY
20
+ cert = OpenSSL::X509::Certificate.new
21
+ cert.version = 2
22
+ cert.serial = (Time.new.to_f() * 100).to_i
23
+ cert.subject = csr.subject
24
+ cert.issuer = root_ca.subject
25
+ cert.not_before = Time.now
26
+ cert.public_key = csr.public_key
27
+ cert.not_after = cert.not_before + 1 * 365 * 24 * 60 * 60
28
+ # todo: add extensions
29
+ cert.sign(root_key, OpenSSL::Digest::SHA256.new)
30
+ c = Vcert::Certificate.new cert:cert.to_pem, chain: [ROOT_CA], private_key: request.private_key
31
+ thumbprint = OpenSSL::Digest::SHA1.new(cert.to_der).to_s
32
+ @cert_cache[thumbprint] = cert
33
+ c
34
+ end
35
+
36
+ def policy(zone_tag)
37
+ key_types = [1024, 2048, 4096, 8192].map {|s| Vcert::KeyType.new("rsa", s) } + Vcert::SUPPORTED_CURVES.map {|c| Vcert::KeyType.new("ecdsa", c) }
38
+ Vcert::Policy.new(policy_id: zone_tag, name: zone_tag, system_generated: false, creation_date: nil,
39
+ subject_cn_regexes: [".*"], subject_o_regexes: [".*"],
40
+ subject_ou_regexes: [".*"], subject_st_regexes: [".*"],
41
+ subject_l_regexes: [".*"], subject_c_regexes: [".*"], san_regexes: [".*"],
42
+ key_types: key_types)
43
+ end
44
+
45
+ def zone_configuration(zone_tag)
46
+ Vcert::ZoneConfiguration.new(
47
+ country: Vcert::CertField.new("US"),
48
+ province: Vcert::CertField.new("Utah"),
49
+ locality: Vcert::CertField.new("Salt Lake City"),
50
+ organization: Vcert::CertField.new("Venafi"),
51
+ organizational_unit: Vcert::CertField.new("DevOps"),
52
+ key_type: Vcert::CertField.new(Vcert::KeyType.new("rsa", 2048), locked: true),
53
+ )
54
+ end
55
+
56
+ def renew(request, generate_new_key: true)
57
+ if request.thumbprint
58
+ if generate_new_key
59
+ new_key = OpenSSL::PKey::RSA.new 2048
60
+ csr = OpenSSL::X509::Request.new
61
+ csr.subject = @cert_cache[request.thumbprint].subject
62
+ csr.public_key = new_key.public_key
63
+ csr.sign new_key, OpenSSL::Digest::SHA256.new
64
+ return Base64.encode64(csr.to_pem), new_key.to_pem
65
+ else
66
+ raise Vcert::VcertError, "can not be implemented"
67
+ end
68
+ end
69
+ unless generate_new_key
70
+ return request.id, request.private_key
71
+ end
72
+ new_key = OpenSSL::PKey::RSA.new 2048
73
+ csr = OpenSSL::X509::Request.new Base64.decode64(request.id)
74
+ csr.public_key = new_key.public_key
75
+ csr.sign new_key, OpenSSL::Digest::SHA256.new
76
+ return Base64.encode64(csr.to_pem), new_key.to_pem
77
+ end
78
+ private
79
+ ROOT_CA = "-----BEGIN CERTIFICATE-----
80
+ MIIDYDCCAkigAwIBAgIBATANBgkqhkiG9w0BAQsFADBBMRMwEQYKCZImiZPyLGQB
81
+ GRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGVmVuYWZpMRIwEAYDVQQDDAlWZW5hZmkg
82
+ Q0EwHhcNMTkxMDIzMTIzNzExWhcNMjkxMDIwMTIzNzExWjBBMRMwEQYKCZImiZPy
83
+ LGQBGRYDb3JnMRYwFAYKCZImiZPyLGQBGRYGVmVuYWZpMRIwEAYDVQQDDAlWZW5h
84
+ ZmkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4NfBNSVzOqj8A
85
+ E6+NSuEufK5EhgE/o8KPCTtM7qvMCa0X3P+T0I5IUDMXz5Yi/TXjhTANolYEz2RS
86
+ 9u5Pdv5dvBCe1hwMhXdLlcxEhLtJrjnQvBUTqzuFUBausvRZvE3GwozoZncakEEP
87
+ OqTvGEpqjbnF1uiIJf944kjIq9oWnPudatOOlCFtpA1TG1mLJg8jcCrbeiXvRo9d
88
+ /dyg7B7URgKdxMukdjCkUMqUwArlu7mnv1kN6UdzhfFRCH0MBH4pisVze9XP/QrV
89
+ MJ+gMlultrpDFuMpiruJyPeapDnGloxtWKQ/aQHlnwwaX8fcaA3ADKUAFr66nFT2
90
+ 14fgmByFAgMBAAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG
91
+ MB0GA1UdDgQWBBSFrxYL7Fd190DFUZFJ4ahzptV7tTAfBgNVHSMEGDAWgBSFrxYL
92
+ 7Fd190DFUZFJ4ahzptV7tTANBgkqhkiG9w0BAQsFAAOCAQEAZsRtfC+4j6RWbWDZ
93
+ 3eRabfY4Nl7z3q3hL2cYo98ZQVb5wssYwKPpX8/DFMnmgiObe0Na5zqaB9PxDBpZ
94
+ 4wkRFpRfQ2oS13dzPMDdW0/IHhWfyWZiUqdpIacWIHvyuotZ/k3IZLT7zc9Lbs2p
95
+ FPmW5/Oe7lNRu3xgqaMuhRid8i426c+fR5YPf32umZtwRnB5hFFE9IlFBPpRl5Z7
96
+ GDJKJBgZi9+sk13a3CM8Zn0A9fiCaRASDPKRVhWPjDJwzLy44WF+1GiMZRCR79MX
97
+ 8B/rNxkrKpWJmjkQj3jqmQOOnp7+QwdZ5OIV7NNlc/Kx2QDV9QV+hnRPetXmsVfy
98
+ y5KjSQ==
99
+ -----END CERTIFICATE-----
100
+ "
101
+ ROOT_KEY = "-----BEGIN RSA PRIVATE KEY-----
102
+ MIIEpAIBAAKCAQEAuDXwTUlczqo/ABOvjUrhLnyuRIYBP6PCjwk7TO6rzAmtF9z/
103
+ k9COSFAzF8+WIv0144UwDaJWBM9kUvbuT3b+XbwQntYcDIV3S5XMRIS7Sa450LwV
104
+ E6s7hVAWrrL0WbxNxsKM6GZ3GpBBDzqk7xhKao25xdboiCX/eOJIyKvaFpz7nWrT
105
+ jpQhbaQNUxtZiyYPI3Aq23ol70aPXf3coOwe1EYCncTLpHYwpFDKlMAK5bu5p79Z
106
+ DelHc4XxUQh9DAR+KYrFc3vVz/0K1TCfoDJbpba6QxbjKYq7icj3mqQ5xpaMbVik
107
+ P2kB5Z8MGl/H3GgNwAylABa+upxU9teH4JgchQIDAQABAoIBAEa/YIUuUdiFhiCv
108
+ btLjGUzTUdK7bKtWZ5irwPyxBYYdiT8K/5VzmdGoC5dvgIf7m8DAHE6ANG0wgaVj
109
+ dO9MEjFJ01BNhwRAFisPYx5Fo/COW2IRej7NmtR+h9ecnz//lBdsDNYM1F19XZ9N
110
+ tJ6nQ51cxSZ4fWIcxdtVfQKlDeN0y7ZanHsltv4cpCCuVaVk8uzI6O5E8dNbDmpR
111
+ Wotefps+9HHREa6uL39SbzU+S5SkdcVofs6/g/eL6RsP4D6VcF+qdBEQ38ffDaSZ
112
+ Q1hOwfTFf6Ahv4HhpCVC6vlIpXJi/RyUu6yqbmInvcXfGHvYoMYhud/lasYZDAm2
113
+ RdGB0gECgYEA7OHnaAqOaaYnE2rQkOAn3ZzA7VRhJgMvPgKUeQoUHDhXJOD/6wWD
114
+ 1/wYd4BKiQyODi5cAlOkLvdrcRlnrGOKRiLgemyNrG2GzJTzgrkJc1IOpRnl2QKw
115
+ w1k0Xrv2qDpoebKMqhxjgEnYp+ddVB7kG561vl7JfhjQidrVqx6y/0UCgYEAxxPQ
116
+ M4myF4JKHpxU4+21JKxB3bTY7CWmKM1ZBon/ZFVKd8bsq7wt0tWP83oxWq5b11o+
117
+ AnWx4CsQQyl7EWanrDoPag2SjfI/q+AySq0VUNjcAsvPLfT2Q7WQQxEMQGoabZ7j
118
+ u8uxkNvZmDy5XGDjcZVdANq2kynC++v1AtwO3EECgYEAnmeeaCuO+kU6ojhuikLr
119
+ Rb3aIZqocFP21n/BK4O62PgwBiBT4qTIerlA30CyFx2HLSKBMqkeBK49cd8sPdI+
120
+ mBIgjJ1ky+ZeGxaMFGGKWUyJMIy18D1lWOyhIayOEAcm8CKe/+6F9zbqo7UK6wLR
121
+ RUsHe+tE0IblhRoKgijASAUCgYEAra7KkXxLhRklw0kPAwBLbqBeoqf6LSS3r5dg
122
+ WUUiLQ4Adzl1GGuH6w5plbmAv6Wo+NyBhzHZq0LG4GGbPlY6aRcKhbMrrm2wQSrL
123
+ lb0mALACWuonaefyxqXsI6cG8lffkM3zz87prwEv+RLZgRACvwDZ8Dng2cmwlIuK
124
+ 6iDFUkECgYBL4U3E+trPuVEQm3Nj9nyIFV1efKDZ+uehPSvglYdO+ca7UgwA0btZ
125
+ iAAu3L3yP3TSJ6SbLV3hX1VoyyNQpUr+ODZo7VWf+MdZDh9XiiuLNqr5tx7yGhOb
126
+ 1JXZ1QPAZeRvALui6fdj5yCHjTvayEL2nzPAFbgFrgVYRoF8L9O0gg==
127
+ -----END RSA PRIVATE KEY-----
128
+ "
129
+
130
+ end
@@ -0,0 +1,359 @@
1
+ require 'openssl'
2
+ require "logger"
3
+ LOG = Logger.new(STDOUT)
4
+
5
+ OpenSSL::PKey::EC.send(:alias_method, :private?, :private_key?)
6
+
7
+ module Vcert
8
+ SUPPORTED_CURVES = ["secp224r1", "prime256v1", "secp521r1"]
9
+ class Request
10
+ attr_accessor :id, :thumbprint
11
+ attr_reader :common_name, :country, :province, :locality, :organization, :organizational_unit, :san_dns,:key_type
12
+
13
+ def initialize(common_name: nil, private_key: nil, key_type: nil,
14
+ organization: nil, organizational_unit: nil, country: nil, province: nil, locality: nil, san_dns: nil,
15
+ friendly_name: nil, csr: nil)
16
+ @common_name = common_name
17
+ @private_key = private_key
18
+ #todo: parse private key and set public
19
+ if key_type != nil && !key_type.instance_of?(KeyType)
20
+ raise Vcert::ClientBadDataError, "key_type bad type. should be Vcert::KeyType. for example KeyType('rsa', 2048)"
21
+ end
22
+ @key_type = key_type
23
+ @organization = organization
24
+ @organizational_unit = organizational_unit
25
+ @country = country
26
+ @province = province
27
+ @locality = locality
28
+ @san_dns = san_dns
29
+ @friendly_name = friendly_name
30
+ @id = nil
31
+ @csr = csr
32
+ end
33
+
34
+ def generate_csr
35
+ if @private_key == nil
36
+ generate_private_key
37
+ end
38
+ subject_attrs = [
39
+ ['CN', @common_name]
40
+ ]
41
+ if @organization != nil
42
+ subject_attrs.push(['O', @organization])
43
+ end
44
+ if @organizational_unit != nil
45
+ if @organizational_unit.kind_of?(Array)
46
+ @organizational_unit.each { |ou| subject_attrs.push(['OU', ou]) }
47
+ else
48
+ subject_attrs.push(['OU', @organizational_unit])
49
+ end
50
+ end
51
+ if @country != nil
52
+ subject_attrs.push(['C', @country])
53
+ end
54
+ if @province != nil
55
+ subject_attrs.push(['ST', @province])
56
+ end
57
+ if @locality != nil
58
+ subject_attrs.push(['L', @locality])
59
+ end
60
+
61
+ LOG.info("Making request from subject array #{subject_attrs.inspect}")
62
+ subject = OpenSSL::X509::Name.new subject_attrs
63
+ csr = OpenSSL::X509::Request.new
64
+ csr.version = 0
65
+ csr.subject = subject
66
+ csr.public_key = @public_key
67
+ if @san_dns != nil
68
+ unless @san_dns.kind_of?(Array)
69
+ @san_dns = [@san_dns]
70
+ end
71
+ #TODO: add check that san_dns is an array
72
+ san_list = @san_dns.map { |domain| "DNS:#{domain}" }
73
+ extensions = [
74
+ OpenSSL::X509::ExtensionFactory.new.create_extension('subjectAltName', san_list.join(','))
75
+ ]
76
+ attribute_values = OpenSSL::ASN1::Set [OpenSSL::ASN1::Sequence(extensions)]
77
+ [
78
+ OpenSSL::X509::Attribute.new('extReq', attribute_values),
79
+ OpenSSL::X509::Attribute.new('msExtReq', attribute_values)
80
+ ].each do |attribute|
81
+ csr.add_attribute attribute
82
+ end
83
+ end
84
+ csr.sign @private_key, OpenSSL::Digest::SHA256.new # todo: changable sign alg
85
+ @csr = csr.to_pem
86
+ end
87
+
88
+ def csr
89
+ # TODO: find a way to pass CSR generation if renew is requested
90
+ if @csr == nil
91
+ generate_csr
92
+ end
93
+ @csr
94
+ end
95
+
96
+ def csr?
97
+ @csr != nil
98
+ end
99
+
100
+ def private_key
101
+ if @private_key == nil
102
+ generate_private_key
103
+ end
104
+ @private_key.to_pem
105
+ end
106
+
107
+ def friendly_name
108
+ if @friendly_name != nil
109
+ return @friendly_name
110
+ end
111
+ @common_name
112
+ end
113
+
114
+ # @param [ZoneConfiguration] zone_config
115
+ def update_from_zone_config(zone_config)
116
+ if zone_config.country.locked || (!@country && !!zone_config.country.value)
117
+ @country = zone_config.country.value
118
+ end
119
+ if zone_config.locality.locked || (!@locality && !!zone_config.locality.value)
120
+ @locality = zone_config.locality.value
121
+ end
122
+ if zone_config.province.locked || (!@province && !!zone_config.province.value)
123
+ @province = zone_config.province.value
124
+ end
125
+ if zone_config.organization.locked || (!@organization && !!zone_config.organization.value)
126
+ @organization = zone_config.organization.value
127
+ end
128
+ if zone_config.organizational_unit.locked || (!@organizational_unit && !!zone_config.organizational_unit.value)
129
+ @organizational_unit = zone_config.organizational_unit.value
130
+ end
131
+ if zone_config.key_type.locked || (@key_type == nil && zone_config.key_type.value != nil)
132
+ @key_type = zone_config.key_type.value
133
+ end
134
+ end
135
+
136
+ private
137
+
138
+
139
+ def generate_private_key
140
+ if @key_type == nil
141
+ @key_type = DEFAULT_KEY_TYPE
142
+ end
143
+ if @key_type.type == "rsa"
144
+ @private_key = OpenSSL::PKey::RSA.new @key_type.option
145
+ @public_key = @private_key.public_key
146
+ elsif @key_type.type == "ecdsa"
147
+ @private_key, @public_key = OpenSSL::PKey::EC.new(@key_type.option), OpenSSL::PKey::EC.new(@key_type.option)
148
+ @private_key.generate_key
149
+ @public_key.public_key = @private_key.public_key
150
+ end
151
+ end
152
+ end
153
+
154
+ class Certificate
155
+ attr_accessor :private_key
156
+ attr_reader :cert, :chain
157
+
158
+ def initialize(cert: nil, chain: nil, private_key: nil)
159
+ @cert = cert
160
+ @chain = chain
161
+ @private_key = private_key
162
+ end
163
+ end
164
+
165
+ class Policy
166
+ attr_reader :policy_id, :name, :system_generated, :creation_date
167
+
168
+ def initialize(policy_id:, name:, system_generated:, creation_date:, subject_cn_regexes:, subject_o_regexes:,
169
+ subject_ou_regexes:, subject_st_regexes:, subject_l_regexes:, subject_c_regexes:, san_regexes:,
170
+ key_types:)
171
+
172
+ @policy_id = policy_id
173
+ @name = name
174
+ @system_generated = system_generated
175
+ @creation_date = creation_date
176
+ @subject_cn_regexes = subject_cn_regexes
177
+ @subject_c_regexes = subject_c_regexes
178
+ @subject_st_regexes = subject_st_regexes
179
+ @subject_l_regexes = subject_l_regexes
180
+ @subject_o_regexes = subject_o_regexes
181
+ @subject_ou_regexes = subject_ou_regexes
182
+ @san_regexes = san_regexes
183
+ @key_types = key_types
184
+ end
185
+
186
+ # @param [Request] request
187
+ def simple_check_request(request)
188
+ if request.csr?
189
+ csr = parse_csr_fields(request.csr)
190
+ unless component_is_valid?(csr[:CN], @subject_cn_regexes)
191
+ raise ValidationError, "Common name #{csr[:CN]} doesnt match #{@subject_cn_regexes}"
192
+ end
193
+ unless component_is_valid?(request.san_dns, @san_regexes, optional: true)
194
+ raise ValidationError, "SANs #{csr[:DNS]} doesnt match #{ @san_regexes }"
195
+ end
196
+ else
197
+ unless component_is_valid?(request.common_name, @subject_cn_regexes)
198
+ raise ValidationError, "Common name #{request.common_name} doesnt match #{@subject_cn_regexes}"
199
+ end
200
+ unless component_is_valid?(request.san_dns, @san_regexes, optional: true)
201
+ raise ValidationError, "SANs #{request.san_dns} doesnt match #{ @san_regexes }"
202
+ end
203
+ end
204
+ end
205
+
206
+ # @param [Request] request
207
+ def check_request(request)
208
+ simple_check_request(request)
209
+ if request.csr?
210
+ csr = parse_csr_fields(request.csr)
211
+ unless component_is_valid?(csr[:C], @subject_c_regexes)
212
+ raise ValidationError, "Country #{csr[:C]} doesnt match #{@subject_c_regexes}"
213
+ end
214
+ unless component_is_valid?(csr[:ST], @subject_st_regexes)
215
+ raise ValidationError, "Province #{csr[:ST]} doesnt match #{@subject_st_regexes}"
216
+ end
217
+ unless component_is_valid?(csr[:L], @subject_l_regexes)
218
+ raise ValidationError, "Locality #{csr[:L]} doesnt match #{@subject_l_regexes}"
219
+ end
220
+ unless component_is_valid?(csr[:O], @subject_o_regexes)
221
+ raise ValidationError, "Organization #{csr[:O]} doesnt match #{@subject_o_regexes}"
222
+ end
223
+ unless component_is_valid?(csr[:OU], @subject_ou_regexes)
224
+ raise ValidationError, "Organizational unit #{csr[:OU]} doesnt match #{@subject_ou_regexes}"
225
+ end
226
+ #todo: add uri, upn, ip, email
227
+ unless is_key_type_is_valid?(csr[:key_type], @key_types)
228
+ raise ValidationError, "Key Type #{csr[:key_type]} doesnt match allowed #{@key_types}"
229
+ end
230
+ else
231
+ # subject
232
+ unless component_is_valid?(request.country, @subject_c_regexes)
233
+ raise ValidationError, "Country #{request.country} doesnt match #{@subject_c_regexes}"
234
+ end
235
+ unless component_is_valid?(request.province, @subject_st_regexes)
236
+ raise ValidationError, "Province #{request.province} doesnt match #{@subject_st_regexes}"
237
+ end
238
+ unless component_is_valid?(request.locality, @subject_l_regexes)
239
+ raise ValidationError, "Locality #{request.locality} doesnt match #{@subject_l_regexes}"
240
+ end
241
+ unless component_is_valid?(request.organization, @subject_o_regexes)
242
+ raise ValidationError, "Organization #{request.organization} doesnt match #{@subject_o_regexes}"
243
+ end
244
+ unless component_is_valid?(request.organizational_unit, @subject_ou_regexes)
245
+ raise ValidationError, "Organizational unit #{request.organizational_unit} doesnt match #{@subject_ou_regexes}"
246
+ end
247
+ #todo: add uri, upn, ip, email
248
+ unless is_key_type_is_valid?(request.key_type, @key_types)
249
+ raise ValidationError, "Key Type #{request.key_type} doesnt match allowed #{@key_types}"
250
+ end
251
+ end
252
+
253
+
254
+ # todo: (!important!) parse csr if it alredy generated (!important!)
255
+ end
256
+
257
+ private
258
+
259
+ def is_key_type_is_valid?(key_type, allowed_key_types)
260
+ if key_type == nil
261
+ key_type = DEFAULT_KEY_TYPE
262
+ end
263
+ for i in 0 ... allowed_key_types.length
264
+ if allowed_key_types[i] == key_type
265
+ return true
266
+ end
267
+ end
268
+ false
269
+ end
270
+
271
+ def component_is_valid?(component, regexps, optional:false)
272
+ if component == nil
273
+ component = []
274
+ end
275
+ unless component.instance_of? Array
276
+ component = [component]
277
+ end
278
+ if component.length == 0 && optional
279
+ return true
280
+ end
281
+ if component.length == 0
282
+ component = [""]
283
+ end
284
+ for i in 0 ... component.length
285
+ unless match_regexps?(component[i], regexps)
286
+ return false
287
+ end
288
+ end
289
+ true
290
+ end
291
+
292
+ def match_regexps?(s, regexps)
293
+ for i in 0 ... regexps.length
294
+ if Regexp.new(regexps[i]).match(s)
295
+ return true
296
+ end
297
+ end
298
+ false
299
+ end
300
+ end
301
+
302
+ class ZoneConfiguration
303
+ attr_reader :country, :province, :locality, :organization, :organizational_unit, :key_type
304
+
305
+ # @param [CertField] country
306
+ # @param [CertField] province
307
+ # @param [CertField] locality
308
+ # @param [CertField] organization
309
+ # @param [CertField] organizational_unit
310
+ # @param [CertField] key_type
311
+ def initialize(country:, province:, locality:, organization:, organizational_unit:, key_type:)
312
+ @country = country
313
+ @province = province
314
+ @locality = locality
315
+ @organization = organization
316
+ @organizational_unit = organizational_unit
317
+ @key_type = key_type
318
+ end
319
+ end
320
+
321
+ class CertField
322
+ attr_reader :value, :locked
323
+
324
+ def initialize(value, locked: false)
325
+ @value = value
326
+ @locked = locked
327
+ end
328
+ end
329
+
330
+ class KeyType
331
+ attr_reader :type, :option
332
+
333
+ def initialize(type, option)
334
+ @type = {"rsa" => "rsa", "ec" => "ecdsa", "ecdsa" => "ecdsa"}[type.downcase]
335
+ if @type == nil
336
+ raise Vcert::VcertError, "bad key type"
337
+ end
338
+ if @type == "rsa"
339
+ unless [512, 1024, 2048, 3072, 4096, 8192].include?(option)
340
+ raise Vcert::VcertError,"bad option for rsa key: #{option}. should be one from list 512, 1024, 2048, 3072, 4096, 8192"
341
+ end
342
+ else
343
+ unless SUPPORTED_CURVES.include?(option)
344
+ raise Vcert::VcertError, "bad option for ec key: #{option}. should be one from list #{ SUPPORTED_CURVES}"
345
+ end
346
+ end
347
+ @option = option
348
+ end
349
+ def ==(other)
350
+ unless other.instance_of? KeyType
351
+ return false
352
+ end
353
+ self.type == other.type && self.option == other.option
354
+ end
355
+ end
356
+
357
+ DEFAULT_KEY_TYPE = KeyType.new("rsa", 2048)
358
+ end
359
+
@@ -0,0 +1,338 @@
1
+ require 'json'
2
+ require 'date'
3
+ require 'base64'
4
+ require 'utils/utils'
5
+
6
+ class Vcert::TPPConnection
7
+ def initialize(url, user, password, trust_bundle: nil)
8
+ @url = normalize_url url
9
+ @user = user
10
+ @password = password
11
+ @token = nil
12
+ @trust_bundle = trust_bundle
13
+ end
14
+
15
+ def request(zone_tag, request)
16
+ data = {:PolicyDN => policy_dn(zone_tag),
17
+ :PKCS10 => request.csr,
18
+ :ObjectName => request.friendly_name,
19
+ :DisableAutomaticRenewal => "true"}
20
+ code, response = post URL_CERTIFICATE_REQUESTS, data
21
+ if code != 200
22
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}"
23
+ end
24
+ request.id = response['CertificateDN']
25
+ end
26
+
27
+ def retrieve(request)
28
+ retrieve_request = {CertificateDN: request.id, Format: "base64", IncludeChain: 'true', RootFirstOrder: "false"}
29
+ code, response = post URL_CERTIFICATE_RETRIEVE, retrieve_request
30
+ if code != 200
31
+ return nil
32
+ end
33
+ full_chain = Base64.decode64(response['CertificateData'])
34
+ cert = parse_full_chain full_chain
35
+ if cert.private_key == nil
36
+ cert.private_key = request.private_key
37
+ end
38
+ cert
39
+ end
40
+
41
+ def policy(zone_tag)
42
+ code, response = post URL_ZONE_CONFIG, {:PolicyDN => policy_dn(zone_tag)}
43
+ if code != 200
44
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}"
45
+ end
46
+ parse_policy_response response, zone_tag
47
+ end
48
+
49
+ def zone_configuration(zone_tag)
50
+ code, response = post URL_ZONE_CONFIG, {:PolicyDN => policy_dn(zone_tag)}
51
+ if code != 200
52
+ raise Vcert::ServerUnexpectedBehaviorError, "Status #{code}"
53
+ end
54
+ parse_zone_configuration response
55
+ end
56
+
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")
60
+ end
61
+
62
+ if request.thumbprint != nil
63
+ request.id = search_by_thumbprint(request.thumbprint)
64
+ end
65
+ renew_req_data = {"CertificateDN": request.id}
66
+ 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)
73
+ 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))
81
+ renew_req_data.merge!(PKCS10: renew_request.csr)
82
+ end
83
+ LOG.info("Trying to renew certificate %s" % request.id)
84
+ _, 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
91
+ else
92
+ raise "Certificate renew error"
93
+ end
94
+
95
+ end
96
+
97
+ private
98
+
99
+ URL_AUTHORIZE = "authorize/"
100
+ URL_CERTIFICATE_REQUESTS = "certificates/request"
101
+ URL_ZONE_CONFIG = "certificates/checkpolicy"
102
+ URL_CERTIFICATE_RETRIEVE = "certificates/retrieve"
103
+ URL_CERTIFICATE_SEARCH = "certificates/"
104
+ URL_CERTIFICATE_RENEW = "certificates/renew"
105
+ URL_SECRET_STORE_SEARCH = "SecretStore/LookupByOwner"
106
+ URL_SECRET_STORE_RETRIEVE = "SecretStore/Retrieve"
107
+
108
+ TOKEN_HEADER_NAME = "x-venafi-api-key"
109
+ ALL_ALLOWED_REGEX = ".*"
110
+
111
+ def auth
112
+ uri = URI.parse(@url)
113
+ request = Net::HTTP.new(uri.host, uri.port)
114
+ request.use_ssl = true
115
+ if @trust_bundle != nil
116
+ request.ca_file = @trust_bundle
117
+ end
118
+ url = uri.path + URL_AUTHORIZE
119
+ data = {:Username => @user, :Password => @password}
120
+ encoded_data = JSON.generate(data)
121
+ response = request.post(url, encoded_data, {"Content-Type" => "application/json"})
122
+ if response.code.to_i != 200
123
+ raise Vcert::AuthenticationError
124
+ end
125
+ data = JSON.parse(response.body)
126
+ token = data['APIKey']
127
+ valid_until = DateTime.strptime(data['ValidUntil'].gsub(/\D/, ''), '%Q')
128
+ @token = token, valid_until
129
+ end
130
+
131
+ def post(url, data)
132
+ if @token == nil || @token[1] < DateTime.now
133
+ auth()
134
+ end
135
+ uri = URI.parse(@url)
136
+ request = Net::HTTP.new(uri.host, uri.port)
137
+ request.use_ssl = true
138
+ if @trust_bundle != nil
139
+ request.ca_file = @trust_bundle
140
+ end
141
+ url = uri.path + url
142
+ encoded_data = JSON.generate(data)
143
+ response = request.post(url, encoded_data, {TOKEN_HEADER_NAME => @token[0], "Content-Type" => "application/json"})
144
+ data = JSON.parse(response.body)
145
+ return response.code.to_i, data
146
+ end
147
+
148
+ def get(url)
149
+ if @token == nil || @token[1] < DateTime.now
150
+ auth()
151
+ end
152
+ uri = URI.parse(@url)
153
+ request = Net::HTTP.new(uri.host, uri.port)
154
+ request.use_ssl = true
155
+ if @trust_bundle != nil
156
+ request.ca_file = @trust_bundle
157
+ end
158
+ url = uri.path + url
159
+ response = request.get(url, {TOKEN_HEADER_NAME => @token[0]})
160
+ # TODO: check valid json
161
+ data = JSON.parse(response.body)
162
+ return response.code.to_i, data
163
+ end
164
+
165
+ def policy_dn(zone)
166
+ if zone == nil || zone == ''
167
+ raise Vcert::ClientBadDataError, "Zone should not be empty"
168
+ end
169
+ if zone =~ /^\\\\VED\\\\Poplicy/
170
+ return zone
171
+ end
172
+ if zone =~ /^\\\\/
173
+ return '\\VED\\Policy' + zone
174
+ else
175
+ return '\\VED\\Policy\\' + zone
176
+ end
177
+ end
178
+
179
+ def normalize_url(url)
180
+ if url.index('http://') == 0
181
+ url = "https://" + url[7..-1]
182
+ elsif url.index('https://') != 0
183
+ url = 'https://' + url
184
+ end
185
+ unless url.end_with?('/')
186
+ url = url + '/'
187
+ end
188
+ unless url.end_with?('/vedsdk/')
189
+ url = url + 'vedsdk/'
190
+ end
191
+ unless url =~ /^https:\/\/[a-z\d]+[-a-z\d.]+[a-z\d][:\d]*\/vedsdk\/$/
192
+ raise Vcert::ClientBadDataError, "Invalid URL for TPP"
193
+ end
194
+ url
195
+ end
196
+
197
+ def parse_full_chain(full_chain)
198
+ pems = parse_pem_list(full_chain)
199
+ Vcert::Certificate.new cert: pems[0], chain: pems[1..-1], private_key: nil
200
+ end
201
+
202
+ def search_by_thumbprint(thumbprint)
203
+ # thumbprint = re.sub(r'[^\dabcdefABCDEF]', "", thumbprint)
204
+ thumbprint = thumbprint.upcase
205
+ status, data = get(URL_CERTIFICATE_SEARCH+"?Thumbprint=#{thumbprint}")
206
+ # TODO: check that data have valid certificate in it
207
+ if status != 200
208
+ raise Vcert::ServerUnexpectedBehaviorError, "Status: #{status}. Message:\n #{data.body.to_s}"
209
+ end
210
+ # TODO: check valid data
211
+ return data['Certificates'][0]['DN']
212
+ end
213
+
214
+ def parse_zone_configuration(data)
215
+ s = data["Policy"]["Subject"]
216
+ country = Vcert::CertField.new s["Country"]["Value"], locked: s["Country"]["Locked"]
217
+ state = Vcert::CertField.new s["State"]["Value"], locked: s["State"]["Locked"]
218
+ city = Vcert::CertField.new s["City"]["Value"], locked: s["City"]["Locked"]
219
+ organization = Vcert::CertField.new s["Organization"]["Value"], locked: s["Organization"]["Locked"]
220
+ organizational_unit = Vcert::CertField.new s["OrganizationalUnit"]["Values"], locked: s["OrganizationalUnit"]["Locked"]
221
+ key_type = Vcert::KeyType.new data["Policy"]["KeyPair"]["KeyAlgorithm"]["Value"], data["Policy"]["KeyPair"]["KeySize"]["Value"]
222
+ Vcert::ZoneConfiguration.new country: country, province: state, locality: city, organization: organization,
223
+ organizational_unit: organizational_unit, key_type: Vcert::CertField.new(key_type)
224
+ end
225
+
226
+ def parse_policy_response(response, zone_tag)
227
+ def addStartEnd(s)
228
+ unless s.index("^") == 0
229
+ s = "^" + s
230
+ end
231
+ unless s.end_with?("$")
232
+ s = s + "$"
233
+ end
234
+ s
235
+ end
236
+
237
+ def escape(value)
238
+ if value.kind_of? Array
239
+ return value.map { |v| addStartEnd(Regexp.escape(v)) }
240
+ else
241
+ return addStartEnd(Regexp.escape(value))
242
+ end
243
+ end
244
+
245
+ policy = response["Policy"]
246
+ s = policy["Subject"]
247
+ if policy["WhitelistedDomains"].empty?
248
+ subjectCNRegex = [ALL_ALLOWED_REGEX]
249
+ else
250
+ if policy["WildcardsAllowed"]
251
+ subjectCNRegex = policy["WhitelistedDomains"].map { |d| addStartEnd('[\w\-*]+' + Regexp.escape("." + d)) }
252
+ else
253
+ subjectCNRegex = policy["WhitelistedDomains"].map { |d| addStartEnd('[\w\-]+' + Regexp.escape("." + d)) }
254
+ end
255
+ end
256
+ if s["OrganizationalUnit"]["Locked"]
257
+ subjectOURegexes = escape(s["OrganizationalUnit"]["Values"])
258
+ else
259
+ subjectOURegexes = [ALL_ALLOWED_REGEX]
260
+ end
261
+ if s["Organization"]["Locked"]
262
+ subjectORegexes = [escape(s["Organization"]["Value"])]
263
+ else
264
+ subjectORegexes = [ALL_ALLOWED_REGEX]
265
+ end
266
+ if s["City"]["Locked"]
267
+ subjectLRegexes = [escape(s["City"]["Value"])]
268
+ else
269
+ subjectLRegexes = [ALL_ALLOWED_REGEX]
270
+ end
271
+ if s["State"]["Locked"]
272
+ subjectSTRegexes = [escape(s["State"]["Value"])]
273
+ else
274
+ subjectSTRegexes = [ALL_ALLOWED_REGEX]
275
+ end
276
+ if s["Country"]["Locked"]
277
+ subjectCRegexes = [escape(s["Country"]["Value"])]
278
+ else
279
+ subjectCRegexes = [ALL_ALLOWED_REGEX]
280
+ end
281
+ if policy["SubjAltNameDnsAllowed"]
282
+ if policy["WhitelistedDomains"].length == 0
283
+ dnsSanRegExs = [ALL_ALLOWED_REGEX]
284
+ else
285
+ dnsSanRegExs = policy["WhitelistedDomains"].map { |d| addStartEnd('[\w-]+' + Regexp.escape("." + d)) }
286
+ end
287
+ else
288
+ dnsSanRegExs = []
289
+ end
290
+ if policy["SubjAltNameIpAllowed"]
291
+ ipSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
292
+ else
293
+ ipSanRegExs = []
294
+ end
295
+ if policy["SubjAltNameEmailAllowed"]
296
+ emailSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
297
+ else
298
+ emailSanRegExs = []
299
+ end
300
+ if policy["SubjAltNameUriAllowed"]
301
+ uriSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
302
+ else
303
+ uriSanRegExs = []
304
+ end
305
+
306
+ if policy["SubjAltNameUpnAllowed"]
307
+ upnSanRegExs = [ALL_ALLOWED_REGEX] # todo: support
308
+ else
309
+ upnSanRegExs = []
310
+ end
311
+ unless policy["KeyPair"]["KeyAlgorithm"]["Locked"]
312
+ key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new("rsa", s) } + Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new("ecdsa", c) }
313
+ else
314
+ if policy["KeyPair"]["KeyAlgorithm"]["Value"] == "RSA"
315
+ if policy["KeyPair"]["KeySize"]["Locked"]
316
+ key_types = [Vcert::KeyType.new("rsa", policy["KeyPair"]["KeySize"]["Value"])]
317
+ else
318
+ key_types = [1024, 2048, 4096, 8192].map { |s| Vcert::KeyType.new("rsa", s) }
319
+ end
320
+ elsif policy["KeyPair"]["KeyAlgorithm"]["Value"] == "EC"
321
+ if policy["KeyPair"]["EllipticCurve"]["Locked"]
322
+ curve = {"p224" => "secp224r1", "p256" => "prime256v1", "p521" => "secp521r1"}[policy["KeyPair"]["EllipticCurve"]["Value"].downcase]
323
+ key_types = [Vcert::KeyType.new("ecdsa", curve)]
324
+ else
325
+ key_types = Vcert::SUPPORTED_CURVES.map { |c| Vcert::KeyType.new("ecdsa", c) }
326
+ end
327
+ end
328
+ end
329
+
330
+ Vcert::Policy.new(policy_id: policy_dn(zone_tag), name: zone_tag, system_generated: false, creation_date: nil,
331
+ subject_cn_regexes: subjectCNRegex, subject_o_regexes: subjectORegexes,
332
+ subject_ou_regexes: subjectOURegexes, subject_st_regexes: subjectSTRegexes,
333
+ subject_l_regexes: subjectLRegexes, subject_c_regexes: subjectCRegexes, san_regexes: dnsSanRegExs,
334
+ key_types: key_types)
335
+ end
336
+ end
337
+
338
+
@@ -0,0 +1,91 @@
1
+ require 'net/https'
2
+ require 'time'
3
+
4
+ TIMEOUT = 420
5
+
6
+ module Vcert
7
+ class VcertError < StandardError ; end
8
+ class AuthenticationError < VcertError; end
9
+ class ServerUnexpectedBehaviorError < VcertError; end
10
+ class ClientBadDataError < VcertError; end
11
+ class ValidationError < VcertError; end
12
+
13
+ class Connection
14
+ def initialize(url: nil, user: nil, password: nil, cloud_token: nil, trust_bundle:nil, fake: false)
15
+ if fake
16
+ @conn = FakeConnection.new
17
+ elsif cloud_token != nil
18
+ @conn = CloudConnection.new url, cloud_token
19
+ elsif user != nil && password != nil && url != nil then
20
+ @conn = TPPConnection.new url, user, password, trust_bundle:trust_bundle
21
+ else
22
+ raise ClientBadDataError, "Invalid credentials list"
23
+ end
24
+ end
25
+
26
+
27
+ # @param [String] zone
28
+ # @param [Request] request
29
+ def request(zone, request)
30
+ @conn.request(zone, request)
31
+ end
32
+
33
+ # @param [Request] request
34
+ # @return [Certificate]
35
+ def retrieve(request)
36
+ @conn.retrieve(request)
37
+ end
38
+
39
+ def revoke(*args)
40
+ @conn.revoke(*args)
41
+ end
42
+
43
+ # @param [String] zone
44
+ # @return [ZoneConfiguration]
45
+ def zone_configuration(zone)
46
+ @conn.zone_configuration(zone)
47
+ end
48
+
49
+ # @param [String] zone
50
+ # @return [Policy]
51
+ def policy(zone)
52
+ @conn.policy(zone)
53
+ end
54
+
55
+ def renew(*args)
56
+ @conn.renew(*args)
57
+ end
58
+
59
+ # @param [Request] req
60
+ # @param [String] zone
61
+ # @param [Integer] timeout
62
+ # @return [Certificate]
63
+ def request_and_retrieve(req, zone, timeout: TIMEOUT)
64
+ request zone, req
65
+ cert = retrieve_loop(req, timeout: timeout)
66
+ return cert
67
+ end
68
+
69
+ def retrieve_loop(req, timeout: TIMEOUT)
70
+ t = Time.new() + timeout
71
+ loop do
72
+ if Time.new() > t
73
+ LOG.info("Waiting certificate #{req.id}")
74
+ break
75
+ end
76
+ certificate = @conn.retrieve(req)
77
+ if certificate != nil
78
+ return certificate
79
+ end
80
+ sleep 10
81
+ end
82
+ return nil
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ require 'fake/fake'
89
+ require 'cloud/cloud'
90
+ require 'tpp/tpp'
91
+ require 'objects/objects'
metadata ADDED
@@ -0,0 +1,49 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vcert
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Denis Subbotin
8
+ - Alexander Rykalin
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-09-18 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Ruby client for Venafi Cloud and Trust Protection Platform
15
+ email: opensource@venafi.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/cloud/cloud.rb
21
+ - lib/fake/fake.rb
22
+ - lib/objects/objects.rb
23
+ - lib/tpp/tpp.rb
24
+ - lib/vcert.rb
25
+ homepage: https://rubygems.org/gems/vcert
26
+ licenses:
27
+ - Apache-2.0
28
+ metadata: {}
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ">="
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ required_rubygems_version: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '0'
43
+ requirements: []
44
+ rubyforge_project:
45
+ rubygems_version: 2.7.6
46
+ signing_key:
47
+ specification_version: 4
48
+ summary: Library for Venafi products
49
+ test_files: []