-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Sigstore sign/verify to CLI (#310)
* Update to support Sigstore sign via CLI Signed-off-by: Ivan Font <[email protected]> * Update to support Sigstore verify via CLI Signed-off-by: Ivan Font <[email protected]> * Update docs for Sigstore sign/verify CLI Signed-off-by: Ivan Font <[email protected]> * Update doc to reflect latest CLI changes Signed-off-by: Ivan Font <[email protected]> * Fix sign.py lint errors Signed-off-by: Ivan Font <[email protected]> * Fix verify.py lint errors Signed-off-by: Ivan Font <[email protected]> * Remvoe --method option from doc for required arg Signed-off-by: Ivan Font <[email protected]> * Fix Python type errors Signed-off-by: Ivan Font <[email protected]> --------- Signed-off-by: Ivan Font <[email protected]>
- Loading branch information
Showing
3 changed files
with
131 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ are supported: | |
|
||
* Bring your own key pair | ||
* Bring your own PKI | ||
- Keyless signing using Sigstore with Fulcio root | ||
* Skip signing (only hash and create a bundle) | ||
|
||
The signing part creates a [sigstore bundle](https://github.com/sigstore/protobuf-specs/blob/main/protos/sigstore_bundle.proto) | ||
|
@@ -68,13 +69,13 @@ $ source .venv/bin/activate | |
## Sign | ||
|
||
```bash | ||
(.venv) $ python3 sign.py --model_path ${MODEL_PATH} --sig_out ${OUTPUT_PATH} --method {private-key, pki} {additional parameters depending on method} | ||
(.venv) $ python3 sign.py --model_path ${MODEL_PATH} --sig_out ${SIG_PATH} {private-key, pki, sigstore} {additional parameters depending on method} | ||
``` | ||
|
||
## Verify | ||
|
||
```bash | ||
(.venv) $ python3 verify.py --model_path ${MODEL_PATH} --method {private-key, pki} {additional parameters depending on method} | ||
(.venv) $ python3 verify.py --model_path ${MODEL_PATH} --sig_path ${SIG_PATH} {private-key, pki, sigstore} {additional parameters depending on method} | ||
``` | ||
|
||
### Examples | ||
|
@@ -83,14 +84,15 @@ $ source .venv/bin/activate | |
|
||
```bash | ||
$ MODEL_PATH='/path/to/your/model' | ||
$ SIG_PATH='./model.sig' | ||
$ openssl ecparam -name secp256k1 -genkey -noout -out ec-secp256k1-priv-key.pem | ||
$ openssl ec -in ec-secp256k1-priv-key.pem -pubout > ec-secp256k1-pub-key.pem | ||
$ source .venv/bin/activate | ||
# SIGN | ||
(.venv) $ python3 sign_model.py --model_path ${MODEL_PATH} --method private-key --private-key ec-secp256k1-priv-key.pem | ||
(.venv) $ python3 sign.py --model_path ${MODEL_PATH} --sig_out ${SIG_PATH} private-key --private-key ec-secp256k1-priv-key.pem | ||
... | ||
#VERIFY | ||
(.venv) $ python3 verify_model.py --model_path ${MODEL_PATH} --method private-key --public-key ec-secp256k1-pub-key.pem | ||
(.venv) $ python3 verify.py --model_path ${MODEL_PATH} --sig_path ${SIG_PATH} private-key --public-key ec-secp256k1-pub-key.pem | ||
... | ||
``` | ||
|
||
|
@@ -104,27 +106,42 @@ In order to sign a model with your own PKI you need to create the following info | |
|
||
```bash | ||
$ MODEL_PATH='/path/to/your/model' | ||
$ SIG_PATH='./model.sig' | ||
$ CERT_CHAIN='/path/to/cert_chain' | ||
$ SIGNING_CERT='/path/to/signing_certificate' | ||
$ PRIVATE_KEY='/path/to/private_key' | ||
# SIGN | ||
(.venv) $ python3 sign_model.py --model_path ${MODEL_PATH} \ | ||
--method pki \ | ||
(.venv) $ python3 sign.py --model_path ${MODEL_PATH} \ | ||
--sig_path ${SIG_PATH} \ | ||
pki \ | ||
--private-key ${PRIVATE_KEY} \ | ||
--signing_cert ${SIGNING_CERT} \ | ||
[--cert_chain ${CERT_CHAIN}] | ||
... | ||
#VERIFY | ||
$ ROOT_CERTS='/path/to/root/certs' | ||
(.venv) $ python3 verify_model.py --model_path ${MODEL_PATH} \ | ||
--method pki \ | ||
--root_certs ${ROOT_CERTS} | ||
(.venv) $ python3 verify.py --model_path ${MODEL_PATH} \ | ||
--sig_path ${SIG_PATH} \ | ||
pki \ | ||
--root_certs ${ROOT_CERTS} | ||
... | ||
``` | ||
|
||
## Sigstore ID providers | ||
#### Keyless signing using Sigstore | ||
|
||
For developers signing models, there are three identity providers that can | ||
```bash | ||
$ MODEL_PATH='/path/to/your/model' | ||
# SIGN | ||
(.venv) $ python3 sign.py --model_path ${MODEL_PATH} sigstore | ||
... | ||
#VERIFY | ||
(.venv) $ python3 verify.py --model_path ${MODEL_PATH} --sig_path ./model.sig sigstore --identity [email protected] --identity-provider https://accounts.example.com | ||
... | ||
``` | ||
|
||
### Sigstore ID providers | ||
|
||
For developers signing models with Sigstore, there are three identity providers that can | ||
be used at the moment: | ||
|
||
* Google's provider is `https://accounts.google.com`. | ||
|
@@ -151,11 +168,13 @@ stored in TFHub, run the following commands: | |
|
||
```bash | ||
model_path=bertseq2seq | ||
sig_path=model.sig | ||
wget "https://tfhub.dev/google/bertseq2seq/bert24_en_de/1?tf-hub-format=compressed" -O "${model_path}".tgz | ||
mkdir -p "${model_path}" | ||
cd "${model_path}" && tar xvzf ../"${model_path}".tgz && rm ../"${model_path}".tgz && cd - | ||
python3 main.py sign --path "${model_path}" | ||
python3 main.py verify --path "${model_path}" \ | ||
python3 sign.py --model_path "${model_path}" sigstore | ||
python3 verify.py --model_path "${model_path}" --sig_path ${sig_path} \ | ||
sigstore \ | ||
--identity-provider https://accounts.google.com \ | ||
--identity [email protected] | ||
``` | ||
|
@@ -173,9 +192,11 @@ After this, we can sign and verify a Bert base model: | |
```bash | ||
model_name=bert-base-uncased | ||
model_path="${model_name}" | ||
sig_path=model.sig | ||
git clone --depth=1 "https://huggingface.co/${model_name}" && rm -rf "${model_name}"/.git | ||
python3 main.py sign --path "${model_path}" | ||
python3 main.py verify --path "${model_path}" \ | ||
python3 sign.py --model_path "${model_path}" | ||
python3 verify.py --model_path "${model_path}" --sig_path ${sig_path} \ | ||
sigstore \ | ||
--identity-provider https://accounts.google.com \ | ||
--identity [email protected] | ||
``` | ||
|
@@ -185,9 +206,11 @@ Similarly, we can sign and verify a Falcon model: | |
```bash | ||
model_name=tiiuae/falcon-7b | ||
model_path=$(echo "${model_name}" | cut -d/ -f2) | ||
sig_path=model.sig | ||
git clone --depth=1 "https://huggingface.co/${model_name}" && rm -rf "${model_name}"/.git | ||
python3 main.py sign --path "${model_path}" | ||
python3 main.py verify --path "${model_path}" \ | ||
python3 sign.py --model_path "${model_path}" | ||
python3 verify.py --model_path "${model_path}" --sig_path ${sig_path} \ | ||
sigstore \ | ||
--identity-provider https://accounts.google.com \ | ||
--identity [email protected] | ||
``` | ||
|
@@ -197,11 +220,13 @@ We can also support models from the PyTorch Hub: | |
```bash | ||
model_name=hustvl/YOLOP | ||
model_path=$(echo "${model_name}" | cut -d/ -f2) | ||
sig_path=model.sig | ||
wget "https://github.com/${model_name}/archive/main.zip" -O "${model_path}".zip | ||
mkdir -p "${model_path}" | ||
cd "${model_path}" && unzip ../"${model_path}".zip && rm ../"${model_path}".zip && shopt -s dotglob && mv YOLOP-main/* . && shopt -u dotglob && rmdir YOLOP-main/ && cd - | ||
python3 main.py sign --path "${model_path}" | ||
python3 main.py verify --path "${model_path}" \ | ||
python3 sign.py --model_path "${model_path}" | ||
python3 verify.py --model_path "${model_path}" --sig_path ${sig_path} \ | ||
sigstore \ | ||
--identity-provider https://accounts.google.com \ | ||
--identity [email protected] | ||
``` | ||
|
@@ -211,9 +236,11 @@ We also support ONNX models, for example Roberta: | |
```bash | ||
model_name=roberta-base-11 | ||
model_path="${model_name}.onnx" | ||
sig_path=model.sig | ||
wget "https://github.com/onnx/models/raw/main/text/machine_comprehension/roberta/model/${model_name}.onnx" | ||
python3 main.py sign --path "${model_path}" | ||
python3 main.py verify --path "${model_path}" \ | ||
python3 sign.py --model_path "${model_path}" | ||
python3 verify.py --model_path "${model_path}" --sig_path ${sig_path} \ | ||
sigstore \ | ||
--identity-provider https://accounts.google.com \ | ||
--identity [email protected] | ||
``` | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,8 @@ | |
from model_signing.signature import pki | ||
from model_signing.signature import verifying | ||
from model_signing.signing import in_toto_signature | ||
from model_signing.signing import signing | ||
from model_signing.signing import sigstore | ||
|
||
|
||
log = logging.getLogger(__name__) | ||
|
@@ -51,7 +53,7 @@ def _arguments() -> argparse.Namespace: | |
method_cmd = parser.add_subparsers( | ||
required=True, | ||
dest="method", | ||
help="method to verify the model: [pki, private-key, skip]", | ||
help="method to verify the model: [pki, private-key, sigstore, skip]", | ||
) | ||
# pki subcommand | ||
pki = method_cmd.add_parser("pki") | ||
|
@@ -73,12 +75,52 @@ def _arguments() -> argparse.Namespace: | |
type=pathlib.Path, | ||
dest="key", | ||
) | ||
|
||
# sigstore subcommand | ||
sigstore = method_cmd.add_parser("sigstore") | ||
sigstore.add_argument( | ||
"--identity", | ||
help="the expected identity of the signer e.g. [email protected]", | ||
required=True, | ||
type=str, | ||
dest="identity", | ||
) | ||
sigstore.add_argument( | ||
"--identity-provider", | ||
help="the identity provider expected e.g. https://accounts.example.com", | ||
required=True, | ||
type=str, | ||
dest="identity_provider", | ||
) | ||
# skip subcommand | ||
method_cmd.add_parser("skip") | ||
|
||
return parser.parse_args() | ||
|
||
|
||
def _get_verifier(args: argparse.Namespace) -> signing.Verifier: | ||
verifier: verifying.Verifier | ||
if args.method == "private-key": | ||
_check_private_key_flags(args) | ||
verifier = key.ECKeyVerifier.from_path(args.key) | ||
return in_toto_signature.IntotoVerifier(verifier) | ||
elif args.method == "pki": | ||
_check_pki_flags(args) | ||
verifier = pki.PKIVerifier.from_paths(args.root_certs) | ||
return in_toto_signature.IntotoVerifier(verifier) | ||
elif args.method == "sigstore": | ||
return sigstore.SigstoreDSSEVerifier( | ||
identity=args.identity, oidc_issuer=args.identity_provider | ||
) | ||
elif args.method == "skip": | ||
return in_toto_signature.IntotoVerifier(fake.FakeVerifier()) | ||
else: | ||
log.error(f"unsupported verification method {args.method}") | ||
log.error( | ||
'supported methods: ["pki", "private-key", "sigstore", "skip"]' | ||
) | ||
exit(-1) | ||
|
||
|
||
def _check_private_key_flags(args: argparse.Namespace): | ||
if args.key == "": | ||
log.error("--public_key must be defined") | ||
|
@@ -90,28 +132,22 @@ def _check_pki_flags(args: argparse.Namespace): | |
log.warning("no root of trust is set using system default") | ||
|
||
|
||
def _get_signature(args: argparse.Namespace) -> signing.Signature: | ||
if args.method == "sigstore": | ||
return sigstore.SigstoreSignature.read(args.sig_path) | ||
else: | ||
return in_toto_signature.IntotoSignature.read(args.sig_path) | ||
|
||
|
||
def main(): | ||
logging.basicConfig(level=logging.INFO) | ||
args = _arguments() | ||
|
||
verifier: verifying.Verifier | ||
log.info(f"Creating verifier for {args.method}") | ||
if args.method == "private-key": | ||
_check_private_key_flags(args) | ||
verifier = key.ECKeyVerifier.from_path(args.key) | ||
elif args.method == "pki": | ||
_check_pki_flags(args) | ||
verifier = pki.PKIVerifier.from_paths(args.root_certs) | ||
elif args.method == "skip": | ||
verifier = fake.FakeVerifier() | ||
else: | ||
log.error(f"unsupported verification method {args.method}") | ||
log.error('supported methods: ["pki", "private-key", "skip"]') | ||
exit(-1) | ||
|
||
verifier = _get_verifier(args) | ||
log.info(f"Verifying model signature from {args.sig_path}") | ||
|
||
sig = in_toto_signature.IntotoSignature.read(args.sig_path) | ||
sig = _get_signature(args) | ||
|
||
def hasher_factory(file_path: pathlib.Path) -> file.FileHasher: | ||
return file.SimpleFileHasher( | ||
|
@@ -122,12 +158,10 @@ def hasher_factory(file_path: pathlib.Path) -> file.FileHasher: | |
file_hasher_factory=hasher_factory | ||
) | ||
|
||
intoto_verifier = in_toto_signature.IntotoVerifier(verifier) | ||
|
||
try: | ||
model.verify( | ||
sig=sig, | ||
verifier=intoto_verifier, | ||
verifier=verifier, | ||
model_path=args.model_path, | ||
serializer=serializer, | ||
ignore_paths=[args.sig_path], | ||
|