r509 0.8.1 → 0.9

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 (203) hide show
  1. data/README.md +343 -151
  2. data/Rakefile +26 -23
  3. data/bin/r509 +126 -112
  4. data/bin/r509-parse +24 -24
  5. data/doc/R509.html +169 -7
  6. data/doc/R509/ASN1.html +370 -0
  7. data/doc/R509/ASN1/GeneralName.html +1121 -0
  8. data/doc/R509/ASN1/GeneralNames.html +843 -0
  9. data/doc/R509/ASN1/NoticeReference.html +392 -0
  10. data/doc/R509/ASN1/PolicyInformation.html +387 -0
  11. data/doc/R509/ASN1/PolicyQualifiers.html +455 -0
  12. data/doc/R509/ASN1/UserNotice.html +386 -0
  13. data/doc/R509/{Crl.html → CRL.html} +7 -7
  14. data/doc/R509/CRL/Administrator.html +1559 -0
  15. data/doc/R509/{Crl/Parser.html → CRL/SignedList.html} +501 -210
  16. data/doc/R509/{Csr.html → CSR.html} +444 -314
  17. data/doc/R509/Cert.html +866 -617
  18. data/doc/R509/Cert/Extensions.html +52 -41
  19. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +70 -35
  20. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +387 -4
  21. data/doc/R509/Cert/Extensions/BasicConstraints.html +61 -25
  22. data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +354 -0
  23. data/doc/R509/Cert/Extensions/CertificatePolicies.html +340 -0
  24. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +440 -49
  25. data/doc/R509/Cert/Extensions/{CrlDistributionPoints.html → InhibitAnyPolicy.html} +52 -35
  26. data/doc/R509/Cert/Extensions/KeyUsage.html +247 -121
  27. data/doc/R509/Cert/Extensions/NameConstraints.html +445 -0
  28. data/doc/R509/Cert/Extensions/OCSPNoCheck.html +239 -0
  29. data/doc/R509/Cert/Extensions/PolicyConstraints.html +424 -0
  30. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +437 -62
  31. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +52 -10
  32. data/doc/R509/CertificateAuthority.html +4 -4
  33. data/doc/R509/CertificateAuthority/Signer.html +154 -187
  34. data/doc/R509/Config.html +6 -6
  35. data/doc/R509/Config/{CaConfig.html → CAConfig.html} +451 -348
  36. data/doc/R509/Config/{CaConfigPool.html → CAConfigPool.html} +47 -47
  37. data/doc/R509/Config/CAProfile.html +1015 -0
  38. data/doc/R509/Config/SubjectItemPolicy.html +86 -86
  39. data/doc/R509/IOHelpers.html +22 -22
  40. data/doc/R509/MessageDigest.html +14 -14
  41. data/doc/R509/NameSanitizer.html +53 -53
  42. data/doc/R509/{Ocsp.html → OCSP.html} +9 -9
  43. data/doc/R509/{Ocsp → OCSP}/Request.html +7 -7
  44. data/doc/R509/{Ocsp → OCSP}/Request/Nonce.html +56 -11
  45. data/doc/R509/{Ocsp → OCSP}/Response.html +44 -44
  46. data/doc/R509/{OidMapper.html → OIDMapper.html} +23 -39
  47. data/doc/R509/PrivateKey.html +415 -168
  48. data/doc/R509/R509Error.html +3 -3
  49. data/doc/R509/{Spki.html → SPKI.html} +354 -192
  50. data/doc/R509/Subject.html +224 -113
  51. data/doc/R509/Validity.html +27 -5
  52. data/doc/R509/Validity/Checker.html +13 -13
  53. data/doc/R509/Validity/DefaultChecker.html +13 -13
  54. data/doc/R509/Validity/DefaultWriter.html +14 -14
  55. data/doc/R509/Validity/Status.html +39 -39
  56. data/doc/R509/Validity/Writer.html +18 -18
  57. data/doc/_index.html +138 -35
  58. data/doc/class_list.html +1 -1
  59. data/doc/css/style.css +10 -0
  60. data/doc/file.README.html +368 -171
  61. data/doc/file.r509.html +92 -69
  62. data/doc/frames.html +1 -1
  63. data/doc/index.html +368 -171
  64. data/doc/method_list.html +910 -390
  65. data/doc/top-level-namespace.html +3 -3
  66. data/lib/r509.rb +32 -16
  67. data/lib/r509/asn1.rb +375 -0
  68. data/lib/r509/cert.rb +381 -364
  69. data/lib/r509/cert/extensions.rb +443 -76
  70. data/lib/r509/certificate_authority.rb +407 -0
  71. data/lib/r509/config.rb +547 -351
  72. data/lib/r509/crl.rb +336 -366
  73. data/lib/r509/csr.rb +278 -289
  74. data/lib/r509/ec-hack.rb +37 -0
  75. data/lib/r509/exceptions.rb +3 -3
  76. data/lib/r509/io_helpers.rb +44 -44
  77. data/lib/r509/message_digest.rb +53 -0
  78. data/lib/r509/ocsp.rb +80 -70
  79. data/lib/r509/oid_mapper.rb +32 -0
  80. data/lib/r509/private_key.rb +228 -0
  81. data/lib/r509/spki.rb +145 -93
  82. data/lib/r509/subject.rb +203 -110
  83. data/lib/r509/validity.rb +70 -68
  84. data/lib/r509/version.rb +2 -2
  85. data/r509.yaml +92 -69
  86. data/spec/asn1_spec.rb +402 -0
  87. data/spec/cert/extensions_spec.rb +957 -494
  88. data/spec/cert_spec.rb +382 -307
  89. data/spec/certificate_authority_spec.rb +668 -250
  90. data/spec/config_spec.rb +515 -302
  91. data/spec/crl_spec.rb +197 -198
  92. data/spec/csr_spec.rb +334 -289
  93. data/spec/fixtures.rb +247 -171
  94. data/spec/fixtures/cert1.der +0 -0
  95. data/spec/fixtures/cert1.pem +0 -0
  96. data/spec/fixtures/cert1_public_key_modulus.txt +0 -0
  97. data/spec/fixtures/cert3.p12 +0 -0
  98. data/spec/fixtures/cert3.pem +0 -0
  99. data/spec/fixtures/cert3_key.pem +0 -0
  100. data/spec/fixtures/cert3_key_des3.pem +0 -0
  101. data/spec/fixtures/cert4.pem +0 -0
  102. data/spec/fixtures/cert5.pem +0 -0
  103. data/spec/fixtures/cert6.pem +0 -0
  104. data/spec/fixtures/cert_expired.pem +0 -0
  105. data/spec/fixtures/cert_inhibit.pem +24 -0
  106. data/spec/fixtures/cert_name_constraints.pem +29 -0
  107. data/spec/fixtures/cert_not_yet_valid.pem +0 -0
  108. data/spec/fixtures/cert_ocsp_no_check.pem +18 -0
  109. data/spec/fixtures/cert_policy_constraints.pem +31 -0
  110. data/spec/fixtures/cert_san.pem +0 -0
  111. data/spec/fixtures/cert_san2.pem +0 -0
  112. data/spec/fixtures/cert_unknown_extension.pem +28 -0
  113. data/spec/fixtures/config_pool_test_minimal.yaml +11 -11
  114. data/spec/fixtures/config_test.yaml +54 -36
  115. data/spec/fixtures/config_test_dsa.yaml +35 -0
  116. data/spec/fixtures/config_test_ec.yaml +35 -0
  117. data/spec/fixtures/config_test_engine_key.yaml +5 -5
  118. data/spec/fixtures/config_test_engine_no_key_name.yaml +4 -4
  119. data/spec/fixtures/config_test_minimal.yaml +4 -4
  120. data/spec/fixtures/config_test_password.yaml +5 -5
  121. data/spec/fixtures/config_test_various.yaml +111 -74
  122. data/spec/fixtures/crl_list_file.txt +0 -0
  123. data/spec/fixtures/crl_with_reason.pem +0 -0
  124. data/spec/fixtures/csr1.der +0 -0
  125. data/spec/fixtures/csr1.pem +0 -0
  126. data/spec/fixtures/csr1_key.der +0 -0
  127. data/spec/fixtures/csr1_key.pem +0 -0
  128. data/spec/fixtures/csr1_key_encrypted_des3.pem +0 -0
  129. data/spec/fixtures/csr1_newlines.pem +0 -0
  130. data/spec/fixtures/csr1_no_begin_end.pem +0 -0
  131. data/spec/fixtures/csr1_public_key_modulus.txt +0 -0
  132. data/spec/fixtures/csr2.pem +0 -0
  133. data/spec/fixtures/csr2_key.pem +0 -0
  134. data/spec/fixtures/csr3.pem +0 -0
  135. data/spec/fixtures/csr4.pem +0 -0
  136. data/spec/fixtures/csr_dsa.pem +0 -0
  137. data/spec/fixtures/csr_invalid_signature.pem +0 -0
  138. data/spec/fixtures/dsa_key.pem +0 -0
  139. data/spec/fixtures/dsa_root.cer +28 -0
  140. data/spec/fixtures/dsa_root.key +20 -0
  141. data/spec/fixtures/ec_csr2.der +0 -0
  142. data/spec/fixtures/ec_csr2.pem +8 -0
  143. data/spec/fixtures/ec_key1.der +0 -0
  144. data/spec/fixtures/ec_key1.pem +6 -0
  145. data/spec/fixtures/ec_key1_encrypted.pem +9 -0
  146. data/spec/fixtures/ec_key2.pem +6 -0
  147. data/spec/fixtures/hmacsha1.sig +1 -0
  148. data/spec/fixtures/hmacsha512.sig +1 -0
  149. data/spec/fixtures/key4.pem +0 -0
  150. data/spec/fixtures/key4_encrypted_des3.pem +0 -0
  151. data/spec/fixtures/missing_key_identifier_ca.cer +0 -0
  152. data/spec/fixtures/missing_key_identifier_ca.key +0 -0
  153. data/spec/fixtures/ocsptest.r509.local.pem +0 -0
  154. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  155. data/spec/fixtures/ocsptest2.r509.local.pem +0 -0
  156. data/spec/fixtures/second_ca.cer +0 -0
  157. data/spec/fixtures/second_ca.key +0 -0
  158. data/spec/fixtures/spkac.der +0 -0
  159. data/spec/fixtures/spkac.txt +0 -0
  160. data/spec/fixtures/spkac_dsa.txt +1 -1
  161. data/spec/fixtures/spkac_dsa_no_verify.txt +1 -0
  162. data/spec/fixtures/spkac_ec.txt +1 -0
  163. data/spec/fixtures/spkac_rsa_newlines.txt +13 -0
  164. data/spec/fixtures/stca.pem +0 -0
  165. data/spec/fixtures/stca_ocsp_request.der +0 -0
  166. data/spec/fixtures/stca_ocsp_response.der +0 -0
  167. data/spec/fixtures/test1.csr +0 -0
  168. data/spec/fixtures/test_ca.cer +0 -0
  169. data/spec/fixtures/test_ca.key +0 -0
  170. data/spec/fixtures/test_ca.p12 +0 -0
  171. data/spec/fixtures/test_ca_des3.key +0 -0
  172. data/spec/fixtures/test_ca_ec.cer +14 -0
  173. data/spec/fixtures/test_ca_ec.key +6 -0
  174. data/spec/fixtures/test_ca_ec_ee.cer +22 -0
  175. data/spec/fixtures/test_ca_ec_ee.key +6 -0
  176. data/spec/fixtures/test_ca_ocsp.cer +0 -0
  177. data/spec/fixtures/test_ca_ocsp.key +0 -0
  178. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  179. data/spec/fixtures/test_ca_ocsp_chain.txt +0 -0
  180. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  181. data/spec/fixtures/test_ca_subroot.cer +0 -0
  182. data/spec/fixtures/test_ca_subroot.key +0 -0
  183. data/spec/fixtures/test_ca_subroot_ocsp.cer +0 -0
  184. data/spec/fixtures/test_ca_subroot_ocsp.key +0 -0
  185. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  186. data/spec/fixtures/unknown_oid.csr +0 -0
  187. data/spec/message_digest_spec.rb +104 -84
  188. data/spec/ocsp_spec.rb +105 -105
  189. data/spec/oid_mapper_spec.rb +21 -21
  190. data/spec/private_key_spec.rb +275 -0
  191. data/spec/r509_spec.rb +35 -0
  192. data/spec/spec_helper.rb +15 -6
  193. data/spec/spki_spec.rb +221 -142
  194. data/spec/subject_spec.rb +232 -164
  195. data/spec/validity_spec.rb +91 -91
  196. metadata +79 -25
  197. data/doc/R509/Config/CaProfile.html +0 -651
  198. data/doc/R509/Crl/Administrator.html +0 -2073
  199. data/lib/r509/certificateauthority.rb +0 -290
  200. data/lib/r509/messagedigest.rb +0 -49
  201. data/lib/r509/oidmapper.rb +0 -32
  202. data/lib/r509/privatekey.rb +0 -185
  203. data/spec/privatekey_spec.rb +0 -198
