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/spki.rb CHANGED
@@ -3,110 +3,162 @@ require 'r509/exceptions'
3
3
  require 'r509/io_helpers'
4
4
 
5
5
  module R509
6
- # class for handling SPKAC/SPKI requests (typically generated by the <keygen> tag
7
- class Spki
8
- include R509::IOHelpers
9
-
10
- attr_reader :subject, :spki, :san_names
11
- # @option opts [String,OpenSSL::Netscape::SPKI] :spki the spki you want to parse
12
- # @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
13
- # @example [['CN','langui.sh'],['ST','Illinois'],['L','Chicago'],['C','US'],['emailAddress','ca@langui.sh']]
14
- # you can also pass OIDs (see tests)
15
- # @option opts [Array] :san_names array of SAN names
16
- def initialize(opts={})
17
- if not opts.kind_of?(Hash)
18
- raise ArgumentError, 'Must provide a hash of options'
19
- end
20
- if opts.has_key?(:spki) and not opts.has_key?(:subject)
21
- raise ArgumentError, "Must provide both spki and subject"
22
- end
23
- if opts.has_key?(:san_names) and not opts[:san_names].kind_of?(Array)
24
- raise ArgumentError, "if san_names are provided they must be in an Array"
25
- end
26
- @spki = OpenSSL::Netscape::SPKI.new(opts[:spki].sub("SPKAC=",""))
27
- @subject = R509::Subject.new(opts[:subject])
28
- @san_names = opts[:san_names] || []
29
- end
6
+ # class for loading/generating SPKAC/SPKI requests (typically generated by the <keygen> tag
7
+ class SPKI
8
+ include R509::IOHelpers
30
9
 
31
- # @return [OpenSSL::PKey::RSA] public key
32
- def public_key
33
- @spki.public_key
34
- end
10
+ attr_reader :spki, :key
11
+ # @option opts [String,OpenSSL::Netscape::SPKI] :spki the spki you want to parse
12
+ # @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). if supplied you do not need to pass an spki.
13
+ # @option opts [String] :message_digest Optional digest. sha1, sha224, sha256, sha384, sha512, md5. Defaults to sha1. Only used if you supply a :key and no :spki
14
+ def initialize(opts={})
15
+ if not opts.kind_of?(Hash)
16
+ raise ArgumentError, 'Must provide a hash of options'
17
+ elsif not opts.has_key?(:spki) and not opts.has_key?(:key)
18
+ raise ArgumentError, 'Must provide either :spki or :key'
19
+ end
35
20
 
36
- # Converts the SPKI into the PEM format
37
- #
38
- # @return [String] the SPKI converted into PEM format.
39
- def to_pem
40
- @spki.to_pem
21
+ if opts.has_key?(:key)
22
+ if opts[:key].kind_of?(R509::PrivateKey)
23
+ @key = opts[:key]
24
+ else
25
+ @key = R509::PrivateKey.new(:key => opts[:key])
26
+ end
27
+ end
28
+ if opts.has_key?(:spki)
29
+ spki = opts[:spki]
30
+ # first let's try cleaning up the input a bit so OpenSSL is happy with it
31
+ # OpenSSL hates SPKAC=
32
+ spki.sub!("SPKAC=","")
33
+ # it really hates newlines (Firefox loves 'em)
34
+ # so let's normalize line endings
35
+ spki.gsub!(/\r\n?/, "\n")
36
+ # and nuke 'em
37
+ spki.gsub!("\n", "")
38
+ # ...and leading/trailing whitespace
39
+ spki.strip!
40
+ @spki = OpenSSL::Netscape::SPKI.new(spki)
41
+ if not @key.nil? and not @spki.verify(@key.public_key) then
42
+ raise R509Error, 'Key does not match SPKI.'
43
+ end
44
+ end
45
+ # create the SPKI from the private key if it wasn't passed in
46
+ if @spki.nil?
47
+ @spki = OpenSSL::Netscape::SPKI.new
48
+ @spki.public_key = @key.public_key
49
+ if @key.dsa?
50
+ #only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
51
+ #post-1.0 you can sign with anything, but let's be conservative
52
+ #see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
53
+ message_digest = R509::MessageDigest.new('dss1')
54
+ elsif opts.has_key?(:message_digest)
55
+ message_digest = R509::MessageDigest.new(opts[:message_digest])
56
+ else
57
+ message_digest = R509::MessageDigest.new('sha1')
41
58
  end
