├── .gitignore ├── README ├── pyx509 ├── __init__.py ├── pkcs7 │ ├── __init__.py │ ├── asn1_models │ │ ├── DSA.py │ │ ├── RSA.py │ │ ├── TST_info.py │ │ ├── X509_certificate.py │ │ ├── __init__.py │ │ ├── att_certificate_v2.py │ │ ├── certificate_extensions.py │ │ ├── crl.py │ │ ├── decoder_workarounds.py │ │ ├── digest_info.py │ │ ├── general_types.py │ │ ├── oid.py │ │ ├── pkcs_signed_data.py │ │ └── tools.py │ ├── debug.py │ ├── digest.py │ ├── pkcs7_decoder.py │ ├── tstamp_helper.py │ └── verifier.py └── pkcs7_models.py ├── setup.py └── x509_parse.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | X.509 Certificate Parser for Python 2 | 3 | This is probably the most complete parser of X.509 certificates in python. 4 | 5 | Requirements: 6 | pyasn1 >= 0.1.4 7 | 8 | Code is in alpha stage! Don't use for anything sensitive. I wrote it (based on 9 | previous work of colleagues) since there is no comprehensive python parser for 10 | X.509 certificates. Often python programmers had to parse openssl output. 11 | 12 | Advantages: 13 | 14 | - I find it less painful to use than parsing output of 'openssl x509' 15 | - somewhat stricter in extension parsing compared to openssl 16 | 17 | Disadvantages: 18 | 19 | - it's slow compared to openssl (about 2.3x compared to RHEL's openssl-1.0-fips) 20 | - currently not very strict in what string types in RDNs it accepts 21 | - API is still rather ugly and has no documentation yet; code is nasty at some 22 | places (and there's some old dangling code like pkcs7/verifier.py) 23 | 24 | Parsing utility: 25 | 26 | There is testing utility that shows how to extract information from certificates 27 | programatically. It can be run from command line: 28 | 29 | python pyx509/x509_parse.py path/to/certificate.der 30 | 31 | Known bugs and quirks: 32 | 33 | - name constraints don't distinguish among various GeneralName subtypes 34 | - some extensions are not shown very nicely when put in string format 35 | - not all extensions are supported 36 | - string types accepted for various RDN subelements are rather too permissive 37 | - RDN string conversion does not conform to RFC 4514 38 | - badly formed extensions are ignored if not marked critical 39 | - easy to switch to more strict behavior 40 | - other clients do this as well; RFC 5280 specifies behavior for unknown 41 | elements in extensions in appendix B.1, but does not cover all cases (e.g. 42 | element exists, but with string type different from spec) 43 | 44 | -------------------------------------------------------------------------------- /pyx509/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiviah/pyx509/a35702c3d514c96d75a1c3498307a16991cdd0d3/pyx509/__init__.py -------------------------------------------------------------------------------- /pyx509/pkcs7/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiviah/pyx509/a35702c3d514c96d75a1c3498307a16991cdd0d3/pyx509/pkcs7/__init__.py -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/DSA.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | 20 | from pyasn1.type import namedtype,univ 21 | 22 | # 7.3.3 DSA Signature Keys 23 | # 24 | #Dss-Parms ::= SEQUENCE { 25 | # p INTEGER, 26 | # q INTEGER, 27 | # g INTEGER } 28 | 29 | class DsaPubKey(univ.Integer): 30 | pass 31 | 32 | class DssParams(univ.Sequence): 33 | componentType = namedtype.NamedTypes( 34 | namedtype.NamedType("p", univ.Integer()), 35 | namedtype.NamedType("q", univ.Integer()), 36 | namedtype.NamedType("g", univ.Integer()), 37 | ) 38 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/RSA.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Created on Dec 9, 2009 21 | 22 | ''' 23 | 24 | # dslib imports 25 | from pyasn1.type import tag,namedtype,univ 26 | from pyasn1 import error 27 | 28 | class Modulus(univ.OctetString): 29 | tagSet = univ.OctetString.tagSet.tagImplicitly( 30 | tag.Tag(tag.tagClassUniversal, tag.tagFormatSimple, 0x02) 31 | ) 32 | 33 | class RsaPubKey(univ.Sequence): 34 | componentType = namedtype.NamedTypes( 35 | namedtype.NamedType("modulus", Modulus()), 36 | namedtype.NamedType("exp", univ.Integer()) 37 | ) 38 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/TST_info.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Model of TSTInfo - timestamp token sent back from TSA to the MVCR. 21 | This token is encapsulated into pkcs7 content and signed by MVCR 22 | (Postsignum Qualified QCA) 23 | ''' 24 | 25 | ''' 26 | TSTInfo ::= SEQUENCE { 27 | version INTEGER { v1(1) }, 28 | policy TSAPolicyId, 29 | messageImprint MessageImprint, 30 | -- MUST have the same value as the similar field in 31 | -- TimeStampReq 32 | serialNumber INTEGER, 33 | -- Time-Stamping users MUST be ready to accommodate integers 34 | -- up to 160 bits. 35 | genTime GeneralizedTime, 36 | accuracy Accuracy OPTIONAL, 37 | ordering BOOLEAN DEFAULT FALSE, 38 | nonce INTEGER OPTIONAL, 39 | -- MUST be present if the similar field was present 40 | -- in TimeStampReq. In that case it MUST have the same value. 41 | tsa [0] GeneralName OPTIONAL, 42 | extensions [1] IMPLICIT Extensions OPTIONAL } 43 | ''' 44 | 45 | # standard library imports 46 | import string 47 | 48 | # dslib imports 49 | from pyasn1.type import tag,namedtype,univ,char,useful 50 | from pyasn1 import error 51 | 52 | # local imports 53 | from X509_certificate import * 54 | from general_types import * 55 | from oid import oid_map as oid_map 56 | from certificate_extensions import * 57 | 58 | 59 | class MessageImprint(univ.Sequence): 60 | componentType = namedtype.NamedTypes( 61 | namedtype.NamedType("algId", AlgorithmIdentifier()), 62 | namedtype.NamedType("imprint", univ.OctetString()) 63 | ) 64 | 65 | class Accuracy(univ.Sequence): 66 | componentType = namedtype.NamedTypes( 67 | namedtype.OptionalNamedType("seconds", univ.Integer()), 68 | namedtype.OptionalNamedType("milis", univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 69 | namedtype.OptionalNamedType("micros", univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))) 70 | ) 71 | 72 | class TSAName(univ.Sequence): 73 | componentType = namedtype.NamedTypes( 74 | namedtype.NamedType("name", RDNSequence().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x4))), 75 | ) 76 | def __str__(self): 77 | return str(self.getComponentByName("name")) 78 | 79 | class TSTInfo(univ.Sequence): 80 | componentType = namedtype.NamedTypes( 81 | namedtype.NamedType("version", univ.Integer()), 82 | namedtype.NamedType("policy", univ.ObjectIdentifier()), 83 | namedtype.NamedType("messageImprint", MessageImprint()), 84 | namedtype.NamedType("serialNum", univ.Integer()), 85 | namedtype.NamedType("genTime", useful.GeneralizedTime()), 86 | namedtype.OptionalNamedType("accuracy", Accuracy()), 87 | namedtype.DefaultedNamedType("ordering", univ.Boolean('False')), 88 | namedtype.OptionalNamedType("nonce", univ.Integer()), 89 | namedtype.OptionalNamedType("tsa", TSAName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 90 | namedtype.OptionalNamedType("extensions", univ.Sequence().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))) 91 | ) 92 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/X509_certificate.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Created on Dec 3, 2009 21 | 22 | ''' 23 | 24 | # standard library imports 25 | import string 26 | 27 | # dslib imports 28 | from pyasn1.type import tag,namedtype,univ,useful 29 | from pyasn1 import error 30 | 31 | # local imports 32 | from tools import * 33 | from oid import oid_map as oid_map 34 | from general_types import * 35 | 36 | 37 | class Extension(univ.Sequence): 38 | componentType = namedtype.NamedTypes( 39 | namedtype.NamedType('extnID', univ.ObjectIdentifier()), 40 | namedtype.DefaultedNamedType('critical', univ.Boolean('False')), 41 | namedtype.NamedType('extnValue', univ.OctetString()) 42 | #namedtype.NamedType('extnValue', ExtensionValue()) 43 | ) 44 | 45 | class Extensions(univ.SequenceOf): 46 | componentType = Extension() 47 | sizeSpec = univ.SequenceOf.sizeSpec 48 | 49 | class SubjectPublicKeyInfo(univ.Sequence): 50 | componentType = namedtype.NamedTypes( 51 | namedtype.NamedType('algorithm', AlgorithmIdentifier()), 52 | namedtype.NamedType('subjectPublicKey', ConvertibleBitString()) 53 | ) 54 | 55 | class UniqueIdentifier(ConvertibleBitString): pass 56 | 57 | class Time(univ.Choice): 58 | componentType = namedtype.NamedTypes( 59 | namedtype.NamedType('utcTime', useful.UTCTime()), 60 | namedtype.NamedType('generalTime', useful.GeneralizedTime()) 61 | ) 62 | 63 | def __str__(self): 64 | return str(self.getComponent()) 65 | 66 | class Validity(univ.Sequence): 67 | componentType = namedtype.NamedTypes( 68 | namedtype.NamedType('notBefore', Time()), 69 | namedtype.NamedType('notAfter', Time()) 70 | ) 71 | 72 | class CertificateSerialNumber(univ.Integer): pass 73 | 74 | class Version(univ.Integer): 75 | namedValues = namedval.NamedValues( 76 | ('v1', 0), ('v2', 1), ('v3', 2) 77 | ) 78 | 79 | class TBSCertificate(univ.Sequence): 80 | componentType = namedtype.NamedTypes( 81 | namedtype.DefaultedNamedType('version', Version('v1', tagSet=Version.tagSet.tagExplicitly(tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0)))), 82 | namedtype.NamedType('serialNumber', CertificateSerialNumber()), 83 | namedtype.NamedType('signature', AlgorithmIdentifier()), 84 | namedtype.NamedType('issuer', Name()), 85 | namedtype.NamedType('validity', Validity()), 86 | namedtype.NamedType('subject', Name()), 87 | namedtype.NamedType('subjectPublicKeyInfo', SubjectPublicKeyInfo()), 88 | namedtype.OptionalNamedType('issuerUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), 89 | namedtype.OptionalNamedType('subjectUniqueID', UniqueIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), 90 | namedtype.OptionalNamedType('extensions', Extensions().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))) 91 | ) 92 | 93 | 94 | class Certificate(univ.Sequence): 95 | componentType = namedtype.NamedTypes( 96 | namedtype.NamedType('tbsCertificate', TBSCertificate()), 97 | namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), 98 | namedtype.NamedType('signatureValue', ConvertibleBitString()) 99 | ) 100 | 101 | class Certificates(univ.SetOf): 102 | componentType = Certificate() 103 | 104 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiviah/pyx509/a35702c3d514c96d75a1c3498307a16991cdd0d3/pyx509/pkcs7/asn1_models/__init__.py -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/att_certificate_v2.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | 20 | # standard library imports 21 | import string 22 | 23 | # dslib imports 24 | from pyasn1.type import tag,namedtype,univ 25 | from pyasn1 import error 26 | 27 | # local imports 28 | from X509_certificate import * 29 | from general_types import * 30 | from oid import oid_map as oid_map 31 | ''' 32 | ASN.1 modules from http://www.ietf.org/rfc/rfc3281.txt 33 | ''' 34 | 35 | 36 | ''' 37 | ObjectDigestInfo ::= SEQUENCE { 38 | digestedObjectType ENUMERATED { 39 | publicKey (0), 40 | publicKeyCert (1), 41 | otherObjectTypes (2) }, 42 | -- otherObjectTypes MUST NOT 43 | -- be used in this profile 44 | otherObjectTypeID OBJECT IDENTIFIER OPTIONAL, 45 | digestAlgorithm AlgorithmIdentifier, 46 | objectDigest BIT STRING 47 | } 48 | 49 | ''' 50 | class ObjectDigestInfo(univ.Sequence): 51 | componentType = namedtype.NamedTypes( 52 | namedtype.OptionalNamedType("digestedObjectType", univ.Enumerated()), 53 | namedtype.OptionalNamedType("otherObjectTypeID", univ.ObjectIdentifier()), 54 | namedtype.OptionalNamedType("digestAlgorithm", AlgorithmIdentifier()), 55 | namedtype.OptionalNamedType("objectDigest", ConvertibleBitString()), 56 | ) 57 | ''' 58 | IssuerSerial ::= SEQUENCE { 59 | issuer GeneralNames, 60 | serial CertificateSerialNumber, 61 | issuerUID UniqueIdentifier OPTIONAL 62 | } 63 | 64 | ''' 65 | class IssuerSerial(univ.Sequence): 66 | componentType = namedtype.NamedTypes( 67 | namedtype.NamedType("issuer", GeneralNames()), 68 | namedtype.NamedType("serial", CertificateSerialNumber()), 69 | namedtype.OptionalNamedType("issuerUID", UniqueIdentifier()), 70 | ) 71 | 72 | ''' 73 | Holder ::= SEQUENCE { 74 | baseCertificateID [0] IssuerSerial OPTIONAL, 75 | -- the issuer and serial number of 76 | -- the holder's Public Key Certificate 77 | entityName [1] GeneralNames OPTIONAL, 78 | -- the name of the claimant or role 79 | objectDigestInfo [2] ObjectDigestInfo OPTIONAL 80 | -- used to directly authenticate the holder, 81 | -- for example, an executable 82 | } 83 | ''' 84 | class Holder(univ.Sequence): 85 | componentType = namedtype.NamedTypes( 86 | namedtype.OptionalNamedType("baseCertificateID", IssuerSerial().\ 87 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 88 | namedtype.OptionalNamedType("entityName", GeneralNames().\ 89 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 90 | namedtype.OptionalNamedType("objectDigestInfo", ObjectDigestInfo().\ 91 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x2))), 92 | ) 93 | ''' 94 | AttCertIssuer ::= CHOICE { 95 | v1Form GeneralNames, -- MUST NOT be used in this 96 | -- profile 97 | v2Form [0] V2Form -- v2 only 98 | } 99 | 100 | V2Form ::= SEQUENCE { 101 | issuerName GeneralNames OPTIONAL, 102 | baseCertificateID [0] IssuerSerial OPTIONAL, 103 | objectDigestInfo [1] ObjectDigestInfo OPTIONAL 104 | -- issuerName MUST be present in this profile 105 | -- baseCertificateID and objectDigestInfo MUST 106 | -- NOT be present in this profile 107 | } 108 | ''' 109 | class V2Form(univ.Sequence): 110 | componentType = namedtype.NamedTypes( 111 | namedtype.OptionalNamedType("issuerName", GeneralNames()), 112 | namedtype.OptionalNamedType("basicCertificateID", IssuerSerial()\ 113 | .subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 114 | namedtype.OptionalNamedType("objectDigestInfo", ObjectDigestInfo()\ 115 | .subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 116 | 117 | ) 118 | 119 | class AttCertIssuer(univ.Choice): 120 | componentType = namedtype.NamedTypes( 121 | namedtype.NamedType("v1Form", GeneralNames()), 122 | namedtype.NamedType("v2Form", V2Form()\ 123 | .subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 124 | ) 125 | 126 | 127 | class AttrCertAttributes(univ.SequenceOf): 128 | pass 129 | 130 | 131 | ''' 132 | AttributeCertificateInfo ::= SEQUENCE { 133 | version AttCertVersion -- version is v2, 134 | holder Holder, 135 | issuer AttCertIssuer, 136 | signature AlgorithmIdentifier, 137 | serialNumber CertificateSerialNumber, 138 | attrCertValidityPeriod AttCertValidityPeriod, 139 | attributes SEQUENCE OF Attribute, 140 | issuerUniqueID UniqueIdentifier OPTIONAL, 141 | extensions Extensions OPTIONAL 142 | } 143 | 144 | ''' 145 | class ACInfo(univ.Sequence): 146 | componentType = namedtype.NamedTypes( 147 | namedtype.DefaultedNamedType("version", Version('v2')), 148 | namedtype.NamedType("holder", Holder()), 149 | namedtype.NamedType("issuer", AttCertIssuer()), 150 | namedtype.NamedType("signature", AlgorithmIdentifier()), 151 | namedtype.NamedType("serialNumber", CertificateSerialNumber()), 152 | namedtype.NamedType("attrCertValidityPeriod", Validity()), 153 | namedtype.NamedType("attributes", AttrCertAttributes()), 154 | namedtype.OptionalNamedType("issuerUniqueID", UniqueIdentifier()), 155 | namedtype.OptionalNamedType("extensions", Extensions()), 156 | ) 157 | 158 | 159 | class AttributeCertificateV2(univ.Sequence): 160 | componentType = namedtype.NamedTypes( 161 | namedtype.NamedType("acInfo", ACInfo()), 162 | namedtype.NamedType("sigAlg", AlgorithmIdentifier()), 163 | namedtype.NamedType("signature", ConvertibleBitString()) 164 | ) 165 | 166 | 167 | 168 | class CertificateChoices(univ.Choice): 169 | componentType = namedtype.NamedTypes( 170 | namedtype.NamedType("certificate", Certificate()), 171 | namedtype.NamedType("extendedC", Certificate().\ 172 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 173 | namedtype.NamedType("v1AttrCert", AttributeCertificateV2().\ 174 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 175 | namedtype.NamedType("v2AttrCert", univ.Sequence().\ 176 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x2))), 177 | namedtype.NamedType("otherCert", univ.Sequence().\ 178 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x3))) 179 | ) 180 | 181 | 182 | class CertificateSet(univ.SetOf): 183 | componentType = CertificateChoices() 184 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/certificate_extensions.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Certificate extensions specifications 21 | ''' 22 | 23 | # standard library imports 24 | import string 25 | 26 | # dslib imports 27 | from pyasn1.type import tag,namedtype,univ 28 | from pyasn1 import error 29 | 30 | # local imports 31 | from tools import * 32 | from oid import oid_map as oid_map 33 | from general_types import * 34 | 35 | 36 | #RDNS sequence otagovana A4 (constructed octet string) 37 | class IssuerName(univ.Sequence): 38 | componentType = namedtype.NamedTypes( 39 | namedtype.NamedType("name", RDNSequence().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x4))), 40 | ) 41 | 42 | class KeyId(univ.Sequence): 43 | componentType = namedtype.NamedTypes( 44 | namedtype.OptionalNamedType("keyIdentifier", univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 45 | namedtype.OptionalNamedType("authorityCertIssuer", IssuerName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 46 | namedtype.OptionalNamedType("authorityCertSerialNum", univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x2))), 47 | ) 48 | 49 | class BasicConstraints(univ.Sequence): 50 | componentType = namedtype.NamedTypes( 51 | namedtype.DefaultedNamedType("ca", univ.Boolean(False)), 52 | namedtype.OptionalNamedType("pathLen", univ.Integer()) 53 | ) 54 | 55 | class AnyQualifier(univ.Choice): 56 | componentType = namedtype.NamedTypes( 57 | namedtype.NamedType("userNotice", univ.Sequence()), 58 | namedtype.NamedType("cpsUri", char.IA5String()), 59 | ) 60 | 61 | class PolicyQualifierInfo(univ.Sequence): 62 | componentType = namedtype.NamedTypes( 63 | namedtype.NamedType("policyQualifierId", univ.ObjectIdentifier()), 64 | namedtype.OptionalNamedType("qualifier", AnyQualifier()) 65 | ) 66 | 67 | class PolicyQualifiers(univ.SequenceOf): 68 | componentType = PolicyQualifierInfo() 69 | 70 | class PolicyInformation(univ.Sequence): 71 | componentType = namedtype.NamedTypes( 72 | namedtype.NamedType("policyIdentifier", univ.ObjectIdentifier()), 73 | namedtype.OptionalNamedType("policyQualifiers", PolicyQualifiers()) 74 | ) 75 | 76 | class CertificatePolicies(univ.SequenceOf): 77 | componentType = PolicyInformation() 78 | 79 | class DpointName(univ.Choice): 80 | componentType = namedtype.NamedTypes( 81 | namedtype.NamedType("fullName", GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 82 | namedtype.NamedType("relativeToIssuer", RelativeDistinguishedName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))) 83 | ) 84 | 85 | 86 | class ReasonFlags(ConvertibleBitString): 87 | pass 88 | 89 | class DistributionPoint(univ.Sequence): 90 | componentType = namedtype.NamedTypes( 91 | namedtype.OptionalNamedType("distPoint", DpointName().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 92 | namedtype.OptionalNamedType("reasons", ReasonFlags().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))), 93 | namedtype.OptionalNamedType("issuer", GeneralNames().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x2))), 94 | ) 95 | 96 | 97 | class CRLDistributionPoints(univ.SequenceOf): 98 | componentType = DistributionPoint() 99 | 100 | class AccessDescription(univ.Sequence): 101 | componentType = namedtype.NamedTypes( 102 | namedtype.NamedType("accessMethod", univ.ObjectIdentifier()), 103 | namedtype.NamedType("accessLocation", GeneralName()), 104 | ) 105 | 106 | class AuthorityInfoAccess(univ.SequenceOf): 107 | componentType = AccessDescription() 108 | 109 | class ExtendedKeyUsage(univ.SequenceOf): 110 | componentType = univ.ObjectIdentifier() 111 | 112 | class Statement(univ.Sequence): 113 | componentType = namedtype.NamedTypes( 114 | namedtype.NamedType("stmtId", univ.ObjectIdentifier()), 115 | namedtype.OptionalNamedType("stmtInfo", univ.Any()), 116 | ) 117 | 118 | class Statements(univ.SequenceOf): 119 | componentType = Statement() 120 | 121 | class SubjectKeyId(univ.OctetString): 122 | pass 123 | 124 | class PolicyConstraints(univ.Sequence): 125 | componentType = namedtype.NamedTypes( 126 | namedtype.OptionalNamedType("requireExplicitPolicy", 127 | univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 128 | namedtype.OptionalNamedType("inhibitPolicyMapping", 129 | univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))), 130 | ) 131 | 132 | class GeneralSubtree(univ.Sequence): 133 | componentType = namedtype.NamedTypes( 134 | namedtype.NamedType("base", GeneralName()), 135 | namedtype.DefaultedNamedType("minimum", 136 | univ.Integer(0).subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 137 | namedtype.OptionalNamedType("maximum", 138 | univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x1))), 139 | ) 140 | 141 | class GeneralSubtrees(univ.SequenceOf): 142 | componentType = GeneralSubtree() 143 | 144 | class NameConstraints(univ.Sequence): 145 | componentType = namedtype.NamedTypes( 146 | namedtype.OptionalNamedType("permittedSubtrees", 147 | GeneralSubtrees().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 148 | namedtype.OptionalNamedType("excludedSubtrees", 149 | GeneralSubtrees().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 150 | ) 151 | 152 | class NetscapeCertType(univ.BitString): 153 | pass 154 | 155 | class ExtensionValue(univ.Choice): 156 | componentType = namedtype.NamedTypes( 157 | namedtype.NamedType("subjectAltName", GeneralNames()), 158 | namedtype.NamedType("authKeyId", KeyId()), 159 | namedtype.NamedType("CRLdistPoints", univ.Sequence()), 160 | namedtype.NamedType("certPolicies", univ.Sequence()), 161 | namedtype.NamedType("basicConstraints", univ.Sequence()), 162 | namedtype.NamedType("keyUsage", ConvertibleBitString()), 163 | namedtype.NamedType("qcStatements", univ.Sequence()), 164 | namedtype.NamedType("subjectKeyId", KeyId()), 165 | ) 166 | 167 | #SignedCertificateTimestampList extension is not in ASN.1, needs to be interpreted from RFC binary encoding 168 | class SCTList(univ.OctetString): 169 | pass 170 | 171 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/crl.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Model of CRL 21 | ''' 22 | 23 | ''' 24 | CertificateList ::= SEQUENCE { 25 | tbsCertList TBSCertList, 26 | signatureAlgorithm AlgorithmIdentifier, 27 | signatureValue BIT STRING } 28 | 29 | TBSCertList ::= SEQUENCE { 30 | version Version OPTIONAL, 31 | -- if present, MUST be v2 32 | signature AlgorithmIdentifier, 33 | issuer Name, 34 | thisUpdate Time, 35 | nextUpdate Time OPTIONAL, 36 | revokedCertificates SEQUENCE OF SEQUENCE { 37 | userCertificate CertificateSerialNumber, 38 | revocationDate Time, 39 | crlEntryExtensions Extensions OPTIONAL 40 | -- if present, version MUST be v2 41 | } OPTIONAL, 42 | crlExtensions [0] EXPLICIT Extensions OPTIONAL 43 | -- if present, version MUST be v2 44 | } 45 | 46 | ''' 47 | 48 | # standard library imports 49 | import string 50 | 51 | # dslib imports 52 | from pyasn1.type import tag,namedtype,univ,useful 53 | from pyasn1 import error 54 | 55 | # local imports 56 | from general_types import * 57 | from X509_certificate import * 58 | 59 | class RevokedCertInfo(univ.Sequence): 60 | ''' 61 | univ.Any type is used instead of this type to avoid 62 | unnecessary parsing. 63 | ''' 64 | componentType = namedtype.NamedTypes( 65 | namedtype.NamedType('userCertificate', CertificateSerialNumber()), 66 | namedtype.NamedType('revocationDate', Time()), 67 | namedtype.OptionalNamedType('crlEntryExts', univ.Any()) 68 | ) 69 | 70 | class RevokedCertList(univ.Any): 71 | pass 72 | 73 | class TbsCertList(univ.Sequence): 74 | componentType = namedtype.NamedTypes( 75 | namedtype.OptionalNamedType('version', Version()), 76 | namedtype.NamedType('signature', AlgorithmIdentifier()), 77 | namedtype.NamedType('issuer', Name()), 78 | namedtype.NamedType('thisUpdate', Time()), 79 | namedtype.OptionalNamedType('nextUpdate', Time()), 80 | namedtype.OptionalNamedType('revokedCertificates', RevokedCertList()), 81 | namedtype.OptionalNamedType('crlExtensions', Extensions().\ 82 | subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0x0))), 83 | ) 84 | 85 | 86 | class RevCertificateList(univ.Sequence): 87 | componentType = namedtype.NamedTypes( 88 | namedtype.NamedType('tbsCertList', TbsCertList()), 89 | namedtype.NamedType('signatureAlgorithm', AlgorithmIdentifier()), 90 | namedtype.NamedType('signatureValue', ConvertibleBitString()) 91 | ) 92 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/decoder_workarounds.py: -------------------------------------------------------------------------------- 1 | from pyasn1.type import univ 2 | from pyasn1.codec.ber import decoder as berDecoder 3 | from pyasn1.codec.der import decoder as derDecoder 4 | 5 | # Clone stock DER decoder and replace its boolean handler so that it permits 6 | # BER encoding of boolean (i.e. 0 => False, anything else => True). 7 | # According to spec, CER/DER should only accept 0 as False and 0xFF as True. 8 | # Though some authors of X.509-cert-creating software didn't get the memo. 9 | 10 | class BooleanFixDerDecoder(derDecoder.Decoder): pass 11 | 12 | # This is a tag->decoder map. We take DER map and replace its Boolean handler 13 | # with stock BER one. That will make the decoder tolerant to BER-encoded 14 | # Booleans in DER substrate. 15 | booleanFixTagMap = derDecoder.tagMap.copy() 16 | booleanFixTagMap[univ.Boolean.tagSet] = berDecoder.BooleanDecoder() 17 | 18 | # Instantiate our modified DER decoder 19 | decode = BooleanFixDerDecoder(booleanFixTagMap, derDecoder.typeMap) 20 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/digest_info.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Created on Dec 9, 2009 21 | 22 | ''' 23 | 24 | # dslib imports 25 | from pyasn1.type import tag,namedtype,univ 26 | from pyasn1 import error 27 | 28 | # local imports 29 | from general_types import AlgorithmIdentifier 30 | 31 | class DigestInfo(univ.Sequence): 32 | componentType = namedtype.NamedTypes( 33 | namedtype.NamedType("digestAgorithm", AlgorithmIdentifier()), 34 | namedtype.NamedType("digest", univ.OctetString()) 35 | ) 36 | 37 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/general_types.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Created on Dec 9, 2009 21 | 22 | ''' 23 | 24 | # dslib imports 25 | from pyasn1.type import tag,namedtype,namedval,univ,char,useful 26 | from pyasn1 import error 27 | 28 | # local imports 29 | from tools import * 30 | from oid import * 31 | 32 | 33 | class ConvertibleBitString(univ.BitString): 34 | ''' 35 | Extends uni.BitString with method that converts value 36 | to the octet string. 37 | ''' 38 | 39 | def toOctets(self): 40 | ''' 41 | Converts bit string into octets string 42 | ''' 43 | def _tuple_to_byte(tuple): 44 | return chr(int(''.join(map(str, tuple)),2)) 45 | 46 | res = '' 47 | byte_len = len(self._value) / 8 48 | for byte_idx in xrange(byte_len): 49 | bit_idx = byte_idx * 8 50 | byte_tuple = self._value[bit_idx:bit_idx + 8] 51 | byte = _tuple_to_byte(byte_tuple) 52 | res += byte 53 | return res 54 | 55 | class DirectoryString(univ.Choice): 56 | componentType = namedtype.NamedTypes( 57 | namedtype.NamedType('teletexString', char.TeletexString()), 58 | namedtype.NamedType('printableString', char.PrintableString()), 59 | namedtype.NamedType('universalString', char.UniversalString()), 60 | namedtype.NamedType('utf8String', char.UTF8String()), 61 | namedtype.NamedType('bmpString', char.BMPString()), 62 | namedtype.NamedType('ia5String', char.IA5String()), #for legacy pkcs9-email 63 | #namedtype.NamedType('gString', univ.OctetString()), 64 | namedtype.NamedType('bitString', univ.BitString()), #needed for X500 Unique Identifier, RFC 4519 65 | ) 66 | def __repr__(self): 67 | try: 68 | c = self.getComponent() 69 | return c.__str__() 70 | except: 71 | return "Choice type not chosen" 72 | def __str__(self): 73 | return repr(self) 74 | 75 | class AttributeValue(DirectoryString): pass 76 | 77 | 78 | class AttributeType(univ.ObjectIdentifier): 79 | def __str__(self): 80 | return tuple_to_OID(self._value) 81 | 82 | class AttributeTypeAndValue(univ.Sequence): 83 | componentType = namedtype.NamedTypes( 84 | namedtype.NamedType('type', AttributeType()), 85 | namedtype.NamedType('value', AttributeValue()) 86 | ) 87 | def __repr__(self): 88 | # s = "%s => %s" % [ self.getComponentByName('type'), self.getComponentByName('value')] 89 | type = self.getComponentByName('type') 90 | value = self.getComponentByName('value') 91 | s = "%s => %s" % (type,value) 92 | return s 93 | 94 | def __str__(self): 95 | return self.__repr__() 96 | 97 | class RelativeDistinguishedName(univ.SetOf): 98 | componentType = AttributeTypeAndValue() 99 | 100 | def __str__(self): 101 | buf = '' 102 | for component in self._componentValues: 103 | buf += str(component) 104 | buf += ',' 105 | buf = buf[:len(buf)-1] 106 | return buf 107 | 108 | class RDNSequence(univ.SequenceOf): 109 | componentType = RelativeDistinguishedName() 110 | 111 | def __str__(self): 112 | buf = '' 113 | for component in self._componentValues: 114 | buf += str(component) 115 | buf += ',' 116 | buf = buf[:len(buf)-1] 117 | return buf 118 | 119 | 120 | class Name(univ.Choice): 121 | componentType = namedtype.NamedTypes( 122 | namedtype.NamedType('', RDNSequence()) 123 | ) 124 | 125 | def __str__(self): 126 | return str(self.getComponent()) 127 | 128 | 129 | class AlgorithmIdentifier(univ.Sequence): 130 | componentType = namedtype.NamedTypes( 131 | namedtype.NamedType('algorithm', univ.ObjectIdentifier()), 132 | namedtype.OptionalNamedType('parameters', univ.Any()) 133 | # XXX syntax screwed? 134 | # namedtype.OptionalNamedType('parameters', univ.ObjectIdentifier()) 135 | ) 136 | def __repr__(self): 137 | tuple = self.getComponentByName('algorithm') 138 | str_oid = tuple_to_OID(tuple) 139 | return str_oid 140 | 141 | def __str__(self): 142 | return repr(self) 143 | 144 | class UniqueIdentifier(ConvertibleBitString): 145 | pass 146 | 147 | ''' 148 | GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName 149 | 150 | GeneralName ::= CHOICE { 151 | otherName [0] AnotherName, 152 | rfc822Name [1] IA5String, 153 | dNSName [2] IA5String, 154 | x400Address [3] ORAddress, 155 | directoryName [4] Name, 156 | ediPartyName [5] EDIPartyName, 157 | uniformResourceIdentifier [6] IA5String, 158 | iPAddress [7] OCTET STRING, 159 | registeredID [8] OBJECT IDENTIFIER } 160 | 161 | -- AnotherName replaces OTHER-NAME ::= TYPE-IDENTIFIER, as 162 | -- TYPE-IDENTIFIER is not supported in the '88 ASN.1 syntax 163 | 164 | AnotherName ::= SEQUENCE { 165 | type-id OBJECT IDENTIFIER, 166 | value [0] EXPLICIT ANY DEFINED BY type-id } 167 | 168 | EDIPartyName ::= SEQUENCE { 169 | nameAssigner [0] DirectoryString OPTIONAL, 170 | partyName [1] DirectoryString } 171 | 172 | ''' 173 | class GeneralName(univ.Choice): 174 | componentType = namedtype.NamedTypes( 175 | namedtype.NamedType('otherName', univ.Sequence().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 176 | namedtype.NamedType('rfc822Name', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 177 | namedtype.NamedType('dNSName', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x2))), 178 | namedtype.NamedType('x400Address', univ.Sequence().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x3))), 179 | namedtype.NamedType('directoryName', Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x4))), 180 | namedtype.NamedType('ediPartyName', univ.Sequence().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x5))), 181 | namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x6))), 182 | namedtype.NamedType('iPAddress', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x7))), 183 | namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x8))), 184 | ) 185 | 186 | class GeneralNames(univ.SequenceOf): 187 | componentType = GeneralName() 188 | def __str__(self): 189 | ret = '' 190 | for part in self._componentValues: 191 | ret+= str(part.getComponent()) 192 | ret+= ' ; ' 193 | return ret[:len(ret)-1] 194 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/oid.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Map of OIDs and their names 21 | ''' 22 | oid_map = { 23 | "1.3.14.3.2.26" : "SHA-1", 24 | "2.16.840.1.101.3.4.2.1" : "SHA-256", 25 | "2.16.840.1.101.3.4.2.2" : "SHA-384", 26 | "2.16.840.1.101.3.4.2.3" : "SHA-512", 27 | "1.2.840.113549.1.7.1" : "data", 28 | "1.2.840.113549.1.7.2" : "signedData", 29 | "1.2.840.113549.1.1.5" : "SHA1/RSA", 30 | "1.2.840.113549.1.1.1" : "RSA", 31 | "1.2.840.113549.1.1.11" : "SHA256/RSA", 32 | "1.2.840.10040.4.1" : "DSA", 33 | "1.2.840.10040.4.3" : "SHA1/DSA", 34 | 35 | "2.5.4.6" : "id-at-countryName", 36 | "2.5.4.10" : "id-at-organizationName ", 37 | "2.5.4.3" : "id-at-commonName", 38 | "2.5.4.11" : "id-at-organizationalUnitName", 39 | 40 | "2.5.29.17" : "id-ce-subjectAltName", 41 | "2.5.29.19" : "basicConstraints", 42 | "2.5.29.32" : "Certificate policies", 43 | "1.3.6.1.5.5.7.1.3" : "id-pe-qcStatements", 44 | "2.5.29.15" : "id-ce-keyUsage", 45 | "2.5.29.14" : "id-ce-subjectKeyIdentifier ", 46 | "2.5.29.31" : "id-ce-CRLDistributionPoints ", 47 | "2.5.29.35" : "id-ce-authorityKeyIdentifier ", 48 | 49 | "2.5.29.20" : "CRL Number", 50 | "2.5.29.21" : "Reason Code", 51 | "2.5.29.24" : "Invalidity Data", 52 | 53 | 54 | "1.2.840.113549.1.9.3" : "contentType", 55 | "1.2.840.113549.1.9.4" : "messageDigest", 56 | "1.2.840.113549.1.9.5" : "Signing Time" 57 | } 58 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/pkcs_signed_data.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Model for pkcs#7 v1.5 signedData content 21 | ''' 22 | 23 | # standard library imports 24 | import string 25 | 26 | # dslib imports 27 | from pyasn1.type import tag,namedtype,univ,useful 28 | from pyasn1 import error 29 | 30 | # local imports 31 | from X509_certificate import Certificates 32 | from att_certificate_v2 import CertificateSet 33 | from general_types import * 34 | from oid import oid_map as oid_map 35 | 36 | 37 | class SignedContent(univ.SequenceOf): 38 | #tagSet = univ.OctetString.tagSet.tagExplicitly( 39 | # tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0) 40 | # ) 41 | componentType = univ.OctetString() 42 | tagSet = univ.SequenceOf.tagSet.tagImplicitly( 43 | tag.Tag(tag.tagClassUniversal, tag.tagFormatConstructed, 0x04) 44 | ) 45 | def getContentValue(self): 46 | values = [] 47 | for idx in xrange(len(self)): 48 | comp = self.getComponentByPosition(idx) 49 | values.append(comp._value) 50 | return "".join(values) 51 | 52 | 53 | class Content(univ.Sequence): 54 | componentType = namedtype.NamedTypes( 55 | namedtype.NamedType("content_type", univ.ObjectIdentifier()), 56 | namedtype.NamedType("signed_content", SignedContent().\ 57 | subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))) 58 | ) 59 | 60 | class AlgIdentifiers(univ.SetOf): 61 | componentType = AlgorithmIdentifier() 62 | 63 | 64 | class SignedData(univ.Sequence): 65 | componentType = namedtype.NamedTypes( 66 | namedtype.NamedType("version", univ.Integer()), 67 | namedtype.NamedType("digestAlgs", AlgIdentifiers()), 68 | namedtype.NamedType("content", Content()) 69 | ) 70 | 71 | class MsgType(univ.ObjectIdentifier): pass 72 | 73 | class SignVersion(univ.Integer):pass 74 | 75 | class IssuerAndSerial(univ.Sequence): 76 | componentType = namedtype.NamedTypes( 77 | namedtype.NamedType("issuer", Name()), 78 | namedtype.NamedType("serialNumber", univ.Integer()) 79 | ) 80 | 81 | class AuthAttributeValue(univ.SetOf): 82 | #componentType = namedtype.NamedTypes( 83 | # namedtype.NamedType('', univ.Any()) 84 | # ) 85 | #componentType = univ.Any() 86 | 87 | def __str__(self): 88 | ''' 89 | Return string of first element in this set 90 | ''' 91 | return str(self.getComponentByPosition(0)) 92 | 93 | class AuthAttribute(univ.Sequence): 94 | componentType = namedtype.NamedTypes( 95 | namedtype.NamedType('type', univ.ObjectIdentifier()), 96 | namedtype.NamedType('value', AuthAttributeValue()) 97 | ) 98 | 99 | 100 | class Attributes(univ.SetOf): 101 | componentType = AuthAttribute() 102 | 103 | class SignerInfo(univ.Sequence): 104 | componentType = namedtype.NamedTypes( 105 | namedtype.NamedType("version", SignVersion()), 106 | namedtype.NamedType("issuerAndSerialNum", IssuerAndSerial()), 107 | namedtype.NamedType("digestAlg", AlgorithmIdentifier()), 108 | namedtype.OptionalNamedType("authAttributes", Attributes().\ 109 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 110 | namedtype.NamedType("encryptAlg", AlgorithmIdentifier()), 111 | namedtype.NamedType("signature", univ.OctetString()), 112 | namedtype.OptionalNamedType("unauthAttributes", Attributes().\ 113 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))) 114 | ) 115 | 116 | class SignerInfos(univ.SetOf): 117 | componentType = SignerInfo() 118 | 119 | class Crl(univ.Sequence): 120 | pass 121 | 122 | class Crls(univ.Set): 123 | componentType = Crl() 124 | 125 | class V1Content(univ.Sequence): 126 | componentType = namedtype.NamedTypes( 127 | namedtype.NamedType("version", univ.Integer()), 128 | namedtype.NamedType("digestAlgs", AlgIdentifiers()), 129 | namedtype.NamedType("content", Content()), 130 | namedtype.OptionalNamedType("certificates", Certificates().\ 131 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 132 | namedtype.OptionalNamedType("crls", Crls().\ 133 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 134 | namedtype.NamedType("signerInfos", SignerInfos()) 135 | ) 136 | 137 | class Message(univ.Sequence): 138 | componentType = namedtype.NamedTypes( 139 | namedtype.NamedType("type", MsgType()), 140 | namedtype.NamedType("content", V1Content().\ 141 | subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))) 142 | ) 143 | #################################### 144 | ####### TIMESTAMP MODEL ############ 145 | #################################### 146 | ''' 147 | version CMSVersion, 148 | digestAlgorithms DigestAlgorithmIdentifiers, 149 | encapContentInfo EncapsulatedContentInfo, 150 | certificates [0] IMPLICIT CertificateSet OPTIONAL, 151 | crls [1] IMPLICIT RevocationInfoChoices OPTIONAL, 152 | signerInfos SignerInfos 153 | ''' 154 | class EncapsulatedContent(univ.Sequence): 155 | componentType = namedtype.NamedTypes( 156 | namedtype.NamedType("eContentType", univ.ObjectIdentifier()), 157 | namedtype.OptionalNamedType("eContent", univ.OctetString().\ 158 | subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 159 | 160 | ) 161 | 162 | class QtsContent(univ.Sequence): 163 | componentType = namedtype.NamedTypes( 164 | namedtype.NamedType("version", univ.Integer()), 165 | namedtype.NamedType("digestAlgorithms", AlgIdentifiers()), 166 | namedtype.NamedType("encapsulatedContentInfo", EncapsulatedContent()), 167 | namedtype.OptionalNamedType("certificates", CertificateSet().\ 168 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))), 169 | namedtype.OptionalNamedType("crls", Crls().\ 170 | subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x1))), 171 | namedtype.NamedType("signerInfos", SignerInfos()), 172 | ) 173 | 174 | class Qts(univ.Sequence): 175 | componentType = namedtype.NamedTypes( 176 | namedtype.NamedType("type", MsgType()), 177 | namedtype.NamedType("content", QtsContent().\ 178 | subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0x0))) 179 | ) 180 | -------------------------------------------------------------------------------- /pyx509/pkcs7/asn1_models/tools.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Some useful tools for working with ASN1 components. 21 | ''' 22 | 23 | # dslib imports 24 | from decoder_workarounds import decode 25 | from pyasn1 import error 26 | 27 | # local imports 28 | from RSA import RsaPubKey 29 | from DSA import DssParams, DsaPubKey 30 | 31 | 32 | def tuple_to_OID(tuple): 33 | """ 34 | Converts OID tuple to OID string 35 | """ 36 | l = len(tuple) 37 | buf = '' 38 | for idx in xrange(l): 39 | if (idx < l-1): 40 | buf += str(tuple[idx]) + '.' 41 | else: 42 | buf += str(tuple[idx]) 43 | return buf 44 | 45 | def get_RSA_pub_key_material(subjectPublicKeyAsn1): 46 | ''' 47 | Extracts modulus and public exponent from 48 | ASN1 bitstring component subjectPublicKey 49 | ''' 50 | # create template for decoder 51 | rsa_key = RsaPubKey() 52 | # convert ASN1 subjectPublicKey component from BITSTRING to octets 53 | pubkey = subjectPublicKeyAsn1.toOctets() 54 | 55 | key = decode(pubkey, asn1Spec=rsa_key)[0] 56 | 57 | mod = key.getComponentByName("modulus")._value 58 | exp = key.getComponentByName("exp")._value 59 | 60 | return {'mod': mod, 'exp': exp} 61 | 62 | def get_DSA_pub_key_material(subjectPublicKeyAsn1, parametersAsn1): 63 | ''' 64 | Extracts DSA parameters p, q, g from 65 | ASN1 bitstring component subjectPublicKey and parametersAsn1 from 66 | 'parameters' field of AlgorithmIdentifier. 67 | ''' 68 | pubkey = subjectPublicKeyAsn1.toOctets() 69 | 70 | key = decode(pubkey, asn1Spec=DsaPubKey())[0] 71 | parameters = decode(str(parametersAsn1), asn1Spec=DssParams())[0] 72 | paramDict = {"pub": int(key)} 73 | 74 | for param in ['p', 'q', 'g']: 75 | paramDict[param] = parameters.getComponentByName(param)._value 76 | 77 | return paramDict 78 | 79 | -------------------------------------------------------------------------------- /pyx509/pkcs7/debug.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Created on Dec 9, 2009 21 | 22 | ''' 23 | def show_bytes(string): 24 | print '--------------' 25 | for byte in string: 26 | print hex(ord(byte)), 27 | print '\n--------------' 28 | 29 | def write_to_file(what, where): 30 | ff = open(where,"w") 31 | ff.write(str(what)) 32 | ff.close() 33 | 34 | -------------------------------------------------------------------------------- /pyx509/pkcs7/digest.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | 20 | # standard library imports 21 | import hashlib 22 | import logging 23 | logger = logging.getLogger("pkcs7.digest") 24 | import base64 25 | 26 | RSA_NAME = "RSA" 27 | SHA1_NAME = "SHA-1" 28 | SHA256_NAME = "SHA-256" 29 | SHA384_NAME = "SHA-384" 30 | SHA512_NAME = "SHA-512" 31 | 32 | def calculate_digest(data, alg): 33 | ''' 34 | Calculates digest according to algorithm 35 | ''' 36 | digest_alg = None 37 | if (alg == SHA1_NAME): 38 | digest_alg = hashlib.sha1() 39 | 40 | if (alg == SHA256_NAME): 41 | digest_alg = hashlib.sha256() 42 | 43 | if (alg == SHA384_NAME): 44 | digest_alg = hashlib.sha384() 45 | 46 | if (alg == SHA512_NAME): 47 | digest_alg = hashlib.sha512() 48 | 49 | if digest_alg is None: 50 | logger.error("Unknown digest algorithm : %s" % alg) 51 | return None 52 | 53 | digest_alg.update(data) 54 | dg = digest_alg.digest() 55 | 56 | logger.debug("Calculated hash from input data: %s" % base64.b64encode(dg)) 57 | return dg 58 | -------------------------------------------------------------------------------- /pyx509/pkcs7/pkcs7_decoder.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Decoding of PKCS7 messages 21 | ''' 22 | 23 | from cStringIO import StringIO 24 | 25 | # dslib imports 26 | from pkcs7.asn1_models.decoder_workarounds import decode 27 | from pyasn1 import error 28 | 29 | # local imports 30 | from asn1_models.pkcs_signed_data import * 31 | from asn1_models.digest_info import * 32 | from asn1_models.TST_info import * 33 | 34 | 35 | class StringView(object): 36 | 37 | def __init__(self, string, start, end): 38 | self._string = string 39 | self._start = start 40 | if end == None: 41 | self._end = len(string) 42 | else: 43 | self._end = end 44 | 45 | def __len__(self): 46 | return self._end - self._start 47 | 48 | def __getitem__(self, key): 49 | if type(key) == int: 50 | if key < 0: 51 | self._string.seek(self._end+key) 52 | return self._string.read(1) 53 | else: 54 | if key >= (self._end - self._start): 55 | raise IndexError() 56 | self._string.seek(self._start+key) 57 | return self._string.read(1) 58 | elif type(key) == slice: 59 | if key.stop == None: 60 | end = self._end 61 | elif key.stop < 0: 62 | end = self._end+key.stop 63 | else: 64 | end = self._start+key.stop 65 | start = self._start+(key.start or 0) 66 | return StringView(self._string, start=start, end=end) 67 | else: 68 | raise IndexError() 69 | 70 | def __str__(self): 71 | self._string.seek(self._start) 72 | return self._string.read(self._end-self._start) 73 | 74 | def __nonzero__(self): 75 | return len(self) 76 | 77 | 78 | def decode_msg(message): 79 | ''' 80 | Decodes message in DER encoding. 81 | Returns ASN1 message object 82 | ''' 83 | # create template for decoder 84 | msg = Message() 85 | # decode pkcs signed message 86 | mess_obj = StringIO(message) 87 | mess_view = StringView(mess_obj, 0, len(message)) 88 | decoded = decode(mess_view, asn1Spec=msg) 89 | message = decoded[0] 90 | return message 91 | 92 | 93 | def decode_qts(qts_bytes): 94 | ''' 95 | Decodes qualified timestamp 96 | ''' 97 | qts = Qts() 98 | decoded = decode(qts_bytes,asn1Spec=qts) 99 | qts = decoded[0] 100 | 101 | return qts 102 | 103 | 104 | def decode_tst(tst_bytes): 105 | ''' 106 | Decodes Timestamp Token 107 | ''' 108 | tst = TSTInfo() 109 | decoded = decode(tst_bytes,asn1Spec=tst) 110 | tst = decoded[0] 111 | 112 | return tst 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /pyx509/pkcs7/tstamp_helper.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | ''' 20 | Module for parsing dmQTimestamp. 21 | dmQtimestamp is base64 encoded DER pkcs7 document containing 22 | signedData component, so it is the same format as the format 23 | of signed data message. Version of content is '3', so there are small 24 | differences. 25 | ''' 26 | 27 | # standard library imports 28 | import logging 29 | logger = logging.getLogger("pkcs7.tstamp_helper") 30 | import base64 31 | 32 | # dslib imports 33 | from dslib.certs.cert_finder import * 34 | from dslib import models 35 | 36 | # local imports 37 | import pkcs7_decoder 38 | import verifier 39 | 40 | 41 | def parse_qts(dmQTimestamp, verify=False): 42 | ''' 43 | Parses QTimestamp and verifies it. 44 | Returns result of verification and TimeStampTOken instance. 45 | ''' 46 | ts = base64.b64decode(dmQTimestamp) 47 | 48 | qts = pkcs7_decoder.decode_qts(ts) 49 | verif_result = None 50 | #if we want to verify the timestamp 51 | if (verify): 52 | verif_result = verifier.verify_qts(qts) 53 | if verif_result: 54 | logger.info("QTimeStamp verified") 55 | else: 56 | logger.error("QTimeStamp verification failed") 57 | else: 58 | logger.info("Verification of timestamp skipped") 59 | 60 | tstData = qts.getComponentByName("content").getComponentByName("encapsulatedContentInfo").getComponentByName("eContent")._value 61 | tstinfo = pkcs7_decoder.decode_tst(tstData) 62 | 63 | t = models.TimeStampToken(tstinfo) 64 | 65 | certificates = qts.getComponentByName("content").getComponentByName("certificates") 66 | # get the signer info and attach signing certificates to the TSTinfo 67 | signer_infos = qts.getComponentByName("content").getComponentByName("signerInfos") 68 | for signer_info in signer_infos: 69 | id = signer_info.getComponentByName("issuerAndSerialNum").\ 70 | getComponentByName("serialNumber")._value 71 | cert = find_cert_by_serial(id, certificates) 72 | if cert is None: 73 | logger.error("No certificate found for timestamp signer") 74 | continue 75 | 76 | t.asn1_certificates.append(cert) 77 | 78 | return verif_result, t 79 | -------------------------------------------------------------------------------- /pyx509/pkcs7/verifier.py: -------------------------------------------------------------------------------- 1 | from certs import cert_finder 2 | 3 | #* pyx509 - Python library for parsing X.509 4 | #* Copyright (C) 2009-2010 CZ.NIC, z.s.p.o. (http://www.nic.cz) 5 | #* 6 | #* This library is free software; you can redistribute it and/or 7 | #* modify it under the terms of the GNU Library General Public 8 | #* License as published by the Free Software Foundation; either 9 | #* version 2 of the License, or (at your option) any later version. 10 | #* 11 | #* This library is distributed in the hope that it will be useful, 12 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 | #* Library General Public License for more details. 15 | #* 16 | #* You should have received a copy of the GNU Library General Public 17 | #* License along with this library; if not, write to the Free 18 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | #* 20 | ''' 21 | Verifying of PKCS7 messages 22 | ''' 23 | 24 | # standard library imports 25 | import logging 26 | logger = logging.getLogger('pkcs7.verifier') 27 | import string 28 | 29 | # dslib imports 30 | from pyasn1.codec.der import encoder 31 | from pyasn1 import error 32 | from dslib.certs.cert_finder import * 33 | 34 | # local imports 35 | from asn1_models.tools import * 36 | from asn1_models.oid import * 37 | from asn1_models.X509_certificate import * 38 | from asn1_models.pkcs_signed_data import * 39 | from asn1_models.RSA import * 40 | from asn1_models.digest_info import * 41 | from rsa_verifier import * 42 | from debug import * 43 | from digest import * 44 | 45 | 46 | MESSAGE_DIGEST_KEY = "1.2.840.113549.1.9.4" 47 | 48 | def _prepare_auth_attributes_to_digest(auth_attributes_instance): 49 | """ 50 | Prepares autheticated attributes field to digesting process. 51 | Replaces implicit tag with SET tag. 52 | """ 53 | implicit_tag = chr(0xa0) # implicit tag of the set of authAtt 54 | set_tag = chr(0x31) # tag of the ASN type "set" 55 | 56 | # encode authentcatdAttributes instance into DER 57 | attrs = encoder.encode(auth_attributes_instance) 58 | # remove implicit tag 59 | if (attrs[0] == implicit_tag): 60 | attrs = attrs.lstrip(implicit_tag) 61 | attrs = str(set_tag) + attrs 62 | 63 | return attrs 64 | 65 | 66 | def _get_key_material(certificate): 67 | """ 68 | Extracts public key material and alg. name from certificate. 69 | Certificate is pyasn1 object Certificate 70 | """ 71 | pubKey = cert_finder._get_tbs_certificate(certificate).\ 72 | getComponentByName("subjectPublicKeyInfo").\ 73 | getComponentByName("subjectPublicKey") 74 | 75 | signing_alg = str(cert_finder._get_tbs_certificate(certificate).\ 76 | getComponentByName("subjectPublicKeyInfo").\ 77 | getComponentByName("algorithm")) 78 | 79 | algorithm = None 80 | if oid_map.has_key(signing_alg): 81 | algorithm = oid_map[signing_alg] 82 | 83 | logger.debug("Extracting key material form public key:") 84 | 85 | if (algorithm is None): 86 | logger.error("Signing algorithm is: unknown OID: %s" % signing_alg) 87 | raise Exception("Unrecognized signing algorithm") 88 | else: 89 | logger.debug("Signing algorithm is: %s" % algorithm) 90 | 91 | key_material = None 92 | if (algorithm == RSA_NAME): 93 | key_material = get_RSA_pub_key_material(pubKey) 94 | 95 | return algorithm, key_material 96 | 97 | def _get_digest_algorithm(signer_info): 98 | ''' 99 | Extracts digest algorithm from signerInfo component. 100 | Returns algorithm's name or raises Exception 101 | ''' 102 | digest_alg = str(signer_info.getComponentByName("digestAlg")) 103 | result = None 104 | if oid_map.has_key(digest_alg): 105 | result = oid_map[digest_alg] 106 | if result is None: 107 | logger.error("Unknown digest algorithm: %s" % digest_alg) 108 | raise Exception("Unrecognized digest algorithm") 109 | 110 | return result 111 | 112 | def _verify_data(data, certificates, signer_infos): 113 | result = False 114 | for signer_info in signer_infos: 115 | id = signer_info.getComponentByName("issuerAndSerialNum").\ 116 | getComponentByName("serialNumber")._value 117 | cert = find_cert_by_serial(id, certificates) 118 | 119 | if cert is None: 120 | raise Exception("No certificate found for serial num %d" % id) 121 | 122 | sig_algorithm, key_material = _get_key_material(cert) 123 | digest_alg = _get_digest_algorithm(signer_info) 124 | 125 | auth_attributes = signer_info.getComponentByName("authAttributes") 126 | 127 | if auth_attributes is None: 128 | data_to_verify = data 129 | else: 130 | for attr in auth_attributes: 131 | # get the messageDigest field of autheticatedAttributes 132 | type = str(attr.getComponentByName("type")) 133 | if (type == MESSAGE_DIGEST_KEY): 134 | value = str(attr.getComponentByName("value")) 135 | # calculate hash of the content of the PKCS7 msg 136 | # to compare it with the message digest in authAttr 137 | calculated = calculate_digest(data, digest_alg) 138 | if (value != calculated): 139 | raise Exception("Digest in authenticated attributes differs\ 140 | from the digest of message!") 141 | # prepare authAttributes to verification - change some headers in it 142 | data_to_verify = _prepare_auth_attributes_to_digest(auth_attributes) 143 | 144 | data_to_verify = calculate_digest(data_to_verify, digest_alg) 145 | #print base64.b64encode(data_to_verify) 146 | signature = signer_info.getComponentByName("signature")._value 147 | 148 | if (sig_algorithm == RSA_NAME): 149 | r = rsa_verify(data_to_verify, signature, key_material) 150 | if not r: 151 | logger.debug("Verification of signature with id %d failed"%id) 152 | return False 153 | else: 154 | result = True 155 | # Note: here we should not have unknown signing algorithm 156 | # .....only RSA for now 157 | return result 158 | 159 | def verify_msg(asn1_pkcs7_msg): 160 | ''' 161 | Method verifies decoded message (built from pyasn1 objects) 162 | Input is decoded pkcs7 message. 163 | ''' 164 | message_content = asn1_pkcs7_msg.getComponentByName("content") 165 | 166 | signer_infos = message_content.getComponentByName("signerInfos") 167 | certificates = message_content.getComponentByName("certificates") 168 | msg = message_content.\ 169 | getComponentByName("content").\ 170 | getComponentByName("signed_content").getContentValue() 171 | 172 | return _verify_data(msg, certificates, signer_infos) 173 | 174 | 175 | def verify_qts(asn1_qts): 176 | qts_content = asn1_qts.getComponentByName("content") 177 | 178 | signer_infos = qts_content.getComponentByName("signerInfos") 179 | certificates = qts_content.getComponentByName("certificates") 180 | msg = qts_content.\ 181 | getComponentByName("encapsulatedContentInfo").\ 182 | getComponentByName("eContent")._value 183 | 184 | return _verify_data(msg, certificates, signer_infos) 185 | 186 | -------------------------------------------------------------------------------- /pyx509/pkcs7_models.py: -------------------------------------------------------------------------------- 1 | 2 | #* pyx509 - Python library for parsing X.509 3 | #* Copyright (C) 2009-2012 CZ.NIC, z.s.p.o. (http://www.nic.cz) 4 | #* 5 | #* This library is free software; you can redistribute it and/or 6 | #* modify it under the terms of the GNU Library General Public 7 | #* License as published by the Free Software Foundation; either 8 | #* version 2 of the License, or (at your option) any later version. 9 | #* 10 | #* This library is distributed in the hope that it will be useful, 11 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | #* Library General Public License for more details. 14 | #* 15 | #* You should have received a copy of the GNU Library General Public 16 | #* License along with this library; if not, write to the Free 17 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | #* 19 | 20 | ''' 21 | Created on Dec 11, 2009 22 | 23 | ''' 24 | 25 | from pyasn1.error import PyAsn1Error 26 | from pkcs7.asn1_models.tools import * 27 | from pkcs7.asn1_models.oid import * 28 | from pkcs7.asn1_models.tools import * 29 | from pkcs7.asn1_models.X509_certificate import * 30 | from pkcs7.asn1_models.certificate_extensions import * 31 | from pkcs7.debug import * 32 | from pkcs7.asn1_models.decoder_workarounds import decode 33 | import datetime, time 34 | import collections, struct 35 | 36 | 37 | class CertificateError(Exception): 38 | pass 39 | 40 | 41 | class Name(): 42 | ''' 43 | Represents Name (structured, tagged). 44 | This is a dictionary. Keys are types of names (mapped from OID to name if 45 | known, see _oid2Name below, otherwise numeric). Values are arrays containing 46 | the names that mapped to given type (because having more values of one type, 47 | e.g. multiple CNs is common). 48 | ''' 49 | _oid2Name = { 50 | "2.5.4.3": "CN", 51 | "2.5.4.6": "C", 52 | "2.5.4.7": "L", 53 | "2.5.4.8": "ST", 54 | "2.5.4.10": "O", 55 | "2.5.4.11": "OU", 56 | 57 | "2.5.4.45": "X500UID", 58 | "1.2.840.113549.1.9.1": "email", 59 | "2.5.4.17": "zip", 60 | "2.5.4.9": "street", 61 | "2.5.4.15": "businessCategory", 62 | "2.5.4.5": "serialNumber", 63 | "2.5.4.43": "initials", 64 | "2.5.4.44": "generationQualifier", 65 | "2.5.4.4": "surname", 66 | "2.5.4.42": "givenName", 67 | "2.5.4.12": "title", 68 | "2.5.4.46": "dnQualifier", 69 | "2.5.4.65": "pseudonym", 70 | "0.9.2342.19200300.100.1.25": "DC", 71 | } 72 | 73 | def __init__(self, name): 74 | self.__attributes = {} 75 | for name_part in name: 76 | for attr in name_part: 77 | type = str(attr.getComponentByPosition(0).getComponentByName('type')) 78 | value = str(attr.getComponentByPosition(0).getComponentByName('value')) 79 | 80 | #use numeric OID form only if mapping is not known 81 | typeStr = Name._oid2Name.get(type) or type 82 | values = self.__attributes.get(typeStr) 83 | if values is None: 84 | self.__attributes[typeStr] = [value] 85 | else: 86 | values.append(value) 87 | 88 | def __str__(self): 89 | ''' Returns the Distinguished name as string. The string for the same 90 | set of attributes is always the same. 91 | ''' 92 | #There is no consensus whether RDNs in DN are ordered or not, this way 93 | #we will have all sets having same components mapped to identical string. 94 | valueStrings = [] 95 | for key in sorted(self.__attributes.keys()): 96 | values = sorted(self.__attributes.get(key)) 97 | valuesStr = ", ".join(["%s=%s" % (key, value) for value in values]) 98 | valueStrings.append(valuesStr) 99 | 100 | return ", ".join(valueStrings) 101 | 102 | 103 | def get_attributes(self): 104 | return self.__attributes.copy() 105 | 106 | class ValidityInterval(): 107 | ''' 108 | Validity interval of a certificate. Values are UTC times. 109 | Attributes: 110 | -valid_from 111 | -valid_to 112 | ''' 113 | def __init__(self, validity): 114 | self.valid_from = self._getGeneralizedTime( 115 | validity.getComponentByName("notBefore")) 116 | self.valid_to = self._getGeneralizedTime( 117 | validity.getComponentByName("notAfter")) 118 | 119 | def get_valid_from_as_datetime(self): 120 | return self.parse_date(self.valid_from) 121 | 122 | def get_valid_to_as_datetime(self): 123 | return self.parse_date(self.valid_to) 124 | 125 | @staticmethod 126 | def _getGeneralizedTime(timeComponent): 127 | """Return time from Time component in YYYYMMDDHHMMSSZ format""" 128 | if timeComponent.getName() == "generalTime": #from pkcs7.asn1_models.X509_certificate.Time 129 | #already in YYYYMMDDHHMMSSZ format 130 | return timeComponent.getComponent()._value 131 | else: #utcTime 132 | #YYMMDDHHMMSSZ format 133 | #UTCTime has only short year format (last two digits), so add 134 | #19 or 20 to make it "full" year; by RFC 5280 it's range 1950..2049 135 | timeValue = timeComponent.getComponent()._value 136 | shortyear = int(timeValue[:2]) 137 | return (shortyear >= 50 and "19" or "20") + timeValue 138 | 139 | @classmethod 140 | def parse_date(cls, date): 141 | """ 142 | parses date string and returns a datetime object; 143 | """ 144 | year = int(date[:4]) 145 | month = int(date[4:6]) 146 | day = int(date[6:8]) 147 | hour = int(date[8:10]) 148 | minute = int(date[10:12]) 149 | try: 150 | #seconds must be present per RFC 5280, but some braindead certs 151 | #omit it 152 | second = int(date[12:14]) 153 | except (ValueError, IndexError): 154 | second = 0 155 | return datetime.datetime(year, month, day, hour, minute, second) 156 | 157 | class PublicKeyInfo(): 158 | ''' 159 | Represents information about public key. 160 | Expects RSA or DSA. 161 | Attributes: 162 | - alg (OID string identifier of algorithm) 163 | - key (dict of parameter name to value; keys "mod", "exp" for RSA and 164 | "pub", "p", "q", "g" for DSA) 165 | - algType - one of the RSA, DSA "enum" below 166 | ''' 167 | UNKNOWN = -1 168 | RSA = 0 169 | DSA = 1 170 | 171 | def __init__(self, public_key_info): 172 | algorithm = public_key_info.getComponentByName("algorithm") 173 | parameters = algorithm.getComponentByName("parameters") 174 | 175 | self.alg = str(algorithm) 176 | bitstr_key = public_key_info.getComponentByName("subjectPublicKey") 177 | 178 | if self.alg == "1.2.840.113549.1.1.1": 179 | self.key = get_RSA_pub_key_material(bitstr_key) 180 | self.algType = PublicKeyInfo.RSA 181 | self.algName = "RSA" 182 | elif self.alg == "1.2.840.10040.4.1": 183 | self.key = get_DSA_pub_key_material(bitstr_key, parameters) 184 | self.algType = PublicKeyInfo.DSA 185 | self.algName = "DSA" 186 | else: 187 | self.key = {} 188 | self.algType = PublicKeyInfo.UNKNOWN 189 | self.algName = self.alg 190 | 191 | class SubjectAltNameExt(): 192 | ''' 193 | Subject alternative name extension. 194 | ''' 195 | def __init__(self, asn1_subjectAltName): 196 | # Creates a dictionary for the component types found in 197 | # SubjectAltName. Each dictionary entry is a list of names 198 | self.values = collections.defaultdict(list) 199 | for gname in asn1_subjectAltName: 200 | component_type = gname.getName() 201 | if component_type == 'iPAddress': 202 | name = self.mk_ip_addr(gname.getComponent()) 203 | else: 204 | name = unicode(str(gname.getComponent()), 'utf-8') 205 | self.values[component_type].append(name) 206 | 207 | def mk_ip_addr(self, octets): 208 | # Converts encoded ipv4 or ipv6 octents into printable strings. 209 | octet_len = len(octets) 210 | octets_as_ints = struct.unpack("B"*octet_len, str(octets)) 211 | if octet_len == 4: 212 | octets_as_str = map(str, octets_as_ints) 213 | return ".".join(octets_as_str) 214 | else: 215 | # IPV6 style addresses 216 | # See http://tools.ietf.org/html/rfc2373#section-2.2 217 | to_hex = lambda x: "%02X" % x 218 | address_chunks = ["".join(map(to_hex, octets_as_ints[x:x+2])) 219 | for x in range(octet_len / 2)] 220 | return ":".join(address_chunks) 221 | 222 | 223 | 224 | class BasicConstraintsExt(): 225 | ''' 226 | Basic constraints of this certificate - is it CA and maximal chain depth. 227 | ''' 228 | def __init__(self, asn1_bConstraints): 229 | self.ca = bool(asn1_bConstraints.getComponentByName("ca")._value) 230 | self.max_path_len = None 231 | if asn1_bConstraints.getComponentByName("pathLen") is not None: 232 | self.max_path_len = asn1_bConstraints.getComponentByName("pathLen")._value 233 | 234 | 235 | class KeyUsageExt(): 236 | ''' 237 | Key usage extension. 238 | ''' 239 | def __init__(self, asn1_keyUsage): 240 | self.digitalSignature = False # (0), 241 | self.nonRepudiation = False # (1), 242 | self.keyEncipherment = False # (2), 243 | self.dataEncipherment = False # (3), 244 | self.keyAgreement = False # (4), 245 | self.keyCertSign = False # (5), 246 | self.cRLSign = False # (6), 247 | self.encipherOnly = False # (7), 248 | self.decipherOnly = False # (8) 249 | 250 | bits = asn1_keyUsage._value 251 | try: 252 | if (bits[0]): self.digitalSignature = True 253 | if (bits[1]): self.nonRepudiation = True 254 | if (bits[2]): self.keyEncipherment = True 255 | if (bits[3]): self.dataEncipherment = True 256 | if (bits[4]): self.keyAgreement = True 257 | if (bits[5]): self.keyCertSign = True 258 | if (bits[6]): self.cRLSign = True 259 | if (bits[7]): self.encipherOnly = True 260 | if (bits[8]): self.decipherOnly = True 261 | except IndexError: 262 | return 263 | 264 | class ExtendedKeyUsageExt(): 265 | ''' 266 | Extended key usage extension. 267 | ''' 268 | #The values of the _keyPurposeAttrs dict will be set to True/False as 269 | #attributes of this objects depending on whether the extKeyUsage lists them. 270 | _keyPurposeAttrs = { 271 | "1.3.6.1.5.5.7.3.1": "serverAuth", 272 | "1.3.6.1.5.5.7.3.2": "clientAuth", 273 | "1.3.6.1.5.5.7.3.3": "codeSigning", 274 | "1.3.6.1.5.5.7.3.4": "emailProtection", 275 | "1.3.6.1.5.5.7.3.5": "ipsecEndSystem", 276 | "1.3.6.1.5.5.7.3.6": "ipsecTunnel", 277 | "1.3.6.1.5.5.7.3.7": "ipsecUser", 278 | "1.3.6.1.5.5.7.3.8": "timeStamping", 279 | } 280 | 281 | def __init__(self, asn1_extKeyUsage): 282 | usageOIDs = set([tuple_to_OID(usageOID) for usageOID in asn1_extKeyUsage]) 283 | 284 | for (oid, attr) in ExtendedKeyUsageExt._keyPurposeAttrs.items(): 285 | setattr(self, attr, oid in usageOIDs) 286 | 287 | class AuthorityKeyIdExt(): 288 | ''' 289 | Authority Key identifier extension. 290 | Identifies key of the authority which was used to sign this certificate. 291 | ''' 292 | def __init__(self, asn1_authKeyId): 293 | if (asn1_authKeyId.getComponentByName("keyIdentifier")) is not None: 294 | self.key_id = asn1_authKeyId.getComponentByName("keyIdentifier")._value 295 | if (asn1_authKeyId.getComponentByName("authorityCertSerialNum")) is not None: 296 | self.auth_cert_sn = asn1_authKeyId.getComponentByName("authorityCertSerialNum")._value 297 | if (asn1_authKeyId.getComponentByName("authorityCertIssuer")) is not None: 298 | issuer = asn1_authKeyId.getComponentByName("authorityCertIssuer") 299 | iss = str(issuer.getComponentByName("name")) 300 | self.auth_cert_issuer = iss 301 | 302 | class SubjectKeyIdExt(): 303 | ''' 304 | Subject Key Identifier extension. Just the octet string. 305 | ''' 306 | def __init__(self, asn1_subKey): 307 | self.subject_key_id = asn1_subKey._value 308 | 309 | class PolicyQualifier(): 310 | ''' 311 | Certificate policy qualifier. Consist of id and 312 | own qualifier (id-qt-cps | id-qt-unotice). 313 | ''' 314 | def __init__(self, asn1_pQual): 315 | self.id = tuple_to_OID(asn1_pQual.getComponentByName("policyQualifierId")) 316 | if asn1_pQual.getComponentByName("qualifier") is not None: 317 | qual = asn1_pQual.getComponentByName("qualifier") 318 | self.qualifier = None 319 | # this is a choice - only one of following types will be non-null 320 | 321 | comp = qual.getComponentByName("cpsUri") 322 | if comp is not None: 323 | self.qualifier = str(comp) 324 | # not parsing userNotice for now 325 | #comp = qual.getComponentByName("userNotice") 326 | #if comp is not None: 327 | # self.qualifier = comp 328 | 329 | class AuthorityInfoAccessExt(): 330 | ''' 331 | Authority information access. 332 | Instance variables: 333 | - id - accessMethod OID as string 334 | - access_location as string 335 | - access_method as string if the OID is known (None otherwise) 336 | ''' 337 | _accessMethods = { 338 | "1.3.6.1.5.5.7.48.1": "ocsp", 339 | "1.3.6.1.5.5.7.48.2": "caIssuers", 340 | } 341 | 342 | def __init__(self, asn1_authInfo): 343 | self.id = tuple_to_OID(asn1_authInfo.getComponentByName("accessMethod")) 344 | self.access_location = str(asn1_authInfo.getComponentByName("accessLocation").getComponent()) 345 | self.access_method = AuthorityInfoAccessExt._accessMethods.get(self.id) 346 | pass 347 | 348 | class CertificatePolicyExt(): 349 | ''' 350 | Certificate policy extension. 351 | COnsist of id and qualifiers. 352 | ''' 353 | def __init__(self, asn1_certPol): 354 | self.id = tuple_to_OID(asn1_certPol.getComponentByName("policyIdentifier")) 355 | self.qualifiers = [] 356 | if (asn1_certPol.getComponentByName("policyQualifiers")): 357 | qualifiers = asn1_certPol.getComponentByName("policyQualifiers") 358 | self.qualifiers = [PolicyQualifier(pq) for pq in qualifiers] 359 | 360 | class Reasons(): 361 | ''' 362 | CRL distribution point reason flags 363 | ''' 364 | def __init__(self, asn1_rflags): 365 | self.unused = False #(0), 366 | self.keyCompromise = False #(1), 367 | self.cACompromise = False #(2), 368 | self.affiliationChanged = False #(3), 369 | self.superseded = False #(4), 370 | self.cessationOfOperation = False #(5), 371 | self.certificateHold = False #(6), 372 | self.privilegeWithdrawn = False #(7), 373 | self.aACompromise = False #(8) 374 | 375 | bits = asn1_rflags._value 376 | try: 377 | if (bits[0]): self.unused = True 378 | if (bits[1]): self.keyCompromise = True 379 | if (bits[2]): self.cACompromise = True 380 | if (bits[3]): self.affiliationChanged = True 381 | if (bits[4]): self.superseded = True 382 | if (bits[5]): self.cessationOfOperation = True 383 | if (bits[6]): self.certificateHold = True 384 | if (bits[7]): self.privilegeWithdrawn = True 385 | if (bits[8]): self.aACompromise = True 386 | except IndexError: 387 | return 388 | 389 | 390 | class CRLdistPointExt(): 391 | ''' 392 | CRL distribution point extension 393 | ''' 394 | def __init__(self, asn1_crl_dp): 395 | dp = asn1_crl_dp.getComponentByName("distPoint") 396 | if dp is not None: 397 | #self.dist_point = str(dp.getComponent()) 398 | self.dist_point = str(dp.getComponentByName("fullName")[0].getComponent()) 399 | else: 400 | self.dist_point = None 401 | reasons = asn1_crl_dp.getComponentByName("reasons") 402 | if reasons is not None: 403 | self.reasons = Reasons(reasons) 404 | else: 405 | self.reasons = None 406 | issuer = asn1_crl_dp.getComponentByName("issuer") 407 | if issuer is not None: 408 | self.issuer = str(issuer) 409 | else: 410 | self.issuer = None 411 | 412 | class QcStatementExt(): 413 | ''' 414 | id_pe_qCStatement 415 | ''' 416 | def __init__(self, asn1_caStatement): 417 | self.oid = str(asn1_caStatement.getComponentByName("stmtId")) 418 | self.statementInfo = asn1_caStatement.getComponentByName("stmtInfo") 419 | if self.statementInfo is not None: 420 | self.statementInfo = str(self.statementInfo) 421 | 422 | class PolicyConstraintsExt: 423 | def __init__(self, asn1_policyConstraints): 424 | self.requireExplicitPolicy = None 425 | self.inhibitPolicyMapping = None 426 | 427 | requireExplicitPolicy = asn1_policyConstraints.getComponentByName("requireExplicitPolicy") 428 | inhibitPolicyMapping = asn1_policyConstraints.getComponentByName("inhibitPolicyMapping") 429 | 430 | if requireExplicitPolicy is not None: 431 | self.requireExplicitPolicy = requireExplicitPolicy._value 432 | 433 | if inhibitPolicyMapping is not None: 434 | self.inhibitPolicyMapping = inhibitPolicyMapping._value 435 | 436 | class NameConstraint: 437 | def __init__(self, base, minimum, maximum): 438 | self.base = base 439 | self.minimum = minimum 440 | self.maximum = maximum 441 | 442 | def __repr__(self): 443 | return "NameConstraint(base: %s, min: %s, max: %s)" % (repr(self.base), self.minimum, self.maximum) 444 | 445 | def __str__(self): 446 | return self.__repr__() 447 | 448 | class NameConstraintsExt: 449 | def __init__(self, asn1_nameConstraints): 450 | self.permittedSubtrees = [] 451 | self.excludedSubtrees = [] 452 | 453 | permittedSubtrees = asn1_nameConstraints.getComponentByName("permittedSubtrees") 454 | excludedSubtrees = asn1_nameConstraints.getComponentByName("excludedSubtrees") 455 | 456 | self.permittedSubtrees = self._parseSubtree(permittedSubtrees) 457 | self.excludedSubtrees = self._parseSubtree(excludedSubtrees) 458 | 459 | def _parseSubtree(self, asn1Subtree): 460 | if asn1Subtree is None: 461 | return [] 462 | 463 | subtreeList = [] 464 | 465 | for subtree in asn1Subtree: 466 | #TODO: somehow extract the fucking type of GeneralName 467 | base = subtree.getComponentByName("base").getComponent()#ByName("dNSName") 468 | if base is None: 469 | continue 470 | 471 | base = str(base) 472 | 473 | minimum = subtree.getComponentByName("minimum")._value 474 | maximum = subtree.getComponentByName("maximum") 475 | if maximum is not None: 476 | maximum = maximum._value 477 | 478 | subtreeList.append(NameConstraint(base, minimum, maximum)) 479 | 480 | return subtreeList 481 | 482 | 483 | class NetscapeCertTypeExt: 484 | def __init__(self, asn1_netscapeCertType): 485 | #https://www.mozilla.org/projects/security/pki/nss/tech-notes/tn3.html 486 | bits = asn1_netscapeCertType._value 487 | self.clientCert = len(bits) > 0 and bool(bits[0]) 488 | self.serverCert = len(bits) > 1 and bool(bits[1]) 489 | self.caCert = len(bits) > 5 and bool(bits[5]) 490 | 491 | class SignedCertificateTimestamp: 492 | 493 | def __init__(self, version, logID, timestamp, extensions, hash_alg, sig_alg, signature): 494 | self.version = version 495 | self.logID = logID 496 | self.timestamp = timestamp 497 | self.extensions = extensions 498 | self.hash_alg = hash_alg 499 | self.sig_alg = sig_alg 500 | self.signature = signature 501 | 502 | class SCTListExt(): 503 | ''' 504 | SignedCertificateTimestampList extension for Certificate Transparency (RFC 6962) 505 | ''' 506 | #Structure of SignedCertificateTimestampList from RFC 6962: 507 | # 508 | # 2 bytes size of sct_list 509 | # 2 bytes SerializedSCT size 510 | # 1 byte sct_version 511 | # 32 bytes log id 512 | # 8 bytes timestamp - milliseconds from epoch 513 | # 2 bytes extensions length 514 | # n bytes extension data 515 | # 1 byte hash algo 516 | # 1 byte signature algo 517 | # 2 byte signature length 518 | # n bytes signature 519 | 520 | def __init__(self, asn1_sctList): 521 | data = asn1_sctList._value 522 | self.scts = [] 523 | 524 | # This parsing is ugly, but we can't use pyasn1 - 525 | # the data is serialized according to RFC 5246. 526 | packed_len, data = self._splitBytes(data, 2) 527 | total_len = struct.unpack("!H", packed_len)[0] 528 | if len(data) != total_len: 529 | raise ValueError("Malformed length of SCT list") 530 | bytes_read = 0 531 | 532 | while bytes_read < total_len: 533 | packed_len, data = self._splitBytes(data, 2) 534 | sct_len = struct.unpack("!H", packed_len)[0] 535 | 536 | bytes_read += sct_len + 2 537 | sct_data, data = self._splitBytes(data, sct_len) 538 | packed_vlt, sct_data = self._splitBytes(sct_data, 41) 539 | version, logid, timestamp = struct.unpack("!B32sQ", packed_vlt) 540 | timestamp = datetime.datetime.fromtimestamp(timestamp/1000.0) 541 | 542 | packed_len, sct_data = self._splitBytes(sct_data, 2) 543 | ext_len = struct.unpack("!H", packed_len)[0] 544 | extensions, sct_data = self._splitBytes(sct_data, ext_len) 545 | 546 | hash_alg, sig_alg, sig_len = struct.unpack("!BBH", sct_data[:4]) 547 | signature = sct_data[4:] 548 | if len(signature) != sig_len: 549 | raise ValueError("SCT signature has incorrect length, expected %d, got %d" % (sig_len, len(signature))) 550 | 551 | self.scts.append(SignedCertificateTimestamp(version, logid, timestamp, extensions, hash_alg, sig_alg, signature)) 552 | 553 | @staticmethod 554 | def _splitBytes(buf, count): 555 | """ 556 | Split buf into two strings (part1, part2) where part1 has count bytes. 557 | @raises ValueError if buf is too short. 558 | """ 559 | if len(buf) < count: 560 | raise ValueError("Malformed structure encountered when parsing SCT, expected %d bytes, got only %d" % (count, len(buf))) 561 | 562 | return buf[:count], buf[count:] 563 | 564 | class ExtensionType: 565 | '''"Enum" of extensions we know how to parse.''' 566 | SUBJ_ALT_NAME = "subjAltNameExt" 567 | AUTH_KEY_ID = "authKeyIdExt" 568 | SUBJ_KEY_ID = "subjKeyIdExt" 569 | BASIC_CONSTRAINTS = "basicConstraintsExt" 570 | KEY_USAGE = "keyUsageExt" 571 | EXT_KEY_USAGE = "extKeyUsageExt" 572 | CERT_POLICIES = "certPoliciesExt" 573 | CRL_DIST_POINTS = "crlDistPointsExt" 574 | STATEMENTS = "statemetsExt" 575 | AUTH_INFO_ACCESS = "authInfoAccessExt" 576 | POLICY_CONSTRAINTS = "policyConstraintsExt" 577 | NAME_CONSTRAINTS = "nameConstraintsExt" 578 | NETSCAPE_CERT_TYPE = "netscapeCertTypeExt" 579 | SCT_LIST = "sctListExt" 580 | 581 | class ExtensionTypes: 582 | #hackish way to enumerate known extensions without writing them twice 583 | knownExtensions = [name for (attr, name) in vars(ExtensionType).items() if attr.isupper()] 584 | 585 | class Extension(): 586 | ''' 587 | Represents one Extension in X509v3 certificate 588 | Attributes: 589 | - id (identifier of extension) 590 | - is_critical 591 | - value (value of extension, needs more parsing - it is in DER encoding) 592 | ''' 593 | #OID: (ASN1Spec, valueConversionFunction, attributeName) 594 | _extensionDecoders = { 595 | "2.5.29.17": (GeneralNames(), lambda v: SubjectAltNameExt(v), ExtensionType.SUBJ_ALT_NAME), 596 | "2.5.29.35": (KeyId(), lambda v: AuthorityKeyIdExt(v), ExtensionType.AUTH_KEY_ID), 597 | "2.5.29.14": (SubjectKeyId(), lambda v: SubjectKeyIdExt(v), ExtensionType.SUBJ_KEY_ID), 598 | "2.5.29.19": (BasicConstraints(), lambda v: BasicConstraintsExt(v), ExtensionType.BASIC_CONSTRAINTS), 599 | "2.5.29.15": (None, lambda v: KeyUsageExt(v), ExtensionType.KEY_USAGE), 600 | "2.5.29.32": (CertificatePolicies(), lambda v: [CertificatePolicyExt(p) for p in v], ExtensionType.CERT_POLICIES), 601 | "2.5.29.31": (CRLDistributionPoints(), lambda v: [CRLdistPointExt(p) for p in v], ExtensionType.CRL_DIST_POINTS), 602 | "1.3.6.1.5.5.7.1.3": (Statements(), lambda v: [QcStatementExt(s) for s in v], ExtensionType.STATEMENTS), 603 | "1.3.6.1.5.5.7.1.1": (AuthorityInfoAccess(), lambda v: [AuthorityInfoAccessExt(s) for s in v], ExtensionType.AUTH_INFO_ACCESS), 604 | "2.5.29.37": (ExtendedKeyUsage(), lambda v: ExtendedKeyUsageExt(v), ExtensionType.EXT_KEY_USAGE), 605 | "2.5.29.36": (PolicyConstraints(), lambda v: PolicyConstraintsExt(v), ExtensionType.POLICY_CONSTRAINTS), 606 | "2.5.29.30": (NameConstraints(), lambda v: NameConstraintsExt(v), ExtensionType.NAME_CONSTRAINTS), 607 | "2.16.840.1.113730.1.1": (NetscapeCertType(), lambda v: NetscapeCertTypeExt(v), ExtensionType.NETSCAPE_CERT_TYPE), 608 | "1.3.6.1.4.1.11129.2.4.2": (SCTList(), lambda v: SCTListExt(v), ExtensionType.SCT_LIST), 609 | } 610 | 611 | def __init__(self, extension): 612 | self.id = tuple_to_OID(extension.getComponentByName("extnID")) 613 | critical = extension.getComponentByName("critical") 614 | self.is_critical = (critical != 0) 615 | self.ext_type = None 616 | 617 | # set the bytes as the extension value 618 | self.value = extension.getComponentByName("extnValue")._value 619 | 620 | # if we know the type of value, parse it 621 | decoderTuple = Extension._extensionDecoders.get(self.id) 622 | if decoderTuple is not None: 623 | try: 624 | (decoderAsn1Spec, decoderFunction, extType) = decoderTuple 625 | v = decode(self.value, asn1Spec=decoderAsn1Spec)[0] 626 | self.value = decoderFunction(v) 627 | self.ext_type = extType 628 | except PyAsn1Error: 629 | #According to RFC 5280, unrecognized extension can be ignored 630 | #unless marked critical, though it doesn't cover all cases. 631 | if self.is_critical: 632 | raise 633 | elif self.is_critical: 634 | raise CertificateError("Critical extension OID %s not understood" % self.id) 635 | 636 | class Certificate(): 637 | ''' 638 | Represents Certificate object. 639 | Attributes: 640 | - version 641 | - serial_number 642 | - signature_algorithm (data are signed with this algorithm) 643 | - issuer (who issued this certificate) 644 | - validity 645 | - subject (for who the certificate was issued) 646 | - pub_key_info 647 | - issuer_uid (optional) 648 | - subject_uid (optional) 649 | - extensions (list of extensions) 650 | ''' 651 | def __init__(self, tbsCertificate): 652 | self.version = tbsCertificate.getComponentByName("version")._value 653 | self.serial_number = tbsCertificate.getComponentByName("serialNumber")._value 654 | self.signature_algorithm = str(tbsCertificate.getComponentByName("signature")) 655 | self.issuer = Name(tbsCertificate.getComponentByName("issuer")) 656 | self.validity = ValidityInterval(tbsCertificate.getComponentByName("validity")) 657 | self.subject = Name(tbsCertificate.getComponentByName("subject")) 658 | self.pub_key_info = PublicKeyInfo(tbsCertificate.getComponentByName("subjectPublicKeyInfo")) 659 | 660 | issuer_uid = tbsCertificate.getComponentByName("issuerUniqueID") 661 | if issuer_uid: 662 | self.issuer_uid = issuer_uid.toOctets() 663 | else: 664 | self.issuer_uid = None 665 | 666 | subject_uid = tbsCertificate.getComponentByName("subjectUniqueID") 667 | if subject_uid: 668 | self.subject_uid = subject_uid.toOctets() 669 | else: 670 | self.subject_uid = None 671 | 672 | self.extensions = self._create_extensions_list(tbsCertificate.getComponentByName('extensions')) 673 | 674 | #make known extensions accessible through attributes 675 | for extAttrName in ExtensionTypes.knownExtensions: 676 | setattr(self, extAttrName, None) 677 | for ext in self.extensions: 678 | if ext.ext_type: 679 | setattr(self, ext.ext_type, ext) 680 | 681 | def _create_extensions_list(self, extensions): 682 | if extensions is None: 683 | return [] 684 | 685 | return [Extension(ext) for ext in extensions] 686 | 687 | class X509Certificate(): 688 | ''' 689 | Represents X509 certificate. 690 | Attributes: 691 | - signature_algorithm (used to sign this certificate) 692 | - signature 693 | - tbsCertificate (the certificate) 694 | ''' 695 | 696 | def __init__(self, certificate): 697 | self.signature_algorithm = str(certificate.getComponentByName("signatureAlgorithm")) 698 | self.signature = certificate.getComponentByName("signatureValue").toOctets() 699 | tbsCert = certificate.getComponentByName("tbsCertificate") 700 | self.tbsCertificate = Certificate(tbsCert) 701 | self.verification_results = None 702 | self.raw_der_data = "" # raw der data for storage are kept here by cert_manager 703 | self.check_crl = True 704 | 705 | def is_verified(self, ignore_missing_crl_check=False): 706 | ''' 707 | Checks if all values of verification_results dictionary are True, 708 | which means that the certificate is valid 709 | ''' 710 | return self._evaluate_verification_results( 711 | self.verification_results, 712 | ignore_missing_crl_check=ignore_missing_crl_check) 713 | 714 | def valid_at_date(self, date, ignore_missing_crl_check=False): 715 | """check validity of all parts of the certificate with regard 716 | to a specific date""" 717 | verification_results = self.verification_results_at_date(date) 718 | return self._evaluate_verification_results( 719 | verification_results, 720 | ignore_missing_crl_check=ignore_missing_crl_check) 721 | 722 | def _evaluate_verification_results(self, verification_results, 723 | ignore_missing_crl_check=False): 724 | if verification_results is None: 725 | return False 726 | for key, value in verification_results.iteritems(): 727 | if value: 728 | pass 729 | elif ignore_missing_crl_check and key=="CERT_NOT_REVOKED" and \ 730 | value is None: 731 | continue 732 | else: 733 | return False 734 | return True 735 | 736 | 737 | def verification_results_at_date(self, date): 738 | if self.verification_results is None: 739 | return None 740 | results = dict(self.verification_results) # make a copy 741 | results["CERT_TIME_VALIDITY_OK"] = self.time_validity_at_date(date) 742 | if self.check_crl: 743 | results["CERT_NOT_REVOKED"] = self.crl_validity_at_date(date) 744 | else: 745 | results["CERT_NOT_REVOKED"] = None 746 | return results 747 | 748 | def time_validity_at_date(self, date): 749 | """check if the time interval of validity of the certificate contains 750 | 'date' provided as argument""" 751 | from_date = self.tbsCertificate.validity.get_valid_from_as_datetime() 752 | to_date = self.tbsCertificate.validity.get_valid_to_as_datetime() 753 | time_ok = to_date >= date >= from_date 754 | return time_ok 755 | 756 | def crl_validity_at_date(self, date): 757 | """check if the certificate was not on the CRL list at a particular date""" 758 | rev_date = self.get_revocation_date() 759 | if not rev_date: 760 | return True 761 | if date >= rev_date: 762 | return False 763 | else: 764 | return True 765 | 766 | def get_revocation_date(self): 767 | from certs.crl_store import CRL_cache_manager 768 | cache = CRL_cache_manager.get_cache() 769 | issuer = str(self.tbsCertificate.issuer) 770 | rev_date = cache.certificate_rev_date(issuer, self.tbsCertificate.serial_number) 771 | if not rev_date: 772 | return None 773 | rev_date = ValidityInterval.parse_date(rev_date) 774 | return rev_date 775 | 776 | 777 | class Attribute(): 778 | """ 779 | One attribute in SignerInfo attributes set 780 | """ 781 | def __init__(self, attribute): 782 | self.type = str(attribute.getComponentByName("type")) 783 | self.value = str(attribute.getComponentByName("value").getComponentByPosition(0)) 784 | #print base64.b64encode(self.value) 785 | 786 | class AutheticatedAttributes(): 787 | """ 788 | Authenticated attributes of signer info 789 | """ 790 | def __init__(self, auth_attributes): 791 | self.attributes = [] 792 | for aa in auth_attributes: 793 | self.attributes.append(Attribute(aa)) 794 | 795 | class SignerInfo(): 796 | """ 797 | Represents information about a signer. 798 | Attributes: 799 | - version 800 | - issuer 801 | - serial_number (of the certificate used to verify this signature) 802 | - digest_algorithm 803 | - encryp_algorithm 804 | - signature 805 | - auth_atributes (optional field, contains authenticated attributes) 806 | """ 807 | def __init__(self, signer_info): 808 | self.version = signer_info.getComponentByName("version")._value 809 | self.issuer = Name(signer_info.getComponentByName("issuerAndSerialNum").getComponentByName("issuer")) 810 | self.serial_number = signer_info.getComponentByName("issuerAndSerialNum").getComponentByName("serialNumber")._value 811 | self.digest_algorithm = str(signer_info.getComponentByName("digestAlg")) 812 | self.encrypt_algorithm = str(signer_info.getComponentByName("encryptAlg")) 813 | self.signature = signer_info.getComponentByName("signature")._value 814 | auth_attrib = signer_info.getComponentByName("authAttributes") 815 | if auth_attrib is None: 816 | self.auth_attributes = None 817 | else: 818 | self.auth_attributes = AutheticatedAttributes(auth_attrib) 819 | 820 | 821 | 822 | ###### 823 | #TSTinfo 824 | ###### 825 | class MsgImprint(): 826 | def __init__(self, asn1_msg_imprint): 827 | self.alg = str(asn1_msg_imprint.getComponentByName("algId")) 828 | self.imprint = str(asn1_msg_imprint.getComponentByName("imprint")) 829 | 830 | class TsAccuracy(): 831 | def __init__(self, asn1_acc): 832 | secs = asn1_acc.getComponentByName("seconds") 833 | if secs: 834 | self.seconds = secs._value 835 | milis = asn1_acc.getComponentByName("milis") 836 | if milis: 837 | self.milis = milis._value 838 | micros = asn1_acc.getComponentByName("micros") 839 | if micros: 840 | self.micros = micros._value 841 | 842 | class TimeStampToken(): 843 | ''' 844 | Holder for Timestamp Token Info - attribute from the qtimestamp. 845 | ''' 846 | def __init__(self, asn1_tstInfo): 847 | self.version = asn1_tstInfo.getComponentByName("version")._value 848 | self.policy = str(asn1_tstInfo.getComponentByName("policy")) 849 | self.msgImprint = MsgImprint(asn1_tstInfo.getComponentByName("messageImprint")) 850 | self.serialNum = asn1_tstInfo.getComponentByName("serialNum")._value 851 | self.genTime = asn1_tstInfo.getComponentByName("genTime")._value 852 | self.accuracy = TsAccuracy(asn1_tstInfo.getComponentByName("accuracy")) 853 | self.tsa = Name(asn1_tstInfo.getComponentByName("tsa")) 854 | # place for parsed certificates in asn1 form 855 | self.asn1_certificates = [] 856 | # place for certificates transformed to X509Certificate 857 | self.certificates = [] 858 | #self.extensions = asn1_tstInfo.getComponentByName("extensions") 859 | 860 | def certificates_contain(self, cert_serial_num): 861 | """ 862 | Checks if set of certificates of this timestamp contains 863 | certificate with specified serial number. 864 | Returns True if it does, False otherwise. 865 | """ 866 | for cert in self.certificates: 867 | if cert.tbsCertificate.serial_number == cert_serial_num: 868 | return True 869 | return False 870 | 871 | def get_genTime_as_datetime(self): 872 | """ 873 | parses the genTime string and returns a datetime object; 874 | it also adjusts the time according to local timezone, so that it is 875 | compatible with other parts of the library 876 | """ 877 | year = int(self.genTime[:4]) 878 | month = int(self.genTime[4:6]) 879 | day = int(self.genTime[6:8]) 880 | hour = int(self.genTime[8:10]) 881 | minute = int(self.genTime[10:12]) 882 | second = int(self.genTime[12:14]) 883 | rest = self.genTime[14:].strip("Z") 884 | if rest: 885 | micro = int(float(rest)*1e6) 886 | else: 887 | micro = 0 888 | tz_delta = datetime.timedelta(seconds=time.daylight and time.altzone 889 | or time.timezone) 890 | return datetime.datetime(year, month, day, hour, minute, second, micro) - tz_delta 891 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # setup-ified it 5 | # 6 | from setuptools import setup, find_packages 7 | 8 | setup( 9 | name = 'pyx509', 10 | version = '0.6.0', # Would really like to link this to a tag/branch/whatever 11 | install_requires = ['pyasn1>=0.1.4',], 12 | author = 'Ondrej Mikle', 13 | author_email = 'ondrej.mikle@gmail.com', 14 | license = 'GPL', 15 | description = 'X.509 Certificate Parser for Python', 16 | url = 'https://github.com/hiviah/pyx509', 17 | classifiers = [ 18 | 'Development Status :: 3 - Alpha', 19 | 'Environment :: Console', 20 | 'Intended Audience :: Developers', 21 | 'License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)', 22 | 'Operating System :: OS Independent', 23 | 'Programming Language :: Python :: 2.6', 24 | 'Programming Language :: Python :: 2.7', 25 | 'Topic :: Security', 26 | ], 27 | packages = find_packages(), 28 | scripts = ['x509_parse.py',], 29 | ) 30 | -------------------------------------------------------------------------------- /x509_parse.py: -------------------------------------------------------------------------------- 1 | #* pyx509 - Python library for parsing X.509 2 | #* Copyright (C) 2009-2012 CZ.NIC, z.s.p.o. (http://www.nic.cz) 3 | #* 4 | #* This library is free software; you can redistribute it and/or 5 | #* modify it under the terms of the GNU Library General Public 6 | #* License as published by the Free Software Foundation; either 7 | #* version 2 of the License, or (at your option) any later version. 8 | #* 9 | #* This library is distributed in the hope that it will be useful, 10 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 | #* Library General Public License for more details. 13 | #* 14 | #* You should have received a copy of the GNU Library General Public 15 | #* License along with this library; if not, write to the Free 16 | #* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 17 | #* 18 | #!/usr/bin/env python 19 | import sys 20 | from binascii import hexlify 21 | 22 | from pyx509.pkcs7.asn1_models.X509_certificate import Certificate 23 | from pyx509.pkcs7_models import X509Certificate, PublicKeyInfo, ExtendedKeyUsageExt 24 | 25 | from pyx509.pkcs7.asn1_models.decoder_workarounds import decode 26 | 27 | def x509_parse(derData): 28 | """Decodes certificate. 29 | @param derData: DER-encoded certificate string 30 | @returns: pkcs7_models.X509Certificate 31 | """ 32 | cert = decode(derData, asn1Spec=Certificate())[0] 33 | x509cert = X509Certificate(cert) 34 | return x509cert 35 | 36 | #Sample usage showing retrieving certificate fields 37 | if __name__ == "__main__": 38 | if len(sys.argv) < 2: 39 | print >> sys.stderr, "Usage: x509_parse.py certificate.der" 40 | sys.exit(1) 41 | 42 | der_file = sys.argv[1] 43 | 44 | x509cert = x509_parse(file(der_file).read()) 45 | tbs = x509cert.tbsCertificate 46 | 47 | print "X.509 version: %d (0x%x)" % (tbs.version + 1, tbs.version) 48 | print "Serial no: 0x%x" % tbs.serial_number 49 | print "Signature algorithm:", x509cert.signature_algorithm 50 | print "Issuer:", str(tbs.issuer) 51 | print "Validity:" 52 | print "\tNot Before:", tbs.validity.get_valid_from_as_datetime() 53 | print "\tNot After:", tbs.validity.get_valid_to_as_datetime() 54 | print "Subject:", str(tbs.subject) 55 | print "Subject Public Key Info:" 56 | print "\tPublic Key Algorithm:", tbs.pub_key_info.algName 57 | 58 | if tbs.issuer_uid: 59 | print "Issuer UID:", hexlify(tbs.issuer_uid) 60 | if tbs.subject_uid: 61 | print "Subject UID:", hexlify(tbs.subject_uid) 62 | 63 | algType = tbs.pub_key_info.algType 64 | algParams = tbs.pub_key_info.key 65 | 66 | if (algType == PublicKeyInfo.RSA): 67 | print "\t\tModulus:", hexlify(algParams["mod"]) 68 | print "\t\tExponent:", algParams["exp"] 69 | elif (algType == PublicKeyInfo.DSA): 70 | print "\t\tPub:", hexlify(algParams["pub"]), 71 | print "\t\tP:", hexlify(algParams["p"]), 72 | print "\t\tQ:", hexlify(algParams["q"]), 73 | print "\t\tG:", hexlify(algParams["g"]), 74 | else: 75 | print "\t\t(parsing keys of this type not implemented)" 76 | 77 | print "\nExtensions:" 78 | if tbs.authInfoAccessExt: 79 | print "\tAuthority Information Access Ext: is_critical:", tbs.authInfoAccessExt.is_critical 80 | for aia in tbs.authInfoAccessExt.value: 81 | print "\t\taccessLocation:", aia.access_location 82 | print "\t\taccessMethod:", aia.access_method 83 | print "\t\toid:", aia.id 84 | if tbs.authKeyIdExt: 85 | print "\tAuthority Key Id Ext: is_critical:", tbs.authKeyIdExt.is_critical 86 | aki = tbs.authKeyIdExt.value 87 | if hasattr(aki, "key_id"): 88 | print "\t\tkey id", hexlify(aki.key_id) 89 | if hasattr(aki, "auth_cert_sn"): 90 | print "\t\tcert serial no", aki.auth_cert_sn 91 | if hasattr(aki, "auth_cert_issuer"): 92 | print "\t\tissuer", aki.auth_cert_issuer 93 | 94 | if tbs.basicConstraintsExt: 95 | print "\tBasic Constraints Ext: is_critical:", tbs.basicConstraintsExt.is_critical 96 | bc = tbs.basicConstraintsExt.value 97 | print "\t\tCA:", bc.ca 98 | print "\t\tmax_path_len:", bc.max_path_len 99 | 100 | if tbs.certPoliciesExt: 101 | print "\tCert Policies Ext: is_critical:", tbs.certPoliciesExt.is_critical 102 | policies = tbs.certPoliciesExt.value 103 | for policy in policies: 104 | print "\t\tpolicy OID:", policy.id 105 | for qualifier in policy.qualifiers: 106 | print "\t\t\toid:", qualifier.id 107 | print "\t\t\tqualifier:", qualifier.qualifier 108 | 109 | if tbs.crlDistPointsExt: 110 | print "\tCRL Distribution Points: is_critical:", tbs.crlDistPointsExt.is_critical 111 | crls = tbs.crlDistPointsExt.value 112 | for crl in crls: 113 | if crl.dist_point: 114 | print "\t\tdistribution point:", crl.dist_point 115 | if crl.issuer: 116 | print "\t\tissuer:", crl.issuer 117 | if crl.reasons: 118 | print "\t\treasons:", crl.reasons 119 | 120 | if tbs.extKeyUsageExt: 121 | print "\tExtended Key Usage: is_critical:", tbs.extKeyUsageExt.is_critical 122 | eku = tbs.extKeyUsageExt.value 123 | set_flags = [flag for flag in ExtendedKeyUsageExt._keyPurposeAttrs.values() if getattr(eku, flag)] 124 | print "\t\t", ",".join(set_flags) 125 | 126 | if tbs.keyUsageExt: 127 | print "\tKey Usage: is_critical:", tbs.keyUsageExt.is_critical 128 | ku = tbs.keyUsageExt.value 129 | flags = ["digitalSignature","nonRepudiation", "keyEncipherment", 130 | "dataEncipherment", "keyAgreement", "keyCertSign", 131 | "cRLSign", "encipherOnly", "decipherOnly", 132 | ] 133 | 134 | set_flags = [flag for flag in flags if getattr(ku, flag)] 135 | print "\t\t", ",".join(set_flags) 136 | 137 | if tbs.policyConstraintsExt: 138 | print "\tPolicy Constraints: is_critical:", tbs.policyConstraintsExt.is_critical 139 | pc = tbs.policyConstraintsExt.value 140 | 141 | print "\t\trequire explicit policy: ", pc.requireExplicitPolicy 142 | print "\t\tinhibit policy mapping: ", pc.inhibitPolicyMapping 143 | 144 | #if tbs.netscapeCertTypeExt: #...partially implemented 145 | 146 | if tbs.subjAltNameExt: 147 | print "\tSubject Alternative Name: is_critical:", tbs.subjAltNameExt.is_critical 148 | san = tbs.subjAltNameExt.value 149 | for component_type, name_list in san.values.items(): 150 | print "\t\t%s: %s" % (component_type, ",".join(name_list)) 151 | 152 | if tbs.subjKeyIdExt: 153 | print "\tSubject Key Id: is_critical:", tbs.subjKeyIdExt.is_critical 154 | ski = tbs.subjKeyIdExt.value 155 | print "\t\tkey id", hexlify(ski.subject_key_id) 156 | 157 | if tbs.nameConstraintsExt: 158 | nce = tbs.nameConstraintsExt.value 159 | print "\tName constraints: is_critical:", tbs.nameConstraintsExt.is_critical 160 | 161 | subtreeFmt = lambda subtrees: ", ".join([str(x) for x in subtrees]) 162 | if nce.permittedSubtrees: 163 | print "\t\tPermitted:", subtreeFmt(nce.permittedSubtrees) 164 | if nce.excludedSubtrees: 165 | print "\t\tExcluded:", subtreeFmt(nce.excludedSubtrees) 166 | 167 | if tbs.sctListExt: 168 | scte = tbs.sctListExt.value 169 | print "\tSigned Certificate Timestamp List: is_critical:", tbs.sctListExt.is_critical 170 | 171 | for sct in scte.scts: 172 | print "\t\tSCT version %d, log ID %s, signed at %s" % (sct.version+1, hexlify(sct.logID), sct.timestamp) 173 | print "\t\t\tSignature info: hash alg id %d, signagure alg id %d" % (sct.hash_alg, sct.sig_alg) 174 | print "\t\t\tSignature:", hexlify(sct.signature) 175 | 176 | print "Signature:", hexlify(x509cert.signature) 177 | 178 | --------------------------------------------------------------------------------