pyca / cryptography

cryptography is a package designed to expose cryptographic primitives and recipes to Python developers.
https://cryptography.io
Other
6.6k stars 1.51k forks source link

RFC5280 Extension `PrivateKeyUsagePeriod` detected as `UnrecognizedExtension` #11195

Open ricardo-reis-1970 opened 3 months ago

ricardo-reis-1970 commented 3 months ago

Platform Version


Ubuntu 24.04 Python 3.12.3


* How you installed ``cryptography``
`pip install cryptography`

* Clear steps for reproducing your bug
I have a CSCA certificate. When I parse it in the command-line, it looks like this:
```bash
$ openssl x509 -in CSCA.cer -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            [...]
        Signature Algorithm: ecdsa-with-SHA256
        [...]
        X509v3 extensions:
            X509v3 Subject Alternative Name: 
                [...]
            X509v3 Issuer Alternative Name: 
                [...]
            X509v3 Basic Constraints: critical
                [...]
            X509v3 Key Usage: critical
                [...]
            X509v3 Private Key Usage Period: 
                [...]
            X509v3 Subject Key Identifier: 
                [...]
            X509v3 Authority Key Identifier: 
                [...]
            X509v3 CRL Distribution Points: 
                [...]
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        [...]

As you can see, extension[4] comes out as Private Key Usage Period, as per RFC5280. It is not a specific ICAO extension.

However, when I load the exact same file via Python:

def get_cert_data(file):
    with open(file, 'r') as pem_file:
        pem_data = pem_file.read()
        if pem_data.startswith('-----BEGIN CERTIFICATE-----'):
            cert = x509.load_pem_x509_certificate(str.encode(pem_data), default_backend())
            return cert
        else:
            pass

csca = get_cert_data('Certificates/CSCA.cer')

then the output of an interactive session is this:

>>> pp(list(map(lambda e: e.value.__class__.__name__, csca.extensions)))
['SubjectAlternativeName',
 'IssuerAlternativeName',
 'BasicConstraints',
 'KeyUsage',
 'UnrecognizedExtension',
 'SubjectKeyIdentifier',
 'AuthorityKeyIdentifier',
 'CRLDistributionPoints']
>>> csca.extensions[4].oid
<ObjectIdentifier(oid=2.5.29.16, name=Unknown OID)>
>>> csca.extensions[4].value
<UnrecognizedExtension(oid=<ObjectIdentifier(oid=2.5.29.16, name=Unknown OID)>, value=b'0"\x80\x0f20221114114914Z\x81\x0f20251114114914Z')>

Both the extension and its very standard OID 2.5.29.16 are unrecognised!

Looking into the code, this is obvious, as both are absent from the relevant files:

# hazmat/_oid.py
class ExtensionOID:
    SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9")
    SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14")
    KEY_USAGE = ObjectIdentifier("2.5.29.15")
    SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17")
    ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18")
    BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
    NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30")
    CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31")
    CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32")
    POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33")
    AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35")
    POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36")
    EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37")
    FRESHEST_CRL = ObjectIdentifier("2.5.29.46")
    INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54")
    ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28")
    AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1")
    SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11")
    OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5")
    TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24")
    CRL_NUMBER = ObjectIdentifier("2.5.29.20")
    DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27")
    PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier(
        "1.3.6.1.4.1.11129.2.4.2"
    )
    PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3")
    SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5")
    MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7")
$ grep "^class" x509/extensions.py 
class DuplicateExtension(Exception):
class ExtensionNotFound(Exception):
class ExtensionType(metaclass=abc.ABCMeta):
class Extensions:
class CRLNumber(ExtensionType):
class AuthorityKeyIdentifier(ExtensionType):
class SubjectKeyIdentifier(ExtensionType):
class AuthorityInformationAccess(ExtensionType):
class SubjectInformationAccess(ExtensionType):
class AccessDescription:
class BasicConstraints(ExtensionType):
class DeltaCRLIndicator(ExtensionType):
class CRLDistributionPoints(ExtensionType):
class FreshestCRL(ExtensionType):
class DistributionPoint:
class ReasonFlags(utils.Enum):
class PolicyConstraints(ExtensionType):
class CertificatePolicies(ExtensionType):
class PolicyInformation:
class UserNotice:
class NoticeReference:
class ExtendedKeyUsage(ExtensionType):
class OCSPNoCheck(ExtensionType):
class PrecertPoison(ExtensionType):
class TLSFeature(ExtensionType):
class TLSFeatureType(utils.Enum):
class InhibitAnyPolicy(ExtensionType):
class KeyUsage(ExtensionType):
class NameConstraints(ExtensionType):
class Extension(typing.Generic[ExtensionTypeVar]):
class GeneralNames:
class SubjectAlternativeName(ExtensionType):
class IssuerAlternativeName(ExtensionType):
class CertificateIssuer(ExtensionType):
class CRLReason(ExtensionType):
class InvalidityDate(ExtensionType):
class PrecertificateSignedCertificateTimestamps(ExtensionType):
class SignedCertificateTimestamps(ExtensionType):
class OCSPNonce(ExtensionType):
class OCSPAcceptableResponses(ExtensionType):
class IssuingDistributionPoint(ExtensionType):
class MSCertificateTemplate(ExtensionType):
class UnrecognizedExtension(ExtensionType):

Is there a reason for having this particular RFC5280 extension left out?

alex commented 3 months ago

The reason is we've never had a use case or request for it.

On Thu, Jul 4, 2024 at 6:47 AM Ricardo Reis @.***> wrote:

  • Versions of Python, cryptography, cffi, pip, and setuptools you're using I'm working on a Linux machine with:

Package Version


cffi 1.16.0 cryptography 42.0.8 pip 24.1.1 setuptools 70.2.0

Platform Version


Ubuntu 24.04 Python 3.12.3

-

How you installed cryptography pip install cryptography

Clear steps for reproducing your bug I have a CSCA certificate. When I parse it in the command-line, it looks like this:

$ openssl x509 -in CSCA.cer -text -noout Certificate: Data: Version: 3 (0x2) Serial Number: [...] Signature Algorithm: ecdsa-with-SHA256 [...] X509v3 extensions: X509v3 Subject Alternative Name: [...] X509v3 Issuer Alternative Name: [...] X509v3 Basic Constraints: critical [...] X509v3 Key Usage: critical [...] X509v3 Private Key Usage Period: [...] X509v3 Subject Key Identifier: [...] X509v3 Authority Key Identifier: [...] X509v3 CRL Distribution Points: [...] Signature Algorithm: ecdsa-with-SHA256 Signature Value: [...]

As you can see, extension[4] comes out as Private Key Usage Period, as per RFC5280. It is not a specific ICAO extension.

However, when I load the exact same file via Python:

def get_cert_data(file): with open(file, 'r') as pem_file: pem_data = pem_file.read() if pem_data.startswith('-----BEGIN CERTIFICATE-----'): cert = x509.load_pem_x509_certificate(str.encode(pem_data), default_backend()) return cert else: pass csca = get_cert_data('Certificates/CSCA.cer')

then the output of an interactive session is this:

pp(list(map(lambda e: e.value.class.name, csca.extensions))) ['SubjectAlternativeName', 'IssuerAlternativeName', 'BasicConstraints', 'KeyUsage', 'UnrecognizedExtension', 'SubjectKeyIdentifier', 'AuthorityKeyIdentifier', 'CRLDistributionPoints']>>> csca.extensions[4].oid<ObjectIdentifier(oid=2.5.29.16, name=Unknown OID)>>>> csca.extensions[4].value<UnrecognizedExtension(oid=<ObjectIdentifier(oid=2.5.29.16, name=Unknown OID)>, value=b'0"\x80\x0f20221114114914Z\x81\x0f20251114114914Z')>

Both the extension and its very standard OID 2.5.29.16 http://oid-info.com/cgi-bin/display?oid=2.5.29.16&a=display are unrecognised!

Looking into the code, this is obvious, as both are absent from the relevant files:

hazmat/_oid.pyclass ExtensionOID:

SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9")
SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14")
KEY_USAGE = ObjectIdentifier("2.5.29.15")
SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17")
ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18")
BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19")
NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30")
CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31")
CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32")
POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33")
AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35")
POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36")
EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37")
FRESHEST_CRL = ObjectIdentifier("2.5.29.46")
INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54")
ISSUING_DISTRIBUTION_POINT = ObjectIdentifier("2.5.29.28")
AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1")
SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11")
OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5")
TLS_FEATURE = ObjectIdentifier("1.3.6.1.5.5.7.1.24")
CRL_NUMBER = ObjectIdentifier("2.5.29.20")
DELTA_CRL_INDICATOR = ObjectIdentifier("2.5.29.27")
PRECERT_SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier(
    "1.3.6.1.4.1.11129.2.4.2"
)
PRECERT_POISON = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.3")
SIGNED_CERTIFICATE_TIMESTAMPS = ObjectIdentifier("1.3.6.1.4.1.11129.2.4.5")
MS_CERTIFICATE_TEMPLATE = ObjectIdentifier("1.3.6.1.4.1.311.21.7")

$ grep "^class" x509/extensions.py class DuplicateExtension(Exception):class ExtensionNotFound(Exception):class ExtensionType(metaclass=abc.ABCMeta):class Extensions:class CRLNumber(ExtensionType):class AuthorityKeyIdentifier(ExtensionType):class SubjectKeyIdentifier(ExtensionType):class AuthorityInformationAccess(ExtensionType):class SubjectInformationAccess(ExtensionType):class AccessDescription:class BasicConstraints(ExtensionType):class DeltaCRLIndicator(ExtensionType):class CRLDistributionPoints(ExtensionType):class FreshestCRL(ExtensionType):class DistributionPoint:class ReasonFlags(utils.Enum):class PolicyConstraints(ExtensionType):class CertificatePolicies(ExtensionType):class PolicyInformation:class UserNotice:class NoticeReference:class ExtendedKeyUsage(ExtensionType):class OCSPNoCheck(ExtensionType):class PrecertPoison(ExtensionType):class TLSFeature(ExtensionType):class TLSFeatureType(utils.Enum):class InhibitAnyPolicy(ExtensionType):class KeyUsage(ExtensionType):class NameConstraints(ExtensionType):class Extension(typing.Generic[ExtensionTypeVar]):class GeneralNames:class SubjectAlternativeName(ExtensionType):class IssuerAlternativeName(ExtensionType):class CertificateIssuer(ExtensionType):class CRLReason(ExtensionType):class InvalidityDate(ExtensionType):class PrecertificateSignedCertificateTimestamps(ExtensionType):class SignedCertificateTimestamps(ExtensionType):class OCSPNonce(ExtensionType):class OCSPAcceptableResponses(ExtensionType):class IssuingDistributionPoint(ExtensionType):class MSCertificateTemplate(ExtensionType):class UnrecognizedExtension(ExtensionType):

Is there a reason for having this particular RFC5280 extension left out?

— Reply to this email directly, view it on GitHub https://github.com/pyca/cryptography/issues/11195, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAAGBDDKQQA2IJPQLROEUTZKUR4DAVCNFSM6AAAAABKLJJTJCVHI2DSMVQWIX3LMV43ASLTON2WKOZSGM4TANJVGY2DONY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

-- All that is necessary for evil to succeed is for good people to do nothing.

ricardo-reis-1970 commented 3 months ago

Well, the use case is ICAO certificates, which are the very core of what my company — Tegona SA — does.

As for the request, here it is:

Dear cryptography developers,

I would like to humbly request that you added the PrivateKeyUsagePeriod extension to your x509 module. From what little I could see from inspecting the source code, I am convinced that for you this would be a small step, but for my company and I this would mean a lot.

On a separate train of thought, I'd like to open the floor to the possibility that one could add extensions to the ones delivered by you, not in a "hacky" way but perhaps in a more structured manner, via some sort of call. The reason I mention this is that for now I could not detect any other missing extension from RFC5280, but I fear more would come up in the near future and we'd be meeting in similar circumstances. Further to this, we are liable to find in days to come bespoke extensions outside the norm.

Quite aware that you work selflessly for us, the Community at large, I hereby leave my deepest thanks in advance, hoping to continue benefitting from your excellent work, while remaining at your disposal for any and all enquiries regarding this subject.

Kind regards, Ricardo

alex commented 3 months ago

We can look at adding this. We'd also be happy to review a pull request to add support if you're interested.

In terms of arbitrary extensions, they are already supported via UnknownExtension, it just requires you to do your own DER encoding/decoding.

On Thu, Jul 4, 2024 at 8:22 AM Ricardo Reis @.***> wrote:

Well, the use case is ICAO certificates, which are the very core of what my company — Tegona SA — does.

As for the request, here it is:

Dear cryptography developers,

I would like to humbly request that you added the PrivateKeyUsagePeriod extension to your x509 module. From what little I could see from inspecting the source code, I am convinced that for you this would be a small step, but for my company and I this would mean a lot.

On a separate train of thought, I'd like to open the floor to the possibility that one could add extensions to the ones delivered by you, not in a "hacky" way but perhaps in a more structured manner, via some sort of call. The reason I mention this is that for now I could not detect any other missing extension from RFC5280, but I fear more would come up in the near future and we'd be meeting in similar circumstances. Further to this, we are liable to find in days to come bespoke extensions outside the norm.

Quite aware that you work selflessly for us, the Community at large, I hereby leave my deepest thanks in advance, hoping to continue benefitting from your excellent work, while remaining at your disposal for any and all enquiries regarding this subject.

Kind regards, Ricardo

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>

-- All that is necessary for evil to succeed is for good people to do nothing.

ricardo-reis-1970 commented 3 months ago

Hi Alex,

That's a great suggestion! I'll have a stab at it, although git tends to be a bit of an incline for me to climb (Yeah, I'm old!).

I think I "kinda sorta" know what to do. I'll get back to you with either a pull request or a desperate cry for help. Give me the weekend.

HamdaanAliQuatil commented 3 months ago

Hey @ricardo-reis-1970 👋 Can I probably lend a hand in this? I'd love to help

ricardo-reis-1970 commented 3 months ago

Hi @HamdaanAliQuatil. I would like to ask "What are you waiting for?", but I don't want to sound rude. Let me instead try a shorter "Yes, please." with a big "Thank you very much!" on the side.

Truth is, I am not quite confortable with OOP and it's all classes in there. Plus, I fear I might introduce noise, as these digital certificates theme is a bit news to me.

Again, thank you very much!

HamdaanAliQuatil commented 2 months ago

@alex I've created a WIP PR. I've read the contribution guidelines, which mention that unit tests accompany every feature to increase coverage. I'm trying to wrap my head around the existing tests and will see how/ where to add another one for this. Can you please confirm if this PR needs a unit test?

Another issue - I wanted to do a manual testing with x509 v3 cert but I'm unable to create a cert with the PrivateKeyUsagePeriod extension using openssl. I've described the issue in detail here.

Can you please let me know what am I doing wrong?

alex commented 2 months ago

Yes, this PR will need tests (we require 100% coverage)

On Wed, Jul 10, 2024, 10:41 AM HamdaanAliQuatil @.***> wrote:

@alex https://github.com/alex I've created a WIP PR https://github.com/pyca/cryptography/pull/11243. I've read the contribution guidelines, which mention that unit tests accompany every feature to increase coverage. I'm trying to wrap my head around the existing tests and will see how/ where to add another one for this. Can you please confirm if this PR needs a unit test?

Another issue - I wanted to do a manual testing with x509 v3 cert but I'm unable to create a cert with the PrivateKeyUsagePeriod extension using openssl. I've described the issue in detail here https://stackoverflow.com/questions/78731059/how-to-generate-x509-v3-cert-with-the-privatekeyusageperiod-extension .

Can you please let me know what am I doing wrong?

— Reply to this email directly, view it on GitHub https://github.com/pyca/cryptography/issues/11195#issuecomment-2220701733, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAAGBCBYYHR7UY25EMA5ALZLVBY5AVCNFSM6AAAAABKLJJTJCVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEMRQG4YDCNZTGM . You are receiving this because you were mentioned.Message ID: @.***>