r509 0.9.2 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +2 -0
- data/CONTRIBUTING.mdown +21 -0
- data/LICENSE +13 -0
- data/README.mdown +548 -0
- data/Rakefile +5 -0
- data/bin/r509 +16 -17
- data/doc/R509.html +42 -26
- data/doc/R509/ASN1.html +22 -16
- data/doc/R509/ASN1/GeneralName.html +180 -173
- data/doc/R509/ASN1/GeneralNames.html +390 -62
- data/doc/R509/CRL.html +9 -7
- data/doc/R509/CRL/Administrator.html +208 -623
- data/doc/R509/CRL/FileReaderWriter.html +856 -0
- data/doc/R509/CRL/ReaderWriter.html +524 -0
- data/doc/R509/CRL/SignedList.html +29 -42
- data/doc/R509/CSR.html +248 -333
- data/doc/R509/Cert.html +364 -491
- data/doc/R509/Cert/Extensions.html +134 -43
- data/doc/R509/Cert/Extensions/AuthorityInfoAccess.html +335 -65
- data/doc/R509/Cert/Extensions/AuthorityKeyIdentifier.html +201 -102
- data/doc/R509/Cert/Extensions/BasicConstraints.html +297 -68
- data/doc/R509/Cert/Extensions/CRLDistributionPoints.html +690 -77
- data/doc/R509/Cert/Extensions/CertificatePolicies.html +293 -43
- data/doc/R509/Cert/Extensions/ExtendedKeyUsage.html +321 -173
- data/doc/R509/Cert/Extensions/GeneralNamesMixin.html +656 -0
- data/doc/R509/Cert/Extensions/InhibitAnyPolicy.html +270 -42
- data/doc/R509/Cert/Extensions/KeyUsage.html +334 -184
- data/doc/R509/Cert/Extensions/NameConstraints.html +363 -93
- data/doc/R509/{ASN1 → Cert/Extensions}/NoticeReference.html +209 -48
- data/doc/R509/Cert/Extensions/OCSPNoCheck.html +244 -17
- data/doc/R509/Cert/Extensions/PolicyConstraints.html +322 -71
- data/doc/R509/{ASN1 → Cert/Extensions}/PolicyInformation.html +204 -43
- data/doc/R509/{ASN1 → Cert/Extensions}/PolicyQualifiers.html +205 -48
- data/doc/R509/Cert/Extensions/SubjectAlternativeName.html +348 -143
- data/doc/R509/Cert/Extensions/SubjectKeyIdentifier.html +165 -13
- data/doc/R509/{ASN1 → Cert/Extensions}/UserNotice.html +204 -43
- data/doc/R509/Cert/Extensions/ValidationMixin.html +120 -0
- data/doc/R509/CertificateAuthority.html +9 -7
- data/doc/R509/CertificateAuthority/OptionsBuilder.html +475 -0
- data/doc/R509/CertificateAuthority/Signer.html +149 -198
- data/doc/R509/Config.html +10 -8
- data/doc/R509/Config/CAConfig.html +708 -625
- data/doc/R509/Config/CAConfigPool.html +179 -31
- data/doc/R509/Config/CertProfile.html +1544 -0
- data/doc/R509/Config/SubjectItemPolicy.html +437 -99
- data/doc/R509/Engine.html +14 -28
- data/doc/R509/Helpers.html +1014 -0
- data/doc/R509/MessageDigest.html +73 -25
- data/doc/R509/NameSanitizer.html +39 -39
- data/doc/R509/OCSP.html +5 -5
- data/doc/R509/OCSP/Request.html +5 -5
- data/doc/R509/OCSP/Request/Nonce.html +5 -5
- data/doc/R509/OCSP/Response.html +7 -7
- data/doc/R509/OIDMapper.html +121 -6
- data/doc/R509/PrivateKey.html +226 -227
- data/doc/R509/R509Error.html +5 -5
- data/doc/R509/SPKI.html +244 -342
- data/doc/R509/Subject.html +241 -70
- data/doc/R509/Validity.html +5 -5
- data/doc/R509/Validity/Checker.html +5 -5
- data/doc/R509/Validity/DefaultChecker.html +5 -9
- data/doc/R509/Validity/DefaultWriter.html +5 -9
- data/doc/R509/Validity/Status.html +5 -5
- data/doc/R509/Validity/Writer.html +5 -5
- data/doc/_index.html +92 -30
- data/doc/class_list.html +2 -2
- data/doc/file.CONTRIBUTING.html +96 -0
- data/doc/file.LICENSE.html +87 -0
- data/doc/file.README.html +279 -389
- data/doc/file.YAML.html +243 -0
- data/doc/file.r509.html +298 -105
- data/doc/file_list.html +11 -2
- data/doc/frames.html +1 -1
- data/doc/index.html +279 -389
- data/doc/js/full_list.js +6 -1
- data/doc/method_list.html +869 -1139
- data/doc/top-level-namespace.html +103 -5
- data/lib/r509.rb +7 -2
- data/lib/r509/asn1.rb +97 -135
- data/lib/r509/cert.rb +17 -106
- data/lib/r509/cert/extensions.rb +13 -676
- data/lib/r509/cert/extensions/authority_info_access.rb +128 -0
- data/lib/r509/cert/extensions/authority_key_identifier.rb +100 -0
- data/lib/r509/cert/extensions/base.rb +142 -0
- data/lib/r509/cert/extensions/basic_constraints.rb +119 -0
- data/lib/r509/cert/extensions/certificate_policies.rb +262 -0
- data/lib/r509/cert/extensions/crl_distribution_points.rb +98 -0
- data/lib/r509/cert/extensions/extended_key_usage.rb +189 -0
- data/lib/r509/cert/extensions/inhibit_any_policy.rb +70 -0
- data/lib/r509/cert/extensions/key_usage.rb +209 -0
- data/lib/r509/cert/extensions/name_constraints.rb +179 -0
- data/lib/r509/cert/extensions/ocsp_no_check.rb +56 -0
- data/lib/r509/cert/extensions/policy_constraints.rb +122 -0
- data/lib/r509/cert/extensions/subject_alternative_name.rb +88 -0
- data/lib/r509/cert/extensions/subject_key_identifier.rb +56 -0
- data/lib/r509/cert/extensions/validation_mixin.rb +42 -0
- data/lib/r509/certificate_authority/options_builder.rb +142 -0
- data/lib/r509/certificate_authority/signer.rb +189 -0
- data/lib/r509/config.rb +3 -600
- data/lib/r509/config/ca_config.rb +414 -0
- data/lib/r509/config/cert_profile.rb +110 -0
- data/lib/r509/config/subject_item_policy.rb +118 -0
- data/lib/r509/crl/administrator.rb +169 -0
- data/lib/r509/crl/reader_writer.rb +109 -0
- data/lib/r509/crl/signed_list.rb +135 -0
- data/lib/r509/csr.rb +35 -116
- data/lib/r509/engine.rb +21 -11
- data/lib/r509/helpers.rb +110 -0
- data/lib/r509/io_helpers.rb +18 -13
- data/lib/r509/message_digest.rb +13 -3
- data/lib/r509/oid_mapper.rb +14 -0
- data/lib/r509/private_key.rb +74 -50
- data/lib/r509/spki.rb +50 -113
- data/lib/r509/subject.rb +24 -2
- data/lib/r509/trollop.rb +788 -0
- data/lib/r509/version.rb +1 -1
- data/r509.yaml +289 -96
- data/spec/asn1_spec.rb +171 -98
- data/spec/cert/extensions/authority_info_access_spec.rb +247 -0
- data/spec/cert/extensions/authority_key_identifier_spec.rb +85 -0
- data/spec/cert/extensions/base_spec.rb +172 -0
- data/spec/cert/extensions/basic_constraints_spec.rb +185 -0
- data/spec/cert/extensions/certificate_policies_spec.rb +288 -0
- data/spec/cert/extensions/crl_distribution_points_spec.rb +149 -0
- data/spec/cert/extensions/extended_key_usage_spec.rb +174 -0
- data/spec/cert/extensions/inhibit_any_policy_spec.rb +92 -0
- data/spec/cert/extensions/key_usage_spec.rb +172 -0
- data/spec/cert/extensions/name_constraints_spec.rb +335 -0
- data/spec/cert/extensions/ocsp_no_check_spec.rb +76 -0
- data/spec/cert/extensions/policy_constraints_spec.rb +155 -0
- data/spec/cert/extensions/subject_alternative_name_spec.rb +354 -0
- data/spec/cert/extensions/subject_key_identifier_spec.rb +64 -0
- data/spec/cert_spec.rb +11 -9
- data/spec/certificate_authority/options_builder_spec.rb +307 -0
- data/spec/certificate_authority/signer_spec.rb +278 -0
- data/spec/config/ca_config_spec.rb +405 -0
- data/spec/config/cert_profile_spec.rb +88 -0
- data/spec/config/subject_item_policy_spec.rb +81 -0
- data/spec/crl/administrator_spec.rb +199 -0
- data/spec/crl/reader_writer_spec.rb +97 -0
- data/spec/crl/signed_list_spec.rb +84 -0
- data/spec/csr_spec.rb +43 -36
- data/spec/engine_spec.rb +51 -0
- data/spec/fixtures.rb +40 -40
- data/spec/fixtures/cert1.pem +1 -1
- data/spec/fixtures/config_pool_test_minimal.yaml +11 -15
- data/spec/fixtures/config_test.yaml +96 -59
- data/spec/fixtures/config_test_dsa.yaml +29 -35
- data/spec/fixtures/config_test_ec.yaml +29 -35
- data/spec/fixtures/config_test_engine_key.yaml +7 -7
- data/spec/fixtures/config_test_engine_no_key_name.yaml +6 -6
- data/spec/fixtures/config_test_minimal.yaml +3 -5
- data/spec/fixtures/config_test_password.yaml +4 -6
- data/spec/fixtures/config_test_various.yaml +147 -137
- data/spec/fixtures/crl_list_file.txt +1 -1
- data/spec/fixtures/test_ca_crl.cer +20 -0
- data/spec/fixtures/test_ca_crl.key +28 -0
- data/spec/fixtures/test_ca_crl.p12 +0 -0
- data/spec/message_digest_spec.rb +6 -0
- data/spec/oid_mapper_spec.rb +11 -0
- data/spec/private_key_spec.rb +19 -18
- data/spec/spec_helper.rb +10 -6
- data/spec/spki_spec.rb +38 -19
- data/spec/subject_spec.rb +16 -0
- metadata +108 -59
- metadata.gz.sig +0 -0
- data/README.md +0 -638
- data/doc/R509/Config/CAProfile.html +0 -1015
- data/doc/R509/IOHelpers.html +0 -564
- data/lib/r509/certificate_authority.rb +0 -407
- data/lib/r509/crl.rb +0 -351
- data/spec/cert/extensions_spec.rb +0 -1095
- data/spec/certificate_authority_spec.rb +0 -681
- data/spec/config_spec.rb +0 -562
- data/spec/crl_spec.rb +0 -226
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'r509/cert/extensions/base'
|
2
|
+
require 'r509/cert/extensions/validation_mixin'
|
3
|
+
|
4
|
+
module R509
|
5
|
+
class Cert
|
6
|
+
module Extensions
|
7
|
+
# RFC 5280 Description (see: http://www.ietf.org/rfc/rfc5280.txt)
|
8
|
+
#
|
9
|
+
# The authority information access extension indicates how to access
|
10
|
+
# information and services for the issuer of the certificate in which
|
11
|
+
# the extension appears. Information and services may include on-line
|
12
|
+
# validation services and CA policy data. (The location of CRLs is not
|
13
|
+
# specified in this extension; that information is provided by the
|
14
|
+
# cRLDistributionPoints extension.) This extension may be included in
|
15
|
+
# end entity or CA certificates. Conforming CAs MUST mark this
|
16
|
+
# extension as non-critical.
|
17
|
+
# You can use this extension to parse an existing extension for easy access
|
18
|
+
# to the contents or create a new one.
|
19
|
+
class AuthorityInfoAccess < OpenSSL::X509::Extension
|
20
|
+
include R509::Cert::Extensions::ValidationMixin
|
21
|
+
|
22
|
+
# friendly name for AIA OID
|
23
|
+
OID = "authorityInfoAccess"
|
24
|
+
Extensions.register_class(self)
|
25
|
+
|
26
|
+
# An R509::ASN1::GeneralNames object of OCSP endpoints (or nil if not present)
|
27
|
+
# @return [R509::ASN1::GeneralNames,nil]
|
28
|
+
attr_reader :ocsp
|
29
|
+
# An R509::ASN1::GeneralNames object of CA Issuers (or nil if not present)
|
30
|
+
# @return [R509::ASN1::GeneralNames,nil]
|
31
|
+
attr_reader :ca_issuers
|
32
|
+
|
33
|
+
# This method takes a hash or an existing Extension object to parse. If passing
|
34
|
+
# a hash you must supply :ocsp_location and/or :ca_issuers_location. These values
|
35
|
+
# must be in the form seen in the examples below.
|
36
|
+
#
|
37
|
+
# @option arg :ocsp_location [Array,R509::ASN1::GeneralNames] Array of hashes (see examples) or GeneralNames object
|
38
|
+
# @option arg :ca_issuers_location [Array] Array of hashes (see examples) or GeneralNames object
|
39
|
+
# @option arg :critical [Boolean] (false)
|
40
|
+
# @example
|
41
|
+
# R509::Cert::Extensions::AuthorityInfoAccess.new(
|
42
|
+
# :ocsp_location => [ { :type => "URI", :value => "http://ocsp.domain.com" } ],
|
43
|
+
# :ca_issuers_location => [ { :type => "dirName", :value => { :CN => 'myCN', :O => 'some Org' } ]
|
44
|
+
# )
|
45
|
+
# @example
|
46
|
+
# name = R509::ASN1::GeneralName.new(:type => "IP", :value => "127.0.0.1")
|
47
|
+
# R509::Cert::Extensions::AuthorityInfoAccess.new(
|
48
|
+
# :ca_issuers_location => [name]
|
49
|
+
# )
|
50
|
+
def initialize(arg)
|
51
|
+
if not R509::Cert::Extensions.is_extension?(arg)
|
52
|
+
arg = build_extension(arg)
|
53
|
+
end
|
54
|
+
|
55
|
+
super(arg)
|
56
|
+
parse_extension
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Hash]
|
60
|
+
def to_h
|
61
|
+
hash = { :critical => self.critical? }
|
62
|
+
hash[:ocsp_location] = R509::Cert::Extensions.names_to_h(@ocsp.names) unless @ocsp.names.empty?
|
63
|
+
hash[:ca_issuers_location] = R509::Cert::Extensions.names_to_h(@ca_issuers.names) unless @ca_issuers.names.empty?
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
|
67
|
+
# @return [YAML]
|
68
|
+
def to_yaml
|
69
|
+
self.to_h.to_yaml
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def parse_extension
|
75
|
+
data = R509::ASN1.get_extension_payload(self)
|
76
|
+
@ocsp= R509::ASN1::GeneralNames.new
|
77
|
+
@ca_issuers= R509::ASN1::GeneralNames.new
|
78
|
+
data.entries.each do |access_description|
|
79
|
+
# AccessDescription ::= SEQUENCE {
|
80
|
+
# accessMethod OBJECT IDENTIFIER,
|
81
|
+
# accessLocation GeneralName }
|
82
|
+
case access_description.entries[0].value
|
83
|
+
when "OCSP"
|
84
|
+
@ocsp.add_item(access_description.entries[1])
|
85
|
+
when "caIssuers"
|
86
|
+
@ca_issuers.add_item(access_description.entries[1])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def build_extension(arg)
|
92
|
+
validate_authority_info_access(arg)
|
93
|
+
aia = []
|
94
|
+
aia_conf = []
|
95
|
+
|
96
|
+
locations = [
|
97
|
+
{ :key => :ocsp_location, :short_name => 'OCSP' },
|
98
|
+
{ :key => :ca_issuers_location, :short_name => 'caIssuers' }
|
99
|
+
]
|
100
|
+
|
101
|
+
locations.each do |pair|
|
102
|
+
validate_location(pair[:key].to_s,arg[pair[:key]])
|
103
|
+
data = arg[pair[:key]]
|
104
|
+
if not data.nil?
|
105
|
+
elements = R509::ASN1::GeneralNames.new(data)
|
106
|
+
elements.names.each do |name|
|
107
|
+
serialize = name.serialize_name
|
108
|
+
aia.push "#{pair[:short_name]};#{serialize[:extension_string]}"
|
109
|
+
aia_conf.push serialize[:conf]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
115
|
+
ef.config = OpenSSL::Config.parse(aia_conf.join("\n"))
|
116
|
+
critical = R509::Cert::Extensions.calculate_critical(arg[:critical], false)
|
117
|
+
return ef.create_extension("authorityInfoAccess",aia.join(","),critical)
|
118
|
+
end
|
119
|
+
|
120
|
+
def validate_authority_info_access(aia)
|
121
|
+
if not aia.kind_of?(Hash) or (aia[:ocsp_location].nil? and aia[:ca_issuers_location].nil?)
|
122
|
+
raise ArgumentError, "You must pass a hash with at least one of the following two keys (:ocsp_location, :ca_issuers_location)"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'r509/cert/extensions/base'
|
2
|
+
|
3
|
+
module R509
|
4
|
+
class Cert
|
5
|
+
module Extensions
|
6
|
+
# RFC 5280 Description (see: http://www.ietf.org/rfc/rfc5280.txt)
|
7
|
+
#
|
8
|
+
# The authority key identifier extension provides a means of
|
9
|
+
# identifying the public key corresponding to the private key used to
|
10
|
+
# sign a certificate. This extension is used where an issuer has
|
11
|
+
# multiple signing keys (either due to multiple concurrent key pairs or
|
12
|
+
# due to changeover). The identification MAY be based on either the
|
13
|
+
# key identifier (the subject key identifier in the issuer's
|
14
|
+
# certificate) or the issuer name and serial number.
|
15
|
+
#
|
16
|
+
# You can use this extension to parse an existing extension for easy access
|
17
|
+
# to the contents or create a new one.
|
18
|
+
class AuthorityKeyIdentifier < OpenSSL::X509::Extension
|
19
|
+
|
20
|
+
# friendly name for Authority Key Identifier OID
|
21
|
+
OID = "authorityKeyIdentifier"
|
22
|
+
# default extension behavior when generating
|
23
|
+
AKI_EXTENSION_DEFAULT = "keyid"
|
24
|
+
Extensions.register_class(self)
|
25
|
+
|
26
|
+
# key_identifier, if present, will be a hex string delimited by colons
|
27
|
+
# @return [String,nil]
|
28
|
+
attr_reader :key_identifier
|
29
|
+
# authority_cert_issuer, if present, will be a GeneralName object
|
30
|
+
# @return [R509::ASN1::GeneralName,nil]
|
31
|
+
attr_reader :authority_cert_issuer
|
32
|
+
# authority_cert_serial_number, if present, will be a hex string delimited by colons
|
33
|
+
# @return [String,nil]
|
34
|
+
attr_reader :authority_cert_serial_number
|
35
|
+
|
36
|
+
|
37
|
+
# @option arg :public_key [OpenSSL::PKey] Required if embedding keyid
|
38
|
+
# @option arg :issuer_subject [R509::Subject] Required if embedding issuer
|
39
|
+
# @option arg :value [String] (keyid) For the rules of :value see: http://www.openssl.org/docs/apps/x509v3_config.html#Authority_Key_Identifier_. If you want to embed issuer you MUST supply :issuer_certificate and not :public_key
|
40
|
+
# @option arg :critical [Boolean] (false)
|
41
|
+
def initialize(arg)
|
42
|
+
if not R509::Cert::Extensions.is_extension?(arg)
|
43
|
+
arg = build_extension(arg)
|
44
|
+
end
|
45
|
+
|
46
|
+
super(arg)
|
47
|
+
parse_extension
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def parse_extension
|
53
|
+
data = R509::ASN1.get_extension_payload(self)
|
54
|
+
# AuthorityKeyIdentifier ::= SEQUENCE {
|
55
|
+
# keyIdentifier [0] KeyIdentifier OPTIONAL,
|
56
|
+
# authorityCertIssuer [1] GeneralNames OPTIONAL,
|
57
|
+
# authorityCertSerialNumber [2] CertificateSerialNumber OPTIONAL }
|
58
|
+
data.entries.each do |el|
|
59
|
+
case el.tag
|
60
|
+
when 0
|
61
|
+
@key_identifier = el.value.unpack("H*")[0].upcase.scan(/../).join(":")
|
62
|
+
when 1
|
63
|
+
@authority_cert_issuer = R509::ASN1::GeneralName.new(el.value.first)
|
64
|
+
when 2
|
65
|
+
arr = el.value.unpack("H*")[0].upcase.scan(/../)
|
66
|
+
# OpenSSL's convention is to drop leading 00s, so let's strip that off if
|
67
|
+
# present
|
68
|
+
if arr[0] == "00"
|
69
|
+
arr.delete_at(0)
|
70
|
+
end
|
71
|
+
@authority_cert_serial_number = arr.join(":")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_extension(arg)
|
77
|
+
arg[:value] = AKI_EXTENSION_DEFAULT unless not arg[:value].nil?
|
78
|
+
validate_authority_key_identifier(arg)
|
79
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
80
|
+
fake_cert = OpenSSL::X509::Certificate.new
|
81
|
+
fake_cert.extensions = [R509::Cert::Extensions::SubjectKeyIdentifier.new(:public_key => arg[:public_key])] unless arg[:public_key].nil?
|
82
|
+
fake_cert.subject = arg[:issuer_subject].name unless arg[:issuer_subject].nil?
|
83
|
+
ef.issuer_certificate = fake_cert
|
84
|
+
critical = R509::Cert::Extensions.calculate_critical(arg[:critical], false)
|
85
|
+
return ef.create_extension("authorityKeyIdentifier", arg[:value], critical) # this could also be keyid:always,issuer:always
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_authority_key_identifier(aki)
|
89
|
+
if aki[:value].downcase.include?("keyid") and aki[:public_key].nil?
|
90
|
+
raise ArgumentError, "You must supply an OpenSSL::PKey object to :public_key if aki value contains keyid (present by default)"
|
91
|
+
end
|
92
|
+
if aki[:value].downcase.include?("issuer") and not aki[:issuer_subject].kind_of?(R509::Subject)
|
93
|
+
raise ArgumentError, "You must supply an R509::Subject object to :issuer_subject if aki value contains issuer"
|
94
|
+
end
|
95
|
+
aki
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'r509/asn1'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
module R509
|
6
|
+
class Cert
|
7
|
+
# module to contain extension classes for R509::Cert
|
8
|
+
module Extensions
|
9
|
+
#
|
10
|
+
# Helper class methods
|
11
|
+
#
|
12
|
+
|
13
|
+
# Takes OpenSSL::X509::Extension objects and wraps each in the appropriate
|
14
|
+
# R509::Cert::Extensions object, and returns them in a hash. The hash is
|
15
|
+
# keyed with the R509 extension class. Extensions without an R509
|
16
|
+
# implementation are ignored (see #get_unknown_extensions).
|
17
|
+
def self.wrap_openssl_extensions( extensions )
|
18
|
+
r509_extensions = {}
|
19
|
+
extensions.each do |openssl_extension|
|
20
|
+
R509_EXTENSION_CLASSES.each do |r509_class|
|
21
|
+
if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
|
22
|
+
if r509_extensions.has_key?(r509_class)
|
23
|
+
raise ArgumentError.new("Only one extension object allowed per OID")
|
24
|
+
end
|
25
|
+
|
26
|
+
r509_extensions[r509_class] = r509_class.new( openssl_extension )
|
27
|
+
break
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
return r509_extensions
|
33
|
+
end
|
34
|
+
|
35
|
+
# Given a list of OpenSSL::X509::Extension objects, returns those without
|
36
|
+
# an R509 implementation.
|
37
|
+
def self.get_unknown_extensions( extensions )
|
38
|
+
unknown_extensions = []
|
39
|
+
extensions.each do |openssl_extension|
|
40
|
+
match_found = false
|
41
|
+
R509_EXTENSION_CLASSES.each do |r509_class|
|
42
|
+
if ( r509_class::OID.downcase == openssl_extension.oid.downcase )
|
43
|
+
match_found = true
|
44
|
+
break
|
45
|
+
end
|
46
|
+
end
|
47
|
+
# if we make it this far (without breaking), we didn't match
|
48
|
+
unknown_extensions << openssl_extension unless match_found
|
49
|
+
end
|
50
|
+
|
51
|
+
return unknown_extensions
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
# Takes an array of R509::ASN1::GeneralName objects and returns a hash that can be
|
56
|
+
# encoded to YAML (used by #to_yaml methods)
|
57
|
+
def self.names_to_h(array)
|
58
|
+
data = []
|
59
|
+
array.each do |name|
|
60
|
+
value = (name.value.kind_of?(R509::Subject))? name.value.to_h : name.value
|
61
|
+
data.push(
|
62
|
+
{
|
63
|
+
:type => name.short_type,
|
64
|
+
:value => value
|
65
|
+
}
|
66
|
+
)
|
67
|
+
end
|
68
|
+
data
|
69
|
+
end
|
70
|
+
|
71
|
+
# Mixed into extensions that have a single generalnames object to
|
72
|
+
# simplify getting data out of them
|
73
|
+
module GeneralNamesMixin
|
74
|
+
# @return [Array<String>] DNS names
|
75
|
+
def dns_names
|
76
|
+
@general_names.dns_names
|
77
|
+
end
|
78
|
+
|
79
|
+
# @return [Array<String>] IP addresses. They will be formatted as strings (dotted quad with optional netmask for IPv4 and colon-hexadecimal with optional netmask for IPv6
|
80
|
+
def ip_addresses
|
81
|
+
@general_names.ip_addresses
|
82
|
+
end
|
83
|
+
alias :ips :ip_addresses
|
84
|
+
|
85
|
+
# @return [Array<String>] email addresses
|
86
|
+
def rfc_822_names
|
87
|
+
@general_names.rfc_822_names
|
88
|
+
end
|
89
|
+
alias :email_names :rfc_822_names
|
90
|
+
|
91
|
+
# @return [Array<String>] URIs (not typically found in SAN extensions)
|
92
|
+
def uris
|
93
|
+
@general_names.uris
|
94
|
+
end
|
95
|
+
|
96
|
+
# @return [Array<R509::Subject>] directory names
|
97
|
+
def directory_names
|
98
|
+
@general_names.directory_names
|
99
|
+
end
|
100
|
+
alias :dir_names :directory_names
|
101
|
+
|
102
|
+
# @return [Array] array of GeneralName objects preserving order found in the extension
|
103
|
+
def names
|
104
|
+
@general_names.names
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
R509_EXTENSION_CLASSES = Set.new
|
110
|
+
|
111
|
+
# Registers a class as being an R509 certificate extension class. Registered
|
112
|
+
# classes are used by #wrap_openssl_extensions to wrap OpenSSL extensions
|
113
|
+
# in R509 extensions, based on the OID.
|
114
|
+
def self.register_class( r509_ext_class )
|
115
|
+
raise ArgumentError.new("R509 certificate extensions must have an OID") if r509_ext_class::OID.nil?
|
116
|
+
R509_EXTENSION_CLASSES << r509_ext_class
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.calculate_critical(critical,default)
|
120
|
+
if critical.kind_of?(TrueClass) or critical.kind_of?(FalseClass)
|
121
|
+
critical
|
122
|
+
else
|
123
|
+
default
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Method attempts to determine if data being passed to an extension is already
|
128
|
+
# an extension/asn.1 data or not.
|
129
|
+
def self.is_extension?(data)
|
130
|
+
return true if data.kind_of?(OpenSSL::X509::Extension)
|
131
|
+
return false if not data.kind_of?(String)
|
132
|
+
begin
|
133
|
+
OpenSSL::X509::Extension.new(data)
|
134
|
+
return true
|
135
|
+
rescue
|
136
|
+
return false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'r509/cert/extensions/base'
|
2
|
+
|
3
|
+
module R509
|
4
|
+
class Cert
|
5
|
+
module Extensions
|
6
|
+
# RFC 5280 Description (see: http://www.ietf.org/rfc/rfc5280.txt)
|
7
|
+
#
|
8
|
+
# The basic constraints extension identifies whether the subject of the
|
9
|
+
# certificate is a CA and the maximum depth of valid certification
|
10
|
+
# paths that include this certificate.
|
11
|
+
#
|
12
|
+
# You can use this extension to parse an existing extension for easy access
|
13
|
+
# to the contents or create a new one.
|
14
|
+
class BasicConstraints < OpenSSL::X509::Extension
|
15
|
+
|
16
|
+
# friendly name for BasicConstraints OID
|
17
|
+
OID = "basicConstraints"
|
18
|
+
Extensions.register_class(self)
|
19
|
+
|
20
|
+
# returns the path length (if present)
|
21
|
+
# @return [Integer,nil]
|
22
|
+
attr_reader :path_length
|
23
|
+
|
24
|
+
# This method takes a hash or an existing Extension object to parse
|
25
|
+
# @option arg :ca [Boolean] The ca key is required and must be set to true (for an issuing CA) or false (everything else).
|
26
|
+
# @option arg :path_length optional [Integer] This option is only allowed if ca is set to TRUE. path_length allows you to define the maximum number of non-self-issued intermediate certificates that may follow this certificate in a valid certification path. For example, if you set this value to 0 then the certificate issued can only issue end entity certificates, not additional subroots. This must be a non-negative integer (>=0).
|
27
|
+
# @option arg :critical [Boolean] (true)
|
28
|
+
def initialize(arg)
|
29
|
+
if not R509::Cert::Extensions.is_extension?(arg)
|
30
|
+
arg = build_extension(arg)
|
31
|
+
end
|
32
|
+
|
33
|
+
super(arg)
|
34
|
+
parse_extension
|
35
|
+
end
|
36
|
+
|
37
|
+
# Check whether the extension value would make the parent certificate a CA
|
38
|
+
# @return [Boolean]
|
39
|
+
def is_ca?
|
40
|
+
return @is_ca == true
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns true if the path length allows this certificate to be used to
|
44
|
+
# create subordinate signing certificates beneath it. Does not check if
|
45
|
+
# there is a pathlen restriction in the cert chain above the current cert
|
46
|
+
# @return [Boolean]
|
47
|
+
def allows_sub_ca?
|
48
|
+
return false unless is_ca?
|
49
|
+
return true if @path_length.nil?
|
50
|
+
return @path_length > 0
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Hash]
|
54
|
+
def to_h
|
55
|
+
hash = { :ca => @is_ca, :critical => self.critical? }
|
56
|
+
hash[:path_length] = @path_length unless @path_length.nil? or not is_ca?
|
57
|
+
hash
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [YAML]
|
61
|
+
def to_yaml
|
62
|
+
self.to_h.to_yaml
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def parse_extension
|
68
|
+
data = R509::ASN1.get_extension_payload(self)
|
69
|
+
@is_ca = false
|
70
|
+
# BasicConstraints ::= SEQUENCE {
|
71
|
+
# cA BOOLEAN DEFAULT FALSE,
|
72
|
+
# pathLenConstraint INTEGER (0..MAX) OPTIONAL }
|
73
|
+
data.entries.each do |entry|
|
74
|
+
if entry.kind_of?(OpenSSL::ASN1::Boolean)
|
75
|
+
@is_ca = entry.value
|
76
|
+
else
|
77
|
+
# There are only two kinds of entries permitted so anything
|
78
|
+
# else is an integer pathlength. it is in OpenSSL::BN form by default
|
79
|
+
# but that's annoying so let's cast it.
|
80
|
+
@path_length = entry.value.to_i
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_extension(arg)
|
86
|
+
validate_basic_constraints(arg)
|
87
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
88
|
+
if arg[:ca] == true
|
89
|
+
bc_value = "CA:TRUE"
|
90
|
+
if not arg[:path_length].nil?
|
91
|
+
bc_value += ",pathlen:#{arg[:path_length]}"
|
92
|
+
end
|
93
|
+
else
|
94
|
+
bc_value = "CA:FALSE"
|
95
|
+
end
|
96
|
+
critical = R509::Cert::Extensions.calculate_critical(arg[:critical], true)
|
97
|
+
return ef.create_extension("basicConstraints", bc_value, critical)
|
98
|
+
end
|
99
|
+
|
100
|
+
# validates the structure of the certificate policies array
|
101
|
+
def validate_basic_constraints(constraints)
|
102
|
+
if constraints.nil? or not constraints.respond_to?(:has_key?) or not constraints.has_key?(:ca)
|
103
|
+
raise ArgumentError, "You must supply a hash with a key named :ca with a boolean value"
|
104
|
+
end
|
105
|
+
if constraints[:ca].nil? or (not constraints[:ca].kind_of?(TrueClass) and not constraints[:ca].kind_of?(FalseClass))
|
106
|
+
raise ArgumentError, "You must supply true/false for the :ca key when specifying basic constraints"
|
107
|
+
end
|
108
|
+
if constraints[:ca] == false and not constraints[:path_length].nil?
|
109
|
+
raise ArgumentError, ":path_length is not allowed when :ca is false"
|
110
|
+
end
|
111
|
+
if constraints[:ca] == true and not constraints[:path_length].nil? and (constraints[:path_length] < 0 or not constraints[:path_length].kind_of?(Integer))
|
112
|
+
raise ArgumentError, "Path length must be a positive integer (>= 0)"
|
113
|
+
end
|
114
|
+
constraints
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|