data/lib/r509/csr.rb CHANGED
@@ -1,324 +1,313 @@
1
1
  require 'openssl'
2
2
  require 'r509/exceptions'
3
3
  require 'r509/io_helpers'
4
- require 'r509/privatekey'
4
+ require 'r509/private_key'
5
+ require 'r509/ec-hack'
6
+ require 'r509/asn1'
5
7
 
6
8
  module R509
7
- # The primary certificate signing request object
8
- class Csr
9
- include R509::IOHelpers
10
-
11
- attr_reader :san_names, :key, :subject, :req, :attributes, :message_digest
12
- # @option opts [String,OpenSSL::X509::Request] :csr a csr
13
- # @option opts [Symbol] :type :rsa/:dsa
14
- # @option opts [Integer] :bit_strength
15
- # @option opts [Array] :san_names List of domains to encode as subjectAltNames
16
- # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
17
- # @example [['CN','langui.sh'],['ST','Illinois'],['L','Chicago'],['C','US'],['emailAddress','ca@langui.sh']]
18
- # you can also pass OIDs (see tests)
19
- # @option opts [String,R509::Cert,OpenSSL::X509::Certificate] :cert takes a cert (used for generating a CSR with the certificate's values)
20
- # @option opts [R509::PrivateKey,String] :key optional private key to supply. either an unencrypted PEM/DER string or an R509::PrivateKey object (use the latter if you need password/hardware support)
21
- def initialize(opts={})
22
- if not opts.kind_of?(Hash)
23
- raise ArgumentError, 'Must provide a hash of options'
24
- end
25
- if (opts.has_key?(:cert) and opts.has_key?(:subject)) or
26
- (opts.has_key?(:cert) and opts.has_key?(:csr)) or
27
- (opts.has_key?(:subject) and opts.has_key?(:csr))
28
- raise ArgumentError, "Can only provide one of cert, subject, or csr"
29
- end
30
- @bit_strength = opts[:bit_strength] || 2048
31
-
32
- if opts.has_key?(:key)
33
- if opts[:key].kind_of?(R509::PrivateKey)
34
- @key = opts[:key]
35
- else
36
- @key = R509::PrivateKey.new(:key => opts[:key])
37
- end
38
- end
39
-
40
- @type = opts[:type] || :rsa
41
- if @type != :rsa and @type != :dsa and @key.nil?
42
- raise ArgumentError, 'Must provide :rsa or :dsa as type when key is nil'
43
- end
44
-
45
- if opts.has_key?(:cert)
46
- domains = opts[:san_names] || []
47
- parsed_domains = prefix_domains(domains)
48
- cert_data = parse_cert(opts[:cert])
49
- merged_domains = cert_data[:subjectAltName].concat(parsed_domains)
50
- create_request(cert_data[:subject],merged_domains) #sets @req
51
- elsif opts.has_key?(:subject)
52
- domains = opts[:san_names] || []
53
- parsed_domains = prefix_domains(domains)
54
- create_request(opts[:subject], parsed_domains) #sets @req
55
- elsif opts.has_key?(:csr)
56
- if opts.has_key?(:san_names)
57
- raise ArgumentError, "You can't add domains to an existing CSR"
58
- end
59
- parse_csr(opts[:csr])
60
- else
61
- raise ArgumentError, "Must provide one of cert, subject, or csr"
62
- end
63
-
64
- if dsa?
65
- #only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
66
- #post-1.0 you can sign with anything, but let's be conservative
67
- #see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
68
- @message_digest = R509::MessageDigest.new('dss1')
69
- elsif opts.has_key?(:message_digest)
70
- @message_digest = R509::MessageDigest.new(opts[:message_digest])
71
- else
72
- @message_digest = R509::MessageDigest.new('sha1')
73
- end
74
-
75
- if not opts.has_key?(:csr)
76
- @req.sign(@key.key, @message_digest.digest)
77
- end
78
- if not @key.nil? and not @req.verify(@key.public_key) then
79
- raise R509Error, 'Key does not match request.'
80
- end
81
-
82
- end
83
-
84
- # Helper method to quickly load a CSR from the filesystem
85
- #
86
- # @param [String] filename Path to file you want to load
87
- # @return [R509::Csr] Csr object
88
- def self.load_from_file( filename )
89
- return R509::Csr.new(:csr => IOHelpers.read_data(filename) )
9
+ # The primary certificate signing request object
10
+ class CSR
11
+ include R509::IOHelpers
12
+
13
+ attr_reader :san, :key, :subject, :req, :attributes, :message_digest
14
+ # @option opts [String,OpenSSL::X509::Request] :csr a csr
15
+ # @option opts [Symbol] :type :rsa/:dsa/:ec required if not providing existing :csr. Defaults to :rsa
16
+ # @option opts [String] :curve_name ("secp384r1") Only used if :type is :ec
17
+ # @option opts [Integer] :bit_strength (2048) Only used if :type is :rsa or :dsa
18
+ # @option opts [String] :message_digest Optional digest. sha1, sha224, sha256, sha384, sha512, md5. Defaults to sha1
19
+ # @option opts [Array] :san_names List of domains, IPs, email addresses, or URIs to encode as subjectAltNames. The type is determined from the structure of the strings via the R509::ASN1.general_name_parser method
20
+ # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
21
+ # @option opts [R509::PrivateKey,String] :key optional private key to supply. either an unencrypted PEM/DER string or an R509::PrivateKey object (use the latter if you need password/hardware support)
22
+ # @example Generate a 4096-bit RSA key + CSR
23
+ # :type => :rsa,
24
+ # :bit_strength => 4096,
25
+ # :subject => [
26
+ # ['CN','somedomain.com'],
27
+ # ['O','My Org'],
28
+ # ['L','City'],
29
+ # ['ST','State'],
30
+ # ['C','US']
31
+ # ]
32
+ # @example Generate an ECDSA key using the secp384r1 curve parameters + CSR and sign with SHA512
33
+ # :type => :ec,
34
+ # :curve_name => 'secp384r1',
35
+ # :message_digest => 'sha512',
36
+ # :subject => [
37
+ # ['CN','somedomain.com'],
38
+ # ]
39
+ def initialize(opts={})
40
+ if not opts.kind_of?(Hash)
41
+ raise ArgumentError, 'Must provide a hash of options'
42
+ end
43
+ if opts.has_key?(:subject) and opts.has_key?(:csr)
44
+ raise ArgumentError, "You must provide :subject or :csr, not both"
45
+ end
46
+ @bit_strength = opts[:bit_strength] || 2048
47
+ @curve_name = opts[:curve_name] || "secp384r1"
48
+
49
+ if opts.has_key?(:key)
50
+ if opts[:key].kind_of?(R509::PrivateKey)
51
+ @key = opts[:key]
52
+ else
53
+ @key = R509::PrivateKey.new(:key => opts[:key])
90
54
  end
