r509 0.8

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