aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorBen Toews <mastahyeti@gmail.com>2019-06-26 15:39:58 -0600
committerSamuel Williams <samuel.williams@oriontransfer.co.nz>2019-09-27 17:57:31 +1200
commitea702a106d3d8136c48f244593de95666be0edf9 (patch)
tree7d619d72ee830c26a50e5370232fb9e58d5d4573 /lib
parent505dae9d09b4513da2370da066cae3bb9a393d16 (diff)
downloadruby-openssl-ea702a106d3d8136c48f244593de95666be0edf9.tar.gz
helpers for accessing AKI/SKI extensions of certs/crls
Diffstat (limited to 'lib')
-rw-r--r--lib/openssl/x509.rb108
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