Pure, portable verification of Sigstore bundles and GitHub artifact attestations
| Step | Check |
|---|---|
| Transparency log | The Rekor v1 entry is from a log of the trusted root. Its signed entry timestamp (SET) verifies, and its integrated time is in the log's validity and not after "now" |
| Inclusion | The RFC 6962 inclusion proof gives the root hash of a checkpoint that the log key signed |
| Certificate | The Fulcio leaf chains to a certificate authority of the trusted root at the integrated time, for code signing |
| Certificate timestamp | An embedded SCT from a trusted CT log verifies |
| Log body | The logged signature, certificate and payload hash are those of the bundle |
| Content | The DSSE signature (or message signature) verifies with the leaf key |
| Artifact | A subject of the in-toto statement (or the message digest) is the artifact digest |
| Policy | The certificate has the issuer, identity and GitHub claims of a policy |
///|
test "verify the attestation of a GitHub release" {
let bundle = @sigstore.Bundle::parse(
github_fixture("cli-2.50.0.sigstore.json"),
)
let digest = @sigstore.ArtifactDigest::parse(
"sha256:7f9795b3ce99351a1bfc6ea3b09b7363cb1eccca19978a046bcb477839efab82",
)
let policy = @sigstore.Policy::github_workflow(
@sigstore.GitHubRepository::parse("cli/cli"),
workflow=@sigstore.WorkflowPath::parse(".github/workflows/deployment.yml"),
)
let now = @sigstore.Instant::from_unix_seconds(1793000000L)
match
@sigstore.verify(
bundle,
digest,
@sigstore.TrustedRoot::public_good(),
policy,
now~,
) {
Verified(facts) => {
assert_eq(facts.integrated_time.unix_seconds(), 1716998983L)
assert_eq(facts.claims.source_repository_ref, Some("refs/heads/trunk"))
guard facts.statement is Some(statement) else { fail("no statement") }
assert_eq(statement.predicate_type, @sigstore.PredicateType::slsa_provenance_v1())
}
Rejected(reason) => fail(reason.message())
}
}| Type | Example | Rules |
|---|---|---|
| Issuer | https://token.actions.githubusercontent.com | An https URL with a host |
| SubjectName | the certificate SAN | Not empty, no spaces |
| GitHubRepository | finos/morphir-rust | owner/repo with GitHub's name rules |
| GitHubOwner | finos | 1 to 39 letters, digits and - |
| WorkflowPath | .github/workflows/release.yml | A .yml or .yaml file directly in .github/workflows/ |
| GitRef | refs/tags/v1.0.0 | refs/ and a name |
| GitHubId | 513894754 | Decimal digits, no leading zero |
| PredicateType | https://slsa.dev/provenance/v1 | An absolute URI |
///|
test "a policy that pins the repository and owner IDs" {
let repository = @sigstore.GitHubRepository::parse("finos/morphir-rust")
let policy = @sigstore.Policy::github_workflow(
repository,
workflow=@sigstore.WorkflowPath::parse(
".github/workflows/release-provider-snapshot.yml",
),
)
.repository_id(@sigstore.GitHubId::parse("513894754"))
.owner_id(@sigstore.GitHubId::parse("35377814"))
let verdict = @sigstore.verify(
@sigstore.Bundle::parse(
github_fixture("morphir-rust-provider-snapshot-0.2.0.sigstore.json"),
),
@sigstore.ArtifactDigest::parse(
"sha256:03d190b8ef97d354f9aca6854542979c36ce5f77c68a3d80de28ac1a77c432bd",
),
@sigstore.TrustedRoot::public_good(),
policy,
now=@sigstore.Instant::from_unix_seconds(1793000000L),
)
assert_true(verdict is Verified(_))
}///|
test "a policy for another repository rejects the bundle" {
let verdict = @sigstore.verify(
@sigstore.Bundle::parse(github_fixture("cli-2.50.0.sigstore.json")),
@sigstore.ArtifactDigest::parse(
"sha256:7f9795b3ce99351a1bfc6ea3b09b7363cb1eccca19978a046bcb477839efab82",
),
@sigstore.TrustedRoot::public_good(),
@sigstore.Policy::github_workflow(
@sigstore.GitHubRepository::parse("cli/go-gh"),
workflow=@sigstore.WorkflowPath::parse(".github/workflows/deployment.yml"),
),
now=@sigstore.Instant::from_unix_seconds(1793000000L),
)
guard verdict is Rejected(Invalid(Policy([IdentityMismatch(..)]))) else {
fail("expected an identity mismatch")
}
}pub suberror BundleError {
UnsupportedBundleMediaType(String)
InvalidBundle(field~ : String, rule~ : String)
} derive(Debug)pub suberror IdentityError {
InvalidIdentity(kind~ : IdentityKind, text~ : String, rule~ : String)
} derive(Debug)pub suberror TrustedRootError {
UnsupportedTrustedRootMediaType(String)
InvalidTrustedRoot(field~ : String, rule~ : String)
} derive(Debug)impl Show for ArtifactDigestpub struct Bundle {
// private fields
}pub struct CertificateClaims {
issuer : String?
subject_names : Array[String]
build_signer_uri : String?
build_signer_digest : String?
runner_environment : String?
source_repository_uri : String?
source_repository_digest : String?
source_repository_ref : String?
source_repository_identifier : String?
source_repository_owner_uri : String?
source_repository_owner_identifier : String?
build_config_uri : String?
build_config_digest : String?
build_trigger : String?
run_invocation_uri : String?
source_repository_visibility : String?
} derive(Eq, Debug)pub enum Failure {
BundleShape(String)
TransparencyLog(String)
TransparencyLogTime(integrated_time~ : Instant, now~ : Instant)
TransparencyLogBody(String)
InclusionProof(String)
Checkpoint(String)
Certificate(CertificateProblem)
CertificateValidity(not_before~ : Instant, not_after~ : Instant, time~ : Instant)
CertificateChain(String)
CertificateTimestamp(String)
PayloadType(String)
ContentSignature
Statement(String)
ArtifactDigestMismatch(ArtifactDigest)
Policy(Array[PolicyMismatch])
} derive(Eq, Debug)impl Eq for GitHubOwnerimpl Show for GitHubOwnerimpl Eq for GitHubRepositoryimpl Show for GitHubRepositorypub struct Policy {
// private fields
}fn Policy::github_workflow(repository : GitHubRepository, workflow~ : WorkflowPath, git_ref? : GitRef, hosted_runner_only? : Bool, predicate_type? : PredicateType) -> Policyfn Policy::signer_workflow(repository : GitHubRepository, path : WorkflowPath, git_ref? : GitRef, issuer? : Issuer) -> Policypub enum PolicyMismatch {
IssuerMismatch(expected~ : String, actual~ : String?)
IdentityMismatch(expected~ : String, actual~ : Array[String])
ClaimMismatch(claim~ : Claim, expected~ : String, actual~ : String?)
RunnerEnvironment(actual~ : String?)
PredicateTypeMismatch(expected~ : PredicateType, actual~ : PredicateType?)
} derive(Eq, Debug)impl Show for PredicateTypepub struct Statement {
predicate_type : PredicateType
subjects : Array[Subject]
payload : Bytes
} derive(Eq, Debug)impl Show for SubjectNamepub struct TrustedRoot {
// private fields
}pub struct TrustedRootSummary {
transparency_logs : Array[TrustedLogSummary]
certificate_authorities : Array[String]
certificate_transparency_logs : Array[TrustedLogSummary]
timestamp_authorities : Array[String]
}pub struct VerifiedBundle {
statement : Statement?
policy_index : Int
claims : CertificateClaims
log_id : Bytes
log_index : Int64
integrated_time : Instant
} derive(Eq, Debug)impl Show for WorkflowPathfn public_good_trusted_root_json() -> Stringfn verify(bundle : Bundle, artifact : ArtifactDigest, root : TrustedRoot, policy : Policy, now~ : Instant, alternatives? : Array[Policy]) -> VerdictInstall
Download zipPure, portable verification of Sigstore bundles and GitHub artifact attestations