59
+ @spki.sign(@key.key,message_digest.digest)
60
+ end
61
+ end
42
62
 
43
- alias :to_s :to_pem
63
+ # @return [OpenSSL::PKey::RSA] public key
64
+ def public_key
65
+ @spki.public_key
66
+ end
44
67
 
45
- # Converts the SPKI into the DER format
46
- #
47
- # @return [String] the SPKI converted into DER format.
48
- def to_der
49
- @spki.to_der
50
- end
68
+ # Verifies the integrity of the signature on the SPKI
69
+ # @return [Boolean]
70
+ def verify_signature
71
+ @spki.verify(public_key)
72
+ end
51
73
 
52
- # Writes the SPKI into the PEM format
53
- #
54
- # @param [String, #write] filename_or_io Either a string of the path for
55
- # the file that you'd like to write, or an IO-like object.
56
- def write_pem(filename_or_io)
57
- write_data(filename_or_io, @spki.to_pem)
58
- end
74
+ # Converts the SPKI into the PEM format
75
+ #
76
+ # @return [String] the SPKI converted into PEM format.
77
+ def to_pem
78
+ @spki.to_pem
79
+ end
59
80
 
60
- # Writes the SPKI into the DER format
61
- #
62
- # @param [String, #write] filename_or_io Either a string of the path for
63
- # the file that you'd like to write, or an IO-like object.
64
- def write_der(filename_or_io)
65
- write_data(filename_or_io, @spki.to_der)
66
- end
81
+ alias :to_s :to_pem
67
82
 
68
- # Returns whether the public key is RSA
69
- #
70
- # @return [Boolean] true if the public key is RSA, false otherwise
71
- def rsa?
72
- @spki.public_key.kind_of?(OpenSSL::PKey::RSA)
73
- end
83
+ # Converts the SPKI into the DER format
84
+ #
85
+ # @return [String] the SPKI converted into DER format.
86
+ def to_der
87
+ @spki.to_der
88
+ end
74
89
 
75
- # Returns whether the public key is DSA
76
- #
77
- # @return [Boolean] true if the public key is DSA, false otherwise
78
- def dsa?
79
- @spki.public_key.kind_of?(OpenSSL::PKey::DSA)
80
- end
90
+ # Writes the SPKI into the PEM format
91
+ #
92
+ # @param [String, #write] filename_or_io Either a string of the path for
93
+ # the file that you'd like to write, or an IO-like object.
94
+ def write_pem(filename_or_io)
95
+ write_data(filename_or_io, @spki.to_pem)
96
+ end
81
97
 
82
- # Returns the bit strength of the key used to create the SPKI
83
- # @return [Integer] the integer bit strength.
84
- def bit_strength
85
- if self.rsa?
86
- return @spki.public_key.n.num_bits
87
- elsif self.dsa?
88
- return @spki.public_key.p.num_bits
89
- end
90
- end
98
+ # Writes the SPKI into the DER format
99
+ #
100
+ # @param [String, #write] filename_or_io Either a string of the path for
101
+ # the file that you'd like to write, or an IO-like object.
102
+ def write_der(filename_or_io)
103
+ write_data(filename_or_io, @spki.to_der)
104
+ end
91
105
 
92
- # Returns key algorithm (RSA/DSA)
93
- #
94
- # @return [String] value of the key algorithm. RSA or DSA
95
- def key_algorithm
96
- if @spki.public_key.kind_of? OpenSSL::PKey::RSA then
97
- 'RSA'
98
- elsif @spki.public_key.kind_of? OpenSSL::PKey::DSA then
99
- 'DSA'
100
- end
101
- end
106
+ # Returns whether the public key is RSA
107
+ #
108
+ # @return [Boolean] true if the public key is RSA, false otherwise
109
+ def rsa?
110
+ @spki.public_key.kind_of?(OpenSSL::PKey::RSA)
111
+ end
102
112
 
