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.
Files changed (162) hide show
  1. data/README.md +447 -0
  2. data/Rakefile +38 -0
  3. data/bin/r509 +96 -0
  4. data/bin/r509-parse +35 -0
  5. data/doc/R509.html +154 -0
  6. data/doc/R509/Cert.html +3954 -0
  7. data/doc/R509/Cert/Extensions.html +360 -0
  8. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
  9. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
  10. data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
  11. data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
  12. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
  13. data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
  14. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
  15. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
  16. data/doc/R509/CertificateAuthority.html +126 -0
  17. data/doc/R509/CertificateAuthority/Signer.html +855 -0
  18. data/doc/R509/Config.html +127 -0
  19. data/doc/R509/Config/CaConfig.html +2144 -0
  20. data/doc/R509/Config/CaConfigPool.html +599 -0
  21. data/doc/R509/Config/CaProfile.html +656 -0
  22. data/doc/R509/Config/SubjectItemPolicy.html +578 -0
  23. data/doc/R509/Crl.html +126 -0
  24. data/doc/R509/Crl/Administrator.html +2077 -0
  25. data/doc/R509/Crl/Parser.html +1224 -0
  26. data/doc/R509/Csr.html +2248 -0
  27. data/doc/R509/IOHelpers.html +564 -0
  28. data/doc/R509/MessageDigest.html +396 -0
  29. data/doc/R509/NameSanitizer.html +319 -0
  30. data/doc/R509/Ocsp.html +128 -0
  31. data/doc/R509/Ocsp/Request.html +126 -0
  32. data/doc/R509/Ocsp/Request/Nonce.html +160 -0
  33. data/doc/R509/Ocsp/Response.html +837 -0
  34. data/doc/R509/OidMapper.html +393 -0
  35. data/doc/R509/PrivateKey.html +1647 -0
  36. data/doc/R509/R509Error.html +134 -0
  37. data/doc/R509/Spki.html +1424 -0
  38. data/doc/R509/Subject.html +836 -0
  39. data/doc/R509/Validity.html +160 -0
  40. data/doc/R509/Validity/Checker.html +320 -0
  41. data/doc/R509/Validity/DefaultChecker.html +283 -0
  42. data/doc/R509/Validity/DefaultWriter.html +330 -0
  43. data/doc/R509/Validity/Status.html +561 -0
  44. data/doc/R509/Validity/Writer.html +394 -0
  45. data/doc/_index.html +501 -0
  46. data/doc/class_list.html +53 -0
  47. data/doc/css/common.css +1 -0
  48. data/doc/css/full_list.css +57 -0
  49. data/doc/css/style.css +328 -0
  50. data/doc/file.README.html +534 -0
  51. data/doc/file.r509.html +149 -0
  52. data/doc/file_list.html +58 -0
  53. data/doc/frames.html +28 -0
  54. data/doc/index.html +534 -0
  55. data/doc/js/app.js +208 -0
  56. data/doc/js/full_list.js +173 -0
  57. data/doc/js/jquery.js +4 -0
  58. data/doc/methods_list.html +1932 -0
  59. data/doc/top-level-namespace.html +112 -0
  60. data/lib/r509.rb +22 -0
  61. data/lib/r509/cert.rb +414 -0
  62. data/lib/r509/cert/extensions.rb +309 -0
  63. data/lib/r509/certificateauthority.rb +290 -0
  64. data/lib/r509/config.rb +407 -0
  65. data/lib/r509/crl.rb +379 -0
  66. data/lib/r509/csr.rb +324 -0
  67. data/lib/r509/exceptions.rb +5 -0
  68. data/lib/r509/io_helpers.rb +52 -0
  69. data/lib/r509/messagedigest.rb +49 -0
  70. data/lib/r509/ocsp.rb +85 -0
  71. data/lib/r509/oidmapper.rb +32 -0
  72. data/lib/r509/privatekey.rb +185 -0
  73. data/lib/r509/spki.rb +112 -0
  74. data/lib/r509/subject.rb +133 -0
  75. data/lib/r509/validity.rb +92 -0
  76. data/lib/r509/version.rb +4 -0
  77. data/r509.yaml +73 -0
  78. data/spec/cert/extensions_spec.rb +632 -0
  79. data/spec/cert_spec.rb +321 -0
  80. data/spec/certificate_authority_spec.rb +260 -0
  81. data/spec/config_spec.rb +349 -0
  82. data/spec/crl_spec.rb +215 -0
  83. data/spec/csr_spec.rb +302 -0
  84. data/spec/fixtures.rb +233 -0
  85. data/spec/fixtures/cert1.der +0 -0
  86. data/spec/fixtures/cert1.pem +24 -0
  87. data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
  88. data/spec/fixtures/cert3.p12 +0 -0
  89. data/spec/fixtures/cert3.pem +28 -0
  90. data/spec/fixtures/cert3_key.pem +27 -0
  91. data/spec/fixtures/cert3_key_des3.pem +30 -0
  92. data/spec/fixtures/cert4.pem +14 -0
  93. data/spec/fixtures/cert5.pem +30 -0
  94. data/spec/fixtures/cert6.pem +26 -0
  95. data/spec/fixtures/cert_expired.pem +26 -0
  96. data/spec/fixtures/cert_not_yet_valid.pem +26 -0
  97. data/spec/fixtures/cert_san.pem +27 -0
  98. data/spec/fixtures/cert_san2.pem +22 -0
  99. data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
  100. data/spec/fixtures/config_test.yaml +41 -0
  101. data/spec/fixtures/config_test_engine_key.yaml +7 -0
  102. data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
  103. data/spec/fixtures/config_test_minimal.yaml +7 -0
  104. data/spec/fixtures/config_test_password.yaml +7 -0
  105. data/spec/fixtures/config_test_various.yaml +100 -0
  106. data/spec/fixtures/crl_list_file.txt +1 -0
  107. data/spec/fixtures/crl_with_reason.pem +17 -0
  108. data/spec/fixtures/csr1.der +0 -0
  109. data/spec/fixtures/csr1.pem +17 -0
  110. data/spec/fixtures/csr1_key.der +0 -0
  111. data/spec/fixtures/csr1_key.pem +27 -0
  112. data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
  113. data/spec/fixtures/csr1_newlines.pem +32 -0
  114. data/spec/fixtures/csr1_no_begin_end.pem +15 -0
  115. data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
  116. data/spec/fixtures/csr2.pem +15 -0
  117. data/spec/fixtures/csr2_key.pem +27 -0
  118. data/spec/fixtures/csr3.pem +16 -0
  119. data/spec/fixtures/csr4.pem +25 -0
  120. data/spec/fixtures/csr_dsa.pem +15 -0
  121. data/spec/fixtures/csr_invalid_signature.pem +13 -0
  122. data/spec/fixtures/dsa_key.pem +20 -0
  123. data/spec/fixtures/key4.pem +27 -0
  124. data/spec/fixtures/key4_encrypted_des3.pem +30 -0
  125. data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
  126. data/spec/fixtures/missing_key_identifier_ca.key +27 -0
  127. data/spec/fixtures/ocsptest.r509.local.pem +27 -0
  128. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  129. data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
  130. data/spec/fixtures/second_ca.cer +26 -0
  131. data/spec/fixtures/second_ca.key +27 -0
  132. data/spec/fixtures/spkac.der +0 -0
  133. data/spec/fixtures/spkac.txt +1 -0
  134. data/spec/fixtures/spkac_dsa.txt +1 -0
  135. data/spec/fixtures/stca.pem +22 -0
  136. data/spec/fixtures/stca_ocsp_request.der +0 -0
  137. data/spec/fixtures/stca_ocsp_response.der +0 -0
  138. data/spec/fixtures/test1.csr +17 -0
  139. data/spec/fixtures/test_ca.cer +22 -0
  140. data/spec/fixtures/test_ca.key +28 -0
  141. data/spec/fixtures/test_ca.p12 +0 -0
  142. data/spec/fixtures/test_ca_des3.key +30 -0
  143. data/spec/fixtures/test_ca_ocsp.cer +26 -0
  144. data/spec/fixtures/test_ca_ocsp.key +27 -0
  145. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  146. data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
  147. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  148. data/spec/fixtures/test_ca_subroot.cer +26 -0
  149. data/spec/fixtures/test_ca_subroot.key +27 -0
  150. data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
  151. data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
  152. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  153. data/spec/fixtures/unknown_oid.csr +17 -0
  154. data/spec/message_digest_spec.rb +89 -0
  155. data/spec/ocsp_spec.rb +111 -0
  156. data/spec/oid_mapper_spec.rb +31 -0
  157. data/spec/privatekey_spec.rb +198 -0
  158. data/spec/spec_helper.rb +14 -0
  159. data/spec/spki_spec.rb +157 -0
  160. data/spec/subject_spec.rb +203 -0
  161. data/spec/validity_spec.rb +98 -0
  162. 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