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/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