91
-
92
- # @return [OpenSSL::PKey::RSA] public key
93
- def public_key
94
- if(@req.kind_of?(OpenSSL::X509::Request)) then
95
- @req.public_key
96
- end
55
+ end
56
+
57
+ @type = opts[:type] || :rsa
58
+ if not [:rsa,:dsa,:ec].include?(@type) and @key.nil?
59
+ raise ArgumentError, 'Must provide :rsa, :dsa, or :ec as type when key is nil'
60
+ end
61
+
62
+ if opts.has_key?(:subject)
63
+ san_names = R509::ASN1.general_name_parser(opts[:san_names] || [])
64
+ create_request(opts[:subject], san_names) #sets @req
65
+ elsif opts.has_key?(:csr)
66
+ if opts.has_key?(:san_names)
67
+ raise ArgumentError, "You can't add domains to an existing CSR"
97
68
  end
69
+ parse_csr(opts[:csr])
70
+ else
71
+ raise ArgumentError, "You must provide :subject or :csr"
72
+ end
73
+
74
+ if dsa?
75
+ #only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
76
+ #post-1.0 you can sign with anything, but let's be conservative
77
+ #see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
78
+ @message_digest = R509::MessageDigest.new('dss1')
79
+ elsif opts.has_key?(:message_digest)
80
+ @message_digest = R509::MessageDigest.new(opts[:message_digest])
81
+ else
82
+ @message_digest = R509::MessageDigest.new('sha1')
83
+ end
84
+
85
+ if not opts.has_key?(:csr)
86
+ @req.sign(@key.key, @message_digest.digest)
87
+ end
88
+ if not @key.nil? and not @req.verify(@key.public_key) then
89
+ raise R509Error, 'Key does not match request.'
90
+ end
98
91
 