103
- # Returns a hash structure you can pass to the Ca
104
- # You will want to call this method if you intend to alter the values
105
- # and then pass them to the Ca class.
106
- #
107
- # @return [Hash] :subject and :san_names you can pass to Ca
108
- def to_hash
109
- { :subject => @subject.dup , :san_names => @san_names.dup }
110
- end
113
+ # Returns whether the public key is DSA
114
+ #
115
+ # @return [Boolean] true if the public key is DSA, false otherwise
116
+ def dsa?
117
+ @spki.public_key.kind_of?(OpenSSL::PKey::DSA)
118
+ end
119
+
120
+ # Returns whether the public key is EC
121
+ #
122
+ # @return [Boolean] true if the public key is EC, false otherwise
123
+ def ec?
124
+ @spki.public_key.kind_of?(OpenSSL::PKey::EC)
125
+ end
126
+
127
+ # Returns the bit strength of the key used to create the SPKI
128
+ # @return [Integer] the integer bit strength.
129
+ def bit_strength
130
+ if self.rsa?
131
+ return @spki.public_key.n.num_bits
132
+ elsif self.dsa?
133
+ return @spki.public_key.p.num_bits
134
+ elsif self.ec?
135
+ raise R509::R509Error, 'Bit strength is not available for EC at this time.'
136
+ end
137
+ end
138
+
139
+ # Returns the short name of the elliptic curve used to generate the public key
140
+ # if the key is EC. If not, raises an error.
141
+ #
142
+ # @return [String] elliptic curve name
143
+ def curve_name
144
+ if self.ec?
145
+ @spki.public_key.group.curve_name
146
+ else
147
+ raise R509::R509Error, 'Curve name is only available with EC SPKIs'
148
+ end
149
+ end
150
+
151
+ # Returns key algorithm (RSA/DSA)
152
+ #
153
+ # @return [String] value of the key algorithm. RSA or DSA
154
+ def key_algorithm
155
+ if self.rsa?
156
+ :rsa
157
+ elsif self.dsa?
158
+ :dsa
159
+ elsif self.ec?
160
+ :ec
161
+ end
111
162
  end
163
+ end
112
164
  end
data/lib/r509/subject.rb CHANGED
@@ -1,133 +1,226 @@
1
1
  require "openssl"
2
2
 
3
3
  module R509
4
- #subject class. Used for building OpenSSL::X509::Name objects in a sane fashion
5
- class Subject
6
- # @param [Array, OpenSSL::X509::Name, R509::Subject] arg
7
- def initialize(arg=nil)
8
- case arg
9
- when Array
10
- @array = arg
11
- when OpenSSL::X509::Name
12
- sanitizer = R509::NameSanitizer.new
13
- @array = sanitizer.sanitize(arg)
14
- when R509::Subject
15
- @array = arg.to_a
16
- else
17
- @array = []
18
- end
19
-
20
- # see if X509 thinks this is okay
21
- name
4
+ # subject class. Used for building OpenSSL::X509::Name objects in a sane fashion
5
+ # @example
6
+ # subject = R509::Subject.new
7
+ # subject.CN= "test.test"
8
+ # subject.organization= "r509 LLC"
9
+ # @example
10
+ # subject = R509::Subject.new([['CN','test.test'],['O','r509 LLC']])
11
+ # @example
12
+ # # you can also use the friendly getter/setters with custom OIDs
13
+ # R509::OIDMapper.register("1.2.3.4.5.6.7.8","COI","customOID")
14
+ # subject = R509::Subject.new
15
+ # subject.COI="test"
16
+ # # or
17
+ # subject.customOID="test"
18
+ # # or
19
+ # subject.custom_oid="test"
20
+ class Subject
21
+ # @param [Array, OpenSSL::X509::Name, R509::Subject, DER, nil] arg
22
+ def initialize(arg=nil)
23
+ if arg.kind_of?(Array)
24
+ @array = arg
25
+ elsif arg.kind_of?(OpenSSL::X509::Name)
26
+ sanitizer = R509::NameSanitizer.new
27
+ @array = sanitizer.sanitize(arg)
28
+ elsif arg.kind_of?(R509::Subject)
29
+ @array = arg.to_a
30
+ else
31
+ @array = []
32
+ if not (begin OpenSSL::ASN1.decode(arg) rescue nil end).nil?
33
+ parse_asn1(arg)
22
34
  end
35
+ end
23
36
 
24
- # @return [OpenSSL::X509::Name]
25
- def name
26
- OpenSSL::X509::Name.new(@array)
27
- end
37
+ # see if X509 thinks this is okay
38
+ name
39
+ end
28
40
 
29
- # @return [Boolean]
30
- def empty?
31
- @array.empty?
32
- end
41
+ # @return [OpenSSL::X509::Name]
42
+ def name
43
+ OpenSSL::X509::Name.new(@array)
44
+ end
33
45
 
34
- # get value for key
35
- def [](key)
36
- @array.each do |item|
37
- if key == item[0]
38
- return item[1]
39
- end
40
- end
41
- return nil
42
- end
46
+ # @return [Boolean]
47
+ def empty?
48
+ @array.empty?
49
+ end
43
50
 
