|
7 | 7 |
|
8 | 8 | import abc
|
9 | 9 | import securesystemslib.keys as sslib_keys
|
| 10 | +import securesystemslib.gpg.functions as gpg |
10 | 11 | from typing import Any, Dict, Optional, Mapping
|
11 | 12 |
|
12 | 13 |
|
@@ -88,6 +89,60 @@ def to_dict(self) -> Dict:
|
88 | 89 |
|
89 | 90 |
|
90 | 91 |
|
| 92 | +class GPGSignature(Signature): |
| 93 | + """A container class containing information about a gpg signature. |
| 94 | +
|
| 95 | + Besides the signature, it also contains other meta information |
| 96 | + needed to uniquely identify the key used to generate the signature. |
| 97 | +
|
| 98 | + Attributes: |
| 99 | + keyid: HEX string used as a unique identifier of the key. |
| 100 | + signature: HEX string representing the signature. |
| 101 | + other_headers: HEX representation of additional GPG headers. |
| 102 | + """ |
| 103 | + def __init__( |
| 104 | + self, |
| 105 | + keyid: str, |
| 106 | + signature: str, |
| 107 | + other_headers: str, |
| 108 | + ): |
| 109 | + super().__init__(keyid, signature) |
| 110 | + self.other_headers = other_headers |
| 111 | + |
| 112 | + |
| 113 | + @classmethod |
| 114 | + def from_dict(cls, signature_dict: Dict) -> "Signature": |
| 115 | + """Creates a GPGSignature object from its JSON/dict representation. |
| 116 | +
|
| 117 | + Args: |
| 118 | + signature_dict: Dict containing valid "keyid", "signature" and |
| 119 | + "other_fields" fields. |
| 120 | +
|
| 121 | + Raises: |
| 122 | + KeyError: If any of the "keyid", "sig" or "other_headers" fields |
| 123 | + are missing from the signature_dict. |
| 124 | +
|
| 125 | + Returns: |
| 126 | + GPGSignature instance. |
| 127 | + """ |
| 128 | + |
| 129 | + return cls( |
| 130 | + signature_dict["keyid"], |
| 131 | + signature_dict["sig"], |
| 132 | + signature_dict["other_headers"] |
| 133 | + ) |
| 134 | + |
| 135 | + |
| 136 | + def to_dict(self) -> Dict: |
| 137 | + """Returns the JSON-serializable dictionary representation of self.""" |
| 138 | + return { |
| 139 | + "keyid": self.keyid, |
| 140 | + "signature": self.signature, |
| 141 | + "other_headers": self.other_headers |
| 142 | + } |
| 143 | + |
| 144 | + |
| 145 | + |
91 | 146 | class Signer:
|
92 | 147 | """Signer interface created to support multiple signing implementations."""
|
93 | 148 |
|
@@ -160,3 +215,62 @@ def sign(self, payload: bytes) -> "Signature":
|
160 | 215 |
|
161 | 216 | sig_dict = sslib_keys.create_signature(self.key_dict, payload)
|
162 | 217 | return Signature(**sig_dict)
|
| 218 | + |
| 219 | + |
| 220 | + |
| 221 | +class GPGSigner(Signer): |
| 222 | + """A securesystemslib gpg implementation of the "Signer" interface. |
| 223 | +
|
| 224 | + Provides a sign method to generate a cryptographic signature with gpg, using |
| 225 | + an RSA, DSA or EdDSA private key identified by the keyid on the instance. |
| 226 | +
|
| 227 | + Args: |
| 228 | + keyid: The keyid of the gpg signing keyid. If not passed the default |
| 229 | + key in the keyring is used. |
| 230 | +
|
| 231 | + homedir: Path to the gpg keyring. If not passed the default keyring |
| 232 | + is used. |
| 233 | +
|
| 234 | + """ |
| 235 | + def __init__( |
| 236 | + self, keyid: Optional[str] = None, homedir: Optional[str] = None |
| 237 | + ): |
| 238 | + self.keyid = keyid |
| 239 | + self.homedir = homedir |
| 240 | + |
| 241 | + |
| 242 | + def sign(self, payload: bytes) -> "GPGSignature": |
| 243 | + """Signs a given payload by the key assigned to the GPGSigner instance. |
| 244 | +
|
| 245 | + Calls the gpg command line utility to sign the passed content with the |
| 246 | + key identified by the passed keyid from the gpg keyring at the passed |
| 247 | + homedir. |
| 248 | +
|
| 249 | + The executed base command is defined in |
| 250 | + securesystemslib.gpg.constants.GPG_SIGN_COMMAND. |
| 251 | +
|
| 252 | + Arguments: |
| 253 | + payload: The bytes to be signed. |
| 254 | +
|
| 255 | + Raises: |
| 256 | + securesystemslib.exceptions.FormatError: |
| 257 | + If the keyid was passed and does not match |
| 258 | + securesystemslib.formats.KEYID_SCHEMA. |
| 259 | +
|
| 260 | + ValueError: the gpg command failed to create a valid signature. |
| 261 | + OSError: the gpg command is not present or non-executable. |
| 262 | + securesystemslib.exceptions.UnsupportedLibraryError: the gpg |
| 263 | + command is not available, or the cryptography library is |
| 264 | + not installed. |
| 265 | + securesystemslib.gpg.exceptions.CommandError: the gpg command |
| 266 | + returned a non-zero exit code. |
| 267 | + securesystemslib.gpg.exceptions.KeyNotFoundError: the used gpg |
| 268 | + version is not fully supported and no public key can be found |
| 269 | + for short keyid. |
| 270 | +
|
| 271 | + Returns: |
| 272 | + Returns a "GPGSignature" class instance. |
| 273 | + """ |
| 274 | + |
| 275 | + sig_dict = gpg.create_signature(payload, self.keyid, self.homedir) |
| 276 | + return GPGSignature(**sig_dict) |
0 commit comments