Description
This issues proposes what the (long-term) APIs will look like. Looking for comments, nothing set in stone.
1. Hash engine
Tracked in #140
Why:
- for callers to customize the hash parameters
- for callers to customize hash implementation, eg using hw-accelerattion or distributed across machines.
class hash_engine:
def name() -> str:
# Shard size
def shard() -> int
# Chunk size
def chunk() -> int
# Standard hash APIs
def update(...):..
def final():..
We will provide default hash engine that we use in this library, with the possibility to customize its parameters:
class shah256pv1:
def __init__(self, shard=1000000, chunk=memory_available())
# All class function from hash_engine
A hash name is parameterized. I suggest something simple like <name>$param1$param2...
. For the existing sha256p, it could be sha256pv1$1000000
for a shard of 1 GB.
2. Serializer
This will serialize a model (folder or file).
Why: some callers may want to serialize models using our library but not sign it with our library.
class serializer:
def __init__(self, hash: hash_engine)
...
def run(
# Path is a file-system path.
# PathIter is a path iterator, for callers who want to keep things in memory.
input: Path | PathIter,
# A list of paths to recompute Tracked in https://github.com/sigstore/model-transparency/issues/160
recompute_paths: []Path = None,
# HashedPathIter is an iterator for path+hash computed
) -> HashedPathIter:
...
)
3. Raw Signer / Verifier
This provides low-level signer: It allows to abstract away how private key is managed (TUF, HSM, static key, etc). The raw verifier lets callers set a custom verifier if needed, eg if the signature uses a different encoding, etc.
class RawSigner:
@abstract
def sign(raw: bytes) -> bytes
# Perform signature via HSM API, use PCKS11 API, use raw bytes from memory, etc
We can have helper function for common raw signers like (one will be the default?):
class ECDSARawSigner(RawSigner):
def from_path(path: filepath.Path):
...
def from_bytes(key: bytes):
# Verifies that 1) the key is of the right type, eg right length and on the right curve, 2) private and public key correspond to the same key pair.
...
def sign(raw):
# Use the raw key
...
4. Signer / Verifier
This will create a generic signer class that can be instantiated for Sigstore, PKI, etc.
class Signer:
def sign(
# Path is a file-system path.
# PathIter is a path iterator, for callers who want to keep things in memory.
# HashedPathIter is an iterator for pre-computed path+hash.
inputs: Path | PathIter | HashedPathIter,
) -> bytes
...
class Verifier:
def verify(
input: Path | PathIter | HashedPathIter,
sig: Path | bytes,
) -> bool
Example for Sigstore keyless:
# sigstore.py
class SigstoreKeylessSigner(Signer)
def __init__(self, rekor_url, fulcio_cert, ...)
def sign(input) -> bytes
class SigstoreKeylessVerifier(Verifier)
def __init__(self, rekor_url, fulcio_tuf, ...)
def verify(input, sig) -> bool
Example for Sigstore key flow:
# sigstore.py
class SigstoreKeyedSigner(Signer)
def __init__(self, rekor_url, raw_signer, ...)
def sign(input) -> bytes
class SigstoreKeyedVerifier(Verifier)
def __init__(self, rekor_url, raw_verifier, ...)
def verify(input, sig) -> bool
Example for PKI:
# pki.py
class PKISigner(Signer)
def __init__(self, certs_chain, raw_signer, ...)
def sign(input) -> bytes
class PKIVerifier(Verifier)
def __init__(self, certs_chain, raw_verifier, ...)
def verify(input, sig) -> bool
Example for ECSigner (no cert):
# ec.py
class ECSigner(Signer)
def __init__(self, raw_signer, ...)
def sign(input) -> bytes
class ECVerifier(Verifier)
def __init__(self, raw_verifier, ...)
def verify(input, sig) -> bool
4. model sign / verify
This is the main entry point for callers to use.
# model.py
def sign(
input: Path | PathIter | HashedPathIter,
sig: Path = defaultSigPath(input),
signer: Signer = SigstoreSigner(),
he: hash_engine = shah256pv1(),
recompute_paths: []Path = None,
ignored_paths: []Path = [".git"],
) -> bytes
# High level logic.
s = serializer(he)
iter = s.run(input, recompute_paths)
m = manifest(iter)
c = signer.sign(m)
sig.write(c)
return c
def verify(
input: Path | PathIter | HashedPathIter,
sig: Path | bytes = defaultSigPath(input),
verifier: Verifier = SigstoreVerifier(),
he: hash_engine = shah256pv1(),
ignored_paths: []Path = [".git"],
) -> bool
# High level logic.
s = serializer(he)
iter = s.run(input, recompute_paths)
m = manifest(iter)
return verifier.verify(m, sig)