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