44
- # set key and value
45
- def []=(key, value)
46
- added = false
47
- @array = @array.map{ |item|
48
- if key == item[0]
49
- added = true
50
- [key, value]
51
- else
52
- item
53
- end
54
- }
55
-
56
- if not added
57
- @array << [key, value]
58
- end
59
-
60
- # see if X509 thinks this is okay
61
- name
62
-
63
- @array
51
+ # get value for key
52
+ def [](key)
53
+ @array.each do |item|
54
+ if key == item[0]
55
+ return item[1]
64
56
  end
57
+ end
58
+ return nil
59
+ end
65
60
 
66
- # @param [String] key item you want deleted
67
- def delete(key)
68
- @array = @array.select do |item|
69
- item[0] != key
70
- end
61
+ # set key and value
62
+ def []=(key, value)
63
+ added = false
64
+ @array = @array.map{ |item|
65
+ if key == item[0]
66
+ added = true
67
+ [key, value]
68
+ else
69
+ item
71
70
  end
71
+ }
72
+
73
+ if not added
74
+ @array << [key, value]
75
+ end
76
+
77
+ # see if X509 thinks this is okay
78
+ name
79
+
80
+ @array
81
+ end
82
+
83
+ # @param [String] key item you want deleted
84
+ def delete(key)
85
+ @array = @array.select do |item|
86
+ item[0] != key
87
+ end
88
+ end
89
+
90
+ # @return [String] string of form /CN=something.com/O=whatever/L=Locality
91
+ def to_s
92
+ name.to_s
93
+ end
72
94
 
73
- # @return [String] string of form /CN=something.com/O=whatever/L=Locality
74
- def to_s
75
- name.to_s
95
+ # @return [Array] Array of form [['CN','langui.sh']]
96
+ def to_a
97
+ @array
98
+ end
99
+
100
+ # @private
101
+ def respond_to?(method_sym, include_private = false)
102
+ method_sym.to_s =~ /([^=]*)/
103
+ oid = oid_check($1)
104
+ if not oid.nil?
105
+ true
106
+ else
107
+ super(method_sym, include_private)
108
+ end
109
+ end
110
+
111
+ private
112
+
113
+ # Try to build methods for getting/setting various subject attributes
114
+ # dynamically. this will also cache methods that get built via instance_eval.
115
+ # This code will also allow you to set subject items for custom oids
116
+ # defined via R509::OIDMapper
117
+ #
118
+ def method_missing(method_sym, *args, &block)
119
+ if method_sym.to_s =~ /(.*)=$/
120
+ sn = oid_check($1)
121
+ if not sn.nil?
122
+ define_dynamic_setter(method_sym,sn)
123
+ send(method_sym, args.first)
124
+ else
125
+ return super(method_sym, *args, &block)
76
126
  end
127
+ else
128
+ sn = oid_check(method_sym)
129
+ if not sn.nil?
130
+ define_dynamic_getter(method_sym,sn)
131
+ send(method_sym)
132
+ else
133
+ return super(method_sym, *args, &block)
134
+ end
135
+ end
136
+ end
77
137
 
78
- # @return [Array] Array of form [['CN','langui.sh'],['O','Org']]
79
- def to_a
80
- @array
138
+ def define_dynamic_setter(name,sn)
139
+ instance_eval <<-RUBY
140
+ def #{name.to_s}(value)
141
+ self["#{sn}"]= value
81
142
  end
143
+ RUBY
82
144
  end
83
145
 
