Last active
August 29, 2015 14:05
-
-
Save zmajstor/a907fac1705e66411163 to your computer and use it in GitHub Desktop.
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' unless defined? OpenSSL | |
# improved extraction and validation of the subjectAltName X509 extension for | |
# 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 | |
# e.g. | |
# otherName=1.3.6.1.4.1.311.20.2.3;UTF8:[email protected] | |
# [email protected] | |
# DNS.1=example1.com | |
# DNS.2=example2.com | |
# URI=http://example.com/ | |
# IP.1=13::17 | |
# IP.2=192.168.7.1 | |
# OtherName, unsupported in current implementation: | |
# ex = cert.extensions.find { |ext| ext.oid == 'subjectAltName' } | |
# | |
# before: | |
# ex.value #=> "email:[email protected], othername:<unsupported>" | |
# | |
# after: | |
# ex.san_value_hash.inspect # => {"email"=>"[email protected]", "otherName"=>{ "upn"=>"[email protected]"}} | |
# cert.san_value_hash # => {"email"=>"[email protected]", "otherName"=>{ "upn"=>"[email protected]"}} | |
# cert.verify_san_other_name_upn("[email protected]") #=> true | |
class OpenSSL::X509::CertificateHelper < Struct.new(:cert) | |
UPN_OID = '1.3.6.1.4.1.311.20.2.3' | |
def verify_san_other_name_upn(upn) | |
!!(/\A#{san_value_hash['otherName']['upn']}\z/i =~ upn) | |
end | |
def verify_san_email(email) | |
!!(/\A#{san_value_hash['email']}\z/i =~ email) | |
end | |
def common_name_value | |
cert.subject.to_a.each do |oid, value| | |
return value if oid == "CN" | |
end | |
end | |
def key_usage_extension | |
@key_usage_extension ||= cert.extensions.detect { |ex| ex.oid == 'keyUsage' } | |
end | |
def key_usage_value | |
key_usage_extension.value if key_usage_extension | |
end | |
def san_extension | |
# return first subjectAltName, or nil if there is no subjectAltName | |
@san_extension ||= cert.extensions.detect { |ex| ex.oid == 'subjectAltName' } | |
end | |
def san_value_hash | |
return {} unless san_extension && (san_extension.oid == 'subjectAltName') | |
octet_str = OpenSSL::ASN1.decode(san_extension.to_der).value.last | |
sequence = OpenSSL::ASN1.decode(octet_str.value) | |
Hash.new.tap do |san_values| | |
sequence.value.each do |san| | |
case san.tag | |
when 0 # otherName | |
if san.value.first.oid == UPN_OID | |
# san_values['otherNameUPN'] = san.value.last.value.first.value | |
san_values['otherName'] = { 'upn' => san.value.last.value.first.value } | |
end | |
when 1 # email | |
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 | |
end | |
end |
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 'test_helper' | |
class TestCertificateHelper < ActiveSupport::TestCase | |
def setup | |
@cert = OpenSSL::X509::Certificate.new File.read(Rails.root.join('test/fixtures/san_cert.pem')) | |
@cert_helper = OpenSSL::X509::CertificateHelper.new @cert | |
end | |
def test_verify_san_other_name_upn | |
assert @cert_helper.verify_san_other_name_upn('[email protected]') | |
refute @cert_helper.verify_san_other_name_upn('[email protected]') | |
end | |
def test_verify_san_email | |
assert @cert_helper.verify_san_email('[email protected]') | |
refute @cert_helper.verify_san_email('[email protected]') | |
refute @cert_helper.verify_san_email('[email protected]') | |
end | |
def test_san_value_hash | |
# san_ex = @cert.extensions.find { |ex| ex.oid == 'subjectAltName' } | |
san = @cert_helper.san_value_hash | |
assert_equal '[email protected]', san['email'] | |
assert_equal '[email protected]', san['otherName']['upn'] | |
assert_not_equal '[email protected]', san['email'] | |
assert_not_equal '[email protected]', san['otherName']['upn'] | |
end | |
def test_key_usage_value | |
assert_equal "Digital Signature, Key Encipherment", @cert_helper.key_usage_value | |
end | |
end |
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----- |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment