Edit on GitHub

model_signing.verifying

High level API for the verification interface of model_signing library.

This module supports configuring the verification method used to verify a model, before performing the verification.

model_signing.verifying.Config().use_sigstore_verifier(
    identity=identity, oidc_issuer=oidc_provider
).verify("finbert", "finbert.sig")

The same verification configuration can be used to verify multiple models:

verifying_config = model_signing.signing.Config().use_elliptic_key_verifier(
    public_key="key.pub"
)

for model in all_models:
    verifying_config.verify(model, f"{model}_sharded.sig")

The API defined here is stable and backwards compatible.

  1# Copyright 2024 The Sigstore Authors
  2#
  3# Licensed under the Apache License, Version 2.0 (the "License");
  4# you may not use this file except in compliance with the License.
  5# You may obtain a copy of the License at
  6#
  7#      http://www.apache.org/licenses/LICENSE-2.0
  8#
  9# Unless required by applicable law or agreed to in writing, software
 10# distributed under the License is distributed on an "AS IS" BASIS,
 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12# See the License for the specific language governing permissions and
 13# limitations under the License.
 14
 15"""High level API for the verification interface of `model_signing` library.
 16
 17This module supports configuring the verification method used to verify a model,
 18before performing the verification.
 19
 20```python
 21model_signing.verifying.Config().use_sigstore_verifier(
 22    identity=identity, oidc_issuer=oidc_provider
 23).verify("finbert", "finbert.sig")
 24```
 25
 26The same verification configuration can be used to verify multiple models:
 27
 28```python
 29verifying_config = model_signing.signing.Config().use_elliptic_key_verifier(
 30    public_key="key.pub"
 31)
 32
 33for model in all_models:
 34    verifying_config.verify(model, f"{model}_sharded.sig")
 35```
 36
 37The API defined here is stable and backwards compatible.
 38"""
 39
 40from collections.abc import Iterable
 41import pathlib
 42import sys
 43
 44from model_signing import hashing
 45from model_signing import manifest
 46from model_signing._signing import sign_certificate as certificate
 47from model_signing._signing import sign_ec_key as ec_key
 48from model_signing._signing import sign_sigstore as sigstore
 49from model_signing._signing import sign_sigstore_pb as sigstore_pb
 50
 51
 52if sys.version_info >= (3, 11):
 53    from typing import Self
 54else:
 55    from typing_extensions import Self
 56
 57
 58class Config:
 59    """Configuration to use when verifying models against signatures.
 60
 61    The verification configuration is needed to determine how to read and verify
 62    the signature. Given we support multiple signing format, the verification
 63    settings must match the signing ones.
 64
 65    The configuration also supports configuring the hashing configuration from
 66    `model_signing.hashing`. This should also match the configuration used
 67    during signing. However, by default, we can attempt to guess it from the
 68    signature.
 69    """
 70
 71    def __init__(self):
 72        """Initializes the default configuration for verification."""
 73        self._hashing_config = None
 74        self._verifier = None
 75        self._uses_sigstore = False
 76
 77    def verify(
 78        self, model_path: hashing.PathLike, signature_path: hashing.PathLike
 79    ):
 80        """Verifies that a model conforms to a signature.
 81
 82        Args:
 83            model_path: The path to the model to verify.
 84
 85        Raises:
 86            ValueError: No verifier has been configured.
 87        """
 88        if self._verifier is None:
 89            raise ValueError("Attempting to verify with no configured verifier")
 90
 91        if self._uses_sigstore:
 92            signature = sigstore.Signature.read(pathlib.Path(signature_path))
 93        else:
 94            signature = sigstore_pb.Signature.read(pathlib.Path(signature_path))
 95
 96        expected_manifest = self._verifier.verify(signature)
 97
 98        if self._hashing_config is None:
 99            self._guess_hashing_config(expected_manifest)
100        if "ignore_paths" in expected_manifest.serialization_type:
101            self._hashing_config.add_ignored_paths(
102                model_path=model_path,
103                paths=expected_manifest.serialization_type["ignore_paths"],
104            )
105        actual_manifest = self._hashing_config.hash(model_path)
106
107        if actual_manifest != expected_manifest:
108            raise ValueError("Signature mismatch")
109
110    def set_hashing_config(self, hashing_config: hashing.Config) -> Self:
111        """Sets the new configuration for hashing models.
112
113        After calling this method, the automatic guessing of the hashing
114        configuration used during signing is no longer possible from within one
115        instance of this class.
116
117        Args:
118            hashing_config: The new hashing configuration.
119
120        Returns:
121            The new signing configuration.
122        """
123        self._hashing_config = hashing_config
124        return self
125
126    def _guess_hashing_config(self, source_manifest: manifest.Manifest) -> None:
127        """Attempts to guess the hashing config from a manifest."""
128        args = source_manifest.serialization_type
129        method = args["method"]
130        # TODO: Once Python 3.9 support is deprecated revert to using `match`
131        if method == "files":
132            self._hashing_config = hashing.Config().use_file_serialization(
133                hashing_algorithm=args["hash_type"],
134                allow_symlinks=args["allow_symlinks"],
135                ignore_paths=args.get("ignore_paths", frozenset()),
136            )
137        elif method == "shards":
138            self._hashing_config = hashing.Config().use_shard_serialization(
139                hashing_algorithm=args["hash_type"],
140                shard_size=args["shard_size"],
141                allow_symlinks=args["allow_symlinks"],
142                ignore_paths=args.get("ignore_paths", frozenset()),
143            )
144        else:
145            raise ValueError("Cannot guess the hashing configuration")
146
147    def use_sigstore_verifier(
148        self, *, identity: str, oidc_issuer: str, use_staging: bool = False
149    ) -> Self:
150        """Configures the verification of signatures produced by Sigstore.
151
152        The verifier in this configuration is changed to one that performs
153        verification of Sigstore signatures (sigstore bundles signed by
154        keyless signing via Sigstore).
155
156        Args:
157            identity: The expected identity that has signed the model.
158            oidc_issuer: The expected OpenID Connect issuer that provided the
159              certificate used for the signature.
160            use_staging: Use staging configurations, instead of production. This
161              is supposed to be set to True only when testing. Default is False.
162
163        Return:
164            The new verification configuration.
165        """
166        self._uses_sigstore = True
167        self._verifier = sigstore.Verifier(
168            identity=identity, oidc_issuer=oidc_issuer, use_staging=use_staging
169        )
170        return self
171
172    def use_elliptic_key_verifier(
173        self, *, public_key: hashing.PathLike
174    ) -> Self:
175        """Configures the verification of signatures generated by a private key.
176
177        The verifier in this configuration is changed to one that performs
178        verification of sgistore bundles signed by an elliptic curve private
179        key. The public key used in the configuration must match the private key
180        used during signing.
181
182        Args:
183            public_key: The path to the public key to verify with.
184
185        Return:
186            The new verification configuration.
187        """
188        self._uses_sigstore = False
189        self._verifier = ec_key.Verifier(pathlib.Path(public_key))
190        return self
191
192    def use_certificate_verifier(
193        self,
194        *,
195        certificate_chain: Iterable[hashing.PathLike] = frozenset(),
196        log_fingerprints: bool = False,
197    ) -> Self:
198        """Configures the verification of signatures generated by a certificate.
199
200        The verifier in this configuration is changed to one that performs
201        verification of sgistore bundles signed by a signing certificate.
202
203        Args:
204            certificate_chain: Certificate chain to establish root of trust. If
205              empty, the operating system's one is used.
206            log_fingerprints: Log certificates' SHA256 fingerprints
207
208        Return:
209            The new verification configuration.
210        """
211        self._uses_sigstore = False
212        self._verifier = certificate.Verifier(
213            [pathlib.Path(c) for c in certificate_chain],
214            log_fingerprints=log_fingerprints,
215        )
216        return self
class Config:
 59class Config:
 60    """Configuration to use when verifying models against signatures.
 61
 62    The verification configuration is needed to determine how to read and verify
 63    the signature. Given we support multiple signing format, the verification
 64    settings must match the signing ones.
 65
 66    The configuration also supports configuring the hashing configuration from
 67    `model_signing.hashing`. This should also match the configuration used
 68    during signing. However, by default, we can attempt to guess it from the
 69    signature.
 70    """
 71
 72    def __init__(self):
 73        """Initializes the default configuration for verification."""
 74        self._hashing_config = None
 75        self._verifier = None
 76        self._uses_sigstore = False
 77
 78    def verify(
 79        self, model_path: hashing.PathLike, signature_path: hashing.PathLike
 80    ):
 81        """Verifies that a model conforms to a signature.
 82
 83        Args:
 84            model_path: The path to the model to verify.
 85
 86        Raises:
 87            ValueError: No verifier has been configured.
 88        """
 89        if self._verifier is None:
 90            raise ValueError("Attempting to verify with no configured verifier")
 91
 92        if self._uses_sigstore:
 93            signature = sigstore.Signature.read(pathlib.Path(signature_path))
 94        else:
 95            signature = sigstore_pb.Signature.read(pathlib.Path(signature_path))
 96
 97        expected_manifest = self._verifier.verify(signature)
 98
 99        if self._hashing_config is None:
100            self._guess_hashing_config(expected_manifest)
101        if "ignore_paths" in expected_manifest.serialization_type:
102            self._hashing_config.add_ignored_paths(
103                model_path=model_path,
104                paths=expected_manifest.serialization_type["ignore_paths"],
105            )
106        actual_manifest = self._hashing_config.hash(model_path)
107
108        if actual_manifest != expected_manifest:
109            raise ValueError("Signature mismatch")
110
111    def set_hashing_config(self, hashing_config: hashing.Config) -> Self:
112        """Sets the new configuration for hashing models.
113
114        After calling this method, the automatic guessing of the hashing
115        configuration used during signing is no longer possible from within one
116        instance of this class.
117
118        Args:
119            hashing_config: The new hashing configuration.
120
121        Returns:
122            The new signing configuration.
123        """
124        self._hashing_config = hashing_config
125        return self
126
127    def _guess_hashing_config(self, source_manifest: manifest.Manifest) -> None:
128        """Attempts to guess the hashing config from a manifest."""
129        args = source_manifest.serialization_type
130        method = args["method"]
131        # TODO: Once Python 3.9 support is deprecated revert to using `match`
132        if method == "files":
133            self._hashing_config = hashing.Config().use_file_serialization(
134                hashing_algorithm=args["hash_type"],
135                allow_symlinks=args["allow_symlinks"],
136                ignore_paths=args.get("ignore_paths", frozenset()),
137            )
138        elif method == "shards":
139            self._hashing_config = hashing.Config().use_shard_serialization(
140                hashing_algorithm=args["hash_type"],
141                shard_size=args["shard_size"],
142                allow_symlinks=args["allow_symlinks"],
143                ignore_paths=args.get("ignore_paths", frozenset()),
144            )
145        else:
146            raise ValueError("Cannot guess the hashing configuration")
147
148    def use_sigstore_verifier(
149        self, *, identity: str, oidc_issuer: str, use_staging: bool = False
150    ) -> Self:
151        """Configures the verification of signatures produced by Sigstore.
152
153        The verifier in this configuration is changed to one that performs
154        verification of Sigstore signatures (sigstore bundles signed by
155        keyless signing via Sigstore).
156
157        Args:
158            identity: The expected identity that has signed the model.
159            oidc_issuer: The expected OpenID Connect issuer that provided the
160              certificate used for the signature.
161            use_staging: Use staging configurations, instead of production. This
162              is supposed to be set to True only when testing. Default is False.
163
164        Return:
165            The new verification configuration.
166        """
167        self._uses_sigstore = True
168        self._verifier = sigstore.Verifier(
169            identity=identity, oidc_issuer=oidc_issuer, use_staging=use_staging
170        )
171        return self
172
173    def use_elliptic_key_verifier(
174        self, *, public_key: hashing.PathLike
175    ) -> Self:
176        """Configures the verification of signatures generated by a private key.
177
178        The verifier in this configuration is changed to one that performs
179        verification of sgistore bundles signed by an elliptic curve private
180        key. The public key used in the configuration must match the private key
181        used during signing.
182
183        Args:
184            public_key: The path to the public key to verify with.
185
186        Return:
187            The new verification configuration.
188        """
189        self._uses_sigstore = False
190        self._verifier = ec_key.Verifier(pathlib.Path(public_key))
191        return self
192
193    def use_certificate_verifier(
194        self,
195        *,
196        certificate_chain: Iterable[hashing.PathLike] = frozenset(),
197        log_fingerprints: bool = False,
198    ) -> Self:
199        """Configures the verification of signatures generated by a certificate.
200
201        The verifier in this configuration is changed to one that performs
202        verification of sgistore bundles signed by a signing certificate.
203
204        Args:
205            certificate_chain: Certificate chain to establish root of trust. If
206              empty, the operating system's one is used.
207            log_fingerprints: Log certificates' SHA256 fingerprints
208
209        Return:
210            The new verification configuration.
211        """
212        self._uses_sigstore = False
213        self._verifier = certificate.Verifier(
214            [pathlib.Path(c) for c in certificate_chain],
215            log_fingerprints=log_fingerprints,
216        )
217        return self

Configuration to use when verifying models against signatures.

The verification configuration is needed to determine how to read and verify the signature. Given we support multiple signing format, the verification settings must match the signing ones.

The configuration also supports configuring the hashing configuration from model_signing.hashing. This should also match the configuration used during signing. However, by default, we can attempt to guess it from the signature.

Config()
72    def __init__(self):
73        """Initializes the default configuration for verification."""
74        self._hashing_config = None
75        self._verifier = None
76        self._uses_sigstore = False

Initializes the default configuration for verification.

def verify( self, model_path: Union[str, bytes, os.PathLike], signature_path: Union[str, bytes, os.PathLike]):
 78    def verify(
 79        self, model_path: hashing.PathLike, signature_path: hashing.PathLike
 80    ):
 81        """Verifies that a model conforms to a signature.
 82
 83        Args:
 84            model_path: The path to the model to verify.
 85
 86        Raises:
 87            ValueError: No verifier has been configured.
 88        """
 89        if self._verifier is None:
 90            raise ValueError("Attempting to verify with no configured verifier")
 91
 92        if self._uses_sigstore:
 93            signature = sigstore.Signature.read(pathlib.Path(signature_path))
 94        else:
 95            signature = sigstore_pb.Signature.read(pathlib.Path(signature_path))
 96
 97        expected_manifest = self._verifier.verify(signature)
 98
 99        if self._hashing_config is None:
100            self._guess_hashing_config(expected_manifest)
101        if "ignore_paths" in expected_manifest.serialization_type:
102            self._hashing_config.add_ignored_paths(
103                model_path=model_path,
104                paths=expected_manifest.serialization_type["ignore_paths"],
105            )
106        actual_manifest = self._hashing_config.hash(model_path)
107
108        if actual_manifest != expected_manifest:
109            raise ValueError("Signature mismatch")

Verifies that a model conforms to a signature.

Arguments:
  • model_path: The path to the model to verify.
Raises:
  • ValueError: No verifier has been configured.
def set_hashing_config(self, hashing_config: model_signing.hashing.Config) -> Self:
111    def set_hashing_config(self, hashing_config: hashing.Config) -> Self:
112        """Sets the new configuration for hashing models.
113
114        After calling this method, the automatic guessing of the hashing
115        configuration used during signing is no longer possible from within one
116        instance of this class.
117
118        Args:
119            hashing_config: The new hashing configuration.
120
121        Returns:
122            The new signing configuration.
123        """
124        self._hashing_config = hashing_config
125        return self

Sets the new configuration for hashing models.

