r509 0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +447 -0
- data/Rakefile +38 -0
- data/bin/r509 +96 -0
- data/bin/r509-parse +35 -0
- data/doc/R509.html +154 -0
- data/doc/R509/Cert.html +3954 -0
- data/doc/R509/Cert/Extensions.html +360 -0
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
- data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
- data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
- data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
- data/doc/R509/CertificateAuthority.html +126 -0
- data/doc/R509/CertificateAuthority/Signer.html +855 -0
- data/doc/R509/Config.html +127 -0
- data/doc/R509/Config/CaConfig.html +2144 -0
- data/doc/R509/Config/CaConfigPool.html +599 -0
- data/doc/R509/Config/CaProfile.html +656 -0
- data/doc/R509/Config/SubjectItemPolicy.html +578 -0
- data/doc/R509/Crl.html +126 -0
- data/doc/R509/Crl/Administrator.html +2077 -0
- data/doc/R509/Crl/Parser.html +1224 -0
- data/doc/R509/Csr.html +2248 -0
- data/doc/R509/IOHelpers.html +564 -0
- data/doc/R509/MessageDigest.html +396 -0
- data/doc/R509/NameSanitizer.html +319 -0
- data/doc/R509/Ocsp.html +128 -0
- data/doc/R509/Ocsp/Request.html +126 -0
- data/doc/R509/Ocsp/Request/Nonce.html +160 -0
- data/doc/R509/Ocsp/Response.html +837 -0
- data/doc/R509/OidMapper.html +393 -0
- data/doc/R509/PrivateKey.html +1647 -0
- data/doc/R509/R509Error.html +134 -0
- data/doc/R509/Spki.html +1424 -0
- data/doc/R509/Subject.html +836 -0
- data/doc/R509/Validity.html +160 -0
- data/doc/R509/Validity/Checker.html +320 -0
- data/doc/R509/Validity/DefaultChecker.html +283 -0
- data/doc/R509/Validity/DefaultWriter.html +330 -0
- data/doc/R509/Validity/Status.html +561 -0
- data/doc/R509/Validity/Writer.html +394 -0
- data/doc/_index.html +501 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +328 -0
- data/doc/file.README.html +534 -0
- data/doc/file.r509.html +149 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +534 -0
- data/doc/js/app.js +208 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/methods_list.html +1932 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/r509.rb +22 -0
- data/lib/r509/cert.rb +414 -0
- data/lib/r509/cert/extensions.rb +309 -0
- data/lib/r509/certificateauthority.rb +290 -0
- data/lib/r509/config.rb +407 -0
- data/lib/r509/crl.rb +379 -0
- data/lib/r509/csr.rb +324 -0
- data/lib/r509/exceptions.rb +5 -0
- data/lib/r509/io_helpers.rb +52 -0
- data/lib/r509/messagedigest.rb +49 -0
- data/lib/r509/ocsp.rb +85 -0
- data/lib/r509/oidmapper.rb +32 -0
- data/lib/r509/privatekey.rb +185 -0
- data/lib/r509/spki.rb +112 -0
- data/lib/r509/subject.rb +133 -0
- data/lib/r509/validity.rb +92 -0
- data/lib/r509/version.rb +4 -0
- data/r509.yaml +73 -0
- data/spec/cert/extensions_spec.rb +632 -0
- data/spec/cert_spec.rb +321 -0
- data/spec/certificate_authority_spec.rb +260 -0
- data/spec/config_spec.rb +349 -0
- data/spec/crl_spec.rb +215 -0
- data/spec/csr_spec.rb +302 -0
- data/spec/fixtures.rb +233 -0
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +24 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +28 -0
- data/spec/fixtures/cert3_key.pem +27 -0
- data/spec/fixtures/cert3_key_des3.pem +30 -0
- data/spec/fixtures/cert4.pem +14 -0
- data/spec/fixtures/cert5.pem +30 -0
- data/spec/fixtures/cert6.pem +26 -0
- data/spec/fixtures/cert_expired.pem +26 -0
- data/spec/fixtures/cert_not_yet_valid.pem +26 -0
- data/spec/fixtures/cert_san.pem +27 -0
- data/spec/fixtures/cert_san2.pem +22 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
- data/spec/fixtures/config_test.yaml +41 -0
- data/spec/fixtures/config_test_engine_key.yaml +7 -0
- data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
- data/spec/fixtures/config_test_minimal.yaml +7 -0
- data/spec/fixtures/config_test_password.yaml +7 -0
- data/spec/fixtures/config_test_various.yaml +100 -0
- data/spec/fixtures/crl_list_file.txt +1 -0
- data/spec/fixtures/crl_with_reason.pem +17 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +17 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +27 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
- data/spec/fixtures/csr1_newlines.pem +32 -0
- data/spec/fixtures/csr1_no_begin_end.pem +15 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
- data/spec/fixtures/csr2.pem +15 -0
- data/spec/fixtures/csr2_key.pem +27 -0
- data/spec/fixtures/csr3.pem +16 -0
- data/spec/fixtures/csr4.pem +25 -0
- data/spec/fixtures/csr_dsa.pem +15 -0
- data/spec/fixtures/csr_invalid_signature.pem +13 -0
- data/spec/fixtures/dsa_key.pem +20 -0
- data/spec/fixtures/key4.pem +27 -0
- data/spec/fixtures/key4_encrypted_des3.pem +30 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
- data/spec/fixtures/missing_key_identifier_ca.key +27 -0
- data/spec/fixtures/ocsptest.r509.local.pem +27 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
- data/spec/fixtures/second_ca.cer +26 -0
- data/spec/fixtures/second_ca.key +27 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +1 -0
- data/spec/fixtures/spkac_dsa.txt +1 -0
- data/spec/fixtures/stca.pem +22 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +17 -0
- data/spec/fixtures/test_ca.cer +22 -0
- data/spec/fixtures/test_ca.key +28 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +30 -0
- data/spec/fixtures/test_ca_ocsp.cer +26 -0
- data/spec/fixtures/test_ca_ocsp.key +27 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +26 -0
- data/spec/fixtures/test_ca_subroot.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +17 -0
- data/spec/message_digest_spec.rb +89 -0
- data/spec/ocsp_spec.rb +111 -0
- data/spec/oid_mapper_spec.rb +31 -0
- data/spec/privatekey_spec.rb +198 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/spki_spec.rb +157 -0
- data/spec/subject_spec.rb +203 -0
- data/spec/validity_spec.rb +98 -0
- metadata +257 -0
@@ -0,0 +1,309 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module R509
|
5
|
+
class Cert
|
6
|
+
module Extensions
|
7
|
+
|
8
|
+
private
|
9
|
+
# Regexes for OpenSSL's parsed values
|
10
|
+
DNS_REGEX = /DNS:([^,\n]+)/
|
11
|
+
IP_ADDRESS_REGEX = /IP:([^,\n]+)/
|
12
|
+
URI_REGEX = /URI:([^,\n]+)/
|
13
|
+
|
14
|
+
R509_EXTENSION_CLASSES = Set.new
|
15
|
+
|
16
|
+
# Registers a class as being an R509 certificate extension class. Registered
|
17
|
+
# classes are used by #wrap_openssl_extensions to wrap OpenSSL extensions
|
18
|
+
# in R509 extensions, based on the OID.
|
19
|
+
def self.register_class( r509_ext_class )
|
20
|
+
raise ArgumentError.new("R509 certificate extensions must have an OID") if r509_ext_class::OID.nil?
|
21
|
+
R509_EXTENSION_CLASSES << r509_ext_class
|
22
|
+
end
|
23
|
+
|
24
|
+
public
|
25
|
+
# Implements the BasicConstraints certificate extension, with methods to
|
26
|
+
# provide access to the components and meaning of the extension's contents.
|
27
|
+
class BasicConstraints < OpenSSL::X509::Extension
|
28
|
+
OID = "basicConstraints"
|
29
|
+
Extensions.register_class(self)
|
30
|
+
|
31
|
+
attr_reader :path_length
|
32
|
+
|
33
|
+
# See OpenSSL::X509::Extension#initialize
|
34
|
+
def initialize(*args)
|
35
|
+
super(*args)
|
36
|
+
|
37
|
+
@is_ca = ! ( self.value =~ /CA:TRUE/ ).nil?
|
38
|
+
pathlen_match = self.value.match( /pathlen:(\d+)/ )
|
39
|
+
@path_length = pathlen_match[1].to_i unless pathlen_match.nil?
|
40
|
+
end
|
41
|
+
|
42
|
+
def is_ca?()
|
43
|
+
return @is_ca == true
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns true if the path length allows this certificate to be used to
|
47
|
+
# sign CA certificates.
|
48
|
+
def allows_sub_ca?()
|
49
|
+
return false if @path_length.nil?
|
50
|
+
return @path_length > 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Implements the KeyUsage certificate extension, with methods to
|
55
|
+
# provide access to the components and meaning of the extension's contents.
|
56
|
+
class KeyUsage < OpenSSL::X509::Extension
|
57
|
+
OID = "keyUsage"
|
58
|
+
Extensions.register_class(self)
|
59
|
+
|
60
|
+
# The OpenSSL friendly name for the "digitalSignature" key use.
|
61
|
+
AU_DIGITAL_SIGNATURE = "Digital Signature"
|
62
|
+
# The OpenSSL friendly name for the "nonRepudiation" key use.
|
63
|
+
AU_NON_REPUDIATION = "Non Repudiation"
|
64
|
+
# The OpenSSL friendly name for the "keyEncipherment" key use.
|
65
|
+
AU_KEY_ENCIPHERMENT = "Key Encipherment"
|
66
|
+
# The OpenSSL friendly name for the "dataEncipherment" key use.
|
67
|
+
AU_DATA_ENCIPHERMENT = "Data Encipherment"
|
68
|
+
# The OpenSSL friendly name for the "keyAgreement" key use.
|
69
|
+
AU_KEY_AGREEMENT = "Key Agreement"
|
70
|
+
# The OpenSSL friendly name for the "keyCertSign" key use.
|
71
|
+
AU_CERTIFICATE_SIGN = "Certificate Sign"
|
72
|
+
# The OpenSSL friendly name for the "cRLSign" key use.
|
73
|
+
AU_CRL_SIGN = "CRL Sign"
|
74
|
+
# The OpenSSL friendly name for the "encipherOnly" key use.
|
75
|
+
AU_ENCIPHER_ONLY = "Encipher Only"
|
76
|
+
# The OpenSSL friendly name for the "decipherOnly" key use.
|
77
|
+
AU_DECIPHER_ONLY = "Decipher Only"
|
78
|
+
|
79
|
+
# An array of the key uses allowed. See the AU_* constants in this class.
|
80
|
+
attr_reader :allowed_uses
|
81
|
+
|
82
|
+
# See OpenSSL::X509::Extension#initialize
|
83
|
+
def initialize(*args)
|
84
|
+
super(*args)
|
85
|
+
|
86
|
+
@allowed_uses = self.value.split(",").map {|use| use.strip}
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if the given use is allowed by this extension.
|
90
|
+
# @param [string] friendly_use_name One of the AU_* constants in this class.
|
91
|
+
def allows?( friendly_use_name )
|
92
|
+
@allowed_uses.include?( friendly_use_name )
|
93
|
+
end
|
94
|
+
|
95
|
+
def digital_signature?
|
96
|
+
allows?( AU_DIGITAL_SIGNATURE )
|
97
|
+
end
|
98
|
+
|
99
|
+
def non_repudiation?
|
100
|
+
allows?( AU_NON_REPUDIATION )
|
101
|
+
end
|
102
|
+
|
103
|
+
def key_encipherment?
|
104
|
+
allows?( AU_KEY_ENCIPHERMENT )
|
105
|
+
end
|
106
|
+
|
107
|
+
def data_encipherment?
|
108
|
+
allows?( AU_DATA_ENCIPHERMENT )
|
109
|
+
end
|
110
|
+
|
111
|
+
def key_agreement?
|
112
|
+
allows?( AU_KEY_AGREEMENT )
|
113
|
+
end
|
114
|
+
|
115
|
+
def certificate_sign?
|
116
|
+
allows?( AU_CERTIFICATE_SIGN )
|
117
|
+
end
|
118
|
+
|
119
|
+
def crl_sign?
|
120
|
+
allows?( AU_CRL_SIGN )
|
121
|
+
end
|
122
|
+
|
123
|
+
def encipher_only?
|
124
|
+
allows?( AU_ENCIPHER_ONLY )
|
125
|
+
end
|
126
|
+
|
127
|
+
def decipher_only?
|
128
|
+
allows?( AU_DECIPHER_ONLY )
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Implements the ExtendedKeyUsage certificate extension, with methods to
|
133
|
+
# provide access to the components and meaning of the extension's contents.
|
134
|
+
class ExtendedKeyUsage < OpenSSL::X509::Extension
|
135
|
+
OID = "extendedKeyUsage"
|
136
|
+
Extensions.register_class(self)
|
137
|
+
|
138
|
+
# The OpenSSL friendly name for the "serverAuth" extended key use.
|
139
|
+
AU_WEB_SERVER_AUTH = "TLS Web Server Authentication"
|
140
|
+
# The OpenSSL friendly name for the "clientAuth" extended key use.
|
141
|
+
AU_WEB_CLIENT_AUTH = "TLS Web Client Authentication"
|
142
|
+
# The OpenSSL friendly name for the "codeSigning" extended key use.
|
143
|
+
AU_CODE_SIGNING = "Code Signing"
|
144
|
+
# The OpenSSL friendly name for the "emailProtection" extended key use.
|
145
|
+
AU_EMAIL_PROTECTION = "E-mail Protection"
|
146
|
+
|
147
|
+
# An array of the key uses allowed. See the AU_* constants in this class.
|
148
|
+
attr_reader :allowed_uses
|
149
|
+
|
150
|
+
# See OpenSSL::X509::Extension#initialize
|
151
|
+
def initialize(*args)
|
152
|
+
super(*args)
|
153
|
+
|
154
|
+
@allowed_uses = self.value.split(",").map {|use| use.strip}
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns true if the given use is allowed by this extension.
|
158
|
+
# @param [string] friendly_use_name One of the AU_* constants in this class.
|
159
|
+
def allows?( friendly_use_name )
|
160
|
+
@allowed_uses.include?( friendly_use_name )
|
161
|
+
end
|
162
|
+
|
163
|
+
def web_server_authentication?
|
164
|
+
allows?( AU_WEB_SERVER_AUTH )
|
165
|
+
end
|
166
|
+
|
167
|
+
def web_client_authentication?
|
168
|
+
allows?( AU_WEB_CLIENT_AUTH )
|
169
|
+
end
|
170
|
+
|
171
|
+
def code_signing?
|
172
|
+
allows?( AU_CODE_SIGNING )
|
173
|
+
end
|
174
|
+
|
175
|
+
def email_protection?
|
176
|
+
allows?( AU_EMAIL_PROTECTION )
|
177
|
+
end
|
178
|
+
|
179
|
+
# ...
|
180
|
+
end
|
181
|
+
|
182
|
+
# Implements the SubjectKeyIdentifier certificate extension, with methods to
|
183
|
+
# provide access to the components and meaning of the extension's contents.
|
184
|
+
class SubjectKeyIdentifier < OpenSSL::X509::Extension
|
185
|
+
OID = "subjectKeyIdentifier"
|
186
|
+
Extensions.register_class(self)
|
187
|
+
|
188
|
+
def key()
|
189
|
+
return self.value
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Implements the AuthorityKeyIdentifier certificate extension, with methods to
|
194
|
+
# provide access to the components and meaning of the extension's contents.
|
195
|
+
class AuthorityKeyIdentifier < OpenSSL::X509::Extension
|
196
|
+
OID = "authorityKeyIdentifier"
|
197
|
+
Extensions.register_class(self)
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
# Implements the SubjectAlternativeName certificate extension, with methods to
|
202
|
+
# provide access to the components and meaning of the extension's contents.
|
203
|
+
class SubjectAlternativeName < OpenSSL::X509::Extension
|
204
|
+
OID = "subjectAltName"
|
205
|
+
Extensions.register_class(self)
|
206
|
+
|
207
|
+
# An array of the DNS alternative names, if any
|
208
|
+
attr_reader :dns_names
|
209
|
+
# An array of the IP-address alternative names, if any
|
210
|
+
attr_reader :ip_addresses
|
211
|
+
# An array of the URI alternative names, if any
|
212
|
+
attr_reader :uris
|
213
|
+
|
214
|
+
# See OpenSSL::X509::Extension#initialize
|
215
|
+
def initialize(*args)
|
216
|
+
super(*args)
|
217
|
+
|
218
|
+
@dns_names = self.value.scan( DNS_REGEX ).map { |match| match[0] }
|
219
|
+
@ip_addresses = self.value.scan( IP_ADDRESS_REGEX ).map { |match| match[0] }
|
220
|
+
@uris = self.value.scan( URI_REGEX ).map { |match| match[0] }
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# Implements the AuthorityInfoAccess certificate extension, with methods to
|
225
|
+
# provide access to the components and meaning of the extension's contents.
|
226
|
+
class AuthorityInfoAccess < OpenSSL::X509::Extension
|
227
|
+
OID = "authorityInfoAccess"
|
228
|
+
Extensions.register_class(self)
|
229
|
+
|
230
|
+
# An array of the OCSP URIs, if any
|
231
|
+
attr_reader :ocsp_uris
|
232
|
+
# An array of the CA issuers URIs, if any
|
233
|
+
attr_reader :ca_issuers_uris
|
234
|
+
|
235
|
+
# See OpenSSL::X509::Extension#initialize
|
236
|
+
def initialize(*args)
|
237
|
+
super(*args)
|
238
|
+
|
239
|
+
@ocsp_uris = self.value.scan( /OCSP - #{URI_REGEX}/ ).map { |match| match[0] }
|
240
|
+
@ca_issuers_uris = self.value.scan( /CA Issuers - #{URI_REGEX}/ ).map { |match| match[0] }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
# Implements the CrlDistributionPoints certificate extension, with methods to
|
245
|
+
# provide access to the components and meaning of the extension's contents.
|
246
|
+
class CrlDistributionPoints < OpenSSL::X509::Extension
|
247
|
+
OID = "crlDistributionPoints"
|
248
|
+
Extensions.register_class(self)
|
249
|
+
|
250
|
+
# An array of the CRL URIs, if any
|
251
|
+
attr_reader :crl_uris
|
252
|
+
|
253
|
+
# See OpenSSL::X509::Extension#initialize
|
254
|
+
def initialize(*args)
|
255
|
+
super(*args)
|
256
|
+
|
257
|
+
@crl_uris = self.value.scan( URI_REGEX ).map { |match| match[0] }
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
#
|
263
|
+
# Helper class methods
|
264
|
+
#
|
265
|
+
|
266
|
+
# Takes OpenSSL::X509::Extension objects and wraps each in the appropriate
|
267
|
+
# R509::Cert::Extensions object, and returns them in a hash. The hash is
|
268
|
+
# keyed with the R509 extension class. Extensions without an R509
|
269
|
+
# implementation are ignored (see #get_unknown_extensions).
|
270
|
+
def self.wrap_openssl_extensions( extensions )
|
271
|
+
r509_extensions = {}
|
272
|
+
extensions.each do |openssl_extension|
|
273
|
+
R509_EXTENSION_CLASSES.each do |r509_class|
|
274
|
+
if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
|
275
|
+
if r509_extensions.has_key?(r509_class)
|
276
|
+
raise ArgumentError.new("Only one extension object allowed per OID")
|
277
|
+
end
|
278
|
+
|
279
|
+
r509_extensions[r509_class] = r509_class.new( openssl_extension )
|
280
|
+
break
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
return r509_extensions
|
286
|
+
end
|
287
|
+
|
288
|
+
# Given a list of OpenSSL::X509::Extension objects, returns those without
|
289
|
+
# an R509 implementation.
|
290
|
+
def self.get_unknown_extensions( extensions )
|
291
|
+
unknown_extensions = []
|
292
|
+
extensions.each do |openssl_extension|
|
293
|
+
match_found = false
|
294
|
+
R509_EXTENSION_CLASSES.each do |r509_class|
|
295
|
+
if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
|
296
|
+
match_found = true
|
297
|
+
break
|
298
|
+
end
|
299
|
+
end
|
300
|
+
# if we make it this far (without breaking), we didn't match
|
301
|
+
unknown_extensions << openssl_extension unless match_found
|
302
|
+
end
|
303
|
+
|
304
|
+
return unknown_extensions
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'r509/config'
|
3
|
+
require 'r509/cert'
|
4
|
+
require 'r509/exceptions'
|
5
|
+
|
6
|
+
# CertificateAuthority related classes
|
7
|
+
module R509::CertificateAuthority
|
8
|
+
# Contains the certification authority signing operation methods
|
9
|
+
class Signer
|
10
|
+
# @param [R509::Config] config
|
11
|
+
def initialize(config=nil)
|
12
|
+
@config = config
|
13
|
+
|
14
|
+
if not @config.nil? and not @config.kind_of?(R509::Config::CaConfig)
|
15
|
+
raise R509::R509Error, "config must be a kind of R509::Config::CaConfig or nil (for self-sign only)"
|
16
|
+
end
|
17
|
+
if not @config.nil? and not @config.ca_cert.has_private_key?
|
18
|
+
raise R509::R509Error, "You must have a private key associated with your CA certificate to issue"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Signs a CSR
|
23
|
+
# @option options :csr [R509::Csr]
|
24
|
+
# @option options :spki [R509::Spki]
|
25
|
+
# @option options :profile_name [String] The CA profile you want to use (eg "server in your config)
|
26
|
+
# @option options :data_hash [Hash] a hash containing the subject and SAN names you want encoded for this cert. Generate by calling Csr#to_hash or Spki#to_hash
|
27
|
+
# @option options :message_digest [String] the message digest to use for this certificate instead of the config's default
|
28
|
+
# @option options :serial [String] the serial number you want to issue the certificate with
|
29
|
+
# @option options :not_before [Time] the notBefore for the certificate
|
30
|
+
# @option options :not_after [Time] the notAfter for the certificate
|
31
|
+
# @return [R509::Cert] the signed cert object
|
32
|
+
def sign(options)
|
33
|
+
if @config.nil?
|
34
|
+
raise R509::R509Error, "When instantiating the signer without a config you can only call #selfsign"
|
35
|
+
elsif @config.num_profiles == 0
|
36
|
+
raise R509::R509Error, "You must have at least one CaProfile on your CaConfig to issue"
|
37
|
+
end
|
38
|
+
|
39
|
+
if options.has_key?(:csr) and options.has_key?(:spki)
|
40
|
+
raise ArgumentError, "You can't pass both :csr and :spki"
|
41
|
+
elsif not options.has_key?(:csr) and not options.has_key?(:spki)
|
42
|
+
raise ArgumentError, "You must supply either :csr or :spki"
|
43
|
+
elsif options.has_key?(:csr)
|
44
|
+
if not options[:csr].kind_of?(R509::Csr)
|
45
|
+
raise ArgumentError, "You must pass an R509::Csr object for :csr"
|
46
|
+
else
|
47
|
+
signable_object = options[:csr]
|
48
|
+
end
|
49
|
+
elsif not options.has_key?(:csr) and options.has_key?(:spki)
|
50
|
+
if not options[:spki].kind_of?(R509::Spki)
|
51
|
+
raise ArgumentError, "You must pass an R509::Spki object for :spki"
|
52
|
+
else
|
53
|
+
signable_object = options[:spki]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
if options.has_key?(:data_hash)
|
58
|
+
san_names = options[:data_hash][:san_names]
|
59
|
+
subject = options[:data_hash][:subject]
|
60
|
+
else
|
61
|
+
san_names = signable_object.to_hash[:san_names]
|
62
|
+
subject = signable_object.to_hash[:subject]
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
if options.has_key?(:csr) and not options[:csr].verify_signature
|
68
|
+
raise R509::R509Error, "Certificate request signature is invalid."
|
69
|
+
end
|
70
|
+
|
71
|
+
#handle DSA here
|
72
|
+
if options.has_key?(:message_digest)
|
73
|
+
message_digest = R509::MessageDigest.new(options[:message_digest])
|
74
|
+
else
|
75
|
+
message_digest = R509::MessageDigest.new(@config.message_digest)
|
76
|
+
end
|
77
|
+
|
78
|
+
profile = @config.profile(options[:profile_name])
|
79
|
+
|
80
|
+
validated_subject = validate_subject(subject,profile)
|
81
|
+
|
82
|
+
cert = build_cert(
|
83
|
+
:subject => validated_subject.name,
|
84
|
+
:issuer => @config.ca_cert.subject,
|
85
|
+
:not_before => options[:not_before],
|
86
|
+
:not_after => options[:not_after],
|
87
|
+
:public_key => signable_object.public_key,
|
88
|
+
:serial => options[:serial]
|
89
|
+
)
|
90
|
+
|
91
|
+
basic_constraints = profile.basic_constraints
|
92
|
+
key_usage = profile.key_usage
|
93
|
+
extended_key_usage = profile.extended_key_usage
|
94
|
+
certificate_policies = profile.certificate_policies
|
95
|
+
|
96
|
+
build_extensions(
|
97
|
+
:subject_certificate => cert,
|
98
|
+
:issuer_certificate => @config.ca_cert.cert,
|
99
|
+
:basic_constraints => basic_constraints,
|
100
|
+
:key_usage => key_usage,
|
101
|
+
:extended_key_usage => extended_key_usage,
|
102
|
+
:certificate_policies => certificate_policies,
|
103
|
+
:san_names => san_names
|
104
|
+
)
|
105
|
+
|
106
|
+
|
107
|
+
#@config.ca_cert.key.key ... ugly. ca_cert returns R509::Cert
|
108
|
+
# #key returns R509::PrivateKey and #key on that returns OpenSSL object we need
|
109
|
+
cert.sign( @config.ca_cert.key.key, message_digest.digest )
|
110
|
+
R509::Cert.new(:cert => cert)
|
111
|
+
end
|
112
|
+
|
113
|
+
# Self-signs a CSR
|
114
|
+
# @option options :csr [R509::Csr]
|
115
|
+
# @option options :message_digest [String] the message digest to use for this certificate (defaults to sha1)
|
116
|
+
# @option options :serial [String] the serial number you want to issue the certificate with (defaults to random)
|
117
|
+
# @option options :not_before [Time] the notBefore for the certificate (defaults to now)
|
118
|
+
# @option options :not_after [Time] the notAfter for the certificate (defaults to 1 year)
|
119
|
+
# @option options :san_names [Array] Optional array of subject alternative names
|
120
|
+
# @return [R509::Cert] the signed cert object
|
121
|
+
def selfsign(options)
|
122
|
+
if not options.kind_of?(Hash)
|
123
|
+
raise ArgumentError, "You must pass a hash of options consisting of at minimum :csr"
|
124
|
+
end
|
125
|
+
csr = options[:csr]
|
126
|
+
if csr.key.nil?
|
127
|
+
raise ArgumentError, 'CSR must also have a private key to self sign'
|
128
|
+
end
|
129
|
+
cert = build_cert(
|
130
|
+
:subject => csr.subject.name,
|
131
|
+
:issuer => csr.subject.name,
|
132
|
+
:not_before => options[:not_before],
|
133
|
+
:not_after => options[:not_after],
|
134
|
+
:public_key => csr.public_key,
|
135
|
+
:serial => options[:serial]
|
136
|
+
)
|
137
|
+
|
138
|
+
if options.has_key?(:san_names)
|
139
|
+
san_names = options[:san_names]
|
140
|
+
else
|
141
|
+
san_names = csr.san_names
|
142
|
+
end
|
143
|
+
|
144
|
+
build_extensions(
|
145
|
+
:subject_certificate => cert,
|
146
|
+
:issuer_certificate => cert,
|
147
|
+
:basic_constraints => "CA:TRUE",
|
148
|
+
:san_names => san_names
|
149
|
+
)
|
150
|
+
|
151
|
+
|
152
|
+
if options.has_key?(:message_digest)
|
153
|
+
message_digest = R509::MessageDigest.new(options[:message_digest])
|
154
|
+
else
|
155
|
+
message_digest = R509::MessageDigest.new('sha1')
|
156
|
+
end
|
157
|
+
|
158
|
+
# Csr#key returns R509::PrivateKey and #key on that returns OpenSSL object we need
|
159
|
+
cert.sign( csr.key.key, message_digest.digest )
|
160
|
+
R509::Cert.new(:cert => cert)
|
161
|
+
end
|
162
|
+
|
163
|
+
private
|
164
|
+
|
165
|
+
def process_san_names(domains)
|
166
|
+
domains.map { |domain| 'DNS: '+domain }.join(",")
|
167
|
+
end
|
168
|
+
|
169
|
+
def build_conf(section,data)
|
170
|
+
conf = ["[#{section}]"]
|
171
|
+
conf.concat data
|
172
|
+
conf.join "\n"
|
173
|
+
end
|
174
|
+
|
175
|
+
def validate_subject(subject,profile)
|
176
|
+
if profile.subject_item_policy.nil? then
|
177
|
+
subject
|
178
|
+
else
|
179
|
+
profile.subject_item_policy.validate_subject(subject)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def build_cert(options)
|
184
|
+
|
185
|
+
cert = OpenSSL::X509::Certificate.new
|
186
|
+
|
187
|
+
cert.subject = options[:subject]
|
188
|
+
cert.issuer = options[:issuer]
|
189
|
+
cert.not_before = calculate_not_before(options[:not_before])
|
190
|
+
cert.not_after = calculate_not_after(options[:not_after],cert.not_before)
|
191
|
+
cert.public_key = options[:public_key]
|
192
|
+
cert.serial = create_serial(options[:serial])
|
193
|
+
cert.version = 2 #2 means v3
|
194
|
+
cert
|
195
|
+
end
|
196
|
+
|
197
|
+
def create_serial(serial)
|
198
|
+
if not serial.nil?
|
199
|
+
serial = OpenSSL::BN.new(serial.to_s)
|
200
|
+
else
|
201
|
+
# generate random serial in accordance with best practices
|
202
|
+
# guidelines state 20-bits of entropy, but we can cram more in
|
203
|
+
# per rfc5280 conforming CAs can make the serial field up to 20 octets
|
204
|
+
# to prevent even the incredibly remote possibility of collision we'll
|
205
|
+
# concatenate current time (to the microsecond) with a random num
|
206
|
+
rand = OpenSSL::BN.rand(96,0) # 96 bits is 12 bytes (octets).
|
207
|
+
serial = OpenSSL::BN.new((Time.now.to_f*1000000).to_i.to_s + rand.to_s)
|
208
|
+
# since second param is 0 the most significant bit must always be 1
|
209
|
+
# this theoretically gives us 95 bits of entropy + microtime, which
|
210
|
+
# adds a non-zero quantity of entropy. depending upon how predictable
|
211
|
+
# your issuance is, this could range from a reasonably large quantity
|
212
|
+
# of entropy to very little
|
213
|
+
end
|
214
|
+
serial
|
215
|
+
end
|
216
|
+
|
217
|
+
def build_extensions(options)
|
218
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
219
|
+
|
220
|
+
ef.subject_certificate = options[:subject_certificate]
|
221
|
+
|
222
|
+
ef.issuer_certificate = options[:issuer_certificate]
|
223
|
+
|
224
|
+
ext = []
|
225
|
+
if not options[:basic_constraints].nil?
|
226
|
+
ext << ef.create_extension("basicConstraints", options[:basic_constraints], true)
|
227
|
+
end
|
228
|
+
if options.has_key?(:key_usage) and not options[:key_usage].empty?
|
229
|
+
ext << ef.create_extension("keyUsage", options[:key_usage].join(","))
|
230
|
+
end
|
231
|
+
if options.has_key?(:extended_key_usage) and not options[:extended_key_usage].empty?
|
232
|
+
ext << ef.create_extension("extendedKeyUsage", options[:extended_key_usage].join(","))
|
233
|
+
end
|
234
|
+
ext << ef.create_extension("subjectKeyIdentifier", "hash")
|
235
|
+
|
236
|
+
#attach the key identifier if it's not a self-sign
|
237
|
+
if not ef.subject_certificate == ef.issuer_certificate and R509::Cert.new(:cert=>options[:issuer_certificate]).extensions['subjectKeyIdentifier']
|
238
|
+
ext << ef.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always")
|
239
|
+
end
|
240
|
+
|
241
|
+
if not options[:certificate_policies].nil? and not options[:certificate_policies].empty?
|
242
|
+
conf = []
|
243
|
+
conf_names = []
|
244
|
+
i = 0
|
245
|
+
options[:certificate_policies].each do |policy|
|
246
|
+
conf << build_conf("certPolicies#{i}",policy)
|
247
|
+
conf_names << "@certPolicies#{i}"
|
248
|
+
i+=1
|
249
|
+
end
|
250
|
+
ef.config = OpenSSL::Config.parse(conf.join("\n"))
|
251
|
+
ext << ef.create_extension("certificatePolicies", conf_names.join(","))
|
252
|
+
end
|
253
|
+
#ef.config = OpenSSL::Config.parse(<<-_end_of_cnf_)
|
254
|
+
#[certPolicies]
|
255
|
+
#CPS.1 = http://www.example.com/cps
|
256
|
+
#_end_of_cnf_
|
257
|
+
|
258
|
+
if options.has_key?(:san_names) and not options[:san_names].empty?
|
259
|
+
ext << ef.create_extension("subjectAltName", process_san_names(options[:san_names]))
|
260
|
+
end
|
261
|
+
|
262
|
+
if not @config.nil? and not @config.cdp_location.nil?
|
263
|
+
ext << ef.create_extension("crlDistributionPoints", @config.cdp_location)
|
264
|
+
end
|
265
|
+
|
266
|
+
if not @config.nil? and not @config.ocsp_location.nil? then
|
267
|
+
ext << ef.create_extension("authorityInfoAccess",
|
268
|
+
"OCSP;" << @config.ocsp_location)
|
269
|
+
end
|
270
|
+
options[:subject_certificate].extensions = ext
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
|
274
|
+
def calculate_not_before(not_before)
|
275
|
+
if not_before.nil?
|
276
|
+
#not_before will be set to 6 hours before now to prevent issues with bad system clocks (clients don't sync)
|
277
|
+
not_before = Time.now - 6 * 60 * 60
|
278
|
+
end
|
279
|
+
not_before
|
280
|
+
end
|
281
|
+
|
282
|
+
def calculate_not_after(not_after,not_before)
|
283
|
+
if not_after.nil?
|
284
|
+
not_after = not_before + 365 * 24 * 60 * 60
|
285
|
+
end
|
286
|
+
not_after
|
287
|
+
end
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|