84
- # Sanitize an X509::Name. The #to_a method replaces unknown OIDs with "UNDEF", but the #to_s
85
- # method doesn't. What we want to do is build the array that would have been produced by #to_a
86
- # if it didn't throw away the OID.
87
- class NameSanitizer
88
- # @option name [OpenSSL::X509::Name]
89
- # @return [Array] array of the form [["OID", "VALUE], ["OID", "VALUE"]] with "UNDEF" replaced by the actual OID
90
- def sanitize(name)
91
- line = name.to_s
92
- array = name.to_a.dup
93
- used_oids = []
94
- undefined_components(array).each do |component|
95
- begin
96
- # get the OID from the subject line that has this value
97
- oids = line.scan(/\/([\d\.]+)=#{component[:value]}/).flatten
98
- if oids.size == 1
99
- oid = oids.first
100
- else
101
- oid = oids.select{ |match| not used_oids.include?(match) }.first
102
- end
103
- # replace the "UNDEF" OID name in the array at the index the UNDEF was found
104
- array[component[:index]][0] = oid
105
- # remove the first occurrence of this in the subject line (so we can handle the same oid/value pair multiple times)
106
- line = line.sub("/#{oid}=#{component[:value]}", "")
107
- # we record which OIDs we've used in case two different unknown OIDs have the same value
108
- used_oids << oid
109
- rescue
110
- # I don't expect this to happen, but if it does we'll just not replace UNDEF and continue
111
- end
112
- end
113
- array
146
+ def define_dynamic_getter(name,sn)
147
+ instance_eval <<-RUBY
148
+ def #{name.to_s}
149
+ self["#{sn}"]
114
150
  end
151
+ RUBY
152
+ end
115
153
 
116
- private
117
-
118
- # get the components from #to_a that are UNDEF
119
- # @option array [Array<OpenSSL::X509::Name>]
120
- # @return [Hash]
121
- # @example
122
- # Return value looks like
123
- # { :index => the index in the original array where we found an UNDEF, :value => the subject component value }
124
- def undefined_components(array)
125
- components = []
126
- array.each_index do |index|
127
- components << { :index => index, :value => array[index][1] } if array[index][0] == "UNDEF"
128
- end
129
- components
154
+ def oid_check(name)
155
+ oid = OpenSSL::ASN1::ObjectId.new(camelize(name))
156
+ oid.short_name
157
+ end
158
+
159
+ def camelize(sym)
160
+ sym.to_s.split('_').inject([]){ |buffer,e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
161
+ end
162
+
163
+ def parse_asn1(asn)
164
+ asn = OpenSSL::ASN1.decode asn
165
+ # parsing a subject DN
166
+ # We have to iterate a sequence, which holds sets. Each set has one value: a sequence, which has 2 values
167
+ # So it's effectively an array of arrays which each have only one element, which is an array of 2 values.
168
+ asn.value.each do |set|
169
+ sn = set.value.first.value.first.value
170
+ val = set.value.first.value.last.value
171
+ self[sn] = val
172
+ end
173
+ end
174
+ end
175
+
176
+ # Sanitize an X509::Name. The #to_a method replaces unknown OIDs with "UNDEF", but the #to_s
177
+ # method doesn't. What we want to do is build the array that would have been produced by #to_a
178
+ # if it didn't throw away the OID.
179
+ # This method is not required as of ruby-1.9.3p125 and up.
180
+ class NameSanitizer
181
+ # @option name [OpenSSL::X509::Name]
182
+ # @return [Array] array of the form [["OID", "VALUE], ["OID", "VALUE"]] with "UNDEF" replaced by the actual OID
183
+ def sanitize(name)
184
+ line = name.to_s
185
+ array = name.to_a.dup
186
+ used_oids = []
187
+ undefined_components(array).each do |component|
188
+ begin
189
+ # get the OID from the subject line that has this value
190
+ oids = line.scan(/\/([\d\.]+)=#{component[:value]}/).flatten
191
+ if oids.size == 1
192
+ oid = oids.first
193
+ else
194
+ oid = oids.select{ |match| not used_oids.include?(match) }.first
195
+ end
196
+ # replace the "UNDEF" OID name in the array at the index the UNDEF was found
197
+ array[component[:index]][0] = oid
198
+ # remove the first occurrence of this in the subject line (so we can handle the same oid/value pair multiple times)
199
+ line = line.sub("/#{oid}=#{component[:value]}", "")
200
+ # we record which OIDs we've used in case two different unknown OIDs have the same value
201
+ used_oids << oid
202
+ rescue
203
+ # I don't expect this to happen, but if it does we'll just not replace UNDEF and continue
130
204
  end
205
+ end
206
+ array
207
+ end
208
+
209
+ private
210
+
211
+ # get the components from #to_a that are UNDEF
212
+ # @option array [Array<OpenSSL::X509::Name>]
213
+ # @return [Hash]
214
+ # @example
215
+ # Return value looks like
216
+ # { :index => the index in the original array where we found an UNDEF, :value => the subject component value }
217
+ def undefined_components(array)
218
+ components = []
219
+ array.each_index do |index|
220
+ components << { :index => index, :value => array[index][1] } if array[index][0] == "UNDEF"
221
+ end
222
+ components
131
223
  end
224
+ end
132
225
 
133
226
  end