-
Notifications
You must be signed in to change notification settings - Fork 165
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Helpers for accessing AKI/SKI extensions of certs/crls #260
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,6 +60,109 @@ def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false} | |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's exception: It's not empty, but it's also exception to expected value. Even if our "expected value" is pretty limited at this time. |
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking at Go's implementation, they only expose the |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another option is to return an array, even if it only contains one thing. |
||
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 @@ def cleanup | |
end | ||
|
||
class Certificate | ||
include Extension::SubjectKeyIdentifier | ||
include Extension::AuthorityKeyIdentifier | ||
|
||
def pretty_print(q) | ||
q.object_group(self) { | ||
q.breakable | ||
|
@@ -192,6 +298,8 @@ def pretty_print(q) | |
end | ||
|
||
class CRL | ||
include Extension::AuthorityKeyIdentifier | ||
|
||
def ==(other) | ||
return false unless CRL === other | ||
to_der == other.to_der | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -111,13 +111,18 @@ def issue_crl(revoke_info, serial, lastup, nextup, extensions, | |
crl | ||
end | ||
|
||
def get_subject_key_id(cert) | ||
def get_subject_key_id(cert, hex: true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just updating the existing method here. You'd like me to change the name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like |
||
asn1_cert = OpenSSL::ASN1.decode(cert) | ||
tbscert = asn1_cert.value[0] | ||
pkinfo = tbscert.value[6] | ||
publickey = pkinfo.value[1] | ||
pkvalue = publickey.value | ||
OpenSSL::Digest::SHA1.hexdigest(pkvalue).scan(/../).join(":").upcase | ||
digest = OpenSSL::Digest::SHA1.digest(pkvalue) | ||
if hex | ||
digest.unpack("H2"*20).join(":").upcase | ||
else | ||
digest | ||
end | ||
end | ||
|
||
def openssl?(major = nil, minor = nil, fix = nil, patch = 0) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is 4?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case, it designates that this is a
directoryName
(see RFC 5280 Appendix A.2), which is aName
(see RFC 5280 Section 4.1.2.4)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it makes sense to add some constants?