r509 0.8.1 → 0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +343 -151
- data/Rakefile +26 -23
- data/bin/r509 +126 -112
- data/bin/r509-parse +24 -24
- data/doc/R509.html +169 -7
- data/doc/R509/ASN1.html +370 -0
- data/doc/R509/ASN1/GeneralName.html +1121 -0
- data/doc/R509/ASN1/GeneralNames.html +843 -0
- data/doc/R509/ASN1/NoticeReference.html +392 -0
- data/doc/R509/ASN1/PolicyInformation.html +387 -0
- data/doc/R509/ASN1/PolicyQualifiers.html +455 -0
- data/doc/R509/ASN1/UserNotice.html +386 -0
- data/doc/R509/{Crl.html → CRL.html} +7 -7
- data/doc/R509/CRL/Administrator.html +1559 -0
- data/doc/R509/{Crl/Parser.html → CRL/SignedList.html} +501 -210
- data/doc/R509/{Csr.html → CSR.html} +444 -314
- data/doc/R509/Cert.html +866 -617
- data/doc/R509/Cert/Extensions.html +52 -41
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +70 -35
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +387 -4
- data/doc/R509/Cert/Extensions/BasicConstraints.html +61 -25
- data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +354 -0
- data/doc/R509/Cert/Extensions/CertificatePolicies.html +340 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +440 -49
- data/doc/R509/Cert/Extensions/{CrlDistributionPoints.html → InhibitAnyPolicy.html} +52 -35
- data/doc/R509/Cert/Extensions/KeyUsage.html +247 -121
- data/doc/R509/Cert/Extensions/NameConstraints.html +445 -0
- data/doc/R509/Cert/Extensions/OCSPNoCheck.html +239 -0
- data/doc/R509/Cert/Extensions/PolicyConstraints.html +424 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +437 -62
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +52 -10
- data/doc/R509/CertificateAuthority.html +4 -4
- data/doc/R509/CertificateAuthority/Signer.html +154 -187
- data/doc/R509/Config.html +6 -6
- data/doc/R509/Config/{CaConfig.html → CAConfig.html} +451 -348
- data/doc/R509/Config/{CaConfigPool.html → CAConfigPool.html} +47 -47
- data/doc/R509/Config/CAProfile.html +1015 -0
- data/doc/R509/Config/SubjectItemPolicy.html +86 -86
- data/doc/R509/IOHelpers.html +22 -22
- data/doc/R509/MessageDigest.html +14 -14
- data/doc/R509/NameSanitizer.html +53 -53
- data/doc/R509/{Ocsp.html → OCSP.html} +9 -9
- data/doc/R509/{Ocsp → OCSP}/Request.html +7 -7
- data/doc/R509/{Ocsp → OCSP}/Request/Nonce.html +56 -11
- data/doc/R509/{Ocsp → OCSP}/Response.html +44 -44
- data/doc/R509/{OidMapper.html → OIDMapper.html} +23 -39
- data/doc/R509/PrivateKey.html +415 -168
- data/doc/R509/R509Error.html +3 -3
- data/doc/R509/{Spki.html → SPKI.html} +354 -192
- data/doc/R509/Subject.html +224 -113
- data/doc/R509/Validity.html +27 -5
- data/doc/R509/Validity/Checker.html +13 -13
- data/doc/R509/Validity/DefaultChecker.html +13 -13
- data/doc/R509/Validity/DefaultWriter.html +14 -14
- data/doc/R509/Validity/Status.html +39 -39
- data/doc/R509/Validity/Writer.html +18 -18
- data/doc/_index.html +138 -35
- data/doc/class_list.html +1 -1
- data/doc/css/style.css +10 -0
- data/doc/file.README.html +368 -171
- data/doc/file.r509.html +92 -69
- data/doc/frames.html +1 -1
- data/doc/index.html +368 -171
- data/doc/method_list.html +910 -390
- data/doc/top-level-namespace.html +3 -3
- data/lib/r509.rb +32 -16
- data/lib/r509/asn1.rb +375 -0
- data/lib/r509/cert.rb +381 -364
- data/lib/r509/cert/extensions.rb +443 -76
- data/lib/r509/certificate_authority.rb +407 -0
- data/lib/r509/config.rb +547 -351
- data/lib/r509/crl.rb +336 -366
- data/lib/r509/csr.rb +278 -289
- data/lib/r509/ec-hack.rb +37 -0
- data/lib/r509/exceptions.rb +3 -3
- data/lib/r509/io_helpers.rb +44 -44
- data/lib/r509/message_digest.rb +53 -0
- data/lib/r509/ocsp.rb +80 -70
- data/lib/r509/oid_mapper.rb +32 -0
- data/lib/r509/private_key.rb +228 -0
- data/lib/r509/spki.rb +145 -93
- data/lib/r509/subject.rb +203 -110
- data/lib/r509/validity.rb +70 -68
- data/lib/r509/version.rb +2 -2
- data/r509.yaml +92 -69
- data/spec/asn1_spec.rb +402 -0
- data/spec/cert/extensions_spec.rb +957 -494
- data/spec/cert_spec.rb +382 -307
- data/spec/certificate_authority_spec.rb +668 -250
- data/spec/config_spec.rb +515 -302
- data/spec/crl_spec.rb +197 -198
- data/spec/csr_spec.rb +334 -289
- data/spec/fixtures.rb +247 -171
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +0 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +0 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +0 -0
- data/spec/fixtures/cert3_key.pem +0 -0
- data/spec/fixtures/cert3_key_des3.pem +0 -0
- data/spec/fixtures/cert4.pem +0 -0
- data/spec/fixtures/cert5.pem +0 -0
- data/spec/fixtures/cert6.pem +0 -0
- data/spec/fixtures/cert_expired.pem +0 -0
- data/spec/fixtures/cert_inhibit.pem +24 -0
- data/spec/fixtures/cert_name_constraints.pem +29 -0
- data/spec/fixtures/cert_not_yet_valid.pem +0 -0
- data/spec/fixtures/cert_ocsp_no_check.pem +18 -0
- data/spec/fixtures/cert_policy_constraints.pem +31 -0
- data/spec/fixtures/cert_san.pem +0 -0
- data/spec/fixtures/cert_san2.pem +0 -0
- data/spec/fixtures/cert_unknown_extension.pem +28 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +11 -11
- data/spec/fixtures/config_test.yaml +54 -36
- data/spec/fixtures/config_test_dsa.yaml +35 -0
- data/spec/fixtures/config_test_ec.yaml +35 -0
- data/spec/fixtures/config_test_engine_key.yaml +5 -5
- data/spec/fixtures/config_test_engine_no_key_name.yaml +4 -4
- data/spec/fixtures/config_test_minimal.yaml +4 -4
- data/spec/fixtures/config_test_password.yaml +5 -5
- data/spec/fixtures/config_test_various.yaml +111 -74
- data/spec/fixtures/crl_list_file.txt +0 -0
- data/spec/fixtures/crl_with_reason.pem +0 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +0 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +0 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +0 -0
- data/spec/fixtures/csr1_newlines.pem +0 -0
- data/spec/fixtures/csr1_no_begin_end.pem +0 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +0 -0
- data/spec/fixtures/csr2.pem +0 -0
- data/spec/fixtures/csr2_key.pem +0 -0
- data/spec/fixtures/csr3.pem +0 -0
- data/spec/fixtures/csr4.pem +0 -0
- data/spec/fixtures/csr_dsa.pem +0 -0
- data/spec/fixtures/csr_invalid_signature.pem +0 -0
- data/spec/fixtures/dsa_key.pem +0 -0
- data/spec/fixtures/dsa_root.cer +28 -0
- data/spec/fixtures/dsa_root.key +20 -0
- data/spec/fixtures/ec_csr2.der +0 -0
- data/spec/fixtures/ec_csr2.pem +8 -0
- data/spec/fixtures/ec_key1.der +0 -0
- data/spec/fixtures/ec_key1.pem +6 -0
- data/spec/fixtures/ec_key1_encrypted.pem +9 -0
- data/spec/fixtures/ec_key2.pem +6 -0
- data/spec/fixtures/hmacsha1.sig +1 -0
- data/spec/fixtures/hmacsha512.sig +1 -0
- data/spec/fixtures/key4.pem +0 -0
- data/spec/fixtures/key4_encrypted_des3.pem +0 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +0 -0
- data/spec/fixtures/missing_key_identifier_ca.key +0 -0
- data/spec/fixtures/ocsptest.r509.local.pem +0 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +0 -0
- data/spec/fixtures/second_ca.cer +0 -0
- data/spec/fixtures/second_ca.key +0 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +0 -0
- data/spec/fixtures/spkac_dsa.txt +1 -1
- data/spec/fixtures/spkac_dsa_no_verify.txt +1 -0
- data/spec/fixtures/spkac_ec.txt +1 -0
- data/spec/fixtures/spkac_rsa_newlines.txt +13 -0
- data/spec/fixtures/stca.pem +0 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +0 -0
- data/spec/fixtures/test_ca.cer +0 -0
- data/spec/fixtures/test_ca.key +0 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +0 -0
- data/spec/fixtures/test_ca_ec.cer +14 -0
- data/spec/fixtures/test_ca_ec.key +6 -0
- data/spec/fixtures/test_ca_ec_ee.cer +22 -0
- data/spec/fixtures/test_ca_ec_ee.key +6 -0
- data/spec/fixtures/test_ca_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_ocsp.key +0 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +0 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +0 -0
- data/spec/fixtures/test_ca_subroot.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +0 -0
- data/spec/message_digest_spec.rb +104 -84
- data/spec/ocsp_spec.rb +105 -105
- data/spec/oid_mapper_spec.rb +21 -21
- data/spec/private_key_spec.rb +275 -0
- data/spec/r509_spec.rb +35 -0
- data/spec/spec_helper.rb +15 -6
- data/spec/spki_spec.rb +221 -142
- data/spec/subject_spec.rb +232 -164
- data/spec/validity_spec.rb +91 -91
- metadata +79 -25
- data/doc/R509/Config/CaProfile.html +0 -651
- data/doc/R509/Crl/Administrator.html +0 -2073
- data/lib/r509/certificateauthority.rb +0 -290
- data/lib/r509/messagedigest.rb +0 -49
- data/lib/r509/oidmapper.rb +0 -32
- data/lib/r509/privatekey.rb +0 -185
- data/spec/privatekey_spec.rb +0 -198
data/lib/r509/ec-hack.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# this hack exists to work around a major issue with the OpenSSL::PKey::EC interface
|
2
|
+
# as it is currently configured in ruby <= 2.0.0-p0
|
3
|
+
# the signing methods on OpenSSL::X509::Request and OpenSSL::X509::Certificate look for
|
4
|
+
# a method named #private? on the PKey object. OpenSSL::PKey::RSA and OpenSSL::PKey::DSA
|
5
|
+
# both define this method, but OpenSSL::PKey::EC defines #private_key? instead. This
|
6
|
+
# will open up the class and add #private? as an alias to allow successful signing
|
7
|
+
if defined?(OpenSSL::PKey::EC) and not OpenSSL::PKey::EC.method_defined?('private?')
|
8
|
+
# marked as @private so it won't appear in the yard doc
|
9
|
+
# @private
|
10
|
+
module OpenSSL::PKey
|
11
|
+
# marked as @private so it won't appear in the yard doc
|
12
|
+
# @private
|
13
|
+
class EC
|
14
|
+
def private?
|
15
|
+
private_key?
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
elsif not defined?(OpenSSL::PKey::EC)
|
20
|
+
# this is a stub implementation for when EC is unavailable. Any method called against
|
21
|
+
# it will raise an R509Error
|
22
|
+
# marked as @private so it won't appear in the yard doc
|
23
|
+
# @private
|
24
|
+
module OpenSSL::PKey
|
25
|
+
# marked as @private so it won't appear in the yard doc
|
26
|
+
# @private
|
27
|
+
class EC
|
28
|
+
UNSUPPORTED = true
|
29
|
+
def initialize(*args)
|
30
|
+
raise R509::R509Error, "EC is unavailable. You may need to recompile Ruby with an OpenSSL that has elliptic curve support."
|
31
|
+
end
|
32
|
+
def method_missing(method, *args, &block)
|
33
|
+
raise R509::R509Error, "EC is unavailable. You may need to recompile Ruby with an OpenSSL that has elliptic curve support."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/r509/exceptions.rb
CHANGED
data/lib/r509/io_helpers.rb
CHANGED
@@ -1,52 +1,52 @@
|
|
1
1
|
module R509
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
18
|
-
end
|
2
|
+
# helper methods for I/O
|
3
|
+
module IOHelpers
|
4
|
+
# Writes data into an IO or file
|
5
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
6
|
+
# the file that you'd like to write, or an IO-like object.
|
7
|
+
# @param [String] data The data that we want to write
|
8
|
+
def self.write_data(filename_or_io, data)
|
9
|
+
if filename_or_io.respond_to?(:write)
|
10
|
+
filename_or_io.write(data)
|
11
|
+
else
|
12
|
+
begin
|
13
|
+
file = File.open(filename_or_io, 'wb:ascii-8bit')
|
14
|
+
return file.write(data)
|
15
|
+
ensure
|
16
|
+
file.close()
|
19
17
|
end
|
18
|
+
end
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
end
|
21
|
+
# Reads data from an IO or file
|
22
|
+
# @param [String, #read] filename_or_io Either a string of the path for
|
23
|
+
# the file that you'd like to read, or an IO-like object.
|
24
|
+
def self.read_data(filename_or_io)
|
25
|
+
if filename_or_io.respond_to?(:read)
|
26
|
+
filename_or_io.read()
|
27
|
+
else
|
28
|
+
begin
|
29
|
+
file = File.open(filename_or_io, 'rb:ascii-8bit')
|
30
|
+
return file.read()
|
31
|
+
ensure
|
32
|
+
file.close() unless file.nil?
|
35
33
|
end
|
34
|
+
end
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
37
|
+
# Writes data into an IO or file
|
38
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
39
|
+
# the file that you'd like to write, or an IO-like object.
|
40
|
+
# @param [String] data The data that we want to write
|
41
|
+
def write_data(filename_or_io, data)
|
42
|
+
IOHelpers.write_data(filename_or_io, data)
|
43
|
+
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
45
|
+
# Reads data from an IO or file
|
46
|
+
# @param [String, #read] filename_or_io Either a string of the path for
|
47
|
+
# the file that you'd like to read, or an IO-like object.
|
48
|
+
def read_data(filename_or_io)
|
49
|
+
IOHelpers.read_data(filename_or_io)
|
51
50
|
end
|
51
|
+
end
|
52
52
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module R509
|
4
|
+
#MessageDigest allows you to specify MDs in a more friendly fashion
|
5
|
+
class MessageDigest
|
6
|
+
attr_reader :name, :digest
|
7
|
+
|
8
|
+
# @param [String,OpenSSL::Digest] arg
|
9
|
+
def initialize(arg)
|
10
|
+
if arg.kind_of?(String)
|
11
|
+
@name = arg.downcase
|
12
|
+
@digest = translate_name_to_digest
|
13
|
+
else
|
14
|
+
@digest = arg
|
15
|
+
@name = translate_digest_to_name
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# @return [OpenSSL::Digest]
|
22
|
+
def translate_name_to_digest
|
23
|
+
case @name
|
24
|
+
when 'sha1' then OpenSSL::Digest::SHA1.new
|
25
|
+
when 'sha224' then OpenSSL::Digest::SHA224.new
|
26
|
+
when 'sha256' then OpenSSL::Digest::SHA256.new
|
27
|
+
when 'sha384' then OpenSSL::Digest::SHA384.new
|
28
|
+
when 'sha512' then OpenSSL::Digest::SHA512.new
|
29
|
+
when 'md5' then OpenSSL::Digest::MD5.new
|
30
|
+
when 'dss1' then OpenSSL::Digest::DSS1.new
|
31
|
+
else
|
32
|
+
@name = "sha1"
|
33
|
+
OpenSSL::Digest::SHA1.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @return [String]
|
38
|
+
def translate_digest_to_name
|
39
|
+
case @digest
|
40
|
+
when OpenSSL::Digest::SHA1 then 'sha1'
|
41
|
+
when OpenSSL::Digest::SHA224 then 'sha224'
|
42
|
+
when OpenSSL::Digest::SHA256 then 'sha256'
|
43
|
+
when OpenSSL::Digest::SHA384 then 'sha384'
|
44
|
+
when OpenSSL::Digest::SHA512 then 'sha512'
|
45
|
+
when OpenSSL::Digest::MD5 then 'md5'
|
46
|
+
when OpenSSL::Digest::DSS1 then 'dss1'
|
47
|
+
else
|
48
|
+
raise ArgumentError, "Unknown digest"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/r509/ocsp.rb
CHANGED
@@ -2,84 +2,94 @@ require 'openssl'
|
|
2
2
|
require 'r509/exceptions'
|
3
3
|
require 'r509/config'
|
4
4
|
|
5
|
-
#
|
6
|
-
module R509::
|
5
|
+
#OCSP module
|
6
|
+
module R509::OCSP
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
# @return [OpenSSL::OCSP] response status of this response
|
27
|
-
def status
|
28
|
-
@ocsp_response.status
|
29
|
-
end
|
8
|
+
#builds OCSP responses
|
9
|
+
class Response
|
10
|
+
# @param ocsp_response [OpenSSL::OCSP::Response]
|
11
|
+
def initialize(ocsp_response)
|
12
|
+
if not ocsp_response.kind_of?(OpenSSL::OCSP::Response)
|
13
|
+
raise R509::R509Error, 'You must pass an OpenSSL::OCSP::Response object to the constructor. See R509::OCSP::Response.parse if you are trying to parse'
|
14
|
+
end
|
15
|
+
@ocsp_response = ocsp_response
|
16
|
+
end
|
17
|
+
# @param [String,OpenSSL::OCSP::Response] ocsp_string parses an existing response
|
18
|
+
# @return [R509::OCSP::Response]
|
19
|
+
def self.parse(ocsp_string)
|
20
|
+
if ocsp_string.nil?
|
21
|
+
raise R509::R509Error, 'You must pass a DER encoded OCSP response to this method'
|
22
|
+
end
|
23
|
+
R509::OCSP::Response.new(OpenSSL::OCSP::Response.new(ocsp_string))
|
24
|
+
end
|
30
25
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
# @return [OpenSSL::OCSP] response status of this response
|
27
|
+
def status
|
28
|
+
@ocsp_response.status
|
29
|
+
end
|
35
30
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
31
|
+
# @return [String] der encoded string
|
32
|
+
def to_der
|
33
|
+
@ocsp_response.to_der
|
34
|
+
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
if certs.kind_of?(Array)
|
46
|
-
stack = certs
|
47
|
-
certs.each do |cert|
|
48
|
-
store.add_cert(cert)
|
49
|
-
end
|
50
|
-
else
|
51
|
-
stack = [certs]
|
52
|
-
store.add_cert(certs)
|
53
|
-
end
|
36
|
+
# @return [OpenSSL::OCSP::BasicResponse]
|
37
|
+
def basic
|
38
|
+
@ocsp_response.basic
|
39
|
+
end
|
54
40
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
41
|
+
# @param [Array<OpenSSL::X509::Certificate>,OpenSSL::X509::Certificate] certs A cert or array of certs to verify against
|
42
|
+
# @return [Boolean] true if the response is valid according to the given root
|
43
|
+
def verify(certs)
|
44
|
+
store = OpenSSL::X509::Store.new
|
45
|
+
if certs.kind_of?(Array)
|
46
|
+
stack = certs
|
47
|
+
certs.each do |cert|
|
48
|
+
store.add_cert(cert)
|
63
49
|
end
|
50
|
+
else
|
51
|
+
stack = [certs]
|
52
|
+
store.add_cert(certs)
|
53
|
+
end
|
64
54
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
55
|
+
#suppress verbosity since #verify will output a warning if it does not match
|
56
|
+
#as well as returning false. we just want the boolean
|
57
|
+
original_verbosity = $VERBOSE
|
58
|
+
$VERBOSE = nil
|
59
|
+
#still a bit unclear on why we add to store and pass in array to verify
|
60
|
+
result = @ocsp_response.basic.verify(stack, store)
|
61
|
+
$VERBOSE = original_verbosity
|
62
|
+
return result
|
70
63
|
end
|
71
64
|
|
72
|
-
#
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
65
|
+
# @param [OpenSSL::OCSP::Request] ocsp_request the OCSP request whose nonce to check
|
66
|
+
# @return [R509::OCSP::Request::Nonce::CONSTANT] the status code of the nonce check
|
67
|
+
def check_nonce(ocsp_request)
|
68
|
+
ocsp_request.check_nonce(@ocsp_response.basic)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#holds OCSP request related items
|
73
|
+
module Request
|
74
|
+
# contains constants r509 uses for OCSP responses
|
75
|
+
module Nonce
|
76
|
+
#these values are defined at
|
77
|
+
#http://www.ruby-doc.org/stdlib-1.9.3/libdoc/openssl/rdoc/OpenSSL/OCSP/Request.html
|
78
|
+
# nonce is present and matches
|
79
|
+
PRESENT_AND_EQUAL = 1
|
80
|
+
|
81
|
+
# nonce is missing in request and response
|
82
|
+
BOTH_ABSENT = 2
|
83
|
+
|
84
|
+
# nonce is present in response only
|
85
|
+
RESPONSE_ONLY = 3
|
86
|
+
|
87
|
+
# nonce is in both request and response, but does not match
|
88
|
+
NOT_EQUAL = 0
|
89
|
+
|
90
|
+
# nonce is present in request only
|
91
|
+
REQUEST_ONLY = -1
|
92
|
+
|
84
93
|
end
|
94
|
+
end
|
85
95
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module R509
|
4
|
+
# Helps map raw OIDs to friendlier short names
|
5
|
+
module OIDMapper
|
6
|
+
# Register an OID so we have a friendly short name
|
7
|
+
# @param [String] oid A string representation of the OID you want to map (e.g. "1.6.2.3.55")
|
8
|
+
# @param [String] short_name The short name (e.g. CN, O, OU, emailAddress)
|
9
|
+
# @param [String] long_name Optional long name. Defaults to the same as short_name
|
10
|
+
# @return [Boolean] success/failure
|
11
|
+
def self.register(oid,short_name,long_name=nil)
|
12
|
+
if long_name.nil?
|
13
|
+
long_name = short_name
|
14
|
+
end
|
15
|
+
OpenSSL::ASN1::ObjectId.register(oid, short_name, long_name)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Register a batch of OIDs so we have friendly short names
|
19
|
+
# @param [Array] oids An array of hashes
|
20
|
+
# @example
|
21
|
+
# R509::OIDMapper.batch_register([
|
22
|
+
# {:oid => "1.2.3.4.5", :short_name => "sName", :long_name => "lName"},
|
23
|
+
# {:oid => "1.2.3.4.6", :short_name => "oName"}
|
24
|
+
# ]
|
25
|
+
def self.batch_register(oids)
|
26
|
+
oids.each do |oid_hash|
|
27
|
+
self.register(oid_hash[:oid],oid_hash[:short_name],oid_hash[:long_name])
|
28
|
+
end
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'r509/io_helpers'
|
3
|
+
require 'r509/exceptions'
|
4
|
+
|
5
|
+
module R509
|
6
|
+
#private key management
|
7
|
+
class PrivateKey
|
8
|
+
include R509::IOHelpers
|
9
|
+
|
10
|
+
# @option opts [Symbol] :type :rsa/:dsa/:ec
|
11
|
+
# @option opts [String] :curve_name ("secp384r1") Only used if :type is :ec
|
12
|
+
# @option opts [Integer] :bit_strength (2048) Only used if :type is :rsa or :dsa.
|
13
|
+
# @option opts [String] :password
|
14
|
+
# @option opts [String,OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] :key
|
15
|
+
# @option opts [OpenSSL::Engine] :engine
|
16
|
+
# @option opts [string] :key_name (used with engine)
|
17
|
+
def initialize(opts={})
|
18
|
+
if not opts.kind_of?(Hash)
|
19
|
+
raise ArgumentError, 'Must provide a hash of options'
|
20
|
+
end
|
21
|
+
|
22
|
+
if opts.has_key?(:engine) and opts.has_key?(:key)
|
23
|
+
raise ArgumentError, 'You can\'t pass both :key and :engine'
|
24
|
+
elsif opts.has_key?(:key_name) and not opts.has_key?(:engine)
|
25
|
+
raise ArgumentError, 'When providing a :key_name you MUST provide an :engine'
|
26
|
+
elsif opts.has_key?(:engine) and not opts.has_key?(:key_name)
|
27
|
+
raise ArgumentError, 'When providing an :engine you MUST provide a :key_name'
|
28
|
+
elsif opts.has_key?(:engine) and opts.has_key?(:key_name)
|
29
|
+
if not opts[:engine].kind_of?(OpenSSL::Engine)
|
30
|
+
raise ArgumentError, 'When providing an engine, it must be of type OpenSSL::Engine'
|
31
|
+
end
|
32
|
+
@engine = opts[:engine]
|
33
|
+
@key_name = opts[:key_name]
|
34
|
+
end
|
35
|
+
|
36
|
+
if opts.has_key?(:key)
|
37
|
+
password = opts[:password] || nil
|
38
|
+
#OpenSSL::PKey.read solves this begin/rescue garbage but is only
|
39
|
+
#available to Ruby 1.9.3+ and may not solve the EC portion
|
40
|
+
begin
|
41
|
+
@key = OpenSSL::PKey::RSA.new(opts[:key],password)
|
42
|
+
rescue OpenSSL::PKey::RSAError
|
43
|
+
begin
|
44
|
+
@key = OpenSSL::PKey::DSA.new(opts[:key],password)
|
45
|
+
rescue
|
46
|
+
begin
|
47
|
+
@key = OpenSSL::PKey::EC.new(opts[:key],password)
|
48
|
+
rescue
|
49
|
+
raise R509::R509Error, "Failed to load private key. Invalid key or incorrect password."
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
else
|
54
|
+
bit_strength = opts[:bit_strength] || 2048
|
55
|
+
type = opts[:type] || :rsa
|
56
|
+
case type
|
57
|
+
when :rsa
|
58
|
+
@key = OpenSSL::PKey::RSA.new(bit_strength)
|
59
|
+
when :dsa
|
60
|
+
@key = OpenSSL::PKey::DSA.new(bit_strength)
|
61
|
+
when :ec
|
62
|
+
curve_name = opts[:curve_name] || "secp384r1"
|
63
|
+
@key = OpenSSL::PKey::EC.new(curve_name)
|
64
|
+
@key.generate_key
|
65
|
+
else
|
66
|
+
raise ArgumentError, 'Must provide :rsa, :dsa , or :ec as type when key or engine is nil'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Helper method to quickly load a private key from the filesystem
|
72
|
+
#
|
73
|
+
# @param [String] filename Path to file you want to load
|
74
|
+
# @return [R509::PrivateKey] PrivateKey object
|
75
|
+
def self.load_from_file( filename, password = nil )
|
76
|
+
return R509::PrivateKey.new(:key => IOHelpers.read_data(filename), :password => password )
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
# Returns the bit strength of the key
|
81
|
+
#
|
82
|
+
# @return [Integer]
|
83
|
+
def bit_strength
|
84
|
+
if self.rsa?
|
85
|
+
return self.public_key.n.num_bits
|
86
|
+
elsif self.dsa?
|
87
|
+
return self.public_key.p.num_bits
|
88
|
+
elsif self.ec?
|
89
|
+
raise R509::R509Error, 'Bit strength is not available for EC at this time.'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns the short name of the elliptic curve used to generate the private key
|
94
|
+
# if the key is EC. If not, raises an error.
|
95
|
+
#
|
96
|
+
# @return [String] elliptic curve name
|
97
|
+
def curve_name
|
98
|
+
if self.ec?
|
99
|
+
self.key.group.curve_name
|
100
|
+
else
|
101
|
+
raise R509::R509Error, 'Curve name is only available with EC private keys'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::Engine pkey] this method may return the PKey object itself or a handle to the private key in the HSM (which will not show the private key, just public)
|
106
|
+
def key
|
107
|
+
if in_hardware?
|
108
|
+
@engine.load_private_key(@key_name)
|
109
|
+
else
|
110
|
+
@key
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# @return [Boolean] whether the key is resident in hardware or not
|
115
|
+
def in_hardware?
|
116
|
+
if not @engine.nil?
|
117
|
+
true
|
118
|
+
else
|
119
|
+
false
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [OpenSSL::PKey::RSA,OpenSSL::PKey::DSA,OpenSSL::PKey::EC] public key
|
124
|
+
def public_key
|
125
|
+
if self.ec?
|
126
|
+
# OpenSSL::PKey::EC.public_key returns an OpenSSL::PKey::EC::Point, which isn't consistent
|
127
|
+
# with the way OpenSSL::PKey::RSA/DSA do it. We could return the original PKey::EC object
|
128
|
+
# but if we do that then it has the private_key as well. Here's a ghetto workaround.
|
129
|
+
# We have to supply the curve name to the temporary key object or else #public_key= fails
|
130
|
+
curve_name = self.key.group.curve_name
|
131
|
+
temp_key = OpenSSL::PKey::EC.new(curve_name)
|
132
|
+
temp_key.public_key=self.key.public_key
|
133
|
+
temp_key
|
134
|
+
else
|
135
|
+
self.key.public_key
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
alias :to_s :public_key
|
140
|
+
|
141
|
+
# Converts the key into the PEM format
|
142
|
+
#
|
143
|
+
# @return [String] the key converted into PEM format.
|
144
|
+
def to_pem
|
145
|
+
if in_hardware?
|
146
|
+
raise R509::R509Error, "This method cannot be called when using keys in hardware"
|
147
|
+
end
|
148
|
+
self.key.to_pem
|
149
|
+
end
|
150
|
+
|
151
|
+
# Converts the key into encrypted PEM format
|
152
|
+
#
|
153
|
+
# @param [String,OpenSSL::Cipher] cipher to use for encryption
|
154
|
+
# full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
|
155
|
+
# (common ones are des3, aes256, aes128)
|
156
|
+
# @param [String] password password
|
157
|
+
# @return [String] the key converted into encrypted PEM format.
|
158
|
+
def to_encrypted_pem(cipher,password)
|
159
|
+
if in_hardware?
|
160
|
+
raise R509::R509Error, "This method cannot be called when using keys in hardware"
|
161
|
+
end
|
162
|
+
cipher = OpenSSL::Cipher::Cipher.new(cipher)
|
163
|
+
self.key.to_pem(cipher,password)
|
164
|
+
end
|
165
|
+
|
166
|
+
|
167
|
+
# Converts the key into the DER format
|
168
|
+
#
|
169
|
+
# @return [String] the key converted into DER format.
|
170
|
+
def to_der
|
171
|
+
if in_hardware?
|
172
|
+
raise R509::R509Error, "This method cannot be called when using keys in hardware"
|
173
|
+
end
|
174
|
+
self.key.to_der
|
175
|
+
end
|
176
|
+
|
177
|
+
# Writes the key into the PEM format
|
178
|
+
#
|
179
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
180
|
+
# the file that you'd like to write, or an IO-like object.
|
181
|
+
def write_pem(filename_or_io)
|
182
|
+
write_data(filename_or_io, self.to_pem)
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
# Writes the key into encrypted PEM format with specified cipher
|
187
|
+
#
|
188
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
189
|
+
# the file that you'd like to write, or an IO-like object.
|
190
|
+
# @param [String,OpenSSL::Cipher] cipher to use for encryption
|
191
|
+
# full list of available ciphers can be obtained with OpenSSL::Cipher.ciphers
|
192
|
+
# (common ones are des3, aes256, aes128)
|
193
|
+
# @param [String] password password
|
194
|
+
def write_encrypted_pem(filename_or_io,cipher,password)
|
195
|
+
write_data(filename_or_io, to_encrypted_pem(cipher,password))
|
196
|
+
end
|
197
|
+
|
198
|
+
# Writes the key into the DER format
|
199
|
+
#
|
200
|
+
# @param [String, #write] filename_or_io Either a string of the path for
|
201
|
+
# the file that you'd like to write, or an IO-like object.
|
202
|
+
def write_der(filename_or_io)
|
203
|
+
write_data(filename_or_io, self.to_der)
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
# Returns whether the key is RSA
|
208
|
+
#
|
209
|
+
# @return [Boolean] true if the key is RSA, false otherwise
|
210
|
+
def rsa?
|
211
|
+
self.key.kind_of?(OpenSSL::PKey::RSA)
|
212
|
+
end
|
213
|
+
|
214
|
+
# Returns whether the key is DSA
|
215
|
+
#
|
216
|
+
# @return [Boolean] true if the key is DSA, false otherwise
|
217
|
+
def dsa?
|
218
|
+
self.key.kind_of?(OpenSSL::PKey::DSA)
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns whether the key is EC
|
222
|
+
#
|
223
|
+
# @return [Boolean] true if the key is EC, false otherwise
|
224
|
+
def ec?
|
225
|
+
self.key.kind_of?(OpenSSL::PKey::EC)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|