After calling this method, the automatic guessing of the hashing configuration used during signing is no longer possible from within one instance of this class.

Arguments:
  • hashing_config: The new hashing configuration.
Returns:

The new signing configuration.

def use_sigstore_verifier( self, *, identity: str, oidc_issuer: str, use_staging: bool = False) -> Self:
148    def use_sigstore_verifier(
149        self, *, identity: str, oidc_issuer: str, use_staging: bool = False
150    ) -> Self:
151        """Configures the verification of signatures produced by Sigstore.
152
153        The verifier in this configuration is changed to one that performs
154        verification of Sigstore signatures (sigstore bundles signed by
155        keyless signing via Sigstore).
156
157        Args:
158            identity: The expected identity that has signed the model.
159            oidc_issuer: The expected OpenID Connect issuer that provided the
160              certificate used for the signature.
161            use_staging: Use staging configurations, instead of production. This
162              is supposed to be set to True only when testing. Default is False.
163
164        Return:
165            The new verification configuration.
166        """
167        self._uses_sigstore = True
168        self._verifier = sigstore.Verifier(
169            identity=identity, oidc_issuer=oidc_issuer, use_staging=use_staging
170        )
171        return self

Configures the verification of signatures produced by Sigstore.

The verifier in this configuration is changed to one that performs verification of Sigstore signatures (sigstore bundles signed by keyless signing via Sigstore).

Arguments:
  • identity: The expected identity that has signed the model.
  • oidc_issuer: The expected OpenID Connect issuer that provided the certificate used for the signature.
  • use_staging: Use staging configurations, instead of production. This is supposed to be set to True only when testing. Default is False.
Return:

The new verification configuration.

def use_elliptic_key_verifier(self, *, public_key: Union[str, bytes, os.PathLike]) -> Self:
173    def use_elliptic_key_verifier(
174        self, *, public_key: hashing.PathLike
175    ) -> Self:
176        """Configures the verification of signatures generated by a private key.
177
178        The verifier in this configuration is changed to one that performs
179        verification of sgistore bundles signed by an elliptic curve private
180        key. The public key used in the configuration must match the private key
181        used during signing.
182
183        Args:
184            public_key: The path to the public key to verify with.
185
186        Return:
187            The new verification configuration.
188        """
189        self._uses_sigstore = False
190        self._verifier = ec_key.Verifier(pathlib.Path(public_key))
191        return self

Configures the verification of signatures generated by a private key.

The verifier in this configuration is changed to one that performs verification of sgistore bundles signed by an elliptic curve private key. The public key used in the configuration must match the private key used during signing.

Arguments:
  • public_key: The path to the public key to verify with.
Return:

The new verification configuration.

def use_certificate_verifier( self, *, certificate_chain: Iterable[typing.Union[str, bytes, os.PathLike]] = frozenset(), log_fingerprints: bool = False) -> Self:
193    def use_certificate_verifier(
194        self,
195        *,
196        certificate_chain: Iterable[hashing.PathLike] = frozenset(),
197        log_fingerprints: bool = False,
198    ) -> Self:
199        """Configures the verification of signatures generated by a certificate.
200
201        The verifier in this configuration is changed to one that performs
202        verification of sgistore bundles signed by a signing certificate.
203
204        Args:
205            certificate_chain: Certificate chain to establish root of trust. If
206              empty, the operating system's one is used.
207            log_fingerprints: Log certificates' SHA256 fingerprints
208
209        Return:
210            The new verification configuration.
211        """
212        self._uses_sigstore = False
213        self._verifier = certificate.Verifier(
214            [pathlib.Path(c) for c in certificate_chain],
215            log_fingerprints=log_fingerprints,
216        )
217        return self

Configures the verification of signatures generated by a certificate.

The verifier in this configuration is changed to one that performs verification of sgistore bundles signed by a signing certificate.

Arguments:
  • certificate_chain: Certificate chain to establish root of trust. If empty, the operating system's one is used.
  • log_fingerprints: Log certificates' SHA256 fingerprints
Return:

The new verification configuration.