1"""Signer interface"""
2
3from __future__ import annotations
4
5import logging
6from abc import ABCMeta, abstractmethod
7from typing import Callable
8
9from securesystemslib.signer._key import Key
10from securesystemslib.signer._signature import Signature
11
12logger = logging.getLogger(__name__)
13
14# NOTE Signer dispatch table is defined here so it's usable by Signer,
15# but is populated in __init__.py (and can be appended by users).
16SIGNER_FOR_URI_SCHEME: dict[str, type] = {}
17"""Signer dispatch table for ``Signer.from_priv_key()``
18
19See ``securesystemslib.signer.SIGNER_FOR_URI_SCHEME`` for default URI schemes,
20and how to register custom implementations.
21"""
22
23# SecretsHandler is a function the calling code can provide to Signer:
24# SecretsHandler will be called if Signer needs additional secrets.
25# The argument is the name of the secret ("PIN", "passphrase", etc).
26# Return value is the secret string.
27SecretsHandler = Callable[[str], str]
28
29
30class Signer(metaclass=ABCMeta):
31 """Signer interface that supports multiple signing implementations.
32
33 Usage example::
34
35 signer = Signer.from_priv_key_uri(uri, pub_key)
36 sig = signer.sign(b"data")
37
38 Note that signer implementations may raise errors (during both
39 ``Signer.from_priv_key_uri()`` and ``Signer.sign()``) that are not
40 documented here: examples could include network errors or file read errors.
41 Applications should use generic try-except here if unexpected raises are
42 not an option.
43
44 See ``SIGNER_FOR_URI_SCHEME`` for supported private key URI schemes.
45
46 Interactive applications may also define a secrets handler that allows
47 asking for user secrets if they are needed::
48
49 from getpass import getpass
50
51 def sec_handler(secret_name:str) -> str:
52 return getpass(f"Enter {secret_name}: ")
53
54 signer = Signer.from_priv_key_uri(uri, pub_key, sec_handler)
55
56 Applications can provide their own Signer and Key implementations::
57
58 from securesystemslib.signer import Signer, SIGNER_FOR_URI_SCHEME
59 from mylib import MySigner
60
61 SIGNER_FOR_URI_SCHEME[MySigner.MY_SCHEME] = MySigner
62
63 This way the application code using signer API continues to work with
64 default signers but now also uses the custom signer when the proper URI is
65 used.
66 """
67
68 @abstractmethod
69 def sign(self, payload: bytes) -> Signature:
70 """Signs a given payload by the key assigned to the Signer instance.
71
72 Arguments:
73 payload: The bytes to be signed.
74
75 Returns:
76 Returns a "Signature" class instance.
77 """
78 raise NotImplementedError # pragma: no cover
79
80 @classmethod
81 @abstractmethod
82 def from_priv_key_uri(
83 cls,
84 priv_key_uri: str,
85 public_key: Key,
86 secrets_handler: SecretsHandler | None = None,
87 ) -> Signer:
88 """Factory constructor for a given private key URI
89
90 Returns a specific Signer instance based on the private key URI and the
91 supported uri schemes listed in ``SIGNER_FOR_URI_SCHEME``.
92
93 Args:
94 priv_key_uri: URI that identifies the private key
95 public_key: Key that is the public portion of this private key
96 secrets_handler: Optional function that may be called if the
97 signer needs additional secrets (like a PIN or passphrase).
98 secrets_handler should return the requested secret string.
99
100 Raises:
101 ValueError: Incorrect arguments
102 Other Signer-specific errors: These could include OSErrors for
103 reading files or network errors for connecting to a KMS.
104 """
105
106 scheme, _, _ = priv_key_uri.partition(":")
107 if scheme not in SIGNER_FOR_URI_SCHEME:
108 raise ValueError(f"Unsupported private key scheme {scheme}")
109
110 signer = SIGNER_FOR_URI_SCHEME[scheme]
111 return signer.from_priv_key_uri(priv_key_uri, public_key, secrets_handler) # type: ignore
112
113 @property
114 @abstractmethod
115 def public_key(self) -> Key:
116 """
117 Returns:
118 Public key the signer is based off.
119 """
120 raise NotImplementedError