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
@@ -0,0 +1,407 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'r509/config'
|
3
|
+
require 'r509/cert'
|
4
|
+
require 'r509/exceptions'
|
5
|
+
require 'r509/ec-hack'
|
6
|
+
|
7
|
+
# CertificateAuthority related classes
|
8
|
+
module R509::CertificateAuthority
|
9
|
+
# Contains the certification authority signing operation methods
|
10
|
+
class Signer
|
11
|
+
# @param [R509::Config] config
|
12
|
+
def initialize(config=nil)
|
13
|
+
@config = config
|
14
|
+
|
15
|
+
if not @config.nil? and not @config.kind_of?(R509::Config::CAConfig)
|
16
|
+
raise R509::R509Error, "config must be a kind of R509::Config::CAConfig or nil (for self-sign only)"
|
17
|
+
end
|
18
|
+
if not @config.nil? and not @config.ca_cert.has_private_key?
|
19
|
+
raise R509::R509Error, "You must have a private key associated with your CA certificate to issue"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Signs a CSR
|
24
|
+
# @option options :csr [R509::CSR]
|
25
|
+
# @option options :spki [R509::SPKI]
|
26
|
+
# @option options :profile_name [String] The CA profile you want to use (eg "server" in your config)
|
27
|
+
# @option options :subject [R509::Subject,OpenSSL::X509::Subject,Array] (optional for R509::CSR, required for R509::SPKI)
|
28
|
+
# @option options :san_names [Array,R509::ASN1::GeneralNames] optional either an array of names that will be automatically parsed to determine their type, or an explicit R509::ASN1::GeneralNames object
|
29
|
+
# @option options :message_digest [String] the message digest to use for this certificate instead of the config's default
|
30
|
+
# @option options :serial [String] the serial number you want to issue the certificate with
|
31
|
+
# @option options :not_before [Time] the notBefore for the certificate
|
32
|
+
# @option options :not_after [Time] the notAfter for the certificate
|
33
|
+
# @return [R509::Cert] the signed cert object
|
34
|
+
def sign(options)
|
35
|
+
if @config.nil?
|
36
|
+
raise R509::R509Error, "When instantiating the signer without a config you can only call #selfsign"
|
37
|
+
elsif @config.num_profiles == 0
|
38
|
+
raise R509::R509Error, "You must have at least one CAProfile on your CAConfig to issue"
|
39
|
+
end
|
40
|
+
|
41
|
+
check_options(options)
|
42
|
+
|
43
|
+
subject, san_names, public_key = extract_public_key_subject_san(options)
|
44
|
+
|
45
|
+
|
46
|
+
if options.has_key?(:csr) and not options[:csr].verify_signature
|
47
|
+
raise R509::R509Error, "Certificate request signature is invalid."
|
48
|
+
end
|
49
|
+
|
50
|
+
# prior to OpenSSL 1.0 DSA could only use DSS1 (aka SHA1) signatures. post-1.0 anything
|
51
|
+
# goes but at the moment we don't enforce this restriction so an OpenSSL error could
|
52
|
+
# bubble up if they do it wrong.
|
53
|
+
message_digest = (options.has_key?(:message_digest))? R509::MessageDigest.new(options[:message_digest]) : R509::MessageDigest.new(@config.message_digest)
|
54
|
+
|
55
|
+
profile = @config.profile(options[:profile_name])
|
56
|
+
|
57
|
+
validated_subject = validate_subject(subject,profile)
|
58
|
+
|
59
|
+
cert = build_cert(
|
60
|
+
:subject => validated_subject.name,
|
61
|
+
:issuer => @config.ca_cert.subject.name,
|
62
|
+
:not_before => options[:not_before],
|
63
|
+
:not_after => options[:not_after],
|
64
|
+
:public_key => public_key,
|
65
|
+
:serial => options[:serial]
|
66
|
+
)
|
67
|
+
|
68
|
+
basic_constraints = profile.basic_constraints
|
69
|
+
key_usage = profile.key_usage
|
70
|
+
extended_key_usage = profile.extended_key_usage
|
71
|
+
certificate_policies = profile.certificate_policies
|
72
|
+
ocsp_no_check = profile.ocsp_no_check
|
73
|
+
|
74
|
+
build_extensions(
|
75
|
+
:subject_certificate => cert,
|
76
|
+
:issuer_certificate => @config.ca_cert.cert,
|
77
|
+
:basic_constraints => basic_constraints,
|
78
|
+
:key_usage => key_usage,
|
79
|
+
:extended_key_usage => extended_key_usage,
|
80
|
+
:ocsp_no_check => ocsp_no_check,
|
81
|
+
:certificate_policies => certificate_policies,
|
82
|
+
:san_names => san_names,
|
83
|
+
:inhibit_any_policy => profile.inhibit_any_policy,
|
84
|
+
:policy_constraints => profile.policy_constraints,
|
85
|
+
:name_constraints => profile.name_constraints
|
86
|
+
)
|
87
|
+
|
88
|
+
|
89
|
+
#@config.ca_cert.key.key ... ugly. ca_cert returns R509::Cert
|
90
|
+
# #key returns R509::PrivateKey and #key on that returns OpenSSL object we need
|
91
|
+
cert.sign( @config.ca_cert.key.key, message_digest.digest )
|
92
|
+
R509::Cert.new(:cert => cert)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Self-signs a CSR
|
96
|
+
# @option options :csr [R509::CSR]
|
97
|
+
# @option options :message_digest [String] the message digest to use for this certificate (defaults to sha1)
|
98
|
+
# @option options :serial [String] the serial number you want to issue the certificate with (defaults to random)
|
99
|
+
# @option options :not_before [Time] the notBefore for the certificate (defaults to now)
|
100
|
+
# @option options :not_after [Time] the notAfter for the certificate (defaults to 1 year)
|
101
|
+
# @option options :san_names [Array,R509::ASN1::GeneralNames] optional either an array of names that will be automatically parsed to determine their type, or an explicit R509::ASN1::GeneralNames object
|
102
|
+
# @return [R509::Cert] the signed cert object
|
103
|
+
def selfsign(options)
|
104
|
+
if not options.kind_of?(Hash)
|
105
|
+
raise ArgumentError, "You must pass a hash of options consisting of at minimum :csr"
|
106
|
+
end
|
107
|
+
csr = options[:csr]
|
108
|
+
if csr.key.nil?
|
109
|
+
raise ArgumentError, 'CSR must also have a private key to self sign'
|
110
|
+
end
|
111
|
+
cert = build_cert(
|
112
|
+
:subject => csr.subject.name,
|
113
|
+
:issuer => csr.subject.name,
|
114
|
+
:not_before => options[:not_before],
|
115
|
+
:not_after => options[:not_after],
|
116
|
+
:public_key => csr.public_key,
|
117
|
+
:serial => options[:serial]
|
118
|
+
)
|
119
|
+
|
120
|
+
sans = (options.has_key?(:san_names))? options[:san_names] : csr.san
|
121
|
+
san_names = parse_san_names(sans)
|
122
|
+
|
123
|
+
build_extensions(
|
124
|
+
:subject_certificate => cert,
|
125
|
+
:issuer_certificate => cert,
|
126
|
+
:basic_constraints => {"ca" => true },
|
127
|
+
:san_names => san_names
|
128
|
+
)
|
129
|
+
|
130
|
+
|
131
|
+
if options.has_key?(:message_digest)
|
132
|
+
message_digest = R509::MessageDigest.new(options[:message_digest])
|
133
|
+
else
|
134
|
+
message_digest = R509::MessageDigest.new('sha1')
|
135
|
+
end
|
136
|
+
|
137
|
+
# CSR#key returns R509::PrivateKey and #key on that returns OpenSSL object we need
|
138
|
+
cert.sign( csr.key.key, message_digest.digest )
|
139
|
+
R509::Cert.new(:cert => cert)
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def check_options(options)
|
145
|
+
if options.has_key?(:csr) and options.has_key?(:spki)
|
146
|
+
raise ArgumentError, "You can't pass both :csr and :spki"
|
147
|
+
elsif not options.has_key?(:csr) and not options.has_key?(:spki)
|
148
|
+
raise ArgumentError, "You must supply either :csr or :spki"
|
149
|
+
elsif options.has_key?(:csr)
|
150
|
+
if not options[:csr].kind_of?(R509::CSR)
|
151
|
+
raise ArgumentError, "You must pass an R509::CSR object for :csr"
|
152
|
+
end
|
153
|
+
elsif not options.has_key?(:csr) and options.has_key?(:spki)
|
154
|
+
if not options[:spki].kind_of?(R509::SPKI)
|
155
|
+
raise ArgumentError, "You must pass an R509::SPKI object for :spki"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def extract_public_key_subject_san(options)
|
161
|
+
if options.has_key?(:csr)
|
162
|
+
subject = (options.has_key?(:subject))? R509::Subject.new(options[:subject]) : options[:csr].subject
|
163
|
+
sans = (options.has_key?(:san_names))? options[:san_names] : options[:csr].san
|
164
|
+
san_names = parse_san_names(sans)
|
165
|
+
public_key = options[:csr].public_key
|
166
|
+
else
|
167
|
+
# spki
|
168
|
+
if not options.has_key?(:subject)
|
169
|
+
raise ArgumentError, "You must supply :subject when passing :spki"
|
170
|
+
end
|
171
|
+
public_key = options[:spki].public_key
|
172
|
+
subject = R509::Subject.new(options[:subject])
|
173
|
+
san_names = parse_san_names(options[:san_names]) # optional
|
174
|
+
end
|
175
|
+
|
176
|
+
[subject,san_names,public_key]
|
177
|
+
end
|
178
|
+
|
179
|
+
def parse_san_names(sans)
|
180
|
+
case sans
|
181
|
+
when nil then nil
|
182
|
+
when R509::ASN1::GeneralNames then sans
|
183
|
+
when Array then R509::ASN1.general_name_parser(sans)
|
184
|
+
else
|
185
|
+
raise ArgumentError, "When passing SAN names it must be provided as either an array of strings or an R509::ASN1::GeneralNames object"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def build_conf(section,hash,index)
|
190
|
+
conf = ["[#{section}]"]
|
191
|
+
conf.push "policyIdentifier=#{hash["policy_identifier"]}" unless hash["policy_identifier"].nil?
|
192
|
+
hash["cps_uris"].each_with_index do |cps,idx|
|
193
|
+
conf.push "CPS.#{idx+1}=\"#{cps}\""
|
194
|
+
end if hash["cps_uris"].respond_to?(:each_with_index)
|
195
|
+
|
196
|
+
user_notice_confs = []
|
197
|
+
hash["user_notices"].each_with_index do |un,k|
|
198
|
+
conf.push "userNotice.#{k+1}=@user_notice#{k+1}#{index}"
|
199
|
+
user_notice_confs.push "[user_notice#{k+1}#{index}]"
|
200
|
+
user_notice_confs.push "explicitText=\"#{un["explicit_text"]}\"" unless un["explicit_text"].nil?
|
201
|
+
# if org is supplied notice numbers is also required (and vice versa). enforced in CAProfile
|
202
|
+
user_notice_confs.push "organization=\"#{un["organization"]}\"" unless un["organization"].nil?
|
203
|
+
user_notice_confs.push "noticeNumbers=\"#{un["notice_numbers"]}\"" unless un["notice_numbers"].nil?
|
204
|
+
end unless not hash["user_notices"].kind_of?(Array)
|
205
|
+
|
206
|
+
conf.concat(user_notice_confs)
|
207
|
+
conf.join "\n"
|
208
|
+
end
|
209
|
+
|
210
|
+
def validate_subject(subject,profile)
|
211
|
+
if profile.subject_item_policy.nil? then
|
212
|
+
subject
|
213
|
+
else
|
214
|
+
profile.subject_item_policy.validate_subject(subject)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def build_cert(options)
|
219
|
+
|
220
|
+
cert = OpenSSL::X509::Certificate.new
|
221
|
+
|
222
|
+
cert.subject = options[:subject]
|
223
|
+
cert.issuer = options[:issuer]
|
224
|
+
cert.not_before = calculate_not_before(options[:not_before])
|
225
|
+
cert.not_after = calculate_not_after(options[:not_after],cert.not_before)
|
226
|
+
cert.public_key = options[:public_key]
|
227
|
+
cert.serial = create_serial(options[:serial])
|
228
|
+
cert.version = 2 #2 means v3
|
229
|
+
cert
|
230
|
+
end
|
231
|
+
|
232
|
+
def create_serial(serial)
|
233
|
+
if not serial.nil?
|
234
|
+
serial = OpenSSL::BN.new(serial.to_s)
|
235
|
+
else
|
236
|
+
# generate random serial in accordance with best practices
|
237
|
+
# guidelines state 20-bits of entropy, but we can cram more in
|
238
|
+
# per rfc5280 conforming CAs can make the serial field up to 20 octets
|
239
|
+
# to prevent even the incredibly remote possibility of collision we'll
|
240
|
+
# concatenate current time (to the microsecond) with a random num
|
241
|
+
rand = OpenSSL::BN.rand(96,0) # 96 bits is 12 bytes (octets).
|
242
|
+
serial = OpenSSL::BN.new((Time.now.to_f*1000000).to_i.to_s + rand.to_s)
|
243
|
+
# since second param is 0 the most significant bit must always be 1
|
244
|
+
# this theoretically gives us 95 bits of entropy + microtime, which
|
245
|
+
# adds a non-zero quantity of entropy. depending upon how predictable
|
246
|
+
# your issuance is, this could range from a reasonably large quantity
|
247
|
+
# of entropy to very little
|
248
|
+
end
|
249
|
+
serial
|
250
|
+
end
|
251
|
+
|
252
|
+
def build_extensions(options)
|
253
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
254
|
+
|
255
|
+
ef.subject_certificate = options[:subject_certificate]
|
256
|
+
|
257
|
+
ef.issuer_certificate = options[:issuer_certificate]
|
258
|
+
|
259
|
+
ext = []
|
260
|
+
if not options[:basic_constraints].nil?
|
261
|
+
bc = options[:basic_constraints]
|
262
|
+
if bc["ca"] == true
|
263
|
+
bc_value = "CA:TRUE"
|
264
|
+
if not bc["path_length"].nil?
|
265
|
+
bc_value += ",pathlen:#{bc["path_length"]}"
|
266
|
+
end
|
267
|
+
else
|
268
|
+
bc_value = "CA:FALSE"
|
269
|
+
end
|
270
|
+
|
271
|
+
ext << ef.create_extension("basicConstraints", bc_value, true)
|
272
|
+
end
|
273
|
+
if not options[:key_usage].nil? and not options[:key_usage].empty?
|
274
|
+
ext << ef.create_extension("keyUsage", options[:key_usage].join(","))
|
275
|
+
end
|
276
|
+
if not options[:extended_key_usage].nil? and not options[:extended_key_usage].empty?
|
277
|
+
ext << ef.create_extension("extendedKeyUsage", options[:extended_key_usage].join(","))
|
278
|
+
end
|
279
|
+
ext << ef.create_extension("subjectKeyIdentifier", "hash")
|
280
|
+
|
281
|
+
#attach the key identifier if it's not a self-sign
|
282
|
+
if not ef.subject_certificate == ef.issuer_certificate and not R509::Cert.new(:cert=>options[:issuer_certificate]).authority_key_identifier.nil?
|
283
|
+
ext << ef.create_extension("authorityKeyIdentifier", "keyid:always") # this could also be keyid:always,issuer:always
|
284
|
+
end
|
285
|
+
|
286
|
+
if not options[:certificate_policies].nil? and options[:certificate_policies].respond_to?(:each)
|
287
|
+
conf = []
|
288
|
+
policy_names = ["ia5org"]
|
289
|
+
options[:certificate_policies].each_with_index do |policy,i|
|
290
|
+
conf << build_conf("certPolicies#{i}",policy,i)
|
291
|
+
policy_names << "@certPolicies#{i}"
|
292
|
+
end
|
293
|
+
ef.config = OpenSSL::Config.parse(conf.join("\n"))
|
294
|
+
ext << ef.create_extension("certificatePolicies", policy_names.join(","))
|
295
|
+
end
|
296
|
+
|
297
|
+
if not options[:san_names].nil? and not options[:san_names].names.empty?
|
298
|
+
serialize = options[:san_names].serialize_names
|
299
|
+
ef.config = OpenSSL::Config.parse(serialize[:conf])
|
300
|
+
ext << ef.create_extension("subjectAltName", serialize[:extension_string])
|
301
|
+
end
|
302
|
+
|
303
|
+
if not @config.nil? and not @config.cdp_location.nil? and not @config.cdp_location.empty?
|
304
|
+
gns = R509::ASN1.general_name_parser(@config.cdp_location)
|
305
|
+
serialize = gns.serialize_names
|
306
|
+
ef.config = OpenSSL::Config.parse(serialize[:conf])
|
307
|
+
ext << ef.create_extension("crlDistributionPoints", serialize[:extension_string])
|
308
|
+
end
|
309
|
+
|
310
|
+
#authorityInfoAccess processing
|
311
|
+
if not @config.nil?
|
312
|
+
aia = []
|
313
|
+
aia_conf = []
|
314
|
+
|
315
|
+
if not @config.ocsp_location.nil? and not @config.ocsp_location.empty?
|
316
|
+
gns = R509::ASN1.general_name_parser(@config.ocsp_location)
|
317
|
+
gns.names.each do |ocsp|
|
318
|
+
serialize = ocsp.serialize_name
|
319
|
+
aia.push "OCSP;#{serialize[:extension_string]}"
|
320
|
+
aia_conf.push serialize[:conf]
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
if not @config.nil? and not @config.ca_issuers_location.nil? and not @config.ca_issuers_location.empty?
|
325
|
+
gns = R509::ASN1.general_name_parser(@config.ca_issuers_location)
|
326
|
+
gns.names.each do |ca_issuers|
|
327
|
+
serialize = ca_issuers.serialize_name
|
328
|
+
aia.push "caIssuers;#{serialize[:extension_string]}"
|
329
|
+
aia_conf.push serialize[:conf]
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
if not aia.empty?
|
334
|
+
ef.config = OpenSSL::Config.parse(aia_conf.join("\n"))
|
335
|
+
ext << ef.create_extension("authorityInfoAccess",aia.join(","))
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
if options[:inhibit_any_policy]
|
340
|
+
ext << ef.create_extension("inhibitAnyPolicy",options[:inhibit_any_policy].to_s,true) # must be set critical per RFC 5280
|
341
|
+
end
|
342
|
+
|
343
|
+
if options[:policy_constraints]
|
344
|
+
pc = options[:policy_constraints]
|
345
|
+
constraints = []
|
346
|
+
constraints << "requireExplicitPolicy:#{pc["require_explicit_policy"]}" unless pc["require_explicit_policy"].nil?
|
347
|
+
constraints << "inhibitPolicyMapping:#{pc["inhibit_policy_mapping"]}" unless pc["inhibit_policy_mapping"].nil?
|
348
|
+
ext << ef.create_extension("policyConstraints",constraints.join(","),true) # must be set critical per RFC 5280
|
349
|
+
end
|
350
|
+
|
351
|
+
if options[:name_constraints]
|
352
|
+
nc = options[:name_constraints]
|
353
|
+
nc_data = []
|
354
|
+
nc_conf = []
|
355
|
+
if not nc["permitted"].nil?
|
356
|
+
gns = R509::ASN1::GeneralNames.new
|
357
|
+
nc["permitted"].each do |p|
|
358
|
+
gns.create_item(:type => p["type"], :value => p["value"])
|
359
|
+
end
|
360
|
+
gns.names.each do |permitted|
|
361
|
+
serialize = permitted.serialize_name
|
362
|
+
nc_data.push "permitted;#{serialize[:extension_string]}"
|
363
|
+
nc_conf.push serialize[:conf]
|
364
|
+
end
|
365
|
+
end
|
366
|
+
if not nc["excluded"].nil?
|
367
|
+
gns = R509::ASN1::GeneralNames.new
|
368
|
+
nc["excluded"].each do |p|
|
369
|
+
gns.create_item(:type => p["type"], :value => p["value"])
|
370
|
+
end
|
371
|
+
gns.names.each do |excluded|
|
372
|
+
serialize = excluded.serialize_name
|
373
|
+
nc_data.push "excluded;#{serialize[:extension_string]}"
|
374
|
+
nc_conf.push serialize[:conf]
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
ef.config = OpenSSL::Config.parse nc_conf.join("\n")
|
379
|
+
ext << ef.create_extension("nameConstraints",nc_data.join(","))
|
380
|
+
end
|
381
|
+
|
382
|
+
if options[:ocsp_no_check]
|
383
|
+
# the value of this extension is not encoded. presence is all that matters
|
384
|
+
ext << ef.create_extension("noCheck","yes")
|
385
|
+
end
|
386
|
+
|
387
|
+
options[:subject_certificate].extensions = ext
|
388
|
+
nil
|
389
|
+
end
|
390
|
+
|
391
|
+
def calculate_not_before(not_before)
|
392
|
+
if not_before.nil?
|
393
|
+
#not_before will be set to 6 hours before now to prevent issues with bad system clocks (clients don't sync)
|
394
|
+
not_before = Time.now - 6 * 60 * 60
|
395
|
+
end
|
396
|
+
not_before
|
397
|
+
end
|
398
|
+
|
399
|
+
def calculate_not_after(not_after,not_before)
|
400
|
+
if not_after.nil?
|
401
|
+
not_after = not_before + 365 * 24 * 60 * 60
|
402
|
+
end
|
403
|
+
not_after
|
404
|
+
end
|
405
|
+
|
406
|
+
end
|
407
|
+
end
|
data/lib/r509/config.rb
CHANGED
@@ -3,405 +3,601 @@ require 'openssl'
|
|
3
3
|
require 'r509/exceptions'
|
4
4
|
require 'r509/io_helpers'
|
5
5
|
require 'r509/subject'
|
6
|
-
require 'r509/
|
6
|
+
require 'r509/private_key'
|
7
7
|
require 'fileutils'
|
8
8
|
require 'pathname'
|
9
9
|
|
10
10
|
module R509
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
11
|
+
# Module to contain all configuration related classes (e.g. CAConfig, CAProfile, SubjectItemPolicy)
|
12
|
+
module Config
|
13
|
+
# Provides access to configuration profiles
|
14
|
+
class CAProfile
|
15
|
+
attr_reader :basic_constraints, :key_usage, :extended_key_usage,
|
16
|
+
:certificate_policies, :subject_item_policy, :ocsp_no_check,
|
17
|
+
:inhibit_any_policy, :policy_constraints, :name_constraints
|
18
|
+
|
19
|
+
# All hash options for CAProfile are optional.
|
20
|
+
# @option opts [String] :basic_constraints
|
21
|
+
# @option opts [Array] :key_usage
|
22
|
+
# @option opts [Array] :extended_key_usage
|
23
|
+
# @option opts [Array] :certificate_policies
|
24
|
+
# @option opts [Boolean] :ocsp_no_check Sets OCSP No Check extension in the certificate if true
|
25
|
+
# @option opts [Integer] :inhibit_any_policy Sets the value of the inhibitAnyPolicy extension
|
26
|
+
# @option opts [Hash] :policy_constraints Sets the value of the policyConstriants extension
|
27
|
+
# @option opts [Hash] :name_constraints Sets the value of the nameConstraints extension
|
28
|
+
# @option opts [R509::Config::SubjectItemPolicy] :subject_item_policy
|
29
|
+
def initialize(opts = {})
|
30
|
+
validate_basic_constraints opts[:basic_constraints]
|
31
|
+
validate_key_usage opts[:key_usage]
|
32
|
+
validate_extended_key_usage opts[:extended_key_usage]
|
33
|
+
validate_certificate_policies opts[:certificate_policies]
|
34
|
+
validate_inhibit_any_policy opts[:inhibit_any_policy]
|
35
|
+
validate_policy_constraints opts[:policy_constraints]
|
36
|
+
validate_name_constraints opts[:name_constraints]
|
37
|
+
@ocsp_no_check = (opts[:ocsp_no_check] == true or opts[:ocsp_no_check] == "true")?true:false
|
38
|
+
validate_subject_item_policy opts[:subject_item_policy]
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
# @private
|
43
|
+
# validates subject item policy
|
44
|
+
def validate_subject_item_policy(sip)
|
45
|
+
if not sip.nil? and not sip.kind_of?(R509::Config::SubjectItemPolicy)
|
46
|
+
raise ArgumentError, "subject_item_policy must be of type R509::Config::SubjectItemPolicy"
|
32
47
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
@subject_item_policy = sip
|
49
|
+
end
|
50
|
+
|
51
|
+
# @private
|
52
|
+
# validates key usage array
|
53
|
+
def validate_key_usage(ku)
|
54
|
+
if not ku.nil? and not ku.kind_of?(Array)
|
55
|
+
raise ArgumentError, "key_usage must be an array of strings (see README)"
|
56
|
+
end
|
57
|
+
@key_usage = ku
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
# validates inhibit any policy
|
62
|
+
def validate_inhibit_any_policy(iap)
|
63
|
+
if not iap.nil?
|
64
|
+
validate_non_negative_integer("Inhibit any policy",iap)
|
65
|
+
end
|
66
|
+
@inhibit_any_policy = iap
|
67
|
+
end
|
68
|
+
|
69
|
+
# @private
|
70
|
+
def validate_policy_constraints(pc)
|
71
|
+
if not pc.nil?
|
72
|
+
if not pc.kind_of?(Hash)
|
73
|
+
raise ArgumentError, 'Policy constraints must be provided as a hash with at least one of the two allowed keys: "inhibit_policy_mapping" and "require_explicit_policy"'
|
74
|
+
end
|
75
|
+
if not pc["inhibit_policy_mapping"].nil?
|
76
|
+
ipm = validate_non_negative_integer("inhibit_policy_mapping",pc["inhibit_policy_mapping"])
|
77
|
+
end
|
78
|
+
if not pc["require_explicit_policy"].nil?
|
79
|
+
rep = validate_non_negative_integer("require_explicit_policy",pc["require_explicit_policy"])
|
80
|
+
end
|
81
|
+
if not ipm and not rep
|
82
|
+
raise ArgumentError, 'Policy constraints must have at least one of two keys: "inhibit_policy_mapping" and "require_explicit_policy" and the value must be non-negative'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@policy_constraints = pc
|
86
|
+
end
|
87
|
+
|
88
|
+
# @private
|
89
|
+
# used by iap and pc validation methods
|
90
|
+
def validate_non_negative_integer(source,value)
|
91
|
+
if not value.kind_of?(Integer) or value < 0
|
92
|
+
raise ArgumentError, "#{source} must be a non-negative integer"
|
93
|
+
end
|
94
|
+
value
|
95
|
+
end
|
96
|
+
|
97
|
+
# @private
|
98
|
+
# validates extended key usage array
|
99
|
+
def validate_extended_key_usage(eku)
|
100
|
+
if not eku.nil? and not eku.kind_of?(Array)
|
101
|
+
raise ArgumentError, "extended_key_usage must be an array of strings (see README)"
|
102
|
+
end
|
103
|
+
@extended_key_usage = eku
|
104
|
+
end
|
105
|
+
|
106
|
+
|
107
|
+
# @private
|
108
|
+
# validates the structure of the certificate policies array
|
109
|
+
def validate_certificate_policies(policies)
|
110
|
+
if not policies.nil?
|
111
|
+
if not policies.respond_to?(:each)
|
112
|
+
raise ArgumentError, "Not a valid certificate policy structure. Must be an array of hashes"
|
113
|
+
else
|
114
|
+
policies.each do |policy|
|
115
|
+
if policy["policy_identifier"].nil?
|
116
|
+
raise ArgumentError, "Each policy requires a policy identifier"
|
117
|
+
end
|
118
|
+
if not policy["cps_uris"].nil?
|
119
|
+
if not policy["cps_uris"].respond_to?(:each)
|
120
|
+
raise ArgumentError, "CPS URIs must be an array of strings"
|
50
121
|
end
|
51
|
-
|
52
|
-
|
53
|
-
if not
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
else
|
60
|
-
raise ArgumentError, "Unknown subject item policy value. Allowed values are required and optional"
|
61
|
-
end
|
122
|
+
end
|
123
|
+
if not policy["user_notices"].nil?
|
124
|
+
if not policy["user_notices"].respond_to?(:each)
|
125
|
+
raise ArgumentError, "User notices must be an array of hashes"
|
126
|
+
else
|
127
|
+
policy["user_notices"].each do |un|
|
128
|
+
if not un["organization"].nil? and un["notice_numbers"].nil?
|
129
|
+
raise ArgumentError, "If you provide an organization you must provide notice numbers"
|
62
130
|
end
|
131
|
+
if not un["notice_numbers"].nil? and un["organization"].nil?
|
132
|
+
raise ArgumentError, "If you provide notice numbers you must provide an organization"
|
133
|
+
end
|
134
|
+
end
|
63
135
|
end
|
136
|
+
end
|
64
137
|
end
|
65
|
-
|
66
|
-
|
67
|
-
# @return [R509::Subject] validated version of the subject or error
|
68
|
-
def validate_subject(subject)
|
69
|
-
# convert the subject components into an array of component names that match
|
70
|
-
# those that are on the required list
|
71
|
-
supplied = subject.to_a.each do |item|
|
72
|
-
@required.include?(item[0])
|
73
|
-
end.map do |item|
|
74
|
-
item[0]
|
75
|
-
end
|
76
|
-
# so we can make sure they gave us everything that's required
|
77
|
-
diff = @required - supplied
|
78
|
-
if not diff.empty?
|
79
|
-
raise R509::R509Error, "This profile requires you supply "+@required.join(", ")
|
80
|
-
end
|
81
|
-
|
82
|
-
# the validated subject contains only those subject components that are either
|
83
|
-
# required or optional
|
84
|
-
R509::Subject.new(subject.to_a.select do |item|
|
85
|
-
@required.include?(item[0]) or @optional.include?(item[0])
|
86
|
-
end)
|
87
|
-
end
|
138
|
+
end
|
139
|
+
@certificate_policies = policies
|
88
140
|
end
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
|
102
|
-
# retrieve a particular config by its name
|
103
|
-
def [](name)
|
104
|
-
@configs[name]
|
141
|
+
end
|
142
|
+
|
143
|
+
# @private
|
144
|
+
def validate_name_constraints(nc)
|
145
|
+
if not nc.nil?
|
146
|
+
if not nc.kind_of?(Hash)
|
147
|
+
raise ArgumentError, "name_constraints must be provided as a hash"
|
148
|
+
end
|
149
|
+
["permitted","excluded"].each do |key|
|
150
|
+
if not nc[key].nil?
|
151
|
+
validate_name_constraints_elements(key,nc[key])
|
105
152
|
end
|
153
|
+
end
|
154
|
+
if (nc["permitted"].nil? or nc["permitted"].empty?) and (nc["excluded"].nil? or nc["excluded"].empty?)
|
155
|
+
raise ArgumentError, "If name_constraints are supplied you must have at least one valid permitted or excluded element"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
@name_constraints = nc
|
159
|
+
end
|
106
160
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
161
|
+
# @private
|
162
|
+
def validate_name_constraints_elements(type,arr)
|
163
|
+
if not arr.kind_of?(Array)
|
164
|
+
raise ArgumentError, "#{type} must be an array"
|
165
|
+
end
|
166
|
+
arr.each do |el|
|
167
|
+
if not el.kind_of?(Hash) or not el.has_key?("type") or not el.has_key?("value")
|
168
|
+
raise ArgumentError, "Elements within the #{type} array must be hashes with both type and value"
|
169
|
+
end
|
170
|
+
if R509::ASN1::GeneralName.map_type_to_tag(el["type"]) == nil
|
171
|
+
raise ArgumentError, "#{el["type"]} is not an allowed type. Check R509::ASN1::GeneralName.map_type_to_tag to see a list of types"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# @private
|
177
|
+
# validates the structure of the certificate policies array
|
178
|
+
def validate_basic_constraints(constraints)
|
179
|
+
if not constraints.nil?
|
180
|
+
if not constraints.respond_to?(:has_key?) or not constraints.has_key?("ca")
|
181
|
+
raise ArgumentError, "You must supply a hash with a key named \"ca\" with a boolean value"
|
182
|
+
end
|
183
|
+
if constraints["ca"].nil? or (not constraints["ca"].kind_of?(TrueClass) and not constraints["ca"].kind_of?(FalseClass))
|
184
|
+
raise ArgumentError, "You must supply true/false for the ca key when specifying basic constraints"
|
185
|
+
end
|
186
|
+
if constraints["ca"] == false and not constraints["path_length"].nil?
|
187
|
+
raise ArgumentError, "path_length is not allowed when ca is false"
|
188
|
+
end
|
189
|
+
if constraints["ca"] == true and not constraints["path_length"].nil? and (constraints["path_length"] < 0 or not constraints["path_length"].kind_of?(Integer))
|
190
|
+
raise ArgumentError, "Path length must be a non-negative integer (>= 0)"
|
191
|
+
end
|
192
|
+
end
|
193
|
+
@basic_constraints = constraints
|
194
|
+
end
|
195
|
+
end
|
111
196
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
197
|
+
# returns information about the subject item policy for a profile
|
198
|
+
class SubjectItemPolicy
|
199
|
+
attr_reader :required, :optional
|
200
|
+
|
201
|
+
# @param [Hash] hash of required/optional subject items. These must be in OpenSSL shortname format.
|
202
|
+
# @example sample hash
|
203
|
+
# {"CN" => "required",
|
204
|
+
# "O" => "required",
|
205
|
+
# "OU" => "optional",
|
206
|
+
# "ST" => "required",
|
207
|
+
# "C" => "required",
|
208
|
+
# "L" => "required",
|
209
|
+
# "emailAddress" => "optional"}
|
210
|
+
def initialize(hash={})
|
211
|
+
if not hash.kind_of?(Hash)
|
212
|
+
raise ArgumentError, "Must supply a hash in form 'shortname'=>'required/optional'"
|
213
|
+
end
|
214
|
+
@required = []
|
215
|
+
@optional = []
|
216
|
+
if not hash.empty?
|
217
|
+
hash.each_pair do |key,value|
|
218
|
+
if value == "required"
|
219
|
+
@required.push(key)
|
220
|
+
elsif value == "optional"
|
221
|
+
@optional.push(key)
|
222
|
+
else
|
223
|
+
raise ArgumentError, "Unknown subject item policy value. Allowed values are required and optional"
|
123
224
|
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# @param [R509::Subject] subject
|
230
|
+
# @return [R509::Subject] validated version of the subject or error
|
231
|
+
def validate_subject(subject)
|
232
|
+
# convert the subject components into an array of component names that match
|
233
|
+
# those that are on the required list
|
234
|
+
supplied = subject.to_a.each do |item|
|
235
|
+
@required.include?(item[0])
|
236
|
+
end.map do |item|
|
237
|
+
item[0]
|
238
|
+
end
|
239
|
+
# so we can make sure they gave us everything that's required
|
240
|
+
diff = @required - supplied
|
241
|
+
if not diff.empty?
|
242
|
+
raise R509::R509Error, "This profile requires you supply "+@required.join(", ")
|
124
243
|
end
|
125
244
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
# @option opts [R509::Cert] :ca_cert Cert+Key pair
|
135
|
-
# @option opts [Integer] :crl_validity_hours (168) The number of hours that
|
136
|
-
# a CRL will be valid. Defaults to 7 days.
|
137
|
-
# @option opts [Hash<String, R509::Config::CaProfile>] :profiles
|
138
|
-
# @option opts [String] :message_digest (SHA1) The hashing algorithm to use.
|
139
|
-
# @option opts [String] :cdp_location
|
140
|
-
# @option opts [String] :ocsp_location
|
141
|
-
# @option opts [String] :crl_number_file The file that we will save
|
142
|
-
# the CRL numbers to. defaults to a StringIO object if not provided
|
143
|
-
# @option opts [String] :crl_list_file The file that we will save
|
144
|
-
# the CRL list data to. defaults to a StringIO object if not provided
|
145
|
-
# @option opts [R509::Cert] :ocsp_cert An optional cert+key pair
|
146
|
-
# OCSP signing delegate
|
147
|
-
# @option opts [Array<OpenSSL::X509::Certificate>] :ocsp_chain An optional array
|
148
|
-
# that constitutes the chain to attach to an OCSP response
|
149
|
-
#
|
150
|
-
def initialize(opts = {} )
|
151
|
-
if not opts.has_key?(:ca_cert) then
|
152
|
-
raise ArgumentError, 'Config object requires that you pass :ca_cert'
|
153
|
-
end
|
154
|
-
|
155
|
-
@ca_cert = opts[:ca_cert]
|
156
|
-
|
157
|
-
if not @ca_cert.kind_of?(R509::Cert) then
|
158
|
-
raise ArgumentError, ':ca_cert must be of type R509::Cert'
|
159
|
-
end
|
160
|
-
|
161
|
-
#ocsp data
|
162
|
-
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].kind_of?(R509::Cert) and not opts[:ocsp_cert].nil?
|
163
|
-
raise ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
|
164
|
-
end
|
165
|
-
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].nil? and not opts[:ocsp_cert].has_private_key?
|
166
|
-
raise ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
|
167
|
-
end
|
168
|
-
@ocsp_cert = opts[:ocsp_cert] unless opts[:ocsp_cert].nil?
|
169
|
-
@ocsp_location = opts[:ocsp_location]
|
170
|
-
@ocsp_chain = opts[:ocsp_chain] if opts[:ocsp_chain].kind_of?(Array)
|
171
|
-
@ocsp_validity_hours = opts[:ocsp_validity_hours] || 168
|
172
|
-
@ocsp_start_skew_seconds = opts[:ocsp_start_skew_seconds] || 3600
|
173
|
-
|
174
|
-
@crl_validity_hours = opts[:crl_validity_hours] || 168
|
175
|
-
@crl_start_skew_seconds = opts[:crl_start_skew_seconds] || 3600
|
176
|
-
@crl_number_file = opts[:crl_number_file] || nil
|
177
|
-
@crl_list_file = opts[:crl_list_file] || nil
|
178
|
-
@cdp_location = opts[:cdp_location]
|
179
|
-
@message_digest = opts[:message_digest] || "SHA1"
|
180
|
-
|
181
|
-
|
245
|
+
# the validated subject contains only those subject components that are either
|
246
|
+
# required or optional
|
247
|
+
R509::Subject.new(subject.to_a.select do |item|
|
248
|
+
@required.include?(item[0]) or @optional.include?(item[0])
|
249
|
+
end)
|
250
|
+
end
|
251
|
+
end
|
182
252
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
253
|
+
# pool of configs, so we can support multiple CAs from a single config file
|
254
|
+
class CAConfigPool
|
255
|
+
# @option configs [Hash<String, R509::Config::CAConfig>] the configs to add to the pool
|
256
|
+
def initialize(configs)
|
257
|
+
@configs = configs
|
258
|
+
end
|
259
|
+
|
260
|
+
# get all the config names
|
261
|
+
def names
|
262
|
+
@configs.keys
|
263
|
+
end
|
264
|
+
|
265
|
+
# retrieve a particular config by its name
|
266
|
+
def [](name)
|
267
|
+
@configs[name]
|
268
|
+
end
|
269
|
+
|
270
|
+
# @return a list of all the configs in this pool
|
271
|
+
def all
|
272
|
+
@configs.values
|
273
|
+
end
|
274
|
+
|
275
|
+
# Loads the named configuration config from a yaml string.
|
276
|
+
# @param [String] name The name of the config within the file. Note
|
277
|
+
# that a single yaml file can contain more than one configuration.
|
278
|
+
# @param [String] yaml_data The filename to load yaml config data from.
|
279
|
+
def self.from_yaml(name, yaml_data, opts = {})
|
280
|
+
conf = YAML.load(yaml_data)
|
281
|
+
configs = {}
|
282
|
+
conf[name].each_pair do |ca_name, data|
|
283
|
+
configs[ca_name] = R509::Config::CAConfig.load_from_hash(data, opts)
|
284
|
+
end
|
285
|
+
R509::Config::CAConfigPool.new(configs)
|
286
|
+
end
|
287
|
+
end
|
189
288
|
|
190
|
-
|
289
|
+
# Stores a configuration for our CA.
|
290
|
+
class CAConfig
|
291
|
+
include R509::IOHelpers
|
292
|
+
extend R509::IOHelpers
|
293
|
+
attr_accessor :ca_cert, :crl_validity_hours, :message_digest,
|
294
|
+
:cdp_location, :crl_start_skew_seconds, :ocsp_location, :ocsp_chain,
|
295
|
+
:ocsp_start_skew_seconds, :ocsp_validity_hours, :crl_number_file, :crl_list_file,
|
296
|
+
:ca_issuers_location
|
297
|
+
|
298
|
+
# @option opts [R509::Cert] :ca_cert Cert+Key pair
|
299
|
+
# @option opts [Integer] :crl_validity_hours (168) The number of hours that
|
300
|
+
# a CRL will be valid. Defaults to 7 days.
|
301
|
+
# @option opts [Hash<String, R509::Config::CAProfile>] :profiles
|
302
|
+
# @option opts [String] :message_digest (SHA1) The hashing algorithm to use.
|
303
|
+
# @option opts [Array] :cdp_location array of strings (URLs)
|
304
|
+
# @option opts [Array] :ocsp_location array of strings (URLs)
|
305
|
+
# @option opts [Array] :ca_issuers_location array of strings (URLs)
|
306
|
+
# @option opts [String] :crl_number_file The file that we will save
|
307
|
+
# the CRL numbers to. defaults to a StringIO object if not provided
|
308
|
+
# @option opts [String] :crl_list_file The file that we will save
|
309
|
+
# the CRL list data to. defaults to a StringIO object if not provided
|
310
|
+
# @option opts [R509::Cert] :ocsp_cert An optional cert+key pair
|
311
|
+
# OCSP signing delegate
|
312
|
+
# @option opts [Array<OpenSSL::X509::Certificate>] :ocsp_chain An optional array
|
313
|
+
# that constitutes the chain to attach to an OCSP response
|
314
|
+
#
|
315
|
+
def initialize(opts = {} )
|
316
|
+
if not opts.has_key?(:ca_cert) then
|
317
|
+
raise ArgumentError, 'Config object requires that you pass :ca_cert'
|
318
|
+
end
|
191
319
|
|
192
|
-
|
193
|
-
def ocsp_cert
|
194
|
-
if @ocsp_cert.nil? then @ca_cert else @ocsp_cert end
|
195
|
-
end
|
320
|
+
@ca_cert = opts[:ca_cert]
|
196
321
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
unless prof.is_a?(R509::Config::CaProfile)
|
201
|
-
raise TypeError, "profile is supposed to be a R509::Config::CaProfile"
|
202
|
-
end
|
203
|
-
@profiles[name] = prof
|
204
|
-
end
|
322
|
+
if not @ca_cert.kind_of?(R509::Cert) then
|
323
|
+
raise ArgumentError, ':ca_cert must be of type R509::Cert'
|
324
|
+
end
|
205
325
|
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
326
|
+
#ocsp data
|
327
|
+
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].kind_of?(R509::Cert) and not opts[:ocsp_cert].nil?
|
328
|
+
raise ArgumentError, ':ocsp_cert, if provided, must be of type R509::Cert'
|
329
|
+
end
|
330
|
+
if opts.has_key?(:ocsp_cert) and not opts[:ocsp_cert].nil? and not opts[:ocsp_cert].has_private_key?
|
331
|
+
raise ArgumentError, ':ocsp_cert must contain a private key, not just a certificate'
|
332
|
+
end
|
333
|
+
@ocsp_cert = opts[:ocsp_cert] unless opts[:ocsp_cert].nil?
|
334
|
+
validate_ocsp_location opts[:ocsp_location]
|
335
|
+
validate_ca_issuers_location opts[:ca_issuers_location]
|
336
|
+
@ocsp_chain = opts[:ocsp_chain] if opts[:ocsp_chain].kind_of?(Array)
|
337
|
+
@ocsp_validity_hours = opts[:ocsp_validity_hours] || 168
|
338
|
+
@ocsp_start_skew_seconds = opts[:ocsp_start_skew_seconds] || 3600
|
339
|
+
|
340
|
+
@crl_validity_hours = opts[:crl_validity_hours] || 168
|
341
|
+
@crl_start_skew_seconds = opts[:crl_start_skew_seconds] || 3600
|
342
|
+
@crl_number_file = opts[:crl_number_file] || nil
|
343
|
+
@crl_list_file = opts[:crl_list_file] || nil
|
344
|
+
validate_cdp_location opts[:cdp_location]
|
345
|
+
@message_digest = opts[:message_digest] || "SHA1"
|
346
|
+
|
347
|
+
|
348
|
+
|
349
|
+
@profiles = {}
|
350
|
+
if opts[:profiles]
|
351
|
+
opts[:profiles].each_pair do |name, prof|
|
352
|
+
set_profile(name, prof)
|
353
|
+
end
|
354
|
+
end
|
214
355
|
|
215
|
-
|
216
|
-
def num_profiles
|
217
|
-
@profiles.count
|
218
|
-
end
|
356
|
+
end
|
219
357
|
|
358
|
+
# @return [R509::Cert] either a custom OCSP cert or the ca_cert
|
359
|
+
def ocsp_cert
|
360
|
+
if @ocsp_cert.nil? then @ca_cert else @ocsp_cert end
|
361
|
+
end
|
220
362
|
|
221
|
-
|
363
|
+
# @param [String] name The name of the profile
|
364
|
+
# @param [R509::Config::CAProfile] prof The profile configuration
|
365
|
+
def set_profile(name, prof)
|
366
|
+
unless prof.is_a?(R509::Config::CAProfile)
|
367
|
+
raise TypeError, "profile is supposed to be a R509::Config::CAProfile"
|
368
|
+
end
|
369
|
+
@profiles[name] = prof
|
370
|
+
end
|
371
|
+
|
372
|
+
# @param [String] prof
|
373
|
+
# @return [R509::Config::CAProfile] The config profile.
|
374
|
+
def profile(prof)
|
375
|
+
if !@profiles.has_key?(prof)
|
376
|
+
raise R509::R509Error, "unknown profile '#{prof}'"
|
377
|
+
end
|
378
|
+
@profiles[prof]
|
379
|
+
end
|
222
380
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
# the current working directory.
|
228
|
-
def self.load_from_hash(conf, opts = {})
|
229
|
-
if conf.nil?
|
230
|
-
raise ArgumentError, "conf not found"
|
231
|
-
end
|
232
|
-
unless conf.kind_of?(Hash)
|
233
|
-
raise ArgumentError, "conf must be a Hash"
|
234
|
-
end
|
381
|
+
# @return [Integer] The number of profiles
|
382
|
+
def num_profiles
|
383
|
+
@profiles.count
|
384
|
+
end
|
235
385
|
|
236
|
-
ca_root_path = Pathname.new(opts[:ca_root_path] || FileUtils.getwd)
|
237
386
|
|
238
|
-
|
239
|
-
raise R509Error, "ca_root_path is not a directory: #{ca_root_path}"
|
240
|
-
end
|
387
|
+
######### Class Methods ##########
|
241
388
|
|
242
|
-
|
389
|
+
# Load the configuration from a data hash. The same type that might be
|
390
|
+
# used when loading from a YAML file.
|
391
|
+
# @param [Hash] conf A hash containing all the configuration options
|
392
|
+
# @option opts [String] :ca_root_path The root path for the CA. Defaults to
|
393
|
+
# the current working directory.
|
394
|
+
def self.load_from_hash(conf, opts = {})
|
395
|
+
if conf.nil?
|
396
|
+
raise ArgumentError, "conf not found"
|
397
|
+
end
|
398
|
+
unless conf.kind_of?(Hash)
|
399
|
+
raise ArgumentError, "conf must be a Hash"
|
400
|
+
end
|
243
401
|
|
244
|
-
|
245
|
-
ca_cert = self.load_with_engine(ca_cert_hash,ca_root_path)
|
246
|
-
end
|
402
|
+
ca_root_path = Pathname.new(opts[:ca_root_path] || FileUtils.getwd)
|
247
403
|
|
248
|
-
|
249
|
-
|
250
|
-
|
404
|
+
unless File.directory?(ca_root_path)
|
405
|
+
raise R509Error, "ca_root_path is not a directory: #{ca_root_path}"
|
406
|
+
end
|
251
407
|
|
252
|
-
|
253
|
-
ca_cert = self.load_with_key(ca_cert_hash,ca_root_path)
|
254
|
-
end
|
408
|
+
ca_cert_hash = conf['ca_cert']
|
255
409
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
end
|
410
|
+
if ca_cert_hash.has_key?('engine')
|
411
|
+
ca_cert = self.load_with_engine(ca_cert_hash,ca_root_path)
|
412
|
+
end
|
260
413
|
|
261
|
-
|
262
|
-
|
263
|
-
|
414
|
+
if ca_cert.nil? and ca_cert_hash.has_key?('pkcs12')
|
415
|
+
ca_cert = self.load_with_pkcs12(ca_cert_hash,ca_root_path)
|
416
|
+
end
|
264
417
|
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
end
|
418
|
+
if ca_cert.nil? and ca_cert_hash.has_key?('cert')
|
419
|
+
ca_cert = self.load_with_key(ca_cert_hash,ca_root_path)
|
420
|
+
end
|
269
421
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
ocsp_chain_data.scan(cert_regex) do |cert|
|
275
|
-
ocsp_chain.push(OpenSSL::X509::Certificate.new(cert))
|
276
|
-
end
|
277
|
-
end
|
422
|
+
if conf.has_key?("ocsp_cert")
|
423
|
+
if conf["ocsp_cert"].has_key?('engine')
|
424
|
+
ocsp_cert = self.load_with_engine(conf["ocsp_cert"],ca_root_path)
|
425
|
+
end
|
278
426
|
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
:ocsp_chain => ocsp_chain,
|
283
|
-
:crl_validity_hours => conf['crl_validity_hours'],
|
284
|
-
:ocsp_validity_hours => conf['ocsp_validity_hours'],
|
285
|
-
:ocsp_start_skew_seconds => conf['ocsp_start_skew_seconds'],
|
286
|
-
:ocsp_location => conf['ocsp_location'],
|
287
|
-
:cdp_location => conf['cdp_location'],
|
288
|
-
:message_digest => conf['message_digest'],
|
289
|
-
}
|
290
|
-
|
291
|
-
if conf.has_key?("crl_list")
|
292
|
-
opts[:crl_list_file] = (ca_root_path + conf['crl_list']).to_s
|
293
|
-
end
|
427
|
+
if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('pkcs12')
|
428
|
+
ocsp_cert = self.load_with_pkcs12(conf["ocsp_cert"],ca_root_path)
|
429
|
+
end
|
294
430
|
|
295
|
-
|
296
|
-
|
297
|
-
|
431
|
+
if ocsp_cert.nil? and conf["ocsp_cert"].has_key?('cert')
|
432
|
+
ocsp_cert = self.load_with_key(conf["ocsp_cert"],ca_root_path)
|
433
|
+
end
|
434
|
+
end
|
298
435
|
|
436
|
+
ocsp_chain = []
|
437
|
+
if conf.has_key?("ocsp_chain")
|
438
|
+
ocsp_chain_data = read_data(ca_root_path+conf["ocsp_chain"])
|
439
|
+
cert_regex = /-----BEGIN CERTIFICATE-----.+?-----END CERTIFICATE-----/m
|
440
|
+
ocsp_chain_data.scan(cert_regex) do |cert|
|
441
|
+
ocsp_chain.push(OpenSSL::X509::Certificate.new(cert))
|
442
|
+
end
|
443
|
+
end
|
299
444
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
end
|
445
|
+
opts = {
|
446
|
+
:ca_cert => ca_cert,
|
447
|
+
:ocsp_cert => ocsp_cert,
|
448
|
+
:ocsp_chain => ocsp_chain,
|
449
|
+
:crl_validity_hours => conf['crl_validity_hours'],
|
450
|
+
:ocsp_validity_hours => conf['ocsp_validity_hours'],
|
451
|
+
:ocsp_start_skew_seconds => conf['ocsp_start_skew_seconds'],
|
452
|
+
:ocsp_location => conf['ocsp_location'],
|
453
|
+
:ca_issuers_location => conf['ca_issuers_location'],
|
454
|
+
:cdp_location => conf['cdp_location'],
|
455
|
+
:message_digest => conf['message_digest'],
|
456
|
+
}
|
457
|
+
|
458
|
+
if conf.has_key?("crl_list")
|
459
|
+
opts[:crl_list_file] = (ca_root_path + conf['crl_list']).to_s
|
460
|
+
end
|
317
461
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
# @param [String] yaml_file The filename to load yaml config data from.
|
322
|
-
def self.load_yaml(conf_name, yaml_file, opts = {})
|
323
|
-
conf = YAML.load_file(yaml_file)
|
324
|
-
self.load_from_hash(conf[conf_name], opts)
|
325
|
-
end
|
462
|
+
if conf.has_key?("crl_number")
|
463
|
+
opts[:crl_number_file] = (ca_root_path + conf['crl_number']).to_s
|
464
|
+
end
|
326
465
|
|
327
|
-
# Loads the named configuration config from a yaml string.
|
328
|
-
# @param [String] conf_name The name of the config within the file. Note
|
329
|
-
# that a single yaml file can contain more than one configuration.
|
330
|
-
# @param [String] yaml_data The filename to load yaml config data from.
|
331
|
-
def self.from_yaml(conf_name, yaml_data, opts = {})
|
332
|
-
conf = YAML.load(yaml_data)
|
333
|
-
self.load_from_hash(conf[conf_name], opts)
|
334
|
-
end
|
335
466
|
|
336
|
-
|
467
|
+
profs = {}
|
468
|
+
conf['profiles'].keys.each do |profile|
|
469
|
+
data = conf['profiles'][profile]
|
470
|
+
if not data["subject_item_policy"].nil?
|
471
|
+
subject_item_policy = R509::Config::SubjectItemPolicy.new(data["subject_item_policy"])
|
472
|
+
end
|
473
|
+
profs[profile] = R509::Config::CAProfile.new(:key_usage => data["key_usage"],
|
474
|
+
:extended_key_usage => data["extended_key_usage"],
|
475
|
+
:basic_constraints => data["basic_constraints"],
|
476
|
+
:certificate_policies => data["certificate_policies"],
|
477
|
+
:ocsp_no_check => data["ocsp_no_check"],
|
478
|
+
:inhibit_any_policy => data["inhibit_any_policy"],
|
479
|
+
:policy_constraints => data["policy_constraints"],
|
480
|
+
:name_constraints => data["name_constraints"],
|
481
|
+
:subject_item_policy => subject_item_policy)
|
482
|
+
end unless conf['profiles'].nil?
|
483
|
+
opts[:profiles] = profs
|
484
|
+
|
485
|
+
# Create the instance.
|
486
|
+
self.new(opts)
|
487
|
+
end
|
488
|
+
|
489
|
+
# Loads the named configuration config from a yaml file.
|
490
|
+
# @param [String] conf_name The name of the config within the file. Note
|
491
|
+
# that a single yaml file can contain more than one configuration.
|
492
|
+
# @param [String] yaml_file The filename to load yaml config data from.
|
493
|
+
def self.load_yaml(conf_name, yaml_file, opts = {})
|
494
|
+
conf = YAML.load_file(yaml_file)
|
495
|
+
self.load_from_hash(conf[conf_name], opts)
|
496
|
+
end
|
497
|
+
|
498
|
+
# Loads the named configuration config from a yaml string.
|
499
|
+
# @param [String] conf_name The name of the config within the file. Note
|
500
|
+
# that a single yaml file can contain more than one configuration.
|
501
|
+
# @param [String] yaml_data The filename to load yaml config data from.
|
502
|
+
def self.from_yaml(conf_name, yaml_data, opts = {})
|
503
|
+
conf = YAML.load(yaml_data)
|
504
|
+
self.load_from_hash(conf[conf_name], opts)
|
505
|
+
end
|
506
|
+
|
507
|
+
private
|
508
|
+
|
509
|
+
def self.load_with_engine(ca_cert_hash,ca_root_path)
|
510
|
+
if ca_cert_hash.has_key?('key')
|
511
|
+
raise ArgumentError, "You can't specify both key and engine"
|
512
|
+
end
|
513
|
+
if ca_cert_hash.has_key?('pkcs12')
|
514
|
+
raise ArgumentError, "You can't specify both engine and pkcs12"
|
515
|
+
end
|
516
|
+
if not ca_cert_hash.has_key?('key_name')
|
517
|
+
raise ArgumentError, "You must supply a key_name with an engine"
|
518
|
+
end
|
337
519
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
520
|
+
if ca_cert_hash['engine'].respond_to?(:load_private_key)
|
521
|
+
#this path is only for testing...ugh
|
522
|
+
engine = ca_cert_hash['engine']
|
523
|
+
else
|
524
|
+
#this path can't be tested by unit tests. bah!
|
525
|
+
engine = OpenSSL::Engine.by_id(ca_cert_hash['engine'])
|
526
|
+
end
|
527
|
+
ca_key = R509::PrivateKey.new(
|
528
|
+
:engine => engine,
|
529
|
+
:key_name => ca_cert_hash['key_name']
|
530
|
+
)
|
531
|
+
ca_cert_file = ca_root_path + ca_cert_hash['cert']
|
532
|
+
ca_cert = R509::Cert.new(
|
533
|
+
:cert => read_data(ca_cert_file),
|
534
|
+
:key => ca_key
|
535
|
+
)
|
536
|
+
ca_cert
|
537
|
+
end
|
538
|
+
|
539
|
+
def self.load_with_pkcs12(ca_cert_hash,ca_root_path)
|
540
|
+
if ca_cert_hash.has_key?('cert')
|
541
|
+
raise ArgumentError, "You can't specify both pkcs12 and cert"
|
542
|
+
end
|
543
|
+
if ca_cert_hash.has_key?('key')
|
544
|
+
raise ArgumentError, "You can't specify both pkcs12 and key"
|
545
|
+
end
|
348
546
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
547
|
+
pkcs12_file = ca_root_path + ca_cert_hash['pkcs12']
|
548
|
+
ca_cert = R509::Cert.new(
|
549
|
+
:pkcs12 => read_data(pkcs12_file),
|
550
|
+
:password => ca_cert_hash['password']
|
551
|
+
)
|
552
|
+
ca_cert
|
553
|
+
end
|
554
|
+
|
555
|
+
def self.load_with_key(ca_cert_hash,ca_root_path)
|
556
|
+
ca_cert_file = ca_root_path + ca_cert_hash['cert']
|
557
|
+
|
558
|
+
if ca_cert_hash.has_key?('key')
|
559
|
+
ca_key_file = ca_root_path + ca_cert_hash['key']
|
560
|
+
ca_key = R509::PrivateKey.new(
|
561
|
+
:key => read_data(ca_key_file),
|
562
|
+
:password => ca_cert_hash['password']
|
563
|
+
)
|
564
|
+
ca_cert = R509::Cert.new(
|
565
|
+
:cert => read_data(ca_cert_file),
|
566
|
+
:key => ca_key
|
567
|
+
)
|
568
|
+
else
|
569
|
+
# in certain cases (OCSP responders for example) we may want
|
570
|
+
# to load a ca_cert with no private key
|
571
|
+
ca_cert = R509::Cert.new(:cert => read_data(ca_cert_file))
|
572
|
+
end
|
573
|
+
ca_cert
|
574
|
+
end
|
367
575
|
|
368
|
-
|
369
|
-
if ca_cert_hash.has_key?('cert')
|
370
|
-
raise R509Error, "You can't specify both pkcs12 and cert"
|
371
|
-
end
|
372
|
-
if ca_cert_hash.has_key?('key')
|
373
|
-
raise R509Error, "You can't specify both pkcs12 and key"
|
374
|
-
end
|
576
|
+
private
|
375
577
|
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
578
|
+
# @private
|
579
|
+
def validate_cdp_location(location)
|
580
|
+
if not location.nil? and not location.kind_of?(Array)
|
581
|
+
raise ArgumentError, "cdp_location must be an array if provided"
|
582
|
+
end
|
583
|
+
@cdp_location = location
|
584
|
+
end
|
383
585
|
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
:password => ca_cert_hash['password']
|
392
|
-
)
|
393
|
-
ca_cert = R509::Cert.new(
|
394
|
-
:cert => read_data(ca_cert_file),
|
395
|
-
:key => ca_key
|
396
|
-
)
|
397
|
-
else
|
398
|
-
# in certain cases (OCSP responders for example) we may want
|
399
|
-
# to load a ca_cert with no private key
|
400
|
-
ca_cert = R509::Cert.new(:cert => read_data(ca_cert_file))
|
401
|
-
end
|
402
|
-
ca_cert
|
403
|
-
end
|
586
|
+
# @private
|
587
|
+
def validate_ocsp_location(location)
|
588
|
+
if not location.nil? and not location.kind_of?(Array)
|
589
|
+
raise ArgumentError, "ocsp_location must be an array if provided"
|
590
|
+
end
|
591
|
+
@ocsp_location = location
|
592
|
+
end
|
404
593
|
|
594
|
+
# @private
|
595
|
+
def validate_ca_issuers_location(location)
|
596
|
+
if not location.nil? and not location.kind_of?(Array)
|
597
|
+
raise ArgumentError, "ca_issuers_location must be an array if provided"
|
405
598
|
end
|
599
|
+
@ca_issuers_location = location
|
600
|
+
end
|
406
601
|
end
|
602
|
+
end
|
407
603
|
end
|