99
- # Verifies the integrity of the signature on the request
100
- # @return [Boolean]
101
- def verify_signature
102
- @req.verify(public_key)
103
- end
92
+ end
104
93
 
105
- # @return [Boolean] Boolean of whether the object contains a private key
106
- def has_private_key?
107
- if not @key.nil?
108
- true
109
- else
110
- false
111
- end
112
- end
94
+ # Helper method to quickly load a CSR from the filesystem
95
+ #
96
+ # @param [String] filename Path to file you want to load
97
+ # @return [R509::CSR] CSR object
98
+ def self.load_from_file( filename )
99
+ return R509::CSR.new(:csr => IOHelpers.read_data(filename) )
100
+ end
113
101
 
114
- # Converts the CSR into the PEM format
115
- #
116
- # @return [String] the CSR converted into PEM format.
117
- def to_pem
118
- @req.to_pem
119
- end
102
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] public key
103
+ def public_key
104
+ if(@req.kind_of?(OpenSSL::X509::Request)) then
105
+ @req.public_key
106
+ end
107
+ end
120
108
 
121
- alias :to_s :to_pem
109
+ # Verifies the integrity of the signature on the request
110
+ # @return [Boolean]
111
+ def verify_signature
112
+ @req.verify(public_key)
113
+ end
122
114
 
