hiviah/pyx509

Error while parsing DER certificate with script

Closed this issue · 6 comments

I used M2Crypto to build a certificate and add an subjectAltName "otherName" extension.

$ x509_parse.py test.der 
Traceback (most recent call last):
  File "/usr/local/bin/x509_parse.py", line 8, in <module>
    load_entry_point('pyx509==0.1.4', 'console_scripts', 'x509_parse.py')()
  File "/Library/Python/2.7/site-packages/pyx509/commands.py", line 36, in print_certificate_info_cmd
    print_certificate_info(file(sys.argv[1]).read())
  File "/Library/Python/2.7/site-packages/pyx509/commands.py", line 29, in print_certificate_info
    X509Certificate.from_der(derData).display()
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 56, in from_der
    return cls(asn1[0])
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 754, in __init__
    self.tbsCertificate = Certificate(tbsCert)
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 724, in __init__
    self.extensions = self._create_extensions_list(tbsCertificate.getComponentByName('extensions'))
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 737, in _create_extensions_list
    return [Extension(ext) for ext in extensions]
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 676, in __init__
    self.value = decoderFunction(v)
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 630, in <lambda>
    "2.5.29.17": (asn1_cert_ext.GeneralNames(), lambda v: SubjectAltNameExt(v),
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 281, in __init__
    value = Name(comp)
  File "/Library/Python/2.7/site-packages/pyx509/models.py", line 131, in __init__
    type = str(attr.getComponentByPosition(0).getComponentByName('type'))
AttributeError: 'int' object has no attribute 'getComponentByPosition'

I think you're using this guy's fork: https://github.com/erny/pyx509/tree/master/pyx509

It has some improvements, but also makes some assumptions that were not tested. My version just ignored any other SAN that wasn't dNSName.

There's this line in models.py:280:

if pos in (0, 3, 5):  # May be wrong

The next line tries to convert the OtherName into string and fails. OtherName has implicit tag [0]. You need to either parse it properly according to the AnotherName IDL in RFC 5280 and turn it into string, or let it be and do not convert it into string (not implicitly defined conversion in pyasn1 apparently).

Yeah, I was using erny's, as he has more recent commits. I wasn't sure if the problem was with his or yours, but you can't post an issue to a fork.

It was suggested by someone else that I do the following from pyasn1 (where val2 is the third component.. the string). However, I'm getting an error, and I'm new to it all.

(val2_o, remainder) = decoder.decode(val2, asn1Spec=univ.OctetString())
(val2_san, remainder) = decoder.decode(val2_o, asn1Spec=rfc2459.SubjectAltName())

I'm getting a "not in asn1Spec" error:

Traceback (most recent call last):
  File "custom_extension.py", line 169, in <module>
    r = test_mkcert()
  File "custom_extension.py", line 150, in test_mkcert
    (val2_o, remainder) = decode(val2, asn1Spec=univ.OctetString())
  File "/Library/Python/2.7/site-packages/pyasn1/codec/ber/decoder.py", line 798, in __call__
    '%r not in asn1Spec: %r' % (tagSet, asn1Spec)
pyasn1.error.PyAsn1Error: TagSet(Tag(tagClass=0, tagFormat=32, tagId=16)) not in asn1Spec: OctetString()
erny commented

Hi.
I added issue tracking to my fork. Feel free to copy the bug .

I'm travelling right now, so I don't have my development computer to test it, but I'd define a class that can be used for AnotherName like this (no guarrantees that it'd work, I didn't test it):

class AnotherName(univ.Sequence):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('typeId', univ.ObjectIdentifier()),
        namedtype.NamedType('value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0)))
        )
    def __repr__(self):
        tuple = self.getComponentByName('typeId')
        str_oid = tuple_to_OID(tuple) #might fail for unknown OIDs
        return str_oid

    def __str__(self):
        return repr(self)

Then you use AnotherName as the value for asn1Spec in decode(). It has an explicit conversion to string, which should work, but as I said, you need to test it.

You should probably also register the new AnotherName in GeneralNames:

class GeneralName(univ.Choice):
    componentType = namedtype.NamedTypes(
        namedtype.NamedType('otherName', AnotherName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))),
    #....

Note that no matter what you'll still need to parse the 'value' field according to the OID set in typeId to get a meaningful value.

We can't make do without creating new types? I'm not doing anything exotic
with ASN.1 (other than trying to use extensions, it seems).

On Sun, Apr 13, 2014 at 12:23 AM, hiviah notifications@github.com wrote:

I'm travelling right now, so I don't have my development computer to test
it, but I'd define a class that can be used for AnotherName like this (no
guarrantees that it'd work, I didn't test it):

class AnotherName(univ.Sequence):
componentType = namedtype.NamedTypes(
namedtype.NamedType('typeId', univ.ObjectIdentifier()),
namedtype.NamedType('value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0)))
)
def repr(self):
tuple = self.getComponentByName('typeId')
str_oid = tuple_to_OID(tuple) #might fail for unknown OIDs
return str_oid

def __str__(self):
    return repr(self)

Then you use AnotherName as the value for asn1Spec in decode(). It has an
explicit conversion to string, which should work, but as I said, you need
to test it.

You should probably also register the new AnotherName in GeneralNames:

class GeneralName(univ.Choice):
componentType = namedtype.NamedTypes(
namedtype.NamedType('otherName', AnotherName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))),
#....

Note that no matter what you'll still need to parse the 'value' field
according to the OID set in typeId to get a meaningful value.

Reply to this email directly or view it on GitHubhttps://github.com//issues/2#issuecomment-40299288
.

For a quick hack, remove the 0 or add an 'elif' in if pos in (0, 3, 5): # May be wrong from models.py. It will avoid string conversion of value stored in implicit tag 0, which is otherName.

I'm not doing anything exotic with ASN.1 (other than trying to use extensions, it seems).

Everything about X.509v3 extensions is exotic and there are many bugs in various implementations (one of my favorites). Have a look at EFF SSL Observatory data or scans.io datasets. Using otherName in SAN definitely is exotic. Generally anything requiring custom OID says "exotic" (policy constraints and policy mapping extensions are the worst, major TLS clients like browsers won't agree on them).