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.
- data/README.md +343 -151
- data/Rakefile +26 -23
- data/bin/r509 +126 -112
- data/bin/r509-parse +24 -24
- data/doc/R509.html +169 -7
- data/doc/R509/ASN1.html +370 -0
- data/doc/R509/ASN1/GeneralName.html +1121 -0
- data/doc/R509/ASN1/GeneralNames.html +843 -0
- data/doc/R509/ASN1/NoticeReference.html +392 -0
- data/doc/R509/ASN1/PolicyInformation.html +387 -0
- data/doc/R509/ASN1/PolicyQualifiers.html +455 -0
- data/doc/R509/ASN1/UserNotice.html +386 -0
- data/doc/R509/{Crl.html → CRL.html} +7 -7
- data/doc/R509/CRL/Administrator.html +1559 -0
- data/doc/R509/{Crl/Parser.html → CRL/SignedList.html} +501 -210
- data/doc/R509/{Csr.html → CSR.html} +444 -314
- data/doc/R509/Cert.html +866 -617
- data/doc/R509/Cert/Extensions.html +52 -41
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +70 -35
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +387 -4
- data/doc/R509/Cert/Extensions/BasicConstraints.html +61 -25
- data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +354 -0
- data/doc/R509/Cert/Extensions/CertificatePolicies.html +340 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +440 -49
- data/doc/R509/Cert/Extensions/{CrlDistributionPoints.html → InhibitAnyPolicy.html} +52 -35
- data/doc/R509/Cert/Extensions/KeyUsage.html +247 -121
- data/doc/R509/Cert/Extensions/NameConstraints.html +445 -0
- data/doc/R509/Cert/Extensions/OCSPNoCheck.html +239 -0
- data/doc/R509/Cert/Extensions/PolicyConstraints.html +424 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +437 -62
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +52 -10
- data/doc/R509/CertificateAuthority.html +4 -4
- data/doc/R509/CertificateAuthority/Signer.html +154 -187
- data/doc/R509/Config.html +6 -6
- data/doc/R509/Config/{CaConfig.html → CAConfig.html} +451 -348
- data/doc/R509/Config/{CaConfigPool.html → CAConfigPool.html} +47 -47
- data/doc/R509/Config/CAProfile.html +1015 -0
- data/doc/R509/Config/SubjectItemPolicy.html +86 -86
- data/doc/R509/IOHelpers.html +22 -22
- data/doc/R509/MessageDigest.html +14 -14
- data/doc/R509/NameSanitizer.html +53 -53
- data/doc/R509/{Ocsp.html → OCSP.html} +9 -9
- data/doc/R509/{Ocsp → OCSP}/Request.html +7 -7
- data/doc/R509/{Ocsp → OCSP}/Request/Nonce.html +56 -11
- data/doc/R509/{Ocsp → OCSP}/Response.html +44 -44
- data/doc/R509/{OidMapper.html → OIDMapper.html} +23 -39
- data/doc/R509/PrivateKey.html +415 -168
- data/doc/R509/R509Error.html +3 -3
- data/doc/R509/{Spki.html → SPKI.html} +354 -192
- data/doc/R509/Subject.html +224 -113
- data/doc/R509/Validity.html +27 -5
- data/doc/R509/Validity/Checker.html +13 -13
- data/doc/R509/Validity/DefaultChecker.html +13 -13
- data/doc/R509/Validity/DefaultWriter.html +14 -14
- data/doc/R509/Validity/Status.html +39 -39
- data/doc/R509/Validity/Writer.html +18 -18
- data/doc/_index.html +138 -35
- data/doc/class_list.html +1 -1
- data/doc/css/style.css +10 -0
- data/doc/file.README.html +368 -171
- data/doc/file.r509.html +92 -69
- data/doc/frames.html +1 -1
- data/doc/index.html +368 -171
- data/doc/method_list.html +910 -390
- data/doc/top-level-namespace.html +3 -3
- data/lib/r509.rb +32 -16
- data/lib/r509/asn1.rb +375 -0
- data/lib/r509/cert.rb +381 -364
- data/lib/r509/cert/extensions.rb +443 -76
- data/lib/r509/certificate_authority.rb +407 -0
- data/lib/r509/config.rb +547 -351
- data/lib/r509/crl.rb +336 -366
- data/lib/r509/csr.rb +278 -289
- data/lib/r509/ec-hack.rb +37 -0
- data/lib/r509/exceptions.rb +3 -3
- data/lib/r509/io_helpers.rb +44 -44
- data/lib/r509/message_digest.rb +53 -0
- data/lib/r509/ocsp.rb +80 -70
- data/lib/r509/oid_mapper.rb +32 -0
- data/lib/r509/private_key.rb +228 -0
- data/lib/r509/spki.rb +145 -93
- data/lib/r509/subject.rb +203 -110
- data/lib/r509/validity.rb +70 -68
- data/lib/r509/version.rb +2 -2
- data/r509.yaml +92 -69
- data/spec/asn1_spec.rb +402 -0
- data/spec/cert/extensions_spec.rb +957 -494
- data/spec/cert_spec.rb +382 -307
- data/spec/certificate_authority_spec.rb +668 -250
- data/spec/config_spec.rb +515 -302
- data/spec/crl_spec.rb +197 -198
- data/spec/csr_spec.rb +334 -289
- data/spec/fixtures.rb +247 -171
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +0 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +0 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +0 -0
- data/spec/fixtures/cert3_key.pem +0 -0
- data/spec/fixtures/cert3_key_des3.pem +0 -0
- data/spec/fixtures/cert4.pem +0 -0
- data/spec/fixtures/cert5.pem +0 -0
- data/spec/fixtures/cert6.pem +0 -0
- data/spec/fixtures/cert_expired.pem +0 -0
- data/spec/fixtures/cert_inhibit.pem +24 -0
- data/spec/fixtures/cert_name_constraints.pem +29 -0
- data/spec/fixtures/cert_not_yet_valid.pem +0 -0
- data/spec/fixtures/cert_ocsp_no_check.pem +18 -0
- data/spec/fixtures/cert_policy_constraints.pem +31 -0
- data/spec/fixtures/cert_san.pem +0 -0
- data/spec/fixtures/cert_san2.pem +0 -0
- data/spec/fixtures/cert_unknown_extension.pem +28 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +11 -11
- data/spec/fixtures/config_test.yaml +54 -36
- data/spec/fixtures/config_test_dsa.yaml +35 -0
- data/spec/fixtures/config_test_ec.yaml +35 -0
- data/spec/fixtures/config_test_engine_key.yaml +5 -5
- data/spec/fixtures/config_test_engine_no_key_name.yaml +4 -4
- data/spec/fixtures/config_test_minimal.yaml +4 -4
- data/spec/fixtures/config_test_password.yaml +5 -5
- data/spec/fixtures/config_test_various.yaml +111 -74
- data/spec/fixtures/crl_list_file.txt +0 -0
- data/spec/fixtures/crl_with_reason.pem +0 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +0 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +0 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +0 -0
- data/spec/fixtures/csr1_newlines.pem +0 -0
- data/spec/fixtures/csr1_no_begin_end.pem +0 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +0 -0
- data/spec/fixtures/csr2.pem +0 -0
- data/spec/fixtures/csr2_key.pem +0 -0
- data/spec/fixtures/csr3.pem +0 -0
- data/spec/fixtures/csr4.pem +0 -0
- data/spec/fixtures/csr_dsa.pem +0 -0
- data/spec/fixtures/csr_invalid_signature.pem +0 -0
- data/spec/fixtures/dsa_key.pem +0 -0
- data/spec/fixtures/dsa_root.cer +28 -0
- data/spec/fixtures/dsa_root.key +20 -0
- data/spec/fixtures/ec_csr2.der +0 -0
- data/spec/fixtures/ec_csr2.pem +8 -0
- data/spec/fixtures/ec_key1.der +0 -0
- data/spec/fixtures/ec_key1.pem +6 -0
- data/spec/fixtures/ec_key1_encrypted.pem +9 -0
- data/spec/fixtures/ec_key2.pem +6 -0
- data/spec/fixtures/hmacsha1.sig +1 -0
- data/spec/fixtures/hmacsha512.sig +1 -0
- data/spec/fixtures/key4.pem +0 -0
- data/spec/fixtures/key4_encrypted_des3.pem +0 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +0 -0
- data/spec/fixtures/missing_key_identifier_ca.key +0 -0
- data/spec/fixtures/ocsptest.r509.local.pem +0 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +0 -0
- data/spec/fixtures/second_ca.cer +0 -0
- data/spec/fixtures/second_ca.key +0 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +0 -0
- data/spec/fixtures/spkac_dsa.txt +1 -1
- data/spec/fixtures/spkac_dsa_no_verify.txt +1 -0
- data/spec/fixtures/spkac_ec.txt +1 -0
- data/spec/fixtures/spkac_rsa_newlines.txt +13 -0
- data/spec/fixtures/stca.pem +0 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +0 -0
- data/spec/fixtures/test_ca.cer +0 -0
- data/spec/fixtures/test_ca.key +0 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +0 -0
- data/spec/fixtures/test_ca_ec.cer +14 -0
- data/spec/fixtures/test_ca_ec.key +6 -0
- data/spec/fixtures/test_ca_ec_ee.cer +22 -0
- data/spec/fixtures/test_ca_ec_ee.key +6 -0
- data/spec/fixtures/test_ca_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_ocsp.key +0 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +0 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +0 -0
- data/spec/fixtures/test_ca_subroot.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +0 -0
- data/spec/message_digest_spec.rb +104 -84
- data/spec/ocsp_spec.rb +105 -105
- data/spec/oid_mapper_spec.rb +21 -21
- data/spec/private_key_spec.rb +275 -0
- data/spec/r509_spec.rb +35 -0
- data/spec/spec_helper.rb +15 -6
- data/spec/spki_spec.rb +221 -142
- data/spec/subject_spec.rb +232 -164
- data/spec/validity_spec.rb +91 -91
- metadata +79 -25
- data/doc/R509/Config/CaProfile.html +0 -651
- data/doc/R509/Crl/Administrator.html +0 -2073
- data/lib/r509/certificateauthority.rb +0 -290
- data/lib/r509/messagedigest.rb +0 -49
- data/lib/r509/oidmapper.rb +0 -32
- data/lib/r509/privatekey.rb +0 -185
- 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
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
63
|
+
# @return [OpenSSL::PKey::RSA] public key
|
64
|
+
def public_key
|
65
|
+
@spki.public_key
|
66
|
+
end
|
44
67
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
-
|
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
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
end
|
37
|
+
# see if X509 thinks this is okay
|
38
|
+
name
|
39
|
+
end
|
28
40
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
41
|
+
# @return [OpenSSL::X509::Name]
|
42
|
+
def name
|
43
|
+
OpenSSL::X509::Name.new(@array)
|
44
|
+
end
|
33
45
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
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
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|