Skip to content

Commit

Permalink
_verify: Check for URI SANs when verifying certificate emails (#288)
Browse files Browse the repository at this point in the history
* _verify: Check for URI SANs when verifying certificate emails

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* test: Add unit tests for verifying SANs

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* test: Update tests to use identity parameter

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* test: Add test to verify issuer and SAN at the same time

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* _utils, _verify: Create helper for checking SAN

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* _utils: Also check `OtherName` in SAN check

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

* _utils: Fix OtherName check

Signed-off-by: Alex Cameron <asc@tetsuo.sh>

Signed-off-by: Alex Cameron <asc@tetsuo.sh>
  • Loading branch information
tetsuo-cpp authored Nov 4, 2022
1 parent 85a96dd commit 2b26cf8
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 11 deletions.
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",
)

0 comments on commit 2b26cf8

Please sign in to comment.