Skip to content

Remove VerificationMaterials (take 2) #937

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

Merged
merged 33 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
73125af
WIP
woodruffw Mar 14, 2024
c99a373
hackety hack
woodruffw Mar 14, 2024
95ea0ff
sigstore: hackety hack
woodruffw Mar 14, 2024
8c54807
sigstore: fill in more Bundle bits
woodruffw Mar 14, 2024
dc60cbf
begin to fixup tests
woodruffw Mar 14, 2024
f9fe13e
sigstore, test: continue test fixups
woodruffw Mar 14, 2024
9aeb0bc
sigstore: re-exports, fix open mode
woodruffw Mar 14, 2024
0b2bcce
sigstore: fixup types, CLI
woodruffw Mar 14, 2024
41f3340
sigstore: docstring coverage
woodruffw Mar 14, 2024
1de6a90
test: fixup signing tests
woodruffw Mar 14, 2024
bae9948
_cli: fix var
woodruffw Mar 14, 2024
29efbbe
sigstore: delete another duplicated helper
woodruffw Mar 14, 2024
6e849b8
workflows/conformance: bump
woodruffw Mar 15, 2024
55f9420
_cli: use public accessors
woodruffw Mar 15, 2024
a3a34e2
_cli: lintage
woodruffw Mar 15, 2024
dc6192e
verify/models: fixup DSSE KindVersion
woodruffw Mar 15, 2024
e07fc4e
workflows/conformance: 0.0.11
woodruffw Mar 15, 2024
2e4d94c
_cli: bytes -> str
woodruffw Mar 15, 2024
ef303cf
sigstore: remove unnecessary client deps
woodruffw Mar 15, 2024
9a9b60e
Merge remote-tracking branch 'origin/main' into ww/rm-verificationmat…
woodruffw Mar 15, 2024
2aa3d50
test: re-add CVE-2022-36056 test
woodruffw Mar 15, 2024
606855b
sigstore: remove _internal.set
woodruffw Mar 15, 2024
d8e0239
test: reintroduce some model tests
woodruffw Mar 15, 2024
d70a3dc
CHANGELOG: begin recording changes
woodruffw Mar 15, 2024
6767b81
test: missing license
woodruffw Mar 15, 2024
349de0a
sigstore: update verify example
woodruffw Mar 15, 2024
a836140
sigstore/sign: update example
woodruffw Mar 15, 2024
be5a0c6
Merge branch 'main' into ww/rm-verificationmaterials-2
woodruffw Mar 20, 2024
e053a31
Merge branch 'main' into ww/rm-verificationmaterials-2
woodruffw Mar 25, 2024
c9b8c29
Merge remote-tracking branch 'origin/main' into ww/rm-verificationmat…
woodruffw Mar 27, 2024
ac65f2e
Merge branch 'main' into ww/rm-verificationmaterials-2
woodruffw Apr 2, 2024
94a4ff6
CHANGELOG: emphasize bundle type
woodruffw Apr 2, 2024
c0b0272
bundle: stricter tlog cardinality
woodruffw Apr 2, 2024
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
32 changes: 23 additions & 9 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,14 @@ All versions prior to 0.9.0 are untracked.

### Removed

