Skip to content

Sign and verify with rekorv2 #1432

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

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions sigstore/_internal/trust.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@
)

from sigstore._internal.fulcio.client import FulcioClient
from sigstore._internal.rekor import RekorLogSubmitter
from sigstore._internal.rekor.client import RekorClient
from sigstore._internal.rekor.client_v2 import RekorV2Client
from sigstore._internal.timestamp import TimestampAuthorityClient
from sigstore._internal.tuf import DEFAULT_TUF_URL, STAGING_TUF_URL, TrustUpdater
from sigstore._utils import (
Expand All @@ -73,7 +75,7 @@
from sigstore.errors import Error, MetadataError, TUFError, VerificationError

# Versions supported by this client
REKOR_VERSIONS = [1]
REKOR_VERSIONS = [1, 2]
TSA_VERSIONS = [1]
FULCIO_VERSIONS = [1]
OIDC_VERSIONS = [1]
Expand Down Expand Up @@ -420,11 +422,19 @@ def _get_valid_services(

return result[:count]

def get_tlogs(self) -> list[RekorClient]:
def get_tlogs(self) -> list[RekorLogSubmitter]:
"""
Returns the rekor transparency log clients to sign with.
"""
return [RekorClient(tlog.url) for tlog in self._tlogs]
result: list[RekorLogSubmitter] = []
for tlog in self._tlogs:
if tlog.major_api_version == 1:
result.append(RekorClient(tlog.url))
elif tlog.major_api_version == 2:
result.append(RekorV2Client(tlog.url))
else:
raise AssertionError(f"Unexpected Rekor v{tlog.major_api_version}")
return result

def get_fulcio(self) -> FulcioClient:
"""
Expand Down
218 changes: 182 additions & 36 deletions sigstore/verify/verifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@

import rekor_types
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.x509 import ExtendedKeyUsage, KeyUsage
from cryptography.x509 import Certificate, ExtendedKeyUsage, KeyUsage
from cryptography.x509.oid import ExtendedKeyUsageOID
from OpenSSL.crypto import (
X509,
Expand All @@ -38,6 +39,8 @@
from pydantic import ValidationError
from rfc3161_client import TimeStampResponse, VerifierBuilder
from rfc3161_client import VerificationError as Rfc3161VerificationError
from sigstore_protobuf_specs.dev.sigstore.common import v1
from sigstore_protobuf_specs.dev.sigstore.rekor import v2

from sigstore import dsse
from sigstore._internal.rekor import _hashedrekord_from_parts
Expand Down Expand Up @@ -418,34 +421,18 @@ def verify_dsse(
# Instead, we manually pick apart the entry body below and verify
# the parts we can (namely the payload hash and signature list).
entry = bundle.log_entry
try:
entry_body = rekor_types.Dsse.model_validate_json(
base64.b64decode(entry.body)
if entry._kind_version.kind != "dsse":
raise VerificationError(
f"Expected entry type dsse, got {entry._kind_version.kind}"
)
except ValidationError as exc:
raise VerificationError(f"invalid DSSE log entry: {exc}")

payload_hash = sha256_digest(envelope._inner.payload).digest.hex()
if (
entry_body.spec.root.payload_hash.algorithm # type: ignore[union-attr]
!= rekor_types.dsse.Algorithm.SHA256
):
raise VerificationError("expected SHA256 payload hash in DSSE log entry")
if payload_hash != entry_body.spec.root.payload_hash.value: # type: ignore[union-attr]
raise VerificationError("log entry payload hash does not match bundle")

# NOTE: Like `dsse._verify`: multiple signatures would be frivolous here,
# but we handle them just in case the signer has somehow produced multiple
# signatures for their envelope with the same signing key.
signatures = [
rekor_types.dsse.Signature(
signature=base64.b64encode(signature.sig).decode(),
verifier=base64_encode_pem_cert(bundle.signing_certificate),
if entry._kind_version.version == "0.0.2":
_validate_dsse_v002_entry_body(bundle)
elif entry._kind_version.version == "0.0.1":
_validate_dsse_v001_entry_body(bundle)
else:
raise VerificationError(
f"Unsupported dsse version {entry._kind_version.version}"
)
for signature in envelope._inner.signatures
]
if signatures != entry_body.spec.root.signatures:
raise VerificationError("log entry signatures do not match bundle")

return (envelope._inner.payload_type, envelope._inner.payload)

Expand Down Expand Up @@ -490,16 +477,175 @@ def verify_artifact(
# (8): verify the consistency of the log entry's body against
# the other bundle materials (and input being verified).
entry = bundle.log_entry
if entry._kind_version.kind != "hashedrekord":
raise VerificationError(
f"Expected entry type hashedrekord, got {entry._kind_version.kind}"
)

if entry._kind_version.version == "0.0.2":
_validate_hashedrekord_v002_entry_body(bundle)
elif entry._kind_version.version == "0.0.1":
_validate_hashedrekord_v001_entry_body(bundle, hashed_input)
else:
raise VerificationError(
f"Unsupported hashedrekord version {entry._kind_version.version}"
)

expected_body = _hashedrekord_from_parts(
bundle.signing_certificate,
bundle._inner.message_signature.signature, # type: ignore[union-attr]
hashed_input,

def _validate_dsse_v001_entry_body(bundle: Bundle) -> None:
"""
Validate the Entry body for dsse v001.
"""
entry = bundle.log_entry
envelope = bundle._dsse_envelope
if envelope is None:
raise VerificationError(
"cannot perform DSSE verification on a bundle without a DSSE envelope"
)
actual_body = rekor_types.Hashedrekord.model_validate_json(
base64.b64decode(entry.body)
try:
entry_body = rekor_types.Dsse.model_validate_json(base64.b64decode(entry.body))
except ValidationError as exc:
raise VerificationError(f"invalid DSSE log entry: {exc}")

payload_hash = sha256_digest(envelope._inner.payload).digest.hex()
if (
entry_body.spec.root.payload_hash.algorithm # type: ignore[union-attr]
!= rekor_types.dsse.Algorithm.SHA256
):
raise VerificationError("expected SHA256 payload hash in DSSE log entry")
if payload_hash != entry_body.spec.root.payload_hash.value: # type: ignore[union-attr]
raise VerificationError("log entry payload hash does not match bundle")

# NOTE: Like `dsse._verify`: multiple signatures would be frivolous here,
# but we handle them just in case the signer has somehow produced multiple
# signatures for their envelope with the same signing key.
signatures = [
rekor_types.dsse.Signature(
signature=base64.b64encode(signature.sig).decode(),
verifier=base64_encode_pem_cert(bundle.signing_certificate),
)
if expected_body != actual_body:
raise VerificationError(
"transparency log entry is inconsistent with other materials"
for signature in envelope._inner.signatures
]
if signatures != entry_body.spec.root.signatures:
raise VerificationError("log entry signatures do not match bundle")


def _validate_dsse_v002_entry_body(bundle: Bundle) -> None:
"""
Validate Entry body for dsse v002.
"""
entry = bundle.log_entry
envelope = bundle._dsse_envelope
if envelope is None:
raise VerificationError(
"cannot perform DSSE verification on a bundle without a DSSE envelope"
)
try:
v2_body = v2.Entry().from_json(base64.b64decode(entry.body))
except ValidationError as exc:
raise VerificationError(f"invalid DSSE log entry: {exc}")

if v2_body.spec.dsse_v002 is None:
raise VerificationError("invalid DSSE log entry: missing dsse_v002 field")

if v2_body.spec.dsse_v002.payload_hash.algorithm != v1.HashAlgorithm.SHA2_256:
raise VerificationError("expected SHA256 hash in DSSE entry")

digest = sha256_digest(envelope._inner.payload).digest
if v2_body.spec.dsse_v002.payload_hash.digest != digest:
raise VerificationError("DSSE entry payload hash does not match bundle")

v2_signatures = [
v2.Signature(
content=signature.sig,
verifier=_v2_verifier_from_certificate(bundle.signing_certificate),
)
for signature in envelope._inner.signatures
]
if v2_signatures != v2_body.spec.dsse_v002.signatures:
raise VerificationError("log entry signatures do not match bundle")


def _validate_hashedrekord_v001_entry_body(
bundle: Bundle, hashed_input: Hashed
) -> None:
"""
Validate the Entry body for hashedrekord v001.
"""
entry = bundle.log_entry
expected_body = _hashedrekord_from_parts(
bundle.signing_certificate,
bundle._inner.message_signature.signature, # type: ignore[union-attr]
hashed_input,
)
actual_body = rekor_types.Hashedrekord.model_validate_json(
base64.b64decode(entry.body)
)
if expected_body != actual_body:
raise VerificationError(
"transparency log entry is inconsistent with other materials"
)


def _validate_hashedrekord_v002_entry_body(bundle: Bundle) -> None:
"""
Validate Entry body for hashedrekord v002.
"""
entry = bundle.log_entry
if bundle._inner.message_signature is None:
raise VerificationError(
"invalid hashedrekord log entry: missing message signature"
)
v2_expected_body = v2.Entry(
kind=entry._kind_version.kind,
api_version=entry._kind_version.version,
spec=v2.Spec(
hashed_rekord_v002=v2.HashedRekordLogEntryV002(
data=v1.HashOutput(
algorithm=bundle._inner.message_signature.message_digest.algorithm,
digest=bundle._inner.message_signature.message_digest.digest,
),
signature=v2.Signature(
content=bundle._inner.message_signature.signature,
verifier=_v2_verifier_from_certificate(bundle.signing_certificate),
),
)
),
)
v2_actual_body = v2.Entry().from_json(base64.b64decode(entry.body))
if v2_expected_body != v2_actual_body:
raise VerificationError(
"transparency log entry is inconsistent with other materials"
)


def _v2_verifier_from_certificate(certificate: Certificate) -> v2.Verifier:
"""
Return a Rekor v2 protobuf Verifier for the signing certificate.

This method decides which signature algorithms are supported for verification
(in a rekor v2 entry), see
https://github.com/sigstore/architecture-docs/blob/main/algorithm-registry.md.
Note that actual signature verification happens in verify_artifact() and
verify_dsse(): New keytypes need to be added here and in those methods.
"""
public_key = certificate.public_key()

if isinstance(public_key, ec.EllipticCurvePublicKey):
if isinstance(public_key.curve, ec.SECP256R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P256_SHA_256
elif isinstance(public_key.curve, ec.SECP384R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P384_SHA_384
elif isinstance(public_key.curve, ec.SECP521R1):
key_details = v1.PublicKeyDetails.PKIX_ECDSA_P521_SHA_512
else:
raise ValueError(f"Unsupported EC curve: {public_key.curve.name}")
else:
raise ValueError(f"Unsupported public key type: {type(public_key)}")

return v2.Verifier(
x509_certificate=v1.X509Certificate(
certificate.public_bytes(encoding=serialization.Encoding.DER)
),
key_details=cast(v1.PublicKeyDetails, key_details),
)
5 changes: 5 additions & 0 deletions test/assets/a.dsse.staging-rekor-v2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
DO NOT MODIFY ME!

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

DO NOT MODIFY ME!
1 change: 1 addition & 0 deletions test/assets/a.dsse.staging-rekor-v2.txt.sigstore.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"mediaType": "application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial": {"certificate": {"rawBytes": "MIIDBDCCAoqgAwIBAgIUYlZafqye+P/bWSMSdvxrr7y+NUEwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjUwNjA5MjEwNjI1WhcNMjUwNjA5MjExNjI1WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEwDj9XB2rrkUTaCgPE3OGPJ+176EZM3u2SK2XLKoMUQn79zywhocahVPybzn/6nMkWkew8SFaDhkL4PCAENNzcqOCAakwggGlMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAdBgNVHQ4EFgQUQ/OiAAk5AAqjN5apYfVwt/M4S5UwHwYDVR0jBBgwFoAUcYYwphR8Ym/599b0BRp/X//rb6wwWQYDVR0RAQH/BE8wTYFLaW5zZWN1cmUtY2xvdWR0b3Atc2hhcmVkLXVzZXJAY2xvdWR0b3AtcHJvZC11cy1lYXN0LmlhbS5nc2VydmljZWFjY291bnQuY29tMCkGCisGAQQBg78wAQEEG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTArBgorBgEEAYO/MAEIBB0MG2h0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbTCBigYKKwYBBAHWeQIEAgR8BHoAeAB2ACswvNxoiMni4dgmKV50H0g5MZYC8pwzy15DQP6yrIZ6AAABl1aEEo4AAAQDAEcwRQIhAJzFA8xqE8owuQqk9ao7NLQy/YoTsy23A+ZU3cdL+MM1AiAZyN3FSWf13Fl3oL+P5jAvv0xRyqGrWEyZJw4KO7XhnDAKBggqhkjOPQQDAwNoADBlAjA9OgkRsqwLbt59TB0Jb15NBBQiaNBRRqUdo2FuSrvEWWDnnynmqo0GygnbCmz2CJwCMQDFCWJExAUGX7v5UQUzDz1pc1b0WvX1wAP2fhbgir2yZZRcsr4OdWz31arOo6USvVI="}, "tlogEntries": [{"logIndex": "689", "logId": {"keyId": "8w1amZ2S5mJIQkQmPxdMuOrL/oJkvFg9MnQXmeOCXck="}, "kindVersion": {"kind": "dsse", "version": "0.0.2"}, "inclusionProof": {"logIndex": "689", "rootHash": "VLopDAB81ENEy7SM2Oe4gxf026TulneLw22pUPlt0qE=", "treeSize": "690", "hashes": ["7G2mWiDIVCMp4cUCF9+qqADG/ICLRt3I2I9nqIWaKnA=", "/Fm4+swicRuu0gv27PWsZ2C1hw3IbCcatPnSV6oTbOw=", "9AF3UpKoSTEa5MS8BHGJxKHH9zVkJgn29s03k14ZtdI=", "QMesRTEZdIgthOEinYE/9J7wGv+VmArDZTICj9POmhY=", "UNUMG62rMwoqCqFKknh4R5Ubkf5Z6dj+Pk0m/1xu8uo="], "checkpoint": {"envelope": "log2025-alpha1.rekor.sigstage.dev\n690\nVLopDAB81ENEy7SM2Oe4gxf026TulneLw22pUPlt0qE=\n\n\u2014 log2025-alpha1.rekor.sigstage.dev 8w1amfdsl47Li2mk9esQ1K+vF9tg8WCLlNKBcoVTzrHr4howD6z2171ij8XW6d48AUEoV4PK1DDz5jHUlCQ98okwLQw=\n"}}, "canonicalizedBody": "eyJhcGlWZXJzaW9uIjoiMC4wLjIiLCJraW5kIjoiZHNzZSIsInNwZWMiOnsiZHNzZVYwMDIiOnsicGF5bG9hZEhhc2giOnsiYWxnb3JpdGhtIjoiU0hBMl8yNTYiLCJkaWdlc3QiOiI0a2QxR3VyKzFmZE1wMHVBZFJyQnBQYTZONXB3OWx0b25pZXdlekg4MmhvPSJ9LCJzaWduYXR1cmVzIjpbeyJjb250ZW50IjoiTUVZQ0lRQ3F6dEJCTXpiYmU3alN6NXFQOE93U3hKWDBFb0VTSGg5d21uRXljUzd3S3dJaEFMd1BIaWt0b2dRY3greFZMWEhsSU56dTI1clRTNW5YRkJ3OEtxcXp5OGZkIiwidmVyaWZpZXIiOnsia2V5RGV0YWlscyI6IlBLSVhfRUNEU0FfUDI1Nl9TSEFfMjU2IiwieDUwOUNlcnRpZmljYXRlIjp7InJhd0J5dGVzIjoiTUlJREJEQ0NBb3FnQXdJQkFnSVVZbFphZnF5ZStQL2JXU01TZHZ4cnI3eStOVUV3Q2dZSUtvWkl6ajBFQXdNd056RVZNQk1HQTFVRUNoTU1jMmxuYzNSdmNtVXVaR1YyTVI0d0hBWURWUVFERXhWemFXZHpkRzl5WlMxcGJuUmxjbTFsWkdsaGRHVXdIaGNOTWpVd05qQTVNakV3TmpJMVdoY05NalV3TmpBNU1qRXhOakkxV2pBQU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRXdEajlYQjJycmtVVGFDZ1BFM09HUEorMTc2RVpNM3UyU0syWExLb01VUW43OXp5d2hvY2FoVlB5YnpuLzZuTWtXa2V3OFNGYURoa0w0UENBRU5OemNxT0NBYWt3Z2dHbE1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBVEJnTlZIU1VFRERBS0JnZ3JCZ0VGQlFjREF6QWRCZ05WSFE0RUZnUVVRL09pQUFrNUFBcWpONWFwWWZWd3QvTTRTNVV3SHdZRFZSMGpCQmd3Rm9BVWNZWXdwaFI4WW0vNTk5YjBCUnAvWC8vcmI2d3dXUVlEVlIwUkFRSC9CRTh3VFlGTGFXNXpaV04xY21VdFkyeHZkV1IwYjNBdGMyaGhjbVZrTFhWelpYSkFZMnh2ZFdSMGIzQXRjSEp2WkMxMWN5MWxZWE4wTG1saGJTNW5jMlZ5ZG1salpXRmpZMjkxYm5RdVkyOXRNQ2tHQ2lzR0FRUUJnNzh3QVFFRUcyaDBkSEJ6T2k4dllXTmpiM1Z1ZEhNdVoyOXZaMnhsTG1OdmJUQXJCZ29yQmdFRUFZTy9NQUVJQkIwTUcyaDBkSEJ6T2k4dllXTmpiM1Z1ZEhNdVoyOXZaMnhsTG1OdmJUQ0JpZ1lLS3dZQkJBSFdlUUlFQWdSOEJIb0FlQUIyQUNzd3ZOeG9pTW5pNGRnbUtWNTBIMGc1TVpZQzhwd3p5MTVEUVA2eXJJWjZBQUFCbDFhRUVvNEFBQVFEQUVjd1JRSWhBSnpGQTh4cUU4b3d1UXFrOWFvN05MUXkvWW9Uc3kyM0ErWlUzY2RMK01NMUFpQVp5TjNGU1dmMTNGbDNvTCtQNWpBdnYweFJ5cUdyV0V5Wkp3NEtPN1hobkRBS0JnZ3Foa2pPUFFRREF3Tm9BREJsQWpBOU9na1JzcXdMYnQ1OVRCMEpiMTVOQkJRaWFOQlJScVVkbzJGdVNydkVXV0RubnlubXFvMEd5Z25iQ216MkNKd0NNUURGQ1dKRXhBVUdYN3Y1VVFVekR6MXBjMWIwV3ZYMXdBUDJmaGJnaXIyeVpaUmNzcjRPZFd6MzFhck9vNlVTdlZJPSJ9fX1dfX19"}], "timestampVerificationData": {"rfc3161Timestamps": [{"signedTimestamp": "MIIE5zADAgEAMIIE3gYJKoZIhvcNAQcCoIIEzzCCBMsCAQMxDTALBglghkgBZQMEAgEwgcEGCyqGSIb3DQEJEAEEoIGxBIGuMIGrAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQg7mKrZuedCow8ht74HmPFNT7ZP18+JAF/WDRwwOFuzn8CFBKaF0PyLXni4RkH6K+ZuzF9x2JcGA8yMDI1MDYwOTIxMDYyOFowAwIBAQIIWJ9Fv2Y6K7CgMqQwMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhoIICEzCCAg8wggGWoAMCAQICFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAoGCCqGSM49BAMDMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQwHhcNMjUwMzI4MDkxNDA2WhcNMzUwMzI2MDgxNDA2WjAuMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxFTATBgNVBAMTDHNpZ3N0b3JlLXRzYTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMdb+Rdx6Q/XoB7pJ6QRZUc+0AUQybuGnlc7fcyS0WNJb5sdZRe1gTNnPQDfGRj0LJg6h5STdkf+/kcS5L5S85HNfSDsd/Le5hhhHAe2oFA3Qhfyst0Uy0itF6P9AIB0HaNqMGgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBSo/GT2KN4u5jtzT1SMUsThnN1TpTAfBgNVHSMEGDAWgBQ7IEZZXrUyTUcwzm5j7nN0R/IEfTAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAKBggqhkjOPQQDAwNnADBkAjBEr1UuhhrRd9/idfU38BDViV40b+ItPx0BcC1EpF+k31e4NJxvFZ6jRyS7xKQLTo0CMFA97ssE16K0D9Q4G1dPaxfWHp/ghKrP4hKYniVj7LdvNEkjmeTWvncj1ZPf/EhZOjGCAdowggHWAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUCjWhBmHV4kFzxomWp/J98n4DfKcwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjUwNjA5MjEwNjI4WjAvBgkqhkiG9w0BCQQxIgQgm3w3T24hj0XJHfurAzfPAUM+UpN9mOfHY9jwsQe6eYkwgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIAb0/+BH/rNZmbczsNejI1Ac/BjkwDNmqEXXdTbnSydEMFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFAo1oQZh1eJBc8aJlqfyffJ+A3ynMAoGCCqGSM49BAMCBGYwZAIwJQ/ArYnYtKS38pLXrZ1A/CT1VGgDRUoSkslIGKlHU98qwoWUjjgmmdbeYakSqfENAjABbYaUoMwznhyQd8CKMo7f092Z3Plwa/enOQqgmyu1dAPpmD8rYr2VEjVEGKcvVoY="}]}}, "dsseEnvelope": {"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEiLCJzdWJqZWN0IjpbeyJuYW1lIjoiYS50eHQiLCJkaWdlc3QiOnsic2hhMjU2IjoiZTI0OGE1ZGI0OTMzZGJhNjU3ODIwMDIzOGM5MWE1N2Y1ZTY1YjkyNWI3MzA1MGFlNzg2OTMzNDY4YjdhYzEwMSJ9fV0sInByZWRpY2F0ZVR5cGUiOiJodHRwczovL3Nsc2EuZGV2L3Byb3ZlbmFuY2UvdjEiLCJwcmVkaWNhdGUiOnsiYnVpbGREZWZpbml0aW9uIjp7ImJ1aWxkVHlwZSI6Imh0dHBzOi8vYWN0aW9ucy5naXRodWIuaW8vYnVpbGR0eXBlcy93b3JrZmxvdy92MSIsImV4dGVybmFsUGFyYW1ldGVycyI6eyJ3b3JrZmxvdyI6eyJyZWYiOiJyZWZzL3RhZ3MvMS4yMS4wIiwicmVwb3NpdG9yeSI6Imh0dHBzOi8vZ2l0aHViLmNvbS9vY3RvLW9yZy9vY3RvLXJlcG8iLCJwYXRoIjoiLmdpdGh1Yi93b3JrZmxvd3MvY2kueWFtbCJ9fSwiaW50ZXJuYWxQYXJhbWV0ZXJzIjp7ImdpdGh1YiI6eyJldmVudF9uYW1lIjoicHVzaCIsInJlcG9zaXRvcnlfaWQiOiIwMDAwMDAwMDAiLCJyZXBvc2l0b3J5X293bmVyX2lkIjoiMDAwMDAwMCIsInJ1bm5lcl9lbnZpcm9ubWVudCI6ImdpdGh1Yi1ob3N0ZWQifX0sInJlc29sdmVkRGVwZW5kZW5jaWVzIjpbeyJ1cmkiOiJnaXQraHR0cHM6Ly9naXRodWIuY29tL29jdG8tb3JnL29jdG8tcmVwb0ByZWZzL3RhZ3MvMS4yMS4wIiwiZGlnZXN0Ijp7ImdpdENvbW1pdCI6IjFhYzkzY2UyMWVlNTI2YjM2ZmQxNTRiOTA1OGQ5N2RmYWE0MjRjNTAifX1dfSwicnVuRGV0YWlscyI6eyJidWlsZGVyIjp7ImlkIjoiaHR0cHM6Ly9naXRodWIuY29tL29jdG8tb3JnL29jdG8tcmVwby8uZ2l0aHViL3dvcmtmbG93cy9kb2NrZXIueWFtbEByZWZzL2hlYWRzL2RldmVsb3BtZW50In0sIm1ldGFkYXRhIjp7Imludm9jYXRpb25JZCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9vY3RvLW9yZy9vY3RvLXJlcG8vYWN0aW9ucy9ydW5zLzEwMzEzOTgzMjE4L2F0dGVtcHRzLzIifX19fQ==", "payloadType": "application/vnd.in-toto+json", "signatures": [{"sig": "MEYCIQCqztBBMzbbe7jSz5qP8OwSxJX0EoESHh9wmnEycS7wKwIhALwPHiktogQcx+xVLXHlINzu25rTS5nXFBw8Kqqzy8fd"}]}}
Loading