123
- # Converts the CSR into the DER format
124
- #
125
- # @return [String] the CSR converted into DER format.
126
- def to_der
127
- @req.to_der
128
- end
115
+ # @return [Boolean] Boolean of whether the object contains a private key
116
+ def has_private_key?
117
+ if not @key.nil?
118
+ true
119
+ else
120
+ false
121
+ end
122
+ end
129
123
 
130
- # Writes the CSR into the PEM format
131
- #
132
- # @param [String, #write] filename_or_io Either a string of the path for
133
- # the file that you'd like to write, or an IO-like object.
134
- def write_pem(filename_or_io)
135
- write_data(filename_or_io, @req.to_pem)
136
- end
124
+ # Converts the CSR into the PEM format
125
+ #
126
+ # @return [String] the CSR converted into PEM format.
127
+ def to_pem
128
+ @req.to_pem
129
+ end
137
130
 
138
- # Writes the CSR into the DER format
139
- #
140
- # @param [String, #write] filename_or_io Either a string of the path for
141
- # the file that you'd like to write, or an IO-like object.
142
- def write_der(filename_or_io)
143
- write_data(filename_or_io, @req.to_der)
144
- end
131
+ alias :to_s :to_pem
145
132
 
146
- # Returns whether the public key is RSA
147
- #
148
- # @return [Boolean] true if the public key is RSA, false otherwise
149
- def rsa?
150
- @req.public_key.kind_of?(OpenSSL::PKey::RSA)
151
- end
133
+ # Converts the CSR into the DER format
134
+ #
135
+ # @return [String] the CSR converted into DER format.
136
+ def to_der
137
+ @req.to_der
138
+ end
152
139
 
