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

Add claims to Attestation #70

Merged
merged 14 commits into from
Nov 27, 2024
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- The `Attestation` type now has a `certificate_claims` property to expose
underlying Fulcio signing certificate extensions
([#70](https://github.com/trailofbits/pypi-attestations/pull/70))

## [0.0.17]

### Fixed
Expand Down
57 changes: 57 additions & 0 deletions src/pypi_attestations/_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,45 @@
from sigstore.verify.policy import VerificationPolicy


# List the claims OID supported
# Source: https://github.com/sigstore/fulcio/blob/main/docs/oid-info.md
# We only support the extensions from 1.3.6.1.4.1.57264.1.8 to .22.
# In particular, `1.3.6.1.4.1.57264.1.7 | OtherName SAN` is not supported
# because we believe this is not used in-the-wild.
_FULCIO_CLAIMS_OIDS = [
# 1.3.6.1.4.1.57264.1.8 | Issuer (V2)
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.8"),
# 1.3.6.1.4.1.57264.1.9 | Build Signer URI
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.9"),
# 1.3.6.1.4.1.57264.1.10 | Build Signer Digest
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.10"),
# 1.3.6.1.4.1.57264.1.11 | Runner Environment
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.11"),
# 1.3.6.1.4.1.57264.1.12 | Source Repository URI
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.12"),
# 1.3.6.1.4.1.57264.1.13 | Source Repository Digest
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.13"),
# 1.3.6.1.4.1.57264.1.14 | Source Repository Ref
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.14"),
# 1.3.6.1.4.1.57264.1.15 | Source Repository Identifier
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.15"),
# 1.3.6.1.4.1.57264.1.16 | Source Repository Owner URI
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.16"),
# 1.3.6.1.4.1.57264.1.17 | Source Repository Owner Identifier
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.17"),
# 1.3.6.1.4.1.57264.1.18 | Build Config URI
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.18"),
# 1.3.6.1.4.1.57264.1.19 | Build Config Digest
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.19"),
# 1.3.6.1.4.1.57264.1.20 | Build Trigger
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.20"),
# 1.3.6.1.4.1.57264.1.21 | Run Invocation URI
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.21"),
# 1.3.6.1.4.1.57264.1.22 | Source Repository Visibility At Signing
x509.ObjectIdentifier("1.3.6.1.4.1.57264.1.22"),
]


class Distribution(BaseModel):
"""Represents a Python package distribution.

Expand Down Expand Up @@ -167,6 +206,24 @@ def sign(cls, signer: Signer, dist: Distribution) -> Attestation:
except ConversionError as e:
raise AttestationError(str(e))

@property
def certificate_claims(self) -> dict[str, str]:
"""Return the claims present in the certificate.

We only return claims present in `_FULCIO_CLAIMS_OIDS`.
Values are decoded and returned as strings.
"""
certificate = x509.load_der_x509_certificate(self.verification_material.certificate)
claims = {}
for extension in certificate.extensions:
if extension.oid in _FULCIO_CLAIMS_OIDS:
# 1.3.6.1.4.1.57264.1.8 through 1.3.6.1.4.1.57264.1.22 are formatted as DER-encoded
# strings; the ASN.1 tag is UTF8String (0x0C) and the tag class is universal.
value = extension.value.value
claims[extension.oid.dotted_string] = _der_decode_utf8string(value)

return claims

def verify(
self,
identity: VerificationPolicy | Publisher,
Expand Down
48 changes: 48 additions & 0 deletions test/assets/pypi_attestations-0.0.16.tar.gz.attestation
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"envelope": {
"signature": "MEQCIEK1TlO0FG/yzoXHFu/ML77ATbXSHGgeMOFyT05x9bH3AiA8hubwYj3wV9yznsZkwJjHcjIHGSPgyJ8UZ+QP/o3img==",
"statement": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoicHlwaV9hdHRlc3RhdGlvbnMtMC4wLjE2LnRhci5neiIsImRpZ2VzdCI6eyJzaGEyNTYiOiJjYmQyYjk0NmZlMTYwNzkzNjA2ZGVlNDUxNmVmNThhYzU5NTk0NTZlNjk2MzBhN2YwM2U3ZGU3M2FhN2YyNzM3In19XSwicHJlZGljYXRlVHlwZSI6Imh0dHBzOi8vZG9jcy5weXBpLm9yZy9hdHRlc3RhdGlvbnMvcHVibGlzaC92MSIsInByZWRpY2F0ZSI6bnVsbH0="
},
"verification_material": {
"certificate": "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",
"transparency_entries": [
{
"canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZW52ZWxvcGVIYXNoIjp7ImFsZ29yaXRobSI6InNoYTI1NiIsInZhbHVlIjoiN2Y0YjdmYzIyMDEwNDQ4ODlkZWZlZDRlNDgzZmQ0NWQ4YWM5MWYwMjcyZTBkOWZjODc3MzAyODY3YzAyYTJmOSJ9LCJwYXlsb2FkSGFzaCI6eyJhbGdvcml0aG0iOiJzaGEyNTYiLCJ2YWx1ZSI6ImQ0ZjYxNWJjZDU1YWQzMDUzNDkxNmE1OWQ4N2EyZDBkZDZjNTE2MWE5ODdiMzg5MzY2YWY2ZWE0YjA2YmU2YzUifSwic2lnbmF0dXJlcyI6W3sic2lnbmF0dXJlIjoiTUVRQ0lFSzFUbE8wRkcveXpvWEhGdS9NTDc3QVRiWFNIR2dlTU9GeVQwNXg5YkgzQWlBOGh1YndZajN3Vjl5em5zWmt3SmpIY2pJSEdTUGd5SjhVWitRUC9vM2ltZz09IiwidmVyaWZpZXIiOiJMUzB0TFMxQ1JVZEpUaUJEUlZKVVNVWkpRMEZVUlMwdExTMHRDazFKU1VjdmFrTkRRbTlQWjBGM1NVSkJaMGxWVWpoMWQwMUVLMnRYYVdaNVUxVmlTR1p6U0RFelJFVmlkazg0ZDBObldVbExiMXBKZW1vd1JVRjNUWGNLVG5wRlZrMUNUVWRCTVZWRlEyaE5UV015Ykc1ak0xSjJZMjFWZFZwSFZqSk5ValIzU0VGWlJGWlJVVVJGZUZaNllWZGtlbVJIT1hsYVV6RndZbTVTYkFwamJURnNXa2RzYUdSSFZYZElhR05PVFdwUmVFMVVRVE5OYWtrd1RYcEpNVmRvWTA1TmFsRjRUVlJCTTAxcVNURk5la2t4VjJwQlFVMUdhM2RGZDFsSUNrdHZXa2w2YWpCRFFWRlpTVXR2V2tsNmFqQkVRVkZqUkZGblFVVjFkRzFxTlVaSGJVTm9hWFZsTVhCaFRIVTRhRTFuVURkQlkzQmxVbmROYUVsc2IwMEtjak15VkdVMlNISlRlREpzT0RCSGVHVmpMMlJDZVVwVE16TlRjR3BCV0ZRMVZVbDZhbmQxWjNOME5rTlhOa2hLVjB0UFEwSmhTWGRuWjFkbFRVRTBSd3BCTVZWa1JIZEZRaTkzVVVWQmQwbElaMFJCVkVKblRsWklVMVZGUkVSQlMwSm5aM0pDWjBWR1FsRmpSRUY2UVdSQ1owNVdTRkUwUlVablVWVlpRbGxIQ213eWFsTTRWVXB4VTFaeFIwTTFWMEl3ZW5RMmNtRXdkMGgzV1VSV1VqQnFRa0puZDBadlFWVXpPVkJ3ZWpGWmEwVmFZalZ4VG1wd1MwWlhhWGhwTkZrS1drUTRkMkpuV1VSV1VqQlNRVkZJTDBKSFVYZFpiMXBuWVVoU01HTklUVFpNZVRsdVlWaFNiMlJYU1hWWk1qbDBURE5TZVZsWGJITmlNbHBwWVZoU2VncE1NMEkxWTBkcmRGbFlVakJhV0U0d1dWaFNjR0l5TlhwTWVUVnVZVmhTYjJSWFNYWmtNamw1WVRKYWMySXpaSHBNTTBwc1lrZFdhR015VlhWbFZ6RnpDbEZJU214YWJrMTJaRWRHYm1ONU9USk5RelIzVEdwRk1rMUVhMGREYVhOSFFWRlJRbWMzT0hkQlVVVkZTekpvTUdSSVFucFBhVGgyWkVjNWNscFhOSFVLV1ZkT01HRlhPWFZqZVRWdVlWaFNiMlJYU2pGak1sWjVXVEk1ZFdSSFZuVmtRelZxWWpJd2QwWlJXVXRMZDFsQ1FrRkhSSFo2UVVKQloxRklZMjFXY3dwYVYwWjZXbFJCTWtKbmIzSkNaMFZGUVZsUEwwMUJSVVJDUTJjeFQwZE5ORTU2U214T2FtUnFUVVJPYWs5WFRYZE5la1pwV1ZSamVGbHFSVEpPVkZKdENscHFWVEJOYlZwdFRXcHJkMWt5VVROTlFsVkhRMmx6UjBGUlVVSm5OemgzUVZGUlJVSXpTbXhpUjFab1l6SlZkMHQzV1V0TGQxbENRa0ZIUkhaNlFVSUtRbEZSWkdSSVNtaGhWM2gyV20xS2NHUklUWFpqU0d4M1lWTXhhR1JJVW14ak0xSm9aRWRzZG1KdVRYZElkMWxMUzNkWlFrSkJSMFIyZWtGQ1FtZFJVZ3BqYlZadFkzazVNRmxYWkhwTU0xbDNUR3BCZFUxVVdYZFBkMWxMUzNkWlFrSkJSMFIyZWtGQ1EwRlJkRVJEZEc5a1NGSjNZM3B2ZGt3elVuWmhNbFoxQ2t4dFJtcGtSMngyWW01TmRWb3liREJoU0ZacFpGaE9iR050VG5aaWJsSnNZbTVSZFZreU9YUk5TRUZIUTJselIwRlJVVUpuTnpoM1FWRnJSVmxuZUdjS1lVaFNNR05JVFRaTWVUbHVZVmhTYjJSWFNYVlpNamwwVEROU2VWbFhiSE5pTWxwcFlWaFNla3d6UWpWalIydDBXVmhTTUZwWVRqQlpXRkp3WWpJMWVncE1lVFZ1WVZoU2IyUlhTWFprTWpsNVlUSmFjMkl6WkhwTU0wcHNZa2RXYUdNeVZYVmxWekZ6VVVoS2JGcHVUWFprUjBadVkzazVNazFETkhkTWFrVXlDazFFWjBkRGFYTkhRVkZSUW1jM09IZEJVVzlGUzJkM2IwNVVhR3BQUkdONVdsUlpNMWw2UVhwWmVteHFUVVJOZUZsdFJUTk5WMGw0VG1wVk1GcHRXVEVLVGtSS2JWcHFTVFZOUjA1clRucEJaRUpuYjNKQ1owVkZRVmxQTDAxQlJVeENRVGhOUkZka2NHUkhhREZaYVRGdllqTk9NRnBYVVhkUlFWbExTM2RaUWdwQ1FVZEVkbnBCUWtSQlVYbEVSRUp2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmRtTkliSGRoVXpGb0NtUklVbXhqTTFKb1pFZHNkbUp1VFhkUFFWbExTM2RaUWtKQlIwUjJla0ZDUkZGUmNVUkRaekZQUjAwMFRucEtiRTVxWkdwTlJFNXFUMWROZDAxNlJta0tXVlJqZUZscVJUSk9WRkp0V21wVk1FMXRXbTFOYW10M1dUSlJNMDFEUlVkRGFYTkhRVkZSUW1jM09IZEJVVFJGUlhkM1VtTnRWbTFqZVRrd1dWZGtlZ3BNTTFsM1RHcEJkVTFVV1hkSFVWbExTM2RaUWtKQlIwUjJla0ZDUkhkUlRFUkJhek5PZWtsNVRrUmpNRTFxVFhkTVoxbExTM2RaUWtKQlIwUjJla0ZDQ2tWQlVXZEVRalZ2WkVoU2QyTjZiM1pNTW1Sd1pFZG9NVmxwTldwaU1qQjJaRWhLYUdGWGVIWmFiVXB3WkVoTmQwWjNXVXRMZDFsQ1FrRkhSSFo2UVVJS1JWRlJTa1JCWTNsTmVrVXdUa1JKZWsxSVFVZERhWE5IUVZGUlFtYzNPSGRCVWtsRldXZDRaMkZJVWpCalNFMDJUSGs1Ym1GWVVtOWtWMGwxV1RJNWRBcE1NMUo1V1Zkc2MySXlXbWxoV0ZKNlRETkNOV05IYTNSWldGSXdXbGhPTUZsWVVuQmlNalY2VEhrMWJtRllVbTlrVjBsMlpESTVlV0V5V25OaU0yUjZDa3d6U214aVIxWm9ZekpWZFdWWE1YTlJTRXBzV201TmRtUkhSbTVqZVRreVRVTTBkMHhxUlRKTlJHZEhRMmx6UjBGUlVVSm5OemgzUVZKTlJVdG5kMjhLVGxSb2FrOUVZM2xhVkZreldYcEJlbGw2YkdwTlJFMTRXVzFGTTAxWFNYaE9hbFV3V20xWk1VNUVTbTFhYWtrMVRVZE9hMDU2UVZoQ1oyOXlRbWRGUlFwQldVOHZUVUZGVlVKQmEwMUNNMHBzWWtkV2FHTXlWWGRhUVZsTFMzZFpRa0pCUjBSMmVrRkNSbEZTVjBSR1VtOWtTRkozWTNwdmRrd3laSEJrUjJneENsbHBOV3BpTWpCMlpFaEthR0ZYZUhaYWJVcHdaRWhOZG1OSWJIZGhVekZvWkVoU2JHTXpVbWhrUjJ4MlltNU5kbGxYVGpCaFZ6bDFZM2s1ZVdSWE5Yb0tUSHBGZUU1NlRYbE9WRmswVFhwbk1Fd3lSakJrUjFaMFkwaFNla3g2UlhkR1oxbExTM2RaUWtKQlIwUjJla0ZDUm1kUlNVUkJXbmRrVjBwellWZE5kd3BuV1d0SFEybHpSMEZSVVVJeGJtdERRa0ZKUldWM1VqVkJTR05CWkZGRVpGQlVRbkY0YzJOU1RXMU5Xa2hvZVZwYWVtTkRiMnR3WlhWT05EaHlaaXRJQ21sdVMwRk1lVzUxYW1kQlFVRmFUVWw1TDI5d1FVRkJSVUYzUWtkTlJWRkRTVVpYUlRNdmQyOTRjM1pEZEN0VFdtMW1NbEpESzFGdk1YZFlabVZLUTJVS0wwaHlOVTVJZW14M1pEQklRV2xDYVdVeFdGSmpkMU5xTkhWbVIwRXlRMEV2ZVRkQ2JuRXhkMVJXYm5NeVVHNVdNRmx2VTNscFVqUnNha0ZMUW1kbmNRcG9hMnBQVUZGUlJFRjNUbkJCUkVKdFFXcEZRV2hJYXpnMlNFVnRkalJyTTJWNk1XcHdOVlIzWm13d2VtcFVVRXRHYWpSaU1HSlpTRzFOVUVaNlNWUkNDbEo2V2t4WGNFZERMMUp4YXpKc2FsVXlObnB1UVdwRlFUaFVWMVpUYldaWWNHMUpXVVUyTlRJck0ybEVNMUpOZHl0NE0zbHBUMlIxYm01SGFVNWlXR1lLVm5Ob04wdHVXbUl2WjNCd09ETnBUakJETkRWRU4yTXhDaTB0TFMwdFJVNUVJRU5GVWxSSlJrbERRVlJGTFMwdExTMEsifV19fQ==",
"inclusionPromise": {
"signedEntryTimestamp": "MEYCIQCd4F6sF6WMgFMMK4iOV2hpd/IEJ/ScLRKagSPw6Pf8CgIhAJdfBmna8lHVMxrxxJfI8YmHG8VsGm0lb9EcJLJ+BL+n"
},
"inclusionProof": {
"checkpoint": {
"envelope": "rekor.sigstore.dev - 1193050959916656506\n25533066\nqvs8JkP8+3Utq03DxtSYzwsDWKXEAoriQSm5nauVCqY=\n\n— rekor.sigstore.dev wNI9ajBFAiEApqCgo2XcekZNseJlFzWEX0GK8tkBHHhCYmAPU9aSEkECIEHC7q9+y8OA1uKSWZvIWaM2HN+N+TTbIMCRn7ES3o95\n"
},
"hashes": [
"N8S5teoiks1Q2l1X06qKPa5RBAnKLBmz+MbDm0HBQLQ=",
"oR60wUolAs2vAjVLrNo9glJpArDAREyv2P2bj3aJt5w=",
"4ZD3GEL8Ur5l8CIMyjk8ZxkKxzEu9Lc3LwV8DQfSbsY=",
"WF8fC14uf8N7BWexcGPtcn2ukIqswoQlCOl4ffrjUuY=",
"bWVWNxTbzTRrZ8w26ffyWKjSyKuFMvkBwb9IlaeYyfU=",
"0xmVoTBKl7Win61Lt53r5MffAMQUIxTZve8Fta/wZtg=",
"benQsKBgqC82O3PMKh73dzqGPFgjXPZfBvOVBC9f7VE=",
"DvpH9wBCyQKtTWiggquWWE+DEos61Wkar5IFAE5f24M=",
"vjvWRq9utVBPAcT20yr9yyI/ov6jd+e3RpU+SDkDepY=",
"4lUF0YOu9XkIDXKXA0wMSzd6VeDY3TZAgmoOeWmS2+Y=",
"gf+9m552B3PnkWnO0o4KdVvjcT3WVHLrCbf1DoVYKFw="
],
"logIndex": "25533065",
"rootHash": "qvs8JkP8+3Utq03DxtSYzwsDWKXEAoriQSm5nauVCqY=",
"treeSize": "25533066"
},
"integratedTime": "1731019406",
"kindVersion": {
"kind": "dsse",
"version": "0.0.1"
},
"logId": {
"keyId": "wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="
},
"logIndex": "147437327"
}
]
},
"version": 1
}
35 changes: 35 additions & 0 deletions test/test_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
dist = impl.Distribution.from_file(dist_path)
dist_bundle_path = _ASSETS / "rfc8785-0.1.2-py3-none-any.whl.sigstore"
dist_attestation_path = _ASSETS / "rfc8785-0.1.2-py3-none-any.whl.attestation"
pypi_attestations_attestation = _ASSETS / "pypi_attestations-0.0.16.tar.gz.attestation"

# produced by actions/attest@v1
gh_signed_dist_path = _ASSETS / "pypi_attestation_models-0.0.4a2.tar.gz"
Expand Down Expand Up @@ -417,6 +418,40 @@ def test_verify_unknown_attestation_type(self, monkeypatch: pytest.MonkeyPatch)
with pytest.raises(impl.VerificationError, match="unknown attestation type: foo"):
attestation.verify(pol, dist, staging=True)

def test_certificate_claims(self) -> None:
attestation = impl.Attestation.model_validate_json(
pypi_attestations_attestation.read_text()
)

results = {
("1.3.6.1.4.1.57264.1.8", "https://token.actions.githubusercontent.com"),
(
"1.3.6.1.4.1.57264.1.9",
"https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.16",
),
("1.3.6.1.4.1.57264.1.10", "58c872e67c03c9c031ba71b1654ff542ff290cd7"),
("1.3.6.1.4.1.57264.1.11", "github-hosted"),
("1.3.6.1.4.1.57264.1.12", "https://github.com/trailofbits/pypi-attestations"),
("1.3.6.1.4.1.57264.1.13", "58c872e67c03c9c031ba71b1654ff542ff290cd7"),
("1.3.6.1.4.1.57264.1.14", "refs/tags/v0.0.16"),
("1.3.6.1.4.1.57264.1.15", "772247423"),
("1.3.6.1.4.1.57264.1.16", "https://github.com/trailofbits"),
("1.3.6.1.4.1.57264.1.17", "2314423"),
(
"1.3.6.1.4.1.57264.1.18",
"https://github.com/trailofbits/pypi-attestations/.github/workflows/release.yml@refs/tags/v0.0.16",
),
("1.3.6.1.4.1.57264.1.19", "58c872e67c03c9c031ba71b1654ff542ff290cd7"),
("1.3.6.1.4.1.57264.1.20", "release"),
(
"1.3.6.1.4.1.57264.1.21",
"https://github.com/trailofbits/pypi-attestations/actions/runs/11732568384/attempts/1",
),
("1.3.6.1.4.1.57264.1.22", "public"),
}

assert not results ^ set(attestation.certificate_claims.items())


def test_from_bundle_missing_signatures() -> None:
bundle = Bundle.from_json(dist_bundle_path.read_bytes())
Expand Down