r509 0.8

Sign up to get free protection for your applications and to get access to all the features.
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