153
- # Returns whether the public key is DSA
154
- #
155
- # @return [Boolean] true if the public key is DSA, false otherwise
156
- def dsa?
157
- @req.public_key.kind_of?(OpenSSL::PKey::DSA)
158
- end
140
+ # Writes the CSR into the PEM format
141
+ #
142
+ # @param [String, #write] filename_or_io Either a string of the path for
143
+ # the file that you'd like to write, or an IO-like object.
144
+ def write_pem(filename_or_io)
145
+ write_data(filename_or_io, @req.to_pem)
146
+ end
159
147
 
160
- # Returns the bit strength of the key used to create the CSR
161
- # @return [Integer] the integer bit strength.
162
- def bit_strength
163
- if self.rsa?
164
- return @req.public_key.n.num_bits
165
- elsif self.dsa?
166
- return @req.public_key.p.num_bits
167
- end
168
- end
148
+ # Writes the CSR into the DER format
149
+ #
150
+ # @param [String, #write] filename_or_io Either a string of the path for
151
+ # the file that you'd like to write, or an IO-like object.
152
+ def write_der(filename_or_io)
153
+ write_data(filename_or_io, @req.to_der)
154
+ end
169
155
 
170
- # Returns subject component
171
- #
172
- # @return [String] value of the subject component requested
173
- def subject_component short_name
174
- @req.subject.to_a.each do |element|
175
- if element[0].downcase == short_name.downcase then
176
- return element[1]
177
- end
178
- end
179
- nil
180
- end
156
+ # Returns whether the public key is RSA
157
+ #
158
+ # @return [Boolean] true if the public key is RSA, false otherwise
159
+ def rsa?
160
+ @req.public_key.kind_of?(OpenSSL::PKey::RSA)
161
+ end
181
162
 
182
- # Returns signature algorithm
183
- #
184
- # @return [String] value of the signature algorithm. E.g. sha1WithRSAEncryption, sha256WithRSAEncryption, md5WithRSAEncryption
185
- def signature_algorithm
186
- @req.signature_algorithm
187
- end
163
+ # Returns whether the public key is DSA
164
+ #
165
+ # @return [Boolean] true if the public key is DSA, false otherwise
166
+ def dsa?
167
+ @req.public_key.kind_of?(OpenSSL::PKey::DSA)
168
+ end
188
169
 
189
- # Returns key algorithm (RSA/DSA)
190
- #
191
- # @return [String] value of the key algorithm. RSA or DSA
192
- def key_algorithm
193
- if @req.public_key.kind_of? OpenSSL::PKey::RSA then
194
- 'RSA'
195
- elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
196
- 'DSA'
197
- end
198
- end
170
+ # Returns whether the public key is EC
171
+ #
172
+ # @return [Boolean] true if the public key is EC, false otherwise
173
+ def ec?
174
+ @req.public_key.kind_of?(OpenSSL::PKey::EC)
175
+ end
199
176
 
200
- # Returns a hash structure you can pass to the Ca.
201
- # You will want to call this method if you intend to alter the values
202
- # and then pass them to the Ca class.
203
- #
204
- # @return [Hash] :subject and :san_names you can pass to Ca
205
- def to_hash
206
- { :subject => @subject.dup , :san_names => @san_names.dup }
207
- end
177
+ # Returns the bit strength of the key used to create the CSR
178
+ # @return [Integer] the integer bit strength.
179
+ def bit_strength
180
+ if self.rsa?
181
+ return @req.public_key.n.num_bits
182
+ elsif self.dsa?
183
+ return @req.public_key.p.num_bits
184
+ elsif self.ec?
185
+ raise R509::R509Error, 'Bit strength is not available for EC at this time.'
186
+ end
187
+ end
208
188
 
