r509 0.8

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 (162) hide show
  1. data/README.md +447 -0
  2. data/Rakefile +38 -0
  3. data/bin/r509 +96 -0
  4. data/bin/r509-parse +35 -0
  5. data/doc/R509.html +154 -0
  6. data/doc/R509/Cert.html +3954 -0
  7. data/doc/R509/Cert/Extensions.html +360 -0
  8. data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
  9. data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
  10. data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
  11. data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
  12. data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
  13. data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
  14. data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
  15. data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
  16. data/doc/R509/CertificateAuthority.html +126 -0
  17. data/doc/R509/CertificateAuthority/Signer.html +855 -0
  18. data/doc/R509/Config.html +127 -0
  19. data/doc/R509/Config/CaConfig.html +2144 -0
  20. data/doc/R509/Config/CaConfigPool.html +599 -0
  21. data/doc/R509/Config/CaProfile.html +656 -0
  22. data/doc/R509/Config/SubjectItemPolicy.html +578 -0
  23. data/doc/R509/Crl.html +126 -0
  24. data/doc/R509/Crl/Administrator.html +2077 -0
  25. data/doc/R509/Crl/Parser.html +1224 -0
  26. data/doc/R509/Csr.html +2248 -0
  27. data/doc/R509/IOHelpers.html +564 -0
  28. data/doc/R509/MessageDigest.html +396 -0
  29. data/doc/R509/NameSanitizer.html +319 -0
  30. data/doc/R509/Ocsp.html +128 -0
  31. data/doc/R509/Ocsp/Request.html +126 -0
  32. data/doc/R509/Ocsp/Request/Nonce.html +160 -0
  33. data/doc/R509/Ocsp/Response.html +837 -0
  34. data/doc/R509/OidMapper.html +393 -0
  35. data/doc/R509/PrivateKey.html +1647 -0
  36. data/doc/R509/R509Error.html +134 -0
  37. data/doc/R509/Spki.html +1424 -0
  38. data/doc/R509/Subject.html +836 -0
  39. data/doc/R509/Validity.html +160 -0
  40. data/doc/R509/Validity/Checker.html +320 -0
  41. data/doc/R509/Validity/DefaultChecker.html +283 -0
  42. data/doc/R509/Validity/DefaultWriter.html +330 -0
  43. data/doc/R509/Validity/Status.html +561 -0
  44. data/doc/R509/Validity/Writer.html +394 -0
  45. data/doc/_index.html +501 -0
  46. data/doc/class_list.html +53 -0
  47. data/doc/css/common.css +1 -0
  48. data/doc/css/full_list.css +57 -0
  49. data/doc/css/style.css +328 -0
  50. data/doc/file.README.html +534 -0
  51. data/doc/file.r509.html +149 -0
  52. data/doc/file_list.html +58 -0
  53. data/doc/frames.html +28 -0
  54. data/doc/index.html +534 -0
  55. data/doc/js/app.js +208 -0
  56. data/doc/js/full_list.js +173 -0
  57. data/doc/js/jquery.js +4 -0
  58. data/doc/methods_list.html +1932 -0
  59. data/doc/top-level-namespace.html +112 -0
  60. data/lib/r509.rb +22 -0
  61. data/lib/r509/cert.rb +414 -0
  62. data/lib/r509/cert/extensions.rb +309 -0
  63. data/lib/r509/certificateauthority.rb +290 -0
  64. data/lib/r509/config.rb +407 -0
  65. data/lib/r509/crl.rb +379 -0
  66. data/lib/r509/csr.rb +324 -0
  67. data/lib/r509/exceptions.rb +5 -0
  68. data/lib/r509/io_helpers.rb +52 -0
  69. data/lib/r509/messagedigest.rb +49 -0
  70. data/lib/r509/ocsp.rb +85 -0
  71. data/lib/r509/oidmapper.rb +32 -0
  72. data/lib/r509/privatekey.rb +185 -0
  73. data/lib/r509/spki.rb +112 -0
  74. data/lib/r509/subject.rb +133 -0
  75. data/lib/r509/validity.rb +92 -0
  76. data/lib/r509/version.rb +4 -0
  77. data/r509.yaml +73 -0
  78. data/spec/cert/extensions_spec.rb +632 -0
  79. data/spec/cert_spec.rb +321 -0
  80. data/spec/certificate_authority_spec.rb +260 -0
  81. data/spec/config_spec.rb +349 -0
  82. data/spec/crl_spec.rb +215 -0
  83. data/spec/csr_spec.rb +302 -0
  84. data/spec/fixtures.rb +233 -0
  85. data/spec/fixtures/cert1.der +0 -0
  86. data/spec/fixtures/cert1.pem +24 -0
  87. data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
  88. data/spec/fixtures/cert3.p12 +0 -0
  89. data/spec/fixtures/cert3.pem +28 -0
  90. data/spec/fixtures/cert3_key.pem +27 -0
  91. data/spec/fixtures/cert3_key_des3.pem +30 -0
  92. data/spec/fixtures/cert4.pem +14 -0
  93. data/spec/fixtures/cert5.pem +30 -0
  94. data/spec/fixtures/cert6.pem +26 -0
  95. data/spec/fixtures/cert_expired.pem +26 -0
  96. data/spec/fixtures/cert_not_yet_valid.pem +26 -0
  97. data/spec/fixtures/cert_san.pem +27 -0
  98. data/spec/fixtures/cert_san2.pem +22 -0
  99. data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
  100. data/spec/fixtures/config_test.yaml +41 -0
  101. data/spec/fixtures/config_test_engine_key.yaml +7 -0
  102. data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
  103. data/spec/fixtures/config_test_minimal.yaml +7 -0
  104. data/spec/fixtures/config_test_password.yaml +7 -0
  105. data/spec/fixtures/config_test_various.yaml +100 -0
  106. data/spec/fixtures/crl_list_file.txt +1 -0
  107. data/spec/fixtures/crl_with_reason.pem +17 -0
  108. data/spec/fixtures/csr1.der +0 -0
  109. data/spec/fixtures/csr1.pem +17 -0
  110. data/spec/fixtures/csr1_key.der +0 -0
  111. data/spec/fixtures/csr1_key.pem +27 -0
  112. data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
  113. data/spec/fixtures/csr1_newlines.pem +32 -0
  114. data/spec/fixtures/csr1_no_begin_end.pem +15 -0
  115. data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
  116. data/spec/fixtures/csr2.pem +15 -0
  117. data/spec/fixtures/csr2_key.pem +27 -0
  118. data/spec/fixtures/csr3.pem +16 -0
  119. data/spec/fixtures/csr4.pem +25 -0
  120. data/spec/fixtures/csr_dsa.pem +15 -0
  121. data/spec/fixtures/csr_invalid_signature.pem +13 -0
  122. data/spec/fixtures/dsa_key.pem +20 -0
  123. data/spec/fixtures/key4.pem +27 -0
  124. data/spec/fixtures/key4_encrypted_des3.pem +30 -0
  125. data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
  126. data/spec/fixtures/missing_key_identifier_ca.key +27 -0
  127. data/spec/fixtures/ocsptest.r509.local.pem +27 -0
  128. data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
  129. data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
  130. data/spec/fixtures/second_ca.cer +26 -0
  131. data/spec/fixtures/second_ca.key +27 -0
  132. data/spec/fixtures/spkac.der +0 -0
  133. data/spec/fixtures/spkac.txt +1 -0
  134. data/spec/fixtures/spkac_dsa.txt +1 -0
  135. data/spec/fixtures/stca.pem +22 -0
  136. data/spec/fixtures/stca_ocsp_request.der +0 -0
  137. data/spec/fixtures/stca_ocsp_response.der +0 -0
  138. data/spec/fixtures/test1.csr +17 -0
  139. data/spec/fixtures/test_ca.cer +22 -0
  140. data/spec/fixtures/test_ca.key +28 -0
  141. data/spec/fixtures/test_ca.p12 +0 -0
  142. data/spec/fixtures/test_ca_des3.key +30 -0
  143. data/spec/fixtures/test_ca_ocsp.cer +26 -0
  144. data/spec/fixtures/test_ca_ocsp.key +27 -0
  145. data/spec/fixtures/test_ca_ocsp.p12 +0 -0
  146. data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
  147. data/spec/fixtures/test_ca_ocsp_response.der +0 -0
  148. data/spec/fixtures/test_ca_subroot.cer +26 -0
  149. data/spec/fixtures/test_ca_subroot.key +27 -0
  150. data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
  151. data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
  152. data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
  153. data/spec/fixtures/unknown_oid.csr +17 -0
  154. data/spec/message_digest_spec.rb +89 -0
  155. data/spec/ocsp_spec.rb +111 -0
  156. data/spec/oid_mapper_spec.rb +31 -0
  157. data/spec/privatekey_spec.rb +198 -0
  158. data/spec/spec_helper.rb +14 -0
  159. data/spec/spki_spec.rb +157 -0
  160. data/spec/subject_spec.rb +203 -0
  161. data/spec/validity_spec.rb +98 -0
  162. metadata +257 -0
