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/csr.rb
CHANGED
@@ -1,324 +1,313 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
require 'r509/exceptions'
|
3
3
|
require 'r509/io_helpers'
|
4
|
-
require 'r509/
|
4
|
+
require 'r509/private_key'
|
5
|
+
require 'r509/ec-hack'
|
6
|
+
require 'r509/asn1'
|
5
7
|
|
6
8
|
module R509
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
domains = opts[:san_names] || []
|
53
|
-
parsed_domains = prefix_domains(domains)
|
54
|
-
create_request(opts[:subject], parsed_domains) #sets @req
|
55
|
-
elsif opts.has_key?(:csr)
|
56
|
-
if opts.has_key?(:san_names)
|
57
|
-
raise ArgumentError, "You can't add domains to an existing CSR"
|
58
|
-
end
|
59
|
-
parse_csr(opts[:csr])
|
60
|
-
else
|
61
|
-
raise ArgumentError, "Must provide one of cert, subject, or csr"
|
62
|
-
end
|
63
|
-
|
64
|
-
if dsa?
|
65
|
-
#only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
|
66
|
-
#post-1.0 you can sign with anything, but let's be conservative
|
67
|
-
#see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
|
68
|
-
@message_digest = R509::MessageDigest.new('dss1')
|
69
|
-
elsif opts.has_key?(:message_digest)
|
70
|
-
@message_digest = R509::MessageDigest.new(opts[:message_digest])
|
71
|
-
else
|
72
|
-
@message_digest = R509::MessageDigest.new('sha1')
|
73
|
-
end
|
74
|
-
|
75
|
-
if not opts.has_key?(:csr)
|
76
|
-
@req.sign(@key.key, @message_digest.digest)
|
77
|
-
end
|
78
|
-
if not @key.nil? and not @req.verify(@key.public_key) then
|
79
|
-
raise R509Error, 'Key does not match request.'
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
# Helper method to quickly load a CSR from the filesystem
|
85
|
-
#
|
86
|
-
# @param [String] filename Path to file you want to load
|
87
|
-
# @return [R509::Csr] Csr object
|
88
|
-
def self.load_from_file( filename )
|
89
|
-
return R509::Csr.new(:csr => IOHelpers.read_data(filename) )
|
9
|
+
# The primary certificate signing request object
|
10
|
+
class CSR
|
11
|
+
include R509::IOHelpers
|
12
|
+
|
13
|
+
attr_reader :san, :key, :subject, :req, :attributes, :message_digest
|
14
|
+
# @option opts [String,OpenSSL::X509::Request] :csr a csr
|
15
|
+
# @option opts [Symbol] :type :rsa/:dsa/:ec required if not providing existing :csr. Defaults to :rsa
|
16
|
+
# @option opts [String] :curve_name ("secp384r1") Only used if :type is :ec
|
17
|
+
# @option opts [Integer] :bit_strength (2048) Only used if :type is :rsa or :dsa
|
18
|
+
# @option opts [String] :message_digest Optional digest. sha1, sha224, sha256, sha384, sha512, md5. Defaults to sha1
|
19
|
+
# @option opts [Array] :san_names List of domains, IPs, email addresses, or URIs to encode as subjectAltNames. The type is determined from the structure of the strings via the R509::ASN1.general_name_parser method
|
20
|
+
# @option opts [R509::Subject,Array,OpenSSL::X509::Name] :subject array of subject items
|
21
|
+
# @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)
|
22
|
+
# @example Generate a 4096-bit RSA key + CSR
|
23
|
+
# :type => :rsa,
|
24
|
+
# :bit_strength => 4096,
|
25
|
+
# :subject => [
|
26
|
+
# ['CN','somedomain.com'],
|
27
|
+
# ['O','My Org'],
|
28
|
+
# ['L','City'],
|
29
|
+
# ['ST','State'],
|
30
|
+
# ['C','US']
|
31
|
+
# ]
|
32
|
+
# @example Generate an ECDSA key using the secp384r1 curve parameters + CSR and sign with SHA512
|
33
|
+
# :type => :ec,
|
34
|
+
# :curve_name => 'secp384r1',
|
35
|
+
# :message_digest => 'sha512',
|
36
|
+
# :subject => [
|
37
|
+
# ['CN','somedomain.com'],
|
38
|
+
# ]
|
39
|
+
def initialize(opts={})
|
40
|
+
if not opts.kind_of?(Hash)
|
41
|
+
raise ArgumentError, 'Must provide a hash of options'
|
42
|
+
end
|
43
|
+
if opts.has_key?(:subject) and opts.has_key?(:csr)
|
44
|
+
raise ArgumentError, "You must provide :subject or :csr, not both"
|
45
|
+
end
|
46
|
+
@bit_strength = opts[:bit_strength] || 2048
|
47
|
+
@curve_name = opts[:curve_name] || "secp384r1"
|
48
|
+
|
49
|
+
if opts.has_key?(:key)
|
50
|
+
if opts[:key].kind_of?(R509::PrivateKey)
|
51
|
+
@key = opts[:key]
|
52
|
+
else
|
53
|
+
@key = R509::PrivateKey.new(:key => opts[:key])
|
90
54
|
end
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
55
|
+
end
|
56
|
+
|
57
|
+
@type = opts[:type] || :rsa
|
58
|
+
if not [:rsa,:dsa,:ec].include?(@type) and @key.nil?
|
59
|
+
raise ArgumentError, 'Must provide :rsa, :dsa, or :ec as type when key is nil'
|
60
|
+
end
|
61
|
+
|
62
|
+
if opts.has_key?(:subject)
|
63
|
+
san_names = R509::ASN1.general_name_parser(opts[:san_names] || [])
|
64
|
+
create_request(opts[:subject], san_names) #sets @req
|
65
|
+
elsif opts.has_key?(:csr)
|
66
|
+
if opts.has_key?(:san_names)
|
67
|
+
raise ArgumentError, "You can't add domains to an existing CSR"
|
97
68
|
end
|
69
|
+
parse_csr(opts[:csr])
|
70
|
+
else
|
71
|
+
raise ArgumentError, "You must provide :subject or :csr"
|
72
|
+
end
|
73
|
+
|
74
|
+
if dsa?
|
75
|
+
#only DSS1 is acceptable for DSA signing in OpenSSL < 1.0
|
76
|
+
#post-1.0 you can sign with anything, but let's be conservative
|
77
|
+
#see: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/PKey/DSA.html
|
78
|
+
@message_digest = R509::MessageDigest.new('dss1')
|
79
|
+
elsif opts.has_key?(:message_digest)
|
80
|
+
@message_digest = R509::MessageDigest.new(opts[:message_digest])
|
81
|
+
else
|
82
|
+
@message_digest = R509::MessageDigest.new('sha1')
|
83
|
+
end
|
84
|
+
|
85
|
+
if not opts.has_key?(:csr)
|
86
|
+
@req.sign(@key.key, @message_digest.digest)
|
87
|
+
end
|
88
|
+
if not @key.nil? and not @req.verify(@key.public_key) then
|
89
|
+
raise R509Error, 'Key does not match request.'
|
90
|
+
end
|
98
91
|
|
99
|
-
|
100
|
-
# @return [Boolean]
|
101
|
-
def verify_signature
|
102
|
-
@req.verify(public_key)
|
103
|
-
end
|
92
|
+
end
|
104
93
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
94
|
+
# Helper method to quickly load a CSR from the filesystem
|
95
|
+
#
|
96
|
+
# @param [String] filename Path to file you want to load
|
97
|
+
# @return [R509::CSR] CSR object
|
98
|
+
def self.load_from_file( filename )
|
99
|
+
return R509::CSR.new(:csr => IOHelpers.read_data(filename) )
|
100
|
+
end
|
113
101
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
102
|
+
# @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] public key
|
103
|
+
def public_key
|
104
|
+
if(@req.kind_of?(OpenSSL::X509::Request)) then
|
105
|
+
@req.public_key
|
106
|
+
end
|
107
|
+
end
|
120
108
|
|
121
|
-
|
109
|
+
# Verifies the integrity of the signature on the request
|
110
|
+
# @return [Boolean]
|
111
|
+
def verify_signature
|
112
|
+
@req.verify(public_key)
|
113
|
+
end
|
122
114
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
115
|
+
# @return [Boolean] Boolean of whether the object contains a private key
|
116
|
+
def has_private_key?
|
117
|
+
if not @key.nil?
|
118
|
+
true
|
119
|
+
else
|
120
|
+
false
|
121
|
+
end
|
122
|
+
end
|
129
123
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
124
|
+
# Converts the CSR into the PEM format
|
125
|
+
#
|
126
|
+
# @return [String] the CSR converted into PEM format.
|
127
|
+
def to_pem
|
128
|
+
@req.to_pem
|
129
|
+
end
|
137
130
|
|
138
|
-
|
139
|
-
#
|
140
|
-
# @param [String, #write] filename_or_io Either a string of the path for
|
141
|
-
# the file that you'd like to write, or an IO-like object.
|
142
|
-
def write_der(filename_or_io)
|
143
|
-
write_data(filename_or_io, @req.to_der)
|
144
|
-
end
|
131
|
+
alias :to_s :to_pem
|
145
132
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
133
|
+
# Converts the CSR into the DER format
|
134
|
+
#
|
135
|
+
# @return [String] the CSR converted into DER format.
|
136
|
+
def to_der
|
137
|
+
@req.to_der
|
138
|
+
end
|
152
139
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
140
|
+
# Writes the CSR into the PEM format
|
141
|
+
#
|
142
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
143
|
+
# the file that you'd like to write, or an IO-like object.
|
144
|
+
def write_pem(filename_or_io)
|
145
|
+
write_data(filename_or_io, @req.to_pem)
|
146
|
+
end
|
159
147
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
168
|
-
end
|
148
|
+
# Writes the CSR into the DER format
|
149
|
+
#
|
150
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
151
|
+
# the file that you'd like to write, or an IO-like object.
|
152
|
+
def write_der(filename_or_io)
|
153
|
+
write_data(filename_or_io, @req.to_der)
|
154
|
+
end
|
169
155
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
return element[1]
|
177
|
-
end
|
178
|
-
end
|
179
|
-
nil
|
180
|
-
end
|
156
|
+
# Returns whether the public key is RSA
|
157
|
+
#
|
158
|
+
# @return [Boolean] true if the public key is RSA, false otherwise
|
159
|
+
def rsa?
|
160
|
+
@req.public_key.kind_of?(OpenSSL::PKey::RSA)
|
161
|
+
end
|
181
162
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
163
|
+
# Returns whether the public key is DSA
|
164
|
+
#
|
165
|
+
# @return [Boolean] true if the public key is DSA, false otherwise
|
166
|
+
def dsa?
|
167
|
+
@req.public_key.kind_of?(OpenSSL::PKey::DSA)
|
168
|
+
end
|
188
169
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
|
196
|
-
'DSA'
|
197
|
-
end
|
198
|
-
end
|
170
|
+
# Returns whether the public key is EC
|
171
|
+
#
|
172
|
+
# @return [Boolean] true if the public key is EC, false otherwise
|
173
|
+
def ec?
|
174
|
+
@req.public_key.kind_of?(OpenSSL::PKey::EC)
|
175
|
+
end
|
199
176
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
177
|
+
# Returns the bit strength of the key used to create the CSR
|
178
|
+
# @return [Integer] the integer bit strength.
|
179
|
+
def bit_strength
|
180
|
+
if self.rsa?
|
181
|
+
return @req.public_key.n.num_bits
|
182
|
+
elsif self.dsa?
|
183
|
+
return @req.public_key.p.num_bits
|
184
|
+
elsif self.ec?
|
185
|
+
raise R509::R509Error, 'Bit strength is not available for EC at this time.'
|
186
|
+
end
|
187
|
+
end
|
208
188
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
#remove extraneous newlines
|
221
|
-
csr.gsub!(/^\s*\n/,'')
|
222
|
-
#and leading/trailing whitespace
|
223
|
-
csr.gsub!(/^\s*|\s*$/,'')
|
224
|
-
if not csr.match(/-----BEGIN.+-----/) and csr.match(/MII/)
|
225
|
-
#if csr is probably PEM (MII is the beginning of every base64
|
226
|
-
#encoded DER) then add the wrapping lines if they aren't provided.
|
227
|
-
#tools like Microsoft's xenroll do this.
|
228
|
-
csr = "-----BEGIN CERTIFICATE REQUEST-----\n"+csr+"\n-----END CERTIFICATE REQUEST-----"
|
229
|
-
end
|
230
|
-
end
|
231
|
-
#and now we try again...
|
232
|
-
@req = OpenSSL::X509::Request.new csr
|
233
|
-
end
|
234
|
-
@subject = R509::Subject.new(@req.subject)
|
235
|
-
@attributes = parse_attributes_from_csr(@req)
|
236
|
-
@san_names = @attributes['subjectAltName'] || []
|
237
|
-
end
|
189
|
+
# Returns the short name of the elliptic curve used to generate the public key
|
190
|
+
# if the key is EC. If not, raises an error.
|
191
|
+
#
|
192
|
+
# @return [String] elliptic curve name
|
193
|
+
def curve_name
|
194
|
+
if self.ec?
|
195
|
+
self.public_key.group.curve_name
|
196
|
+
else
|
197
|
+
raise R509::R509Error, 'Curve name is only available with EC CSRs'
|
198
|
+
end
|
199
|
+
end
|
238
200
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
@key = R509::PrivateKey.new(:type => @type,
|
247
|
-
:bit_strength => @bit_strength)
|
248
|
-
end
|
249
|
-
@req.public_key = @key.public_key
|
250
|
-
add_san_extension(domains)
|
251
|
-
@attributes = parse_attributes_from_csr(@req)
|
252
|
-
@san_names = @attributes['subjectAltName'] || []
|
201
|
+
# Returns subject component
|
202
|
+
#
|
203
|
+
# @return [String] value of the subject component requested
|
204
|
+
def subject_component short_name
|
205
|
+
@req.subject.to_a.each do |element|
|
206
|
+
if element[0].downcase == short_name.downcase then
|
207
|
+
return element[1]
|
253
208
|
end
|
209
|
+
end
|
210
|
+
nil
|
211
|
+
end
|
254
212
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
if (extension.oid == 'subjectAltName') then
|
262
|
-
domains_to_add = parse_san_extension(extension)
|
263
|
-
end
|
264
|
-
}
|
265
|
-
{:subject => parsed_cert.subject, :subjectAltName => domains_to_add}
|
266
|
-
end
|
213
|
+
# Returns signature algorithm
|
214
|
+
#
|
215
|
+
# @return [String] value of the signature algorithm. E.g. sha1WithRSAEncryption, sha256WithRSAEncryption, md5WithRSAEncryption
|
216
|
+
def signature_algorithm
|
217
|
+
@req.signature_algorithm
|
218
|
+
end
|
267
219
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
@seq = set_value
|
281
|
-
extensions = @seq.value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
|
282
|
-
extensions.each { |ext|
|
283
|
-
attributes[ext.oid] = {'value' => ext.value, 'critical'=> ext.critical? }
|
284
|
-
if ext.oid == 'subjectAltName' then
|
285
|
-
domains_from_csr = ext.value.gsub(/DNS:/,'').split(',')
|
286
|
-
domains_from_csr = domains_from_csr.collect {|x| x.strip }
|
287
|
-
attributes[ext.oid] = domains_from_csr
|
288
|
-
end
|
289
|
-
}
|
290
|
-
}
|
291
|
-
end
|
292
|
-
attributes
|
293
|
-
end
|
220
|
+
# Returns key algorithm (RSA/DSA/EC)
|
221
|
+
#
|
222
|
+
# @return [Symbol] value of the key algorithm. :rsa, :dsa, :ec
|
223
|
+
def key_algorithm
|
224
|
+
if @req.public_key.kind_of? OpenSSL::PKey::RSA then
|
225
|
+
:rsa
|
226
|
+
elsif @req.public_key.kind_of? OpenSSL::PKey::DSA then
|
227
|
+
:dsa
|
228
|
+
elsif @req.public_key.kind_of? OpenSSL::PKey::EC then
|
229
|
+
:ec
|
230
|
+
end
|
231
|
+
end
|
294
232
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
233
|
+
private
|
234
|
+
|
235
|
+
def parse_csr(csr)
|
236
|
+
begin
|
237
|
+
@req = OpenSSL::X509::Request.new csr
|
238
|
+
rescue OpenSSL::X509::RequestError
|
239
|
+
#let's try to load this thing by handling a few
|
240
|
+
#common error cases
|
241
|
+
if csr.kind_of?(String)
|
242
|
+
#normalize line endings (really just for the next replace)
|
243
|
+
csr.gsub!(/\r\n?/, "\n")
|
244
|
+
#remove extraneous newlines
|
245
|
+
csr.gsub!(/^\s*\n/,'')
|
246
|
+
#and leading/trailing whitespace
|
247
|
+
csr.gsub!(/^\s*|\s*$/,'')
|
248
|
+
if not csr.match(/-----BEGIN.+-----/) and csr.match(/MII/)
|
249
|
+
#if csr is probably PEM (MII is the beginning of every base64
|
250
|
+
#encoded DER) then add the wrapping lines if they aren't provided.
|
251
|
+
#tools like Microsoft's xenroll do this.
|
252
|
+
csr = "-----BEGIN CERTIFICATE REQUEST-----\n"+csr+"\n-----END CERTIFICATE REQUEST-----"
|
253
|
+
end
|
303
254
|
end
|
255
|
+
#and now we try again...
|
256
|
+
@req = OpenSSL::X509::Request.new csr
|
257
|
+
end
|
258
|
+
@subject = R509::Subject.new(@req.subject)
|
259
|
+
parse_san_attribute_from_csr(@req)
|
260
|
+
end
|
304
261
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
262
|
+
def create_request(subject,san_names)
|
263
|
+
@req = OpenSSL::X509::Request.new
|
264
|
+
@req.version = 0
|
265
|
+
@subject = R509::Subject.new(subject)
|
266
|
+
@req.subject = @subject.name
|
267
|
+
if @key.nil?
|
268
|
+
@key = R509::PrivateKey.new(:type => @type, :bit_strength => @bit_strength, :curve_name => @curve_name)
|
269
|
+
end
|
270
|
+
@req.public_key = @key.public_key
|
271
|
+
add_san_extension(san_names)
|
272
|
+
parse_san_attribute_from_csr(@req)
|
273
|
+
end
|
315
274
|
|
316
|
-
|
317
|
-
|
275
|
+
# @return [Array] array of GeneralName objects
|
276
|
+
def parse_san_attribute_from_csr(req)
|
277
|
+
san = nil
|
278
|
+
set = nil
|
279
|
+
req.attributes.each do |attribute|
|
280
|
+
if attribute.oid == 'extReq'
|
281
|
+
set = OpenSSL::ASN1.decode attribute.value
|
282
|
+
extensions = set.value[0].value.collect{|asn1ext| OpenSSL::X509::Extension.new(asn1ext) }
|
283
|
+
r509_extensions = R509::Cert::Extensions.wrap_openssl_extensions( extensions )
|
284
|
+
if not r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].nil?
|
285
|
+
san = r509_extensions[R509::Cert::Extensions::SubjectAlternativeName].general_names
|
286
|
+
end
|
287
|
+
break
|
318
288
|
end
|
289
|
+
end
|
290
|
+
@san = san
|
291
|
+
end
|
319
292
|
|
320
|
-
|
321
|
-
|
293
|
+
def add_san_extension(san_names)
|
294
|
+
if not san_names.nil? and not san_names.names.empty?
|
295
|
+
names = san_names.names.uniq
|
296
|
+
general_names = R509::ASN1::GeneralNames.new
|
297
|
+
names.each do |domain|
|
298
|
+
general_names.add_item(domain)
|
322
299
|
end
|
300
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
301
|
+
serialized = general_names.serialize_names
|
302
|
+
ef.config = OpenSSL::Config.parse(serialized[:conf])
|
303
|
+
ex = []
|
304
|
+
ex << ef.create_extension("subjectAltName", serialized[:extension_string])
|
305
|
+
request_extension_set = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence(ex)])
|
306
|
+
@req.add_attribute(OpenSSL::X509::Attribute.new("extReq", request_extension_set))
|
307
|
+
parse_san_attribute_from_csr(@req)
|
308
|
+
end
|
323
309
|
end
|
310
|
+
|
311
|
+
|
312
|
+
end
|
324
313
|
end
|