r509 0.8.1 → 0.9

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