r509 0.8
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.
- 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
|