Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Certificate chain validation always fails with "required EKU not found" error #12078

Closed
vEpiphyte opened this issue Nov 29, 2024 · 2 comments
Closed

Comments

@vEpiphyte
Copy link

When attempting to port some code from PyOpenSSL X509 store verification to using the ClientVerifier ( see #10393 ), I ran across an issue with using code signing certificates and getting a "required EKU not found" exception. This appears to be a duplicate of #11352 but I have a self-contained reproducer.

Running the reproducer raises the following exception:

> python x509_err.py 
eku err reproducer
<Certificate(subject=<Name(CN=My cool CA)>, ...)>
<Certificate(subject=<Name(CN=secondary CA)>, ...)>
<Certificate(subject=<Name(CN=code signer)>, ...)>
<Extension(oid=<ObjectIdentifier(oid=2.5.29.37, name=extendedKeyUsage)>, critical=False, value=<ExtendedKeyUsage([<ObjectIdentifier(oid=1.3.6.1.5.5.7.3.3, name=codeSigning)>])>)>
Verifying certificate signature.
RUH ROH: validation failed: required EKU not found (encountered processing <Certificate(subject=<Name(CN=code signer)>, ...)>)
Traceback (most recent call last):
  File "/home/user/project/x509_err.py", line 150, in <module>
    main()
  File "/home/user/project/x509_err.py", line 144, in main
    client = verifyCodeSign(codecert, cacerts=[rootcert, immcert])
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/user/project/x509_err.py", line 131, in verifyCodeSign
    raise e
  File "/home/user/project/x509_err.py", line 128, in verifyCodeSign
    verifiedclient = verifier.verify(leaf=cert, intermediates=[])
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
cryptography.hazmat.bindings._rust.x509.VerificationError: validation failed: required EKU not found (encountered processing <Certificate(subject=<Name(CN=code signer)>, ...)>)

Here is the reproducer:

import os
import binascii
import datetime

from typing import List, Tuple, Union, Optional

import cryptography.x509 as c_x509
import cryptography.hazmat.primitives.hashes as c_hashes
import cryptography.hazmat.primitives.asymmetric.rsa as c_rsa
import cryptography.hazmat.primitives.asymmetric.dsa as c_dsa
import cryptography.hazmat.primitives.asymmetric.types as c_types

# Legacy
NSCERTTYPE_OID = '2.16.840.1.113730.1.1'
NSCERTTYPE_CLIENT = b'\x03\x02\x07\x80'   # client
NSCERTTYPE_SERVER = b'\x03\x02\x06@'      # server
NSCERTTYPE_OBJSIGN = b'\x03\x02\x04\x10'  # objsign

PkeyAndCert = Tuple[c_rsa.RSAPrivateKey, c_x509.Certificate]
Pkey = Union[c_rsa.RSAPrivateKey | c_dsa.DSAPrivateKey]
# time (in millis) constants
year = 1000 * 60 * 60 * 24 * 365
ONE_YEARS_TD = datetime.timedelta(milliseconds=year)

crypto_numbits = 4096
signing_digest = c_hashes.SHA256

def guid():
    return binascii.hexlify(os.urandom(16)).decode('utf8')

def _genPrivKey() -> c_rsa.RSAPrivateKey:
    return c_rsa.generate_private_key(65537, crypto_numbits)

def _genCertBuilder(name: str, pubkey: c_types.PublicKeyTypes) -> c_x509.CertificateBuilder:
    builder = c_x509.CertificateBuilder()
    builder = builder.subject_name(c_x509.Name([
        c_x509.NameAttribute(c_x509.NameOID.COMMON_NAME, name),
    ]))

    now = datetime.datetime.now(datetime.UTC)
    builder = builder.not_valid_before(now)
    builder = builder.not_valid_after(now + ONE_YEARS_TD)  # certificates are good for 1 years
    builder = builder.serial_number(int(guid(), 16))
    builder = builder.public_key(pubkey)
    return builder

def selfSignCert(builder: c_x509.CertificateBuilder, pkey: Pkey) -> c_x509.Certificate:
        attr = builder._subject_name.get_attributes_for_oid(c_x509.NameOID.COMMON_NAME)[0]
        name = attr.value
        builder = builder.issuer_name(c_x509.Name([
            c_x509.NameAttribute(c_x509.NameOID.COMMON_NAME, name),
        ]))
        certificate = builder.sign(
            private_key=pkey, algorithm=signing_digest(),
        )
        return certificate

def signCertAs(builder: c_x509.CertificateBuilder, signas: PkeyAndCert) -> c_x509.Certificate:
    cakey, cacert = signas

    attr = cacert.subject.get_attributes_for_oid(c_x509.NameOID.COMMON_NAME)[0]
    name = attr.value

    builder = builder.issuer_name(c_x509.Name([
        c_x509.NameAttribute(c_x509.NameOID.COMMON_NAME, name),
    ]))
    certificate = builder.sign(
        private_key=cakey, algorithm=signing_digest(),
    )
    return certificate

def genCaCert(name: str, signas: Optional[PkeyAndCert] =None) -> PkeyAndCert:
    prvkey = _genPrivKey()
    builder = _genCertBuilder(name, prvkey.public_key())
    builder = builder.add_extension(
        c_x509.BasicConstraints(ca=True, path_length=None), critical=False,
    )
    if signas:
        cert = signCertAs(builder, signas)
    else:
        cert = selfSignCert(builder, prvkey)
    return prvkey, cert

def genCodeCert(name, signas: Optional[PkeyAndCert] = None) -> PkeyAndCert:
    prvkey = _genPrivKey()
    pubkey = prvkey.public_key()

    builder = _genCertBuilder(name, pubkey)
    builder = builder.add_extension(c_x509.UnrecognizedExtension(
        oid=c_x509.ObjectIdentifier(NSCERTTYPE_OID), value=NSCERTTYPE_OBJSIGN),
        critical=False,
    )
    builder = builder.add_extension(
        c_x509.KeyUsage(digital_signature=True, key_encipherment=False, data_encipherment=False,
                        key_agreement=False, key_cert_sign=False, crl_sign=False, encipher_only=False,
                        decipher_only=False, content_commitment=False),
        critical=False,
    )
    builder = builder.add_extension(c_x509.ExtendedKeyUsage([c_x509.oid.ExtendedKeyUsageOID.CODE_SIGNING]),
                                    critical=False)
    builder = builder.add_extension(c_x509.BasicConstraints(ca=False, path_length=None), critical=False)

    if signas is not None:
        cert = signCertAs(builder, signas)
    else:
        cert = selfSignCert(builder, prvkey)

    return prvkey, cert

def verifyCodeSign(cert: c_x509.Certificate, cacerts=List[c_x509.Certificate]) -> c_x509.verification.VerifiedClient:
    reqext = c_x509.ExtendedKeyUsage([c_x509.oid.ExtendedKeyUsageOID.CODE_SIGNING])

    eku = cert.extensions.get_extension_for_oid(c_x509.oid.ExtensionOID.EXTENDED_KEY_USAGE)
    if reqext != eku.value:
        mesg = 'Certificate is not for code signing.'
        raise ValueError(mesg)

    store = c_x509.verification.Store(cacerts)
    builder = c_x509.verification.PolicyBuilder()
    builder = builder.store(store)
    builder = builder.max_chain_depth(16) # example value
    verifier = builder.build_client_verifier()
    ekuz = cert.extensions.get_extension_for_class(c_x509.ExtendedKeyUsage)
    print(ekuz)
    try:
        # No untrusted intermediates present...
        print('Verifying certificate signature.')
        verifiedclient = verifier.verify(leaf=cert, intermediates=[])
    except Exception as e:
        print(f'RUH ROH: {e}')
        raise e

    return verifiedclient

def main():
    print('eku err reproducer')
    rootkey, rootcert = genCaCert('My cool CA')
    print(rootcert)
    immkey, immcert = genCaCert('secondary CA', signas=(rootkey, rootcert))
    print(immcert)
    codekey, codecert = genCodeCert('code signer', signas=(immkey, immcert))
    print(codecert)

    client = verifyCodeSign(codecert, cacerts=[rootcert, immcert])
    print(client)

    return 0

if __name__ == '__main__':
    main()

Python and Cryptography packages for reference:

> python -V
Python 3.11.10
> python -m pip list | grep -E "cffi|pip|setuptools|cryptography"
cffi                          1.17.1
cryptography                  44.0.0
pip                           24.3.1
setuptools                    65.5.0
@alex
Copy link
Member

alex commented Nov 29, 2024

Thanks for sharing the reproducer. This is actually being tracked in #11165

@alex alex closed this as completed Nov 29, 2024
@vEpiphyte
Copy link
Author

Thanks Alex!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants