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
@@ -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
|