* **BREAKING API CHANGE**: `SigningResult.input_digest` has been removed;
users who expect to access the input digest may do so by inspecting the
`hashedrekord` or `dsse`-specific `SigningResult.content`
([#804](https://github.com/sigstore/sigstore-python/pull/804))
* **BREAKING API CHANGE**: `SigningResult` has been removed.
The public signing APIs now return `Bundle`.

* **BREAKING API CHANGE**: `VerificationMaterials.hashed_input` has been removed
([#904](https://github.com/sigstore/sigstore-python/pull/904))
* **BREAKING API CHANGE**: `VerificationMaterials` has been removed.
The public verification APIs now accept `Bundle`.

### Changed

* **BREAKING API CHANGE**: `sigstore.sign.SigningResult` has been removed
([#862](https://github.com/sigstore/sigstore-python/pull/862))

* **BREAKING API CHANGE**: The `Signer.sign(...)` API now returns a `Bundle`,
instead of a `SigningResult` ([#862](https://github.com/sigstore/sigstore-python/pull/862))

Expand All @@ -64,6 +59,25 @@ All versions prior to 0.9.0 are untracked.
an `IO[bytes]` for input. Other input types (such as `Hashed` and
`Statement`) are unchanged ([#921](https://github.com/sigstore/sigstore-python/pull/921))

* **BREAKING API CHANGE**: `Verifier.verify(...)` now takes a `Bundle`,
instead of a `VerificationMaterials` ([#937](https://github.com/sigstore/sigstore-python/pull/937))

* sigstore-python now requires inclusion proofs in all signing and verification
flows, regardless of bundle version of input types. Inputs that do not
have an inclusion proof (such as detached materials) cause an online lookup
before any further processing is performed
([#937](https://github.com/sigstore/sigstore-python/pull/937))

* sigstore-python now generates "v3" bundles by default during signing
([#937](https://github.com/sigstore/sigstore-python/pull/937))

* CLI: Bundles are now always verified offline. The offline flag has no effect.
([#937](https://github.com/sigstore/sigstore-python/pull/937))

* CLI: "Detached" materials are now always verified online, due to a lack of
an inclusion proof. Passing `--offline` with detached materials will cause
an error ([#937](https://github.com/sigstore/sigstore-python/pull/937))

## [2.1.2]

This is a corrective release for [2.1.1].
Expand Down
78 changes: 41 additions & 37 deletions sigstore/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,25 @@
from textwrap import dedent
from typing import NoReturn, Optional, TextIO, Union, cast

from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.x509 import load_pem_x509_certificate
from rich.logging import RichHandler
from sigstore_protobuf_specs.dev.sigstore.bundle.v1 import Bundle

from sigstore import __version__
from sigstore._internal.fulcio.client import (
DEFAULT_FULCIO_URL,
ExpiredCertificate,
FulcioClient,
)
from sigstore._internal.rekor import _hashedrekord_from_parts
from sigstore._internal.rekor.client import (
DEFAULT_REKOR_URL,
RekorClient,
)
from sigstore._internal.trustroot import KeyringPurpose, TrustedRoot
from sigstore._utils import PEMCert, cert_der_to_pem, sha256_digest
from sigstore._utils import sha256_digest
from sigstore.errors import Error
from sigstore.hashes import Hashed
from sigstore.oidc import (
DEFAULT_OAUTH_ISSUER_URL,
STAGING_OAUTH_ISSUER_URL,
Expand All @@ -48,15 +51,13 @@
detect_credential,
)
from sigstore.sign import SigningContext
from sigstore.transparency import LogEntry
from sigstore.verify import (
CertificateVerificationFailure,
LogEntryMissing,
VerificationMaterials,
Verifier,
policy,
)
from sigstore.verify.models import VerificationFailure
from sigstore.verify.models import Bundle, VerificationFailure

logging.basicConfig(format="%(message)s", datefmt="[%X]", handlers=[RichHandler()])
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -635,12 +636,12 @@ def _sign(args: argparse.Namespace) -> None:
raise exp_certificate

print("Using ephemeral certificate:")
cert = result.verification_material.x509_certificate_chain.certificates[0]
cert_pem = cert_der_to_pem(cert.raw_bytes)
cert = result.signing_certificate
cert_pem = cert.public_bytes(Encoding.PEM).decode()
print(cert_pem)

print(
f"Transparency log entry created at index: {result.verification_material.tlog_entries[0].log_index}"
f"Transparency log entry created at index: {result.log_entry.log_index}"
)

sig_output: TextIO
Expand All @@ -649,7 +650,9 @@ def _sign(args: argparse.Namespace) -> None:
else:
sig_output = sys.stdout

signature = base64.b64encode(result.message_signature.signature).decode()
signature = base64.b64encode(
result._inner.message_signature.signature
).decode()
print(signature, file=sig_output)
if outputs["sig"] is not None:
print(f"Signature written to {outputs['sig']}")
Expand All @@ -667,13 +670,13 @@ def _sign(args: argparse.Namespace) -> None:

def _collect_verification_state(
args: argparse.Namespace,
) -> tuple[Verifier, list[tuple[Path, VerificationMaterials]]]:
) -> tuple[Verifier, list[tuple[Path, Hashed, Bundle]]]:
"""
Performs CLI functionality common across all `sigstore verify` subcommands.

Returns a tuple of the active verifier instance and a list of `(file, materials)`
tuples, where `file` is the path to the file being verified (for display
purposes) and `materials` is the `VerificationMaterials` to verify with.
Returns a tuple of the active verifier instance and a list of `(path, hashed, bundle)`
tuples, where `path` is the filename for display purposes, `hashed` is the
pre-hashed input to the file being verified and `bundle` is the `Bundle` to verify with.
"""

# Fail if --certificate, --signature, or --bundle is specified and we
Expand All @@ -689,6 +692,10 @@ def _collect_verification_state(
if args.bundle and (args.certificate or args.signature):
_die(args, "--bundle cannot be used with --certificate or --signature")

# Fail if `--certificate` or `--signature` is used with `--offline`.
if args.offline and (args.certificate or args.signature):
_die(args, "--offline cannot be used with --certificate or --signature")

# The converse of `sign`: we build up an expected input map and check
# that we have everything so that we can fail early.
input_map = {}
Expand Down Expand Up @@ -764,39 +771,38 @@ def _collect_verification_state(

all_materials = []
for file, inputs in input_map.items():
cert_pem: str
signature: bytes
entry: LogEntry | None = None
with file.open(mode="rb") as io:
hashed = sha256_digest(io)

if "bundle" in inputs:
# Load the bundle
logger.debug(f"Using bundle from: {inputs['bundle']}")

bundle_bytes = inputs["bundle"].read_bytes()
bundle = Bundle().from_json(bundle_bytes)

materials = VerificationMaterials.from_bundle(
bundle=bundle, offline=args.offline
)
bundle = Bundle.from_json(bundle_bytes)
else:
# Load the signing certificate
logger.debug(f"Using certificate from: {inputs['cert']}")
cert_pem = inputs["cert"].read_text()
cert = load_pem_x509_certificate(inputs["cert"].read_bytes())

# Load the signature
logger.debug(f"Using signature from: {inputs['sig']}")
b64_signature = inputs["sig"].read_text()
signature = base64.b64decode(b64_signature)

materials = VerificationMaterials(
cert_pem=PEMCert(cert_pem),
signature=signature,
rekor_entry=entry,
offline=args.offline,
# When using "detached" materials, we *must* retrieve the log
# entry from the online log.
# TODO: This should be abstracted somewhere much better.
log_entry = verifier._rekor.log.entries.retrieve.post(
_hashedrekord_from_parts(cert, signature, hashed)
)
if log_entry is None:
_die(args, f"No matching log entry for {file}'s verification materials")
bundle = Bundle.from_parts(cert, signature, log_entry)

logger.debug(f"Verifying contents from: {file}")

all_materials.append((file, materials))
all_materials.append((file, hashed, bundle))

return (verifier, all_materials)

Expand Down Expand Up @@ -856,17 +862,17 @@ def diagnostics(self) -> str:


def _verify_identity(args: argparse.Namespace) -> None:
verifier, files_with_materials = _collect_verification_state(args)
verifier, materials = _collect_verification_state(args)

for file, materials in files_with_materials:
for file, hashed, bundle in materials:
policy_ = policy.Identity(
identity=args.cert_identity,
issuer=args.cert_oidc_issuer,
)

result = verifier.verify(
input_=file.read_bytes(),
materials=materials,
input_=hashed,
bundle=bundle,
policy=policy_,
)

Expand Down Expand Up @@ -901,11 +907,9 @@ def _verify_github(args: argparse.Namespace) -> None:

policy_ = policy.AllOf(inner_policies)

verifier, files_with_materials = _collect_verification_state(args)
for file, materials in files_with_materials:
result = verifier.verify(
input_=file.read_bytes(), materials=materials, policy=policy_
)
verifier, materials = _collect_verification_state(args)
for file, hashed, bundle in materials:
result = verifier.verify(input_=hashed, bundle=bundle, policy=policy_)

if result:
print(f"OK: {file}")
Expand Down
9 changes: 6 additions & 3 deletions sigstore/_internal/merkle.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@
The data format for the Merkle tree nodes is described in IETF's RFC 6962.
"""

from __future__ import annotations

import base64
import hashlib
import struct
import typing
from typing import List, Tuple

from sigstore._utils import HexStr
from sigstore.transparency import LogEntry

if typing.TYPE_CHECKING:
from sigstore.transparency import LogEntry


class InvalidInclusionProofError(Exception):
Expand Down Expand Up @@ -99,8 +104,6 @@ def _hash_leaf(leaf: bytes) -> bytes:
def verify_merkle_inclusion(entry: LogEntry) -> None:
"""Verify the Merkle Inclusion Proof for a given Rekor entry."""
inclusion_proof = entry.inclusion_proof
if inclusion_proof is None:
raise InvalidInclusionProofError("Rekor entry has no inclusion proof")

# Figure out which subset of hashes corresponds to the inner and border nodes.
inner, border = _decomp_inclusion_proof(
Expand Down
30 changes: 30 additions & 0 deletions sigstore/_internal/rekor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,37 @@
APIs for interacting with Rekor.
"""

import base64

import rekor_types
from cryptography.x509 import Certificate

from sigstore._utils import base64_encode_pem_cert
from sigstore.hashes import Hashed

from .checkpoint import SignedCheckpoint
from .client import RekorClient

__all__ = ["RekorClient", "SignedCheckpoint"]


# TODO: This should probably live somewhere better.
def _hashedrekord_from_parts(
cert: Certificate, sig: bytes, hashed: Hashed
) -> rekor_types.Hashedrekord:
return rekor_types.Hashedrekord(
spec=rekor_types.hashedrekord.HashedrekordV001Schema(
signature=rekor_types.hashedrekord.Signature(
content=base64.b64encode(sig).decode(),
public_key=rekor_types.hashedrekord.PublicKey(
content=base64_encode_pem_cert(cert),
),
),
data=rekor_types.hashedrekord.Data(
hash=rekor_types.hashedrekord.Hash(
algorithm=hashed._as_hashedrekord_algorithm(),
value=hashed.digest.hex(),
)
),
)
)
8 changes: 6 additions & 2 deletions sigstore/_internal/rekor/checkpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,17 @@
import base64
import re
import struct
import typing
from dataclasses import dataclass
from typing import List

from pydantic import BaseModel, Field, StrictStr

from sigstore._internal.trustroot import KeyringSignatureError, RekorKeyring
from sigstore._utils import KeyID
from sigstore.transparency import LogEntry

if typing.TYPE_CHECKING:
from sigstore.transparency import LogEntry


@dataclass(frozen=True)
Expand Down Expand Up @@ -164,7 +167,8 @@ def from_text(cls, text: str) -> SignedNote:

def verify(self, rekor_keyring: RekorKeyring, key_id: KeyID) -> None:
"""
Verify the `SignedNote` with using the given RekorClient by verifying each contained signature.
Verify the `SignedNote` using the given RekorKeyring by verifying
each contained signature.
"""

note = str.encode(self.note)
Expand Down
55 changes: 0 additions & 55 deletions sigstore/_internal/set.py

This file was deleted.

Loading
Loading