synta 0.1.12

ASN.1 parser, decoder, and encoder library with DER/BER support and C FFI
Documentation
#!/usr/bin/env python3
"""
Tests for CertificateBuilder.sign(context=), CsrBuilder.sign(context=),
and create_pkcs12(cipher=, mac_algorithm=).
"""

import datetime
import traceback

import pytest

import synta
import synta.ext as ext
import synta.oids as oids


def _make_ec_key_and_name(cn="Test"):
    key = synta.PrivateKey.generate_ec("P-256")
    name = synta.NameBuilder().common_name(cn).build()
    return key, name


def _validity():
    now = datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc)
    later = datetime.datetime(2026, 1, 1, tzinfo=datetime.timezone.utc)
    return now, later


# ── CertificateBuilder.sign(context=) ────────────────────────────────────────

def test_cert_sign_without_context():
    key, name = _make_ec_key_and_name("No Context")
    now, later = _validity()
    cert = (
        synta.CertificateBuilder()
        .issuer_name(name).subject_name(name)
        .public_key(key.public_key).serial_number(1)
        .not_valid_before_utc(now).not_valid_after_utc(later)
        .sign(key, "sha256")
    )
    assert cert.signature_algorithm_oid == oids.ECDSA_WITH_SHA256


def test_cert_sign_with_context_none():
    key, name = _make_ec_key_and_name("Explicit None")
    now, later = _validity()
    cert = (
        synta.CertificateBuilder()
        .issuer_name(name).subject_name(name)
        .public_key(key.public_key).serial_number(1)
        .not_valid_before_utc(now).not_valid_after_utc(later)
        .sign(key, "sha256", context=None)
    )
    assert cert.signature_algorithm_oid == oids.ECDSA_WITH_SHA256


def test_cert_sign_result_is_certificate():
    key, name = _make_ec_key_and_name("Result Type")
    now, later = _validity()
    cert = (
        synta.CertificateBuilder()
        .issuer_name(name).subject_name(name)
        .public_key(key.public_key).serial_number(1)
        .not_valid_before_utc(now).not_valid_after_utc(later)
        .sign(key, "sha256")
    )
    assert isinstance(cert, synta.Certificate)
    assert cert.serial_number == 1


# ── CsrBuilder.sign(context=) ────────────────────────────────────────────────

def test_csr_sign_without_context():
    key, name = _make_ec_key_and_name("CSR No Context")
    csr = (
        synta.CsrBuilder()
        .subject_name(name).public_key(key.public_key)
        .sign(key, "sha256")
    )
    assert csr.signature_algorithm_oid == oids.ECDSA_WITH_SHA256


def test_csr_sign_with_context_none():
    key, name = _make_ec_key_and_name("CSR None Context")
    csr = (
        synta.CsrBuilder()
        .subject_name(name).public_key(key.public_key)
        .sign(key, "sha256", context=None)
    )
    assert csr.signature_algorithm_oid == oids.ECDSA_WITH_SHA256


def test_csr_sign_result_is_certification_request():
    key, name = _make_ec_key_and_name("CSR Result")
    csr = (
        synta.CsrBuilder()
        .subject_name(name).public_key(key.public_key)
        .sign(key, "sha256")
    )
    assert isinstance(csr, synta.CertificationRequest)


# ── create_pkcs12(cipher=, mac_algorithm=) ────────────────────────────────────

def _make_ca_cert():
    key, name = _make_ec_key_and_name("PKCS12 CA")
    now, later = _validity()
    cert = (
        synta.CertificateBuilder()
        .issuer_name(name).subject_name(name)
        .public_key(key.public_key).serial_number(1)
        .not_valid_before_utc(now).not_valid_after_utc(later)
        .add_extension(str(oids.BASIC_CONSTRAINTS), True, ext.basic_constraints(ca=True))
        .sign(key, "sha256")
    )
    return key, cert


def test_create_pkcs12_default():
    _, cert = _make_ca_cert()
    pfx = synta.create_pkcs12([cert])
    assert isinstance(pfx, bytes)
    assert len(pfx) > 0
    certs_back = synta.load_pkcs12_certificates(pfx, None)
    assert len(certs_back) >= 1


def test_create_pkcs12_with_key():
    key, cert = _make_ca_cert()
    pfx = synta.create_pkcs12([cert], private_key=key)
    assert isinstance(pfx, bytes)
    assert len(pfx) > 0


def test_create_pkcs12_cipher_aes256():
    _, cert = _make_ca_cert()
    try:
        pfx = synta.create_pkcs12([cert], password=b"test", cipher="aes256")
        assert isinstance(pfx, bytes)
    except (ValueError, NotImplementedError):
        pytest.skip("openssl feature not enabled")


def test_create_pkcs12_cipher_aes128():
    _, cert = _make_ca_cert()
    try:
        pfx = synta.create_pkcs12([cert], password=b"test", cipher="aes128")
        assert isinstance(pfx, bytes)
    except (ValueError, NotImplementedError):
        pytest.skip("openssl feature not enabled")


def test_create_pkcs12_mac_sha384():
    _, cert = _make_ca_cert()
    try:
        pfx = synta.create_pkcs12([cert], password=b"test", mac_algorithm="sha384")
        assert isinstance(pfx, bytes)
    except (ValueError, NotImplementedError):
        pytest.skip("openssl feature not enabled")


def test_create_pkcs12_mac_sha512():
    _, cert = _make_ca_cert()
    try:
        pfx = synta.create_pkcs12([cert], password=b"test", mac_algorithm="sha512")
        assert isinstance(pfx, bytes)
    except (ValueError, NotImplementedError):
        pytest.skip("openssl feature not enabled")


# ── Manual runner ─────────────────────────────────────────────────────────────

def main():
    tests = [
        test_cert_sign_without_context,
        test_cert_sign_with_context_none,
        test_cert_sign_result_is_certificate,
        test_csr_sign_without_context,
        test_csr_sign_with_context_none,
        test_csr_sign_result_is_certification_request,
        test_create_pkcs12_default,
        test_create_pkcs12_with_key,
        test_create_pkcs12_cipher_aes256,
        test_create_pkcs12_cipher_aes128,
        test_create_pkcs12_mac_sha384,
        test_create_pkcs12_mac_sha512,
    ]
    passed = failed = skipped = 0
    for t in tests:
        try:
            t()
            print(f"  ok  {t.__name__}")
            passed += 1
        except pytest.skip.Exception as e:
            print(f"  skip {t.__name__}: {e}")
            skipped += 1
        except Exception as e:
            print(f"  FAIL {t.__name__}: {e}")
            traceback.print_exc()
            failed += 1
    print(f"\n{passed} passed, {skipped} skipped, {failed} failed")
    if failed:
        raise SystemExit(1)


if __name__ == "__main__":
    main()