209
- private
210
-
211
- def parse_csr(csr)
212
- begin
213
- @req = OpenSSL::X509::Request.new csr
214
- rescue OpenSSL::X509::RequestError
215
- #let's try to load this thing by handling a few
216
- #common error cases
217
- if csr.kind_of?(String)
218
- #normalize line endings (really just for the next replace)
219
- csr.gsub!(/\r\n?/, "\n")
220
- #remove extraneous newlines
221
- csr.gsub!(/^\s*\n/,'')
222
- #and leading/trailing whitespace
223
- csr.gsub!(/^\s*|\s*$/,'')
224
- if not csr.match(/-----BEGIN.+-----/) and csr.match(/MII/)
225
- #if csr is probably PEM (MII is the beginning of every base64
226
- #encoded DER) then add the wrapping lines if they aren't provided.
227
- #tools like Microsoft's xenroll do this.
228
- csr = "-----BEGIN CERTIFICATE REQUEST-----\n"+csr+"\n-----END CERTIFICATE REQUEST-----"
229
- end
230
- end
231
- #and now we try again...
232
- @req = OpenSSL::X509::Request.new csr
233
- end
234
- @subject = R509::Subject.new(@req.subject)
235
- @attributes = parse_attributes_from_csr(@req)
236
- @san_names = @attributes['subjectAltName'] || []
237
- end
189
+ # Returns the short name of the elliptic curve used to generate the public key
190
+ # if the key is EC. If not, raises an error.
191
+ #
192
+ # @return [String] elliptic curve name
193
+ def curve_name
194
+ if self.ec?
195
+ self.public_key.group.curve_name
196
+ else
197
+ raise R509::R509Error, 'Curve name is only available with EC CSRs'
198
+ end
199
+ end
238
200
 
239
- def create_request(subject,domains=[])
240
- domains.uniq! #de-duplicate the array
241
- @req = OpenSSL::X509::Request.new
242
- @req.version = 0
243
- @subject = R509::Subject.new(subject)
244
- @req.subject = @subject.name
245
- if @key.nil?
246
- @key = R509::PrivateKey.new(:type => @type,
247
- :bit_strength => @bit_strength)
248
- end
249
- @req.public_key = @key.public_key
250
- add_san_extension(domains)
251
- @attributes = parse_attributes_from_csr(@req)
252
- @san_names = @attributes['subjectAltName'] || []
201
+ # Returns subject component
202
+ #
203
+ # @return [String] value of the subject component requested
204
+ def subject_component short_name
205
+ @req.subject.to_a.each do |element|
206
+ if element[0].downcase == short_name.downcase then
207
+ return element[1]
253
208
  end
209
+ end
210
+ nil
211
+ end
254
212
 
255
- # parses an existing cert to get data to add to new CSR
256
- def parse_cert(cert)
257
- domains_to_add = []
258
- san_extension = nil
259
- parsed_cert = OpenSSL::X509::Certificate.new(cert)
260
- parsed_cert.extensions.each { |extension|
261
- if (extension.oid == 'subjectAltName') then
262
- domains_to_add = parse_san_extension(extension)
263
- end
264
- }
265
- {:subject => parsed_cert.subject, :subjectAltName => domains_to_add}
266
- end
213
+ # Returns signature algorithm
214
+ #
215
+ # @return [String] value of the signature algorithm. E.g. sha1WithRSAEncryption, sha256WithRSAEncryption, md5WithRSAEncryption
216
+ def signature_algorithm
217
+ @req.signature_algorithm
218
+ end
267
219
 
268
- # @return [Hash] attributes of a CSR
269
- def parse_attributes_from_csr(req)
270
- attributes = Hash.new
271
- domains_from_csr = []
272
- set = nil
273
- req.attributes.each { |attribute|
274
- if attribute.oid == 'extReq' then
275
- set = OpenSSL::ASN1.decode attribute.value
276
- end
277
- }
278
- if !set.nil? then
279
- set.value.each { |set_value|
280
- @seq = set_value
281
- extensions = @seq.value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
282
- extensions.each { |ext|
283
- attributes[ext.oid] = {'value' => ext.value, 'critical'=> ext.critical? }
284
- if ext.oid == 'subjectAltName' then
285
- domains_from_csr = ext.value.gsub(/DNS:/,'').split(',')
286
- domains_from_csr = domains_from_csr.collect {|x| x.strip }
287
- attributes[ext.oid] = domains_from_csr
288
- end
289
- }
290
- }
291
- end
292
- attributes
293
- end
220
+ # Returns key algorithm (RSA/DSA/EC)
221
+ #
222
+ # @return [Symbol] value of the key algorithm. :rsa, :dsa, :ec
223
+ def key_algorithm
224
+ if @req.public_key.kind_of? OpenSSL::PKey::RSA then
225
+ :rsa
226
+ elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
227
+ :dsa
228
+ elsif @req.public_key.kind_of? OpenSSL::PKey::EC then
229
+ :ec
230
+ end
231
+ end
294
232
 
295
- #takes OpenSSL::X509::Extension object
296
- def parse_san_extension(extension)
297
- san_string = extension.value
298
- stripped = []
299
- san_string.split(',').each{ |name|
300
- stripped.push name.strip
301
- }
302
- stripped
233
+ private
234
+
235
+ def parse_csr(csr)
236
+ begin
237
+ @req = OpenSSL::X509::Request.new csr
238
+ rescue OpenSSL::X509::RequestError
239
+ #let's try to load this thing by handling a few
240
+ #common error cases
241
+ if csr.kind_of?(String)
242
+ #normalize line endings (really just for the next replace)
243
+ csr.gsub!(/\r\n?/, "\n")
244
+ #remove extraneous newlines
245
+ csr.gsub!(/^\s*\n/,'')
246
+ #and leading/trailing whitespace
247
+ csr.gsub!(/^\s*|\s*$/,'')
248
+ if not csr.match(/-----BEGIN.+-----/) and csr.match(/MII/)
249
+ #if csr is probably PEM (MII is the beginning of every base64
250
+ #encoded DER) then add the wrapping lines if they aren't provided.
251
+ #tools like Microsoft's xenroll do this.
252
+ csr = "-----BEGIN CERTIFICATE REQUEST-----\n"+csr+"\n-----END CERTIFICATE REQUEST-----"
253
+ end
303
254
  end
255
+ #and now we try again...
256
+ @req = OpenSSL::X509::Request.new csr
257
+ end
258
+ @subject = R509::Subject.new(@req.subject)
259
+ parse_san_attribute_from_csr(@req)
260
+ end
304
261
 
305
- def add_san_extension(domains_to_add)
306
- if(domains_to_add.size > 0) then
307
- ef = OpenSSL::X509::ExtensionFactory.new
308
- ex = []
309
- ex << ef.create_extension("subjectAltName", domains_to_add.join(', '))
310
- request_extension_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(ex)])
311
- @req.add_attribute(OpenSSL::X509::Attribute.new("extReq", request_extension_set))
312
- @san_names = strip_prefix(domains_to_add)
313
- end
314
- end
262
+ def create_request(subject,san_names)
263
+ @req = OpenSSL::X509::Request.new
264
+ @req.version = 0
265
+ @subject = R509::Subject.new(subject)
266
+ @req.subject = @subject.name
267
+ if @key.nil?
268
+ @key = R509::PrivateKey.new(:type => @type, :bit_strength => @bit_strength, :curve_name => @curve_name)
269
+ end
270
+ @req.public_key = @key.public_key
271
+ add_san_extension(san_names)
272
+ parse_san_attribute_from_csr(@req)
273
+ end
315
274
 
316
- def prefix_domains(domains)
317
- domains.map { |domain| 'DNS: '+domain }
275
+ # @return [Array] array of GeneralName objects
276
+ def parse_san_attribute_from_csr(req)
277
+ san = nil
278
+ set = nil
279
+ req.attributes.each do |attribute|
280
+ if attribute.oid == 'extReq'
281
+ set = OpenSSL::ASN1.decode attribute.value
282
+ extensions = set.value[0].value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
283
+ r509_extensions = R509::Cert::Extensions.wrap_openssl_extensions( extensions )
284
+ if not r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].nil?
285
+ san = r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].general_names
286
+ end
287
+ break
318
288
  end
289
+ end
290
+ @san = san
291
+ end
319
292
 
320
- def strip_prefix(domains)
321
- domains.map{ |name| name.gsub(/DNS:/,'').strip }
293
+ def add_san_extension(san_names)
294
+ if not san_names.nil? and not san_names.names.empty?
295
+ names = san_names.names.uniq
296
+ general_names = R509::ASN1::GeneralNames.new
297
+ names.each do |domain|
298
+ general_names.add_item(domain)
322
299
  end
300
+ ef = OpenSSL::X509::ExtensionFactory.new
301
+ serialized = general_names.serialize_names
302
+ ef.config = OpenSSL::Config.parse(serialized[:conf])
303
+ ex = []
304
+ ex << ef.create_extension("subjectAltName", serialized[:extension_string])
305
+ request_extension_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(ex)])
306
+ @req.add_attribute(OpenSSL::X509::Attribute.new("extReq", request_extension_set))
307
+ parse_san_attribute_from_csr(@req)
308
+ end
323
309
  end
310
+
311
+
312
+ end
324
313
  end