@@ -0,0 +1,32 @@
1
+ require 'openssl'
2
+
3
+ module R509
4
+ # Helps map raw OIDs to friendlier short names
5
+ class OidMapper
6
+ # Register an OID so we have a friendly short name
7
+ # @param [String] oid A string representation of the OID you want to map (e.g. "1.6.2.3.55")
8
+ # @param [String] short_name The short name (e.g. CN, O, OU, emailAddress)
9
+ # @param [String] long_name Optional long name. Defaults to the same as short_name
10
+ # @return [Boolean] success/failure
11
+ def self.register(oid,short_name,long_name=nil)
12
+ if long_name.nil?
13
+ long_name = short_name
14
+ end
15
+ OpenSSL::ASN1::ObjectId.register(oid, short_name, long_name)
16
+ end
17
+
18
+ # Register a batch of OIDs so we have friendly short names
19
+ # @param [Array] oids An array of hashes
20
+ # @example
21
+ # R509::OidMapper.batch_register([
22
+ # {:oid => "1.2.3.4.5", :short_name => "sName", :long_name => "lName"},
23
+ # {:oid => "1.2.3.4.6", :short_name => "oName"}
24
+ # ]
25
+ def self.batch_register(oids)
26
+ oids.each do |oid_hash|
27
+ self.register(oid_hash[:oid],oid_hash[:short_name],oid_hash[:long_name])
28
+ end
29
+ nil
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,185 @@
1
+ require 'openssl'
2
+ require 'r509/io_helpers'
3
+ require 'r509/exceptions'
4
+
5
+ module R509
6
+ #private key management
7
+ class PrivateKey
8
+ include R509::IOHelpers
9
+
10
+ # @option opts [Symbol] :type :rsa/:dsa
11
+ # @option opts [Integer] :bit_strength
12
+ # @option opts [String] :password
13
+ # @option opts [String,OpenSSL::PKey::RSA,OpenSSL::PKey::DSA] :key
14
+ # @option opts [OpenSSL::Engine] :engine
15
+ # @option opts [string] :key_name (used with engine)
16
+ def initialize(opts)
17
+ if not opts.kind_of?(Hash)
18
+ raise ArgumentError, 'Must provide a hash of options'
19
+ end
20
+
21
+ if opts.has_key?(:engine) and opts.has_key?(:key)
22
+ raise ArgumentError, 'You can\'t pass both :key and :engine'
23
+ elsif opts.has_key?(:key_name) and not opts.has_key?(:engine)
24
+ raise ArgumentError, 'When providing a :key_name you MUST provide an :engine'
25
+ elsif opts.has_key?(:engine) and not opts.has_key?(:key_name)
26
+ raise ArgumentError, 'When providing an :engine you MUST provide a :key_name'
27
+ elsif opts.has_key?(:engine) and opts.has_key?(:key_name)
28
+ if not opts[:engine].kind_of?(OpenSSL::Engine)
29
+ raise ArgumentError, 'When providing an engine, it must be of type OpenSSL::Engine'
30
+ end
31
+ @engine = opts[:engine]
32
+ @key_name = opts[:key_name]
33
+ end
34
+
35
+ if opts.has_key?(:key)
36
+ password = opts[:password] || nil
37
+ #OpenSSL::PKey.read solves this begin/rescue garbage but is only
38
+ #available to Ruby 1.9.3+
39
+ begin
40
+ @key = OpenSSL::PKey::RSA.new(opts[:key],password)
41
+ rescue OpenSSL::PKey::RSAError
42
+ begin
43
+ @key = OpenSSL::PKey::DSA.new(opts[:key],password)
44
+ rescue
45
+ raise R509::R509Error, "Failed to load private key. Invalid key or incorrect password."
46
+ end
47
+ end
48
+ else
49
+ bit_strength = opts[:bit_strength] || 2048
50
+ type = opts[:type] || :rsa
51
+ case type
52
+ when :rsa
53
+ @key = OpenSSL::PKey::RSA.new(bit_strength)
54
+ when :dsa
55
+ @key = OpenSSL::PKey::DSA.new(bit_strength)
56
+ else
57
+ raise ArgumentError, 'Must provide :rsa or :dsa as type when key or engine is nil'
58
+ end
59
+ end
60
+ end
61
+
62
+ # Helper method to quickly load a private key from the filesystem
63
+ #
64
+ # @param [String] filename Path to file you want to load
65
+ # @return [R509::PrivateKey] PrivateKey object
66
+ def self.load_from_file( filename, password = nil )
67
+ return R509::PrivateKey.new(:key => IOHelpers.read_data(filename), :password => password )
68
+ end
69
+
70
+
71
+ # @return [Integer]
72
+ def bit_strength
73
+ if self.rsa?
74
+ return self.public_key.n.num_bits
75
+ elsif self.dsa?
76
+ return self.public_key.p.num_bits
77
+ end
78
+ end
79
+
80
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::Engine pkey] this method may return the PKey object itself or a handle to the private key in the HSM (which will not show the private key, just public)
81
+ def key
82
+ if in_hardware?
83
+ @engine.load_private_key(@key_name)
84
+ else
85
+ @key
86
+ end
87
+ end
88
+
89
+ # @return [Boolean] whether the key is resident in hardware or not
90
+ def in_hardware?
91
+ if not @engine.nil?
92
+ true
93
+ else
94
+ false
95
+ end
96
+ end
97
+
98
+ # @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA] public key
99
+ def public_key
100
+ self.key.public_key
101
+ end
102
+
103
+ alias :to_s :public_key
104
+
105
+ # Converts the key into the PEM format
106
+ #
107
+ # @return [String] the key converted into PEM format.
108
+ def to_pem
109
+ if in_hardware?
110
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
111
+ end
112
+ self.key.to_pem
113
+ end
114
+
115
+ # Converts the key into encrypted PEM format
116
+ #
117
+ # @param [String,OpenSSL::Cipher] cipher to use for encryption
118
+ # full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
119
+ # (common ones are des3, aes256, aes128)
120
+ # @param [String] password password
121
+ # @return [String] the key converted into encrypted PEM format.
122
+ def to_encrypted_pem(cipher,password)
123
+ if in_hardware?
124
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
125
+ end
126
+ cipher = OpenSSL::Cipher::Cipher.new(cipher)
127
+ self.key.to_pem(cipher,password)
128
+ end
129
+
130
+
131
+ # Converts the key into the DER format
132
+ #
133
+ # @return [String] the key converted into DER format.
134
+ def to_der
135
+ if in_hardware?
136
+ raise R509::R509Error, "This method cannot be called when using keys in hardware"
137
+ end
138
+ self.key.to_der
139
+ end
140
+
141
+ # Writes the key into the PEM format
142
+ #
143
+ # @param [String, #write] filename_or_io Either a string of the path for
144
+ # the file that you'd like to write, or an IO-like object.
145
+ def write_pem(filename_or_io)
146
+ write_data(filename_or_io, self.to_pem)
147
+ end
148
+
149
+
150
+ # Writes the key into encrypted PEM format with specified cipher
151
+ #
152
+ # @param [String, #write] filename_or_io Either a string of the path for
153
+ # the file that you'd like to write, or an IO-like object.
154
+ # @param [String,OpenSSL::Cipher] cipher to use for encryption
155
+ # full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
156
+ # (common ones are des3, aes256, aes128)
157
+ # @param [String] password password
158
+ def write_encrypted_pem(filename_or_io,cipher,password)
159
+ write_data(filename_or_io, to_encrypted_pem(cipher,password))
160
+ end
161
+
162
+ # Writes the key into the DER format
163
+ #
164
+ # @param [String, #write] filename_or_io Either a string of the path for
165
+ # the file that you'd like to write, or an IO-like object.
166
+ def write_der(filename_or_io)
167
+ write_data(filename_or_io, self.to_der)
168
+ end
169
+
170
+
171
+ # Returns whether the public key is RSA
172
+ #
173
+ # @return [Boolean] true if the public key is RSA, false otherwise
174
+ def rsa?
175
+ self.key.kind_of?(OpenSSL::PKey::RSA)
176
+ end
177
+
178
+ # Returns whether the public key is DSA
179
+ #
180
+ # @return [Boolean] true if the public key is DSA, false otherwise
181
+ def dsa?
182
+ self.key.kind_of?(OpenSSL::PKey::DSA)
183
+ end
184
+ end
185
+ end
data/lib/r509/spki.rb ADDED
@@ -0,0 +1,112 @@
1
+ require 'openssl'
2
+ require 'r509/exceptions'
3
+ require 'r509/io_helpers'
4
+
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
30
+
31
+ # @return [OpenSSL::PKey::RSA] public key
32
+ def public_key
33
+ @spki.public_key
34
+ end
35
+
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
41
+ end
42
+
43
+ alias :to_s :to_pem
44
+
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
51
+
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
59
+
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
67
+
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
74
+
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
81
+
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
91
+
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
102
+
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
111
+ end
112
+ end
@@ -0,0 +1,133 @@
1
+ require "openssl"
2
+
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
22
+ end
23
+
24
+ # @return [OpenSSL::X509::Name]
25
+ def name
26
+ OpenSSL::X509::Name.new(@array)
27
+ end
28
+
29
+ # @return [Boolean]
30
+ def empty?
31
+ @array.empty?
32
+ end
33
+
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
43
+
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
64
+ end
65
+
66
+ # @param [String] key item you want deleted
67
+ def delete(key)
68
+ @array = @array.select do |item|
69
+ item[0] != key
70
+ end
71
+ end
72
+
73
+ # @return [String] string of form /CN=something.com/O=whatever/L=Locality
74
+ def to_s
75
+ name.to_s
76
+ end
77
+
78
+ # @return [Array] Array of form [['CN','langui.sh'],['O','Org']]
79
+ def to_a
80
+ @array
81
+ end
82
+ end
83
+
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
114
+ end
115
+
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
130
+ end
131
+ end
132
+
133
+ end