I have some data that pyOpenSSL gave me, '0\r\x82\x0bexample.com'
. This should be the value of a subjectAltName X509 extension. I tried to encode the necessary parts of the ASN1 specification for this extension using pyasn1 (and based on one of the pyasn1 examples):
from pyasn1.type import univ, constraint, char, namedtype
from pyasn1.codec.der.decoder import decode
MAX = 64
class DirectoryString(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType(
'teletexString', char.TeletexString().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType(
'printableString', char.PrintableString().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType(
'universalString', char.UniversalString().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType(
'utf8String', char.UTF8String().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType(
'bmpString', char.BMPString().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
namedtype.NamedType(
'ia5String', char.IA5String().subtype(
subtypeSpec=constraint.ValueSizeConstraint(1, MAX))),
)
class AttributeValue(DirectoryString):
pass
class AttributeType(univ.ObjectIdentifier):
pass
class AttributeTypeAndValue(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('type', AttributeType()),
namedtype.NamedType('value', AttributeValue()),
)
class RelativeDistinguishedName(univ.SetOf):
componentType = AttributeTypeAndValue()
class RDNSequence(univ.SequenceOf):
componentType = RelativeDistinguishedName()
class Name(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('', RDNSequence()),
)
class Extension(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('extnID', univ.ObjectIdentifier()),
namedtype.DefaultedNamedType('critical', univ.Boolean('False')),
namedtype.NamedType('extnValue', univ.OctetString()),
)
class Extensions(univ.SequenceOf):
componentType = Extension()
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
class GeneralName(univ.Choice):
componentType = namedtype.NamedTypes(
# namedtype.NamedType('otherName', AnotherName()),
namedtype.NamedType('rfc822Name', char.IA5String()),
namedtype.NamedType('dNSName', char.IA5String()),
# namedtype.NamedType('x400Address', ORAddress()),
namedtype.NamedType('directoryName', Name()),
# namedtype.NamedType('ediPartyName', EDIPartyName()),
namedtype.NamedType('uniformResourceIdentifier', char.IA5String()),
namedtype.NamedType('iPAddress', univ.OctetString()),
namedtype.NamedType('registeredID', univ.ObjectIdentifier()),
)
class GeneralNames(univ.SequenceOf):
componentType = GeneralName()
sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX)
class SubjectAltName(GeneralNames):
pass
print decode('0\r\x82\x0bexample.com', asn1Spec=GeneralNames())
Clearly I got a little bored near the end and didn't fully specify the GeneralName
type. However, the test string should contain a dNSName
, not one of the skipped values, so I hope it doesn't matter.
When the program is run, it fails with an error I'm not able to interpret:
Traceback (most recent call last):
File "x509.py", line 94, in <module>
print decode('0\r\x82\x0bexample.com', asn1Spec=GeneralNames())
File "/usr/lib/pymodules/python2.6/pyasn1/v1/codec/ber/decoder.py", line 493, in __call__
length, stGetValueDecoder, decodeFun
File "/usr/lib/pymodules/python2.6/pyasn1/v1/codec/ber/decoder.py", line 202, in valueDecoder
substrate, asn1Spec
File "/usr/lib/pymodules/python2.6/pyasn1/v1/codec/ber/decoder.py", line 453, in __call__
__chosenSpec.getTypeMap().has_key(tagSet):
File "/usr/lib/pymodules/python2.6/pyasn1/v1/type/univ.py", line 608, in getTypeMap
return Set.getComponentTypeMap(self)
File "/usr/lib/pymodules/python2.6/pyasn1/v1/type/univ.py", line 535, in getComponentTypeMap
def getComponentTypeMap(self): return self._componentType.getTypeMap(1)
File "/usr/lib/pymodules/python2.6/pyasn1/v1/type/namedtype.py", line 126, in getTypeMap
'Duplicate type %s in map %s'%(k,self.__typeMap)
pyasn1.error.PyAsn1Error: Duplicate type TagSet(Tag(tagClass=0, tagFormat=0, tagId=22)) in map {TagSet(Tag(tagClass=0, tagFormat=0, tagId=22)): IA5String()}
Any tips on where I went wrong and how to successfully parse this extension type with pyasn1 would be much appreciated.
I posted this question on the pyasn1-users list and Ilya Etingof (the author of pyasn1) pointed out my mistake. In brief, each NamedType
in GeneralName.componentType
needs to be given tag information. This is done with the subtype
method. For example, instead of:
namedtype.NamedType('rfc822Name', char.IA5String()),
the definition should be:
namedtype.NamedType('rfc822Name', char.IA5String().subtype(
implicitTag=tag.Tag(tag.tagClassContext,
tag.tagFormatSimple, 1))),
where 1
comes from the ASN.1 definition of GeneralName:
GeneralName ::= CHOICE {
otherName [0] OtherName,
rfc822Name [1] IA5String,
dNSName [2] IA5String,
x400Address [3] ORAddress,
directoryName [4] Name,
ediPartyName [5] EDIPartyName,
uniformResourceIdentifier [6] IA5String,
iPAddress [7] OCTET STRING,
registeredID [8] OBJECT IDENTIFIER
}
After defining a tag for each of these fields of the componentType
, parsing succeeds:
(GeneralNames().setComponentByPosition(
0, GeneralName().setComponentByPosition(1, IA5String('example.com'))), '')
Coming in way late with this answer but instead of writing the ASN.1 Schema by hand you can also use the RF2459 module provided in pyasn1-modules (also authored by Ilya Etingof)
Minimally this code should work and will hopefully be enough to get you started on more complex ANS.1 constructs. Make sure you have run pip install pyasn1
, pip install pyasn1-modules
and pip install pyopenssl
otherwise you'll get import errors.
# Import pyasn and the proper decode function
import pyasn1
from pyasn1.codec.der.decoder import decode as asn1_decoder
# Import SubjectAltName from rfc2459 module
from pyasn1_modules.rfc2459 import SubjectAltName
# Import native Python type encoder
from pyasn1.codec.native.encoder import encode as nat_encoder
# Import OpenSSL tools for working with certs.
from OpenSSL import crypto
# Read raw certificate file
with open('PATH/TO/CERTIFICATE.crt', 'r') as cert_f:
raw_cert = cert_f.read()
cert = crypto.load_certificate(crypto.FILETYPE_PEM, raw_cert)
# Note this example assumes SubjectAltName is the only Extension for this cert.
raw_alt_names = cert.get_extension(0).get_data()
decoded_alt_names, _ = asn1_decoder(raw_alt_names, asn1Spec=SubjectAltName())
# Unless a raw string of ASN.1 is what you need encode back to native Python types
py_alt_names = nat_encoder(decoded_alt_names)
# And Finally a plain Python list of UTF-8 encoded strings representing the SubjectAltNames
subject_alt_names = [ x['dNSName'].decode('utf-8') for x in py_alt_names]
The output of this will be something like
['cdn1.example.com', 'cdn2.example.com']
If the cert you are working on has multiple extensions you will need to use get_extension_count from the X509 object and get_short_name from the X509Extension object provided in pyopenssl.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With