r509 0.8.1 → 0.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +343 -151
- data/Rakefile +26 -23
- data/bin/r509 +126 -112
- data/bin/r509-parse +24 -24
- data/doc/R509.html +169 -7
- data/doc/R509/ASN1.html +370 -0
- data/doc/R509/ASN1/GeneralName.html +1121 -0
- data/doc/R509/ASN1/GeneralNames.html +843 -0
- data/doc/R509/ASN1/NoticeReference.html +392 -0
- data/doc/R509/ASN1/PolicyInformation.html +387 -0
- data/doc/R509/ASN1/PolicyQualifiers.html +455 -0
- data/doc/R509/ASN1/UserNotice.html +386 -0
- data/doc/R509/{Crl.html → CRL.html} +7 -7
- data/doc/R509/CRL/Administrator.html +1559 -0
- data/doc/R509/{Crl/Parser.html → CRL/SignedList.html} +501 -210
- data/doc/R509/{Csr.html → CSR.html} +444 -314
- data/doc/R509/Cert.html +866 -617
- data/doc/R509/Cert/Extensions.html +52 -41
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +70 -35
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +387 -4
- data/doc/R509/Cert/Extensions/BasicConstraints.html +61 -25
- data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +354 -0
- data/doc/R509/Cert/Extensions/CertificatePolicies.html +340 -0
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +440 -49
- data/doc/R509/Cert/Extensions/{CrlDistributionPoints.html → InhibitAnyPolicy.html} +52 -35
- data/doc/R509/Cert/Extensions/KeyUsage.html +247 -121
- data/doc/R509/Cert/Extensions/NameConstraints.html +445 -0
- data/doc/R509/Cert/Extensions/OCSPNoCheck.html +239 -0
- data/doc/R509/Cert/Extensions/PolicyConstraints.html +424 -0
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +437 -62
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +52 -10
- data/doc/R509/CertificateAuthority.html +4 -4
- data/doc/R509/CertificateAuthority/Signer.html +154 -187
- data/doc/R509/Config.html +6 -6
- data/doc/R509/Config/{CaConfig.html → CAConfig.html} +451 -348
- data/doc/R509/Config/{CaConfigPool.html → CAConfigPool.html} +47 -47
- data/doc/R509/Config/CAProfile.html +1015 -0
- data/doc/R509/Config/SubjectItemPolicy.html +86 -86
- data/doc/R509/IOHelpers.html +22 -22
- data/doc/R509/MessageDigest.html +14 -14
- data/doc/R509/NameSanitizer.html +53 -53
- data/doc/R509/{Ocsp.html → OCSP.html} +9 -9
- data/doc/R509/{Ocsp → OCSP}/Request.html +7 -7
- data/doc/R509/{Ocsp → OCSP}/Request/Nonce.html +56 -11
- data/doc/R509/{Ocsp → OCSP}/Response.html +44 -44
- data/doc/R509/{OidMapper.html → OIDMapper.html} +23 -39
- data/doc/R509/PrivateKey.html +415 -168
- data/doc/R509/R509Error.html +3 -3
- data/doc/R509/{Spki.html → SPKI.html} +354 -192
- data/doc/R509/Subject.html +224 -113
- data/doc/R509/Validity.html +27 -5
- data/doc/R509/Validity/Checker.html +13 -13
- data/doc/R509/Validity/DefaultChecker.html +13 -13
- data/doc/R509/Validity/DefaultWriter.html +14 -14
- data/doc/R509/Validity/Status.html +39 -39
- data/doc/R509/Validity/Writer.html +18 -18
- data/doc/_index.html +138 -35
- data/doc/class_list.html +1 -1
- data/doc/css/style.css +10 -0
- data/doc/file.README.html +368 -171
- data/doc/file.r509.html +92 -69
- data/doc/frames.html +1 -1
- data/doc/index.html +368 -171
- data/doc/method_list.html +910 -390
- data/doc/top-level-namespace.html +3 -3
- data/lib/r509.rb +32 -16
- data/lib/r509/asn1.rb +375 -0
- data/lib/r509/cert.rb +381 -364
- data/lib/r509/cert/extensions.rb +443 -76
- data/lib/r509/certificate_authority.rb +407 -0
- data/lib/r509/config.rb +547 -351
- data/lib/r509/crl.rb +336 -366
- data/lib/r509/csr.rb +278 -289
- data/lib/r509/ec-hack.rb +37 -0
- data/lib/r509/exceptions.rb +3 -3
- data/lib/r509/io_helpers.rb +44 -44
- data/lib/r509/message_digest.rb +53 -0
- data/lib/r509/ocsp.rb +80 -70
- data/lib/r509/oid_mapper.rb +32 -0
- data/lib/r509/private_key.rb +228 -0
- data/lib/r509/spki.rb +145 -93
- data/lib/r509/subject.rb +203 -110
- data/lib/r509/validity.rb +70 -68
- data/lib/r509/version.rb +2 -2
- data/r509.yaml +92 -69
- data/spec/asn1_spec.rb +402 -0
- data/spec/cert/extensions_spec.rb +957 -494
- data/spec/cert_spec.rb +382 -307
- data/spec/certificate_authority_spec.rb +668 -250
- data/spec/config_spec.rb +515 -302
- data/spec/crl_spec.rb +197 -198
- data/spec/csr_spec.rb +334 -289
- data/spec/fixtures.rb +247 -171
- data/spec/fixtures/cert1.der +0 -0
- data/spec/fixtures/cert1.pem +0 -0
- data/spec/fixtures/cert1_public_key_modulus.txt +0 -0
- data/spec/fixtures/cert3.p12 +0 -0
- data/spec/fixtures/cert3.pem +0 -0
- data/spec/fixtures/cert3_key.pem +0 -0
- data/spec/fixtures/cert3_key_des3.pem +0 -0
- data/spec/fixtures/cert4.pem +0 -0
- data/spec/fixtures/cert5.pem +0 -0
- data/spec/fixtures/cert6.pem +0 -0
- data/spec/fixtures/cert_expired.pem +0 -0
- data/spec/fixtures/cert_inhibit.pem +24 -0
- data/spec/fixtures/cert_name_constraints.pem +29 -0
- data/spec/fixtures/cert_not_yet_valid.pem +0 -0
- data/spec/fixtures/cert_ocsp_no_check.pem +18 -0
- data/spec/fixtures/cert_policy_constraints.pem +31 -0
- data/spec/fixtures/cert_san.pem +0 -0
- data/spec/fixtures/cert_san2.pem +0 -0
- data/spec/fixtures/cert_unknown_extension.pem +28 -0
- data/spec/fixtures/config_pool_test_minimal.yaml +11 -11
- data/spec/fixtures/config_test.yaml +54 -36
- data/spec/fixtures/config_test_dsa.yaml +35 -0
- data/spec/fixtures/config_test_ec.yaml +35 -0
- data/spec/fixtures/config_test_engine_key.yaml +5 -5
- data/spec/fixtures/config_test_engine_no_key_name.yaml +4 -4
- data/spec/fixtures/config_test_minimal.yaml +4 -4
- data/spec/fixtures/config_test_password.yaml +5 -5
- data/spec/fixtures/config_test_various.yaml +111 -74
- data/spec/fixtures/crl_list_file.txt +0 -0
- data/spec/fixtures/crl_with_reason.pem +0 -0
- data/spec/fixtures/csr1.der +0 -0
- data/spec/fixtures/csr1.pem +0 -0
- data/spec/fixtures/csr1_key.der +0 -0
- data/spec/fixtures/csr1_key.pem +0 -0
- data/spec/fixtures/csr1_key_encrypted_des3.pem +0 -0
- data/spec/fixtures/csr1_newlines.pem +0 -0
- data/spec/fixtures/csr1_no_begin_end.pem +0 -0
- data/spec/fixtures/csr1_public_key_modulus.txt +0 -0
- data/spec/fixtures/csr2.pem +0 -0
- data/spec/fixtures/csr2_key.pem +0 -0
- data/spec/fixtures/csr3.pem +0 -0
- data/spec/fixtures/csr4.pem +0 -0
- data/spec/fixtures/csr_dsa.pem +0 -0
- data/spec/fixtures/csr_invalid_signature.pem +0 -0
- data/spec/fixtures/dsa_key.pem +0 -0
- data/spec/fixtures/dsa_root.cer +28 -0
- data/spec/fixtures/dsa_root.key +20 -0
- data/spec/fixtures/ec_csr2.der +0 -0
- data/spec/fixtures/ec_csr2.pem +8 -0
- data/spec/fixtures/ec_key1.der +0 -0
- data/spec/fixtures/ec_key1.pem +6 -0
- data/spec/fixtures/ec_key1_encrypted.pem +9 -0
- data/spec/fixtures/ec_key2.pem +6 -0
- data/spec/fixtures/hmacsha1.sig +1 -0
- data/spec/fixtures/hmacsha512.sig +1 -0
- data/spec/fixtures/key4.pem +0 -0
- data/spec/fixtures/key4_encrypted_des3.pem +0 -0
- data/spec/fixtures/missing_key_identifier_ca.cer +0 -0
- data/spec/fixtures/missing_key_identifier_ca.key +0 -0
- data/spec/fixtures/ocsptest.r509.local.pem +0 -0
- data/spec/fixtures/ocsptest.r509.local_ocsp_request.der +0 -0
- data/spec/fixtures/ocsptest2.r509.local.pem +0 -0
- data/spec/fixtures/second_ca.cer +0 -0
- data/spec/fixtures/second_ca.key +0 -0
- data/spec/fixtures/spkac.der +0 -0
- data/spec/fixtures/spkac.txt +0 -0
- data/spec/fixtures/spkac_dsa.txt +1 -1
- data/spec/fixtures/spkac_dsa_no_verify.txt +1 -0
- data/spec/fixtures/spkac_ec.txt +1 -0
- data/spec/fixtures/spkac_rsa_newlines.txt +13 -0
- data/spec/fixtures/stca.pem +0 -0
- data/spec/fixtures/stca_ocsp_request.der +0 -0
- data/spec/fixtures/stca_ocsp_response.der +0 -0
- data/spec/fixtures/test1.csr +0 -0
- data/spec/fixtures/test_ca.cer +0 -0
- data/spec/fixtures/test_ca.key +0 -0
- data/spec/fixtures/test_ca.p12 +0 -0
- data/spec/fixtures/test_ca_des3.key +0 -0
- data/spec/fixtures/test_ca_ec.cer +14 -0
- data/spec/fixtures/test_ca_ec.key +6 -0
- data/spec/fixtures/test_ca_ec_ee.cer +22 -0
- data/spec/fixtures/test_ca_ec_ee.key +6 -0
- data/spec/fixtures/test_ca_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_ocsp.key +0 -0
- data/spec/fixtures/test_ca_ocsp.p12 +0 -0
- data/spec/fixtures/test_ca_ocsp_chain.txt +0 -0
- data/spec/fixtures/test_ca_ocsp_response.der +0 -0
- data/spec/fixtures/test_ca_subroot.cer +0 -0
- data/spec/fixtures/test_ca_subroot.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.cer +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp.key +0 -0
- data/spec/fixtures/test_ca_subroot_ocsp_response.der +0 -0
- data/spec/fixtures/unknown_oid.csr +0 -0
- data/spec/message_digest_spec.rb +104 -84
- data/spec/ocsp_spec.rb +105 -105
- data/spec/oid_mapper_spec.rb +21 -21
- data/spec/private_key_spec.rb +275 -0
- data/spec/r509_spec.rb +35 -0
- data/spec/spec_helper.rb +15 -6
- data/spec/spki_spec.rb +221 -142
- data/spec/subject_spec.rb +232 -164
- data/spec/validity_spec.rb +91 -91
- metadata +79 -25
- data/doc/R509/Config/CaProfile.html +0 -651
- data/doc/R509/Crl/Administrator.html +0 -2073
- data/lib/r509/certificateauthority.rb +0 -290
- data/lib/r509/messagedigest.rb +0 -49
- data/lib/r509/oidmapper.rb +0 -32
- data/lib/r509/privatekey.rb +0 -185
- data/spec/privatekey_spec.rb +0 -198
data/lib/r509/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
|