From ea702a106d3d8136c48f244593de95666be0edf9 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 26 Jun 2019 15:39:58 -0600 Subject: helpers for accessing AKI/SKI extensions of certs/crls --- lib/openssl/x509.rb | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) (limited to 'lib') 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 -- cgit v1.2.3