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

_verify: Check for URI SANs when verifying certificate emails #288

Merged
merged 8 commits into from
Nov 4, 2022
24 changes: 24 additions & 0 deletions sigstore/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@

from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec, rsa
from cryptography.x509 import (
Certificate,
ObjectIdentifier,
OtherName,
RFC822Name,
SubjectAlternativeName,
UniformResourceIdentifier,
)

PublicKey = Union[rsa.RSAPublicKey, ec.EllipticCurvePublicKey]

Expand Down Expand Up @@ -59,3 +67,19 @@ def key_id(key: PublicKey) -> bytes:
)

return hashlib.sha256(public_bytes).digest()


def cert_contains_identity(cert: Certificate, expected_cert_identity: str) -> bool:
"""
Check that the certificate's SubjectAlternativeName contains a given identity.
"""
san_ext = cert.extensions.get_extension_for_class(SubjectAlternativeName)
return (
expected_cert_identity in san_ext.value.get_values_for_type(RFC822Name)
or expected_cert_identity
in san_ext.value.get_values_for_type(UniformResourceIdentifier)
or OtherName(
ObjectIdentifier("1.3.6.1.4.1.57264.1.7"), expected_cert_identity.encode()
)
in san_ext.value.get_values_for_type(OtherName)
)
18 changes: 7 additions & 11 deletions sigstore/_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
ExtensionNotFound,
KeyUsage,
ObjectIdentifier,
RFC822Name,
SubjectAlternativeName,
load_pem_x509_certificate,
)
from cryptography.x509.oid import ExtendedKeyUsageOID
Expand All @@ -53,6 +51,7 @@
)
from sigstore._internal.rekor import RekorClient, RekorEntry
from sigstore._internal.set import InvalidSetError, verify_set
from sigstore._utils import cert_contains_identity

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -207,15 +206,12 @@ def verify(
reason="Extended usage does not contain `code signing`"
)

if expected_cert_identity is not None:
# Check that SubjectAlternativeName contains signer identity
san_ext = cert.extensions.get_extension_for_class(SubjectAlternativeName)
if expected_cert_identity not in san_ext.value.get_values_for_type(
RFC822Name
):
return VerificationFailure(
reason=f"Subject name does not contain identity: {expected_cert_identity}"
)
if expected_cert_identity is not None and not cert_contains_identity(
cert, expected_cert_identity
):
return VerificationFailure(
reason=f"Subject name does not contain identity: {expected_cert_identity}"
)

if expected_cert_oidc_issuer is not None:
# Check that the OIDC issuer extension is present, and contains the expected
Expand Down
5 changes: 5 additions & 0 deletions test/assets/c.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DO NOT MODIFY ME!

this is "c.txt", a sample input for sigstore-python's unit tests.

DO NOT MODIFY ME!
23 changes: 23 additions & 0 deletions test/assets/c.txt.crt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-----BEGIN CERTIFICATE-----
MIIDwTCCA0igAwIBAgIUdXPCI40ren/SEkqxmHcCc6lIV7MwCgYIKoZIzj0EAwMw
NzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRl
cm1lZGlhdGUwHhcNMjIxMTAzMDc0NTM1WhcNMjIxMTAzMDc1NTM1WjAAMHYwEAYH
KoZIzj0CAQYFK4EEACIDYgAELVUlqi4FjTw4mHzuyE8sOsK6mVvzOTv0EX7ot+aZ
ftaf+ato9xuemqA69qARscFPwG15It1F9PVdKUOeJkTPjZC+lRHNAIeamJpilskz
xqR6fisI7q72zHY8OhgMnSSHo4ICSjCCAkYwDgYDVR0PAQH/BAQDAgeAMBMGA1Ud
JQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQWBBREb7Dfm1g8gILV3K9rT9WSF7GnzzAf
BgNVHSMEGDAWgBRxhjCmFHxib/n31vQFGn9f/+tvrDBmBgNVHREBAf8EXDBahlho
dHRwczovL2dpdGh1Yi5jb20vc2lnc3RvcmUvc2lnc3RvcmUtcHl0aG9uLy5naXRo
dWIvd29ya2Zsb3dzL2NpLnltbEByZWZzL3B1bGwvMjg4L21lcmdlMDkGCisGAQQB
g78wAQEEK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5j
b20wGgYKKwYBBAGDvzABAgQMcHVsbF9yZXF1ZXN0MDYGCisGAQQBg78wAQMEKDNi
ZTcyMzU2ZWY0NTE3YmI4ZTUwZjI5Njg4N2Y5YzU3ODZmOTAzMTYwEAYKKwYBBAGD
vzABBAQCQ0kwJgYKKwYBBAGDvzABBQQYc2lnc3RvcmUvc2lnc3RvcmUtcHl0aG9u
MCEGCisGAQQBg78wAQYEE3JlZnMvcHVsbC8yODgvbWVyZ2UwgYoGCisGAQQB1nkC
BAIEfAR6AHgAdgArMLzcaIjJ4uHYJiledB9IOTGWAvKcM8teQ0D+sqyGegAAAYQ8
c9igAAAEAwBHMEUCIQCn/JSbLxs0ds3Nycn0yINUQABeltbAmcYDFEn/sdm50gIg
fm4lKdhXJoWHJRC8IS7MxYI3yR/oNzX6dntuqpHJ24YwCgYIKoZIzj0EAwMDZwAw
ZAIwE0F3B/HgHn+ov6axOY0TMR/hv2DUVlC3qkGBQEEMtglf5qtT+a9g7aQ5g4pG
of+JAjB+qUeUdSAyGPDK+5Ti6aROy0oAbwl+B3bH7QmmZ/i5M++PXIW4l4lcuAmA
UkjTgLw=
-----END CERTIFICATE-----
1 change: 1 addition & 0 deletions test/assets/c.txt.sig
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
MGUCMAQYRaYOdZEOT3C3WP22sC9+2euiFGYbC4VNefWVL31+MAL7oKMWsHsBwh1ngjTZHAIxALuUf+mzlACBqYUSTTwl3LFIGUGl8g3Z6wkTMsqdI1NrtHj0rVpcWA1DIO4GhGOM5w==
54 changes: 54 additions & 0 deletions test/test_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,57 @@ def test_verify_result_boolish():
assert not VerificationFailure(reason="foo")
assert not CertificateVerificationFailure(reason="foo", exception=ValueError("bar"))
assert VerificationSuccess()


@pytest.mark.online
def test_verifier_issuer(signed_asset):
a_assets = signed_asset("a.txt")

verifier = Verifier.staging()
assert verifier.verify(
a_assets[0],
a_assets[1],
a_assets[2],
expected_cert_oidc_issuer="https://github.com/login/oauth",
)


@pytest.mark.online
def test_verifier_san_email(signed_asset):
a_assets = signed_asset("a.txt")

verifier = Verifier.staging()
assert verifier.verify(
a_assets[0],
a_assets[1],
a_assets[2],
expected_cert_identity="william@yossarian.net",
)


@pytest.mark.online
def test_verifier_san_uri(signed_asset):
a_assets = signed_asset("c.txt")

verifier = Verifier.staging()
assert verifier.verify(
a_assets[0],
a_assets[1],
a_assets[2],
expected_cert_identity="https://github.com/sigstore/"
"sigstore-python/.github/workflows/ci.yml@refs/pull/288/merge",
)


@pytest.mark.online
def test_verifier_issuer_and_san(signed_asset):
a_assets = signed_asset("a.txt")

verifier = Verifier.staging()
assert verifier.verify(
a_assets[0],
a_assets[1],
a_assets[2],
expected_cert_identity="william@yossarian.net",
expected_cert_oidc_issuer="https://github.com/login/oauth",
)