diff options
author | Ben Toews <mastahyeti@gmail.com> | 2019-06-26 15:39:58 -0600 |
---|---|---|
committer | Samuel Williams <samuel.williams@oriontransfer.co.nz> | 2019-09-27 17:57:31 +1200 |
commit | ea702a106d3d8136c48f244593de95666be0edf9 (patch) | |
tree | 7d619d72ee830c26a50e5370232fb9e58d5d4573 /lib | |
parent | 505dae9d09b4513da2370da066cae3bb9a393d16 (diff) | |
download | ruby-openssl-ea702a106d3d8136c48f244593de95666be0edf9.tar.gz |
helpers for accessing AKI/SKI extensions of certs/crls
Diffstat (limited to 'lib')
-rw-r--r-- | lib/openssl/x509.rb | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/lib/openssl/x509.rb b/lib/openssl/x509.rb index 98358f90..89f15933 100644 --- a/lib/openssl/x509.rb +++ b/lib/openssl/x509.rb @@ -60,6 +60,109 @@ module OpenSSL def to_a [ self.oid, self.value, self.critical? ] end + + module Helpers + def find_extension(oid) + extensions.find { |e| e.oid == oid } + end + + def x509_name_from_general_name(gn_asn1) + unless gn_asn1.tag_class == :CONTEXT_SPECIFIC + raise ASN1::ASN1Error "invalid GeneralName" + end + + if gn_asn1.tag == 4 + Name.new(gn_asn1.value.first.to_der) + else + # TODO: raise error instead of returning nil? + # this is some other kind of general name. + nil + end + end + end + + module SubjectKeyIdentifier + include Helpers + + # Described in RFC5280 Section 4.2.1.2 + # + # Returns a binary String key identifier or nil. + def subject_key_identifier + ext = find_extension("subjectKeyIdentifier") + return nil if ext.nil? + + ski_asn1 = ASN1.decode(ext.value_der) + if ext.critical? || ski_asn1.tag_class != :UNIVERSAL || ski_asn1.tag != ASN1::OCTET_STRING + raise ASN1::ASN1Error "invalid extension" + end + + ski_asn1.value + end + end + + module AuthorityKeyIdentifier + include Helpers + + # Described in RFC5280 Section 4.2.1.1 + # + # Returns a Hash with keys :key_identifier, :authority_cert_issuer, + # and :authority_cert_serial_number. + def authority_key_identifier + ext = find_extension("authorityKeyIdentifier") + return nil if ext.nil? + + aki_asn1 = ASN1.decode(ext.value_der) + if ext.critical? || aki_asn1.tag_class != :UNIVERSAL || aki_asn1.tag != ASN1::SEQUENCE + raise ASN1::ASN1Error "invalid extension" + end + + fields = {} + + key_id = aki_asn1.value.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0 + end + + issuer = aki_asn1.value.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 1 + end + + serial = aki_asn1.value.find do |v| + v.tag_class == :CONTEXT_SPECIFIC && v.tag == 2 + end + + # must have neither or both of issuer and serial + if (!issuer.nil? && serial.nil?) || (issuer.nil? && !serial.nil?) + raise ASN1::ASN1Error "invalid extension" + end + + fields[:key_identifier] = if !key_id.nil? + key_id.value + else + nil + end + + fields[:authority_cert_issuer] = if !issuer.nil? + # TODO raise if there are more than one values? GeneralNames is a + # SEQUENCE. + x509_name_from_general_name(issuer.value.first) + else + nil + end + + fields[:authority_cert_serial_number] = if !serial.nil? + # encode/decode as integer to get value parsed as BN + ASN1.decode(ASN1::ASN1Data.new( + serial.value, + ASN1::INTEGER, + :UNIVERSAL + ).to_der).value + else + nil + end + + fields + end + end end class Name @@ -179,6 +282,9 @@ module OpenSSL end class Certificate + include Extension::SubjectKeyIdentifier + include Extension::AuthorityKeyIdentifier + def pretty_print(q) q.object_group(self) { q.breakable @@ -192,6 +298,8 @@ module OpenSSL end class CRL + include Extension::AuthorityKeyIdentifier + def ==(other) return false unless CRL === other to_der == other.to_der |