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.
- data/README.md +447 -0
- data/Rakefile +38 -0
- data/bin/r509 +96 -0
- data/bin/r509-parse +35 -0
- data/doc/R509.html +154 -0
- data/doc/R509/Cert.html +3954 -0
- data/doc/R509/Cert/Extensions.html +360 -0
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +391 -0
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +148 -0
- data/doc/R509/Cert/Extensions/BasicConstraints.html +482 -0
- data/doc/R509/Cert/Extensions/CrlDistributionPoints.html +316 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +780 -0
- data/doc/R509/Cert/Extensions/KeyUsage.html +1230 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +467 -0
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +216 -0
- data/doc/R509/CertificateAuthority.html +126 -0
- data/doc/R509/CertificateAuthority/Signer.html +855 -0
- data/doc/R509/Config.html +127 -0
- data/doc/R509/Config/CaConfig.html +2144 -0
- data/doc/R509/Config/CaConfigPool.html +599 -0
- data/doc/R509/Config/CaProfile.html +656 -0
- data/doc/R509/Config/SubjectItemPolicy.html +578 -0
- data/doc/R509/Crl.html +126 -0
- data/doc/R509/Crl/Administrator.html +2077 -0
- data/doc/R509/Crl/Parser.html +1224 -0
- data/doc/R509/Csr.html +2248 -0
- data/doc/R509/IOHelpers.html +564 -0
- data/doc/R509/MessageDigest.html +396 -0
- data/doc/R509/NameSanitizer.html +319 -0
- data/doc/R509/Ocsp.html +128 -0
- data/doc/R509/Ocsp/Request.html +126 -0
- data/doc/R509/Ocsp/Request/Nonce.html +160 -0
- data/doc/R509/Ocsp/Response.html +837 -0
- data/doc/R509/OidMapper.html +393 -0
- data/doc/R509/PrivateKey.html +1647 -0
- data/doc/R509/R509Error.html +134 -0
- data/doc/R509/Spki.html +1424 -0
- data/doc/R509/Subject.html +836 -0
- data/doc/R509/Validity.html +160 -0
- data/doc/R509/Validity/Checker.html +320 -0
- data/doc/R509/Validity/DefaultChecker.html +283 -0
- data/doc/R509/Validity/DefaultWriter.html +330 -0
- data/doc/R509/Validity/Status.html +561 -0
- data/doc/R509/Validity/Writer.html +394 -0
- data/doc/_index.html +501 -0
- data/doc/class_list.html +53 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +328 -0
- data/doc/file.README.html +534 -0
- data/doc/file.r509.html +149 -0
- data/doc/file_list.html +58 -0
- data/doc/frames.html +28 -0
- data/doc/index.html +534 -0
- data/doc/js/app.js +208 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +4 -0
- data/doc/methods_list.html +1932 -0
- data/doc/top-level-namespace.html +112 -0
- data/lib/r509.rb +22 -0
- data/lib/r509/cert.rb +414 -0
- data/lib/r509/cert/extensions.rb +309 -0
- data/lib/r509/certificateauthority.rb +290 -0
- data/lib/r509/config.rb +407 -0
- data/lib/r509/crl.rb +379 -0
- data/lib/r509/csr.rb +324 -0
- data/lib/r509/exceptions.rb +5 -0
- data/lib/r509/io_helpers.rb +52 -0
- data/lib/r509/messagedigest.rb +49 -0
- data/lib/r509/ocsp.rb +85 -0
- data/lib/r509/oidmapper.rb +32 -0
- data/lib/r509/privatekey.rb +185 -0
- data/lib/r509/spki.rb +112 -0
- data/lib/r509/subject.rb +133 -0
- data/lib/r509/validity.rb +92 -0
- data/lib/r509/version.rb +4 -0
- data/r509.yaml +73 -0
- data/spec/cert/extensions_spec.rb +632 -0
- data/spec/cert_spec.rb +321 -0
- data/spec/certificate_authority_spec.rb +260 -0
- data/spec/config_spec.rb +349 -0
- data/spec/crl_spec.rb +215 -0
- data/spec/csr_spec.rb +302 -0
- data/spec/fixtures.rb +233 -0
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +24 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +1 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +28 -0
- data/spec/fixtures/cert3_key.pem +27 -0
- data/spec/fixtures/cert3_key_des3.pem +30 -0
- data/spec/fixtures/cert4.pem +14 -0
- data/spec/fixtures/cert5.pem +30 -0
- data/spec/fixtures/cert6.pem +26 -0
- data/spec/fixtures/cert_expired.pem +26 -0
- data/spec/fixtures/cert_not_yet_valid.pem +26 -0
- data/spec/fixtures/cert_san.pem +27 -0
- data/spec/fixtures/cert_san2.pem +22 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +15 -0
- data/spec/fixtures/config_test.yaml +41 -0
- data/spec/fixtures/config_test_engine_key.yaml +7 -0
- data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -0
- data/spec/fixtures/config_test_minimal.yaml +7 -0
- data/spec/fixtures/config_test_password.yaml +7 -0
- data/spec/fixtures/config_test_various.yaml +100 -0
- data/spec/fixtures/crl_list_file.txt +1 -0
- data/spec/fixtures/crl_with_reason.pem +17 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +17 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +27 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +30 -0
- data/spec/fixtures/csr1_newlines.pem +32 -0
- data/spec/fixtures/csr1_no_begin_end.pem +15 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +1 -0
- data/spec/fixtures/csr2.pem +15 -0
- data/spec/fixtures/csr2_key.pem +27 -0
- data/spec/fixtures/csr3.pem +16 -0
- data/spec/fixtures/csr4.pem +25 -0
- data/spec/fixtures/csr_dsa.pem +15 -0
- data/spec/fixtures/csr_invalid_signature.pem +13 -0
- data/spec/fixtures/dsa_key.pem +20 -0
- data/spec/fixtures/key4.pem +27 -0
- data/spec/fixtures/key4_encrypted_des3.pem +30 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +21 -0
- data/spec/fixtures/missing_key_identifier_ca.key +27 -0
- data/spec/fixtures/ocsptest.r509.local.pem +27 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +27 -0
- data/spec/fixtures/second_ca.cer +26 -0
- data/spec/fixtures/second_ca.key +27 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +1 -0
- data/spec/fixtures/spkac_dsa.txt +1 -0
- data/spec/fixtures/stca.pem +22 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +17 -0
- data/spec/fixtures/test_ca.cer +22 -0
- data/spec/fixtures/test_ca.key +28 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +30 -0
- data/spec/fixtures/test_ca_ocsp.cer +26 -0
- data/spec/fixtures/test_ca_ocsp.key +27 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +48 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +26 -0
- data/spec/fixtures/test_ca_subroot.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +25 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +27 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +17 -0
- data/spec/message_digest_spec.rb +89 -0
- data/spec/ocsp_spec.rb +111 -0
- data/spec/oid_mapper_spec.rb +31 -0
- data/spec/privatekey_spec.rb +198 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/spki_spec.rb +157 -0
- data/spec/subject_spec.rb +203 -0
- data/spec/validity_spec.rb +98 -0
- 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
|
data/lib/r509/subject.rb
ADDED
|
@@ -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
|