Last active
August 29, 2015 14:05
-
-
Save zmajstor/8fe69dd2f92e749268d9 to your computer and use it in GitHub Desktop.
improved extraction and validation of the subjectAltName X509 extension; ASN1 decode for subjectAltName extension containing funny msUPN from Microsoft PKI (not decoded by out-of-the-box ruby OpenSSL library)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-----BEGIN CERTIFICATE----- | |
MIIExDCCA6ygAwIBAgITUgAACqTdV8IeZ2qIFgAAAAAKpDANBgkqhkiG9w0BAQUF | |
ADBJMRMwEQYKCZImiZPyLGQBGRYDbmV0MRYwFAYKCZImiZPyLGQBGRYGcHJvbWRt | |
MRowGAYDVQQDExFQcm9tZG1ORVRSb290Q0F2MTAeFw0xNDA4MTUxODIwNDBaFw0x | |
NTA4MTUxODIwNDBaMB0xGzAZBgNVBAMMElpvcmFuIE1hanN0b3JvdmnEhzCCASIw | |
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM2z4QYHjTHNbSkl4UFF1E1X5zgG | |
TdVX5b6gpYjwlTpBNbFDNwAIePjJHZ/IMwpFpJzHU/G9chGx67D+RsfCkd1LHdnR | |
P0WOLKXsCImZIneAfvMt8gXDdeHzcXtNapgoIfjnAAZs4UN9Lxvd4KN2UpsSYKu+ | |
jhRrmDE3vtedMLK7l1pnKSK6HCjch6zdnF9JytsdApGjcwKm4ubr/0y17XQvcrzl | |
21C0NRE77am/nR/3Jd3/qZjA7OT99KCjTx3OYXqZvU0s/4IEEBIyAWWFkxFQHlsE | |
oTid2CquLycK5Nmi1mqV4IbLnA60Jb1jXteX03WM96dk98NobRrT5jCgOvsCAwEA | |
AaOCAc8wggHLMA4GA1UdDwEB/wQEAwIFoDA3BgNVHREEMDAugQ16bUBwcm9tZG0u | |
Y29toB0GCisGAQQBgjcUAgOgDwwNem1AcHJvbWRtLm5ldDAdBgNVHQ4EFgQUhz4n | |
3F4HHGwNekWp9j9hbMwdiM4wHwYDVR0jBBgwFoAUOkBhIRg6S6cooasjqbukaO4M | |
ErkwTAYDVR0fBEUwQzBBoD+gPYY7aHR0cDovL2Nsb3VkcGtpLnByb21kbS5uZXQv | |
Q2VydEVucm9sbC9Qcm9tZG1ORVRSb290Q0F2MS5jcmwwawYIKwYBBQUHAQEEXzBd | |
MFsGCCsGAQUFBzAChk9odHRwOi8vY2xvdWRwa2kucHJvbWRtLm5ldC9DZXJ0RW5y | |
b2xsL2Nsb3VkcGtpLnByb21kbS5uZXRfUHJvbWRtTkVUUm9vdENBdjEuY3J0MD0G | |
CSsGAQQBgjcVBwQwMC4GJisGAQQBgjcVCIaUvg2Bi6h/hL2LJ4e21l+C9aIfCIbq | |
iRmHpuIVAgFkAgEMMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAnBgkr | |
BgEEAYI3FQoEGjAYMAoGCCsGAQUFBwMCMAoGCCsGAQUFBwMBMA0GCSqGSIb3DQEB | |
BQUAA4IBAQA9WtQnIZiI0Ysra4USEnqcC/29fFJlU7TCFCHV2NjiqWoQeff28O7x | |
Wq+i32/HzAObCUUZXZbOyz0AypMEiIctllrj0Ckhj66yrmc4h+SQJP9i4K0p60M5 | |
Yi3LD4fxweVjqv+lMPTmr4s9tc+WOqR5Egjx2ith8mkSolRB2OI307AuLUsBt9ll | |
km/XM1zptOADm8euzQ3fiaSt1kvj8r0FnqkJMYUzwbTcGovNUz9af9u3gZckQin9 | |
73JcW91VKLG8gCL0y7xo+Byv4/UoDiI17biEzkCtgKetk4d4hH68FcmK1DURS8E2 | |
i2+daiavtmzeCXjH/4EbwJVUGcYE5PuR | |
-----END CERTIFICATE----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'openssl' | |
# improved extraction and validation of the subjectAltName X509 extension | |
# Universal Principal Name (UPN) | |
# Widely deployed Microsoft OtherName name form (OID 1.3.6.1.4.1.311.20.2.3) | |
# Format: user@domain | |
# Encoding: UTF-8 | |
# Matching: case ignore | |
# OtherName, unsupported in current implementation: | |
# san = cert.extensions.find { |ext| ext.oid == 'subjectAltName' } | |
# san.to_h.inspect # => | |
# before: | |
# {"oid"=>"subjectAltName", "value"=>"email:[email protected], othername:<unsupported>", "critical"=>false} | |
# after: | |
# {"oid"=>"subjectAltName", "value"=>"email:[email protected], othername:<unsupported>", "critical"=>false, "Email"=>"[email protected]", "OtherName"=>"[email protected]"} | |
module OpenSSL | |
module X509 | |
UPN_OID = '1.3.6.1.4.1.311.20.2.3' | |
UPN_TAG, EMAIL_TAG = 0, 1 | |
class Certificate | |
def verify_san_upn(upn) | |
/\A#{san_hash['OtherName']}\z/i =~ upn | |
end | |
def verify_san_email(email) | |
/\A#{san_hash['Email']}\z/i =~ email | |
end | |
def san_hash | |
subject_alt_name.to_h if subject_alt_name | |
end | |
def subject_alt_name | |
@subject_alt_name ||= self.extensions.find { |e| 'subjectAltName' == e.oid } | |
end | |
end | |
class Extension | |
def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false} | |
# {"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?} | |
san_values = {} | |
if self.oid == "subjectAltName" | |
ostr = OpenSSL::ASN1.decode(self.to_der).value.last | |
sequence = OpenSSL::ASN1.decode(ostr.value) | |
sequence.value.each do |san| | |
case san.tag | |
when 0 # UPN_TAG | |
san_values["OtherName"] = san.value.last.value.first.value if san.value.first.oid == UPN_OID | |
when 1 # EMAIL_TAG | |
san_values["Email"] = san.value | |
when 2 # dNSName in GeneralName (RFC5280) | |
san_values["dNSName"] = san.value # hostname | |
when 7 # iPAddress in GeneralName (RFC5280) | |
# follows GENERAL_NAME_print() in x509v3/v3_alt.c | |
if san.value.size == 4 | |
san_values["iPAddress"] = san.value.unpack('C*').join('.') | |
elsif san.value.size == 16 | |
san_values["iPAddress"] = san.value.unpack('n*').map { |e| sprintf("%X", e) }.join(':') | |
end | |
end | |
end | |
end | |
{ "oid" => self.oid, "value" => self.value, "critical" => self.critical? }.merge(san_values) | |
end | |
end | |
end | |
end | |
require 'test/unit' | |
class TestSubjectAltName < Test::Unit::TestCase | |
def setup | |
@cert = OpenSSL::X509::Certificate.new File.read('san_cert.pem') | |
end | |
def test_verify_san_upn | |
assert @cert.verify_san_upn('[email protected]') | |
refute @cert.verify_san_upn('[email protected]') | |
end | |
def test_verify_san_email | |
assert @cert.verify_san_email('[email protected]') | |
refute @cert.verify_san_email('[email protected]') | |
refute @cert.verify_san_email('[email protected]') | |
end | |
def test_san_to_h | |
san = @cert.extensions.find { |ex| ex.oid == 'subjectAltName' } | |
if san && (san_hash = san.to_h) | |
assert_equal san_hash['Email'], '[email protected]' | |
assert_equal san_hash['OtherName'], '[email protected]' | |
assert_not_equal san_hash['Email'], '[email protected]' | |
assert_not_equal san_hash['OtherName'], '[email protected]' | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment