From d51accc84efda24c4386b08a422901361e0ac66b Mon Sep 17 00:00:00 2001 From: Pradyumna Krishna Date: Fri, 27 Jan 2023 20:26:37 +0530 Subject: [PATCH 1/4] Adds in-toto flavored GPG Key, Signer, and Signature Adds in-toto flavored legacy GPGKey, GPGSigner, and GPGSignature to be used with other Signer implementation added with the DSSE. DSSE won't support this legacy GPG api but it is useful in supporting old signature wrapper with gpg compatibilities, i.e. `Metablock` to generate in-toto Metadata and signing with GPG. This newer `GPGSigner` and `GPGKey` implementation provided by securesystemslib utilizes updated metadata formats, that is incompatible with current in-toto signature wrappers. Hence, these `_LegacyGPGSigner` and `_LegacyGPGKey` implementation is provided to continue old wrapper support without any breaking change. Signed-off-by: Pradyumna Krishna --- in_toto/models/gpg.py | 241 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 in_toto/models/gpg.py diff --git a/in_toto/models/gpg.py b/in_toto/models/gpg.py new file mode 100644 index 000000000..eca879e26 --- /dev/null +++ b/in_toto/models/gpg.py @@ -0,0 +1,241 @@ +# Copyright New York University and the in-toto contributors +# SPDX-License-Identifier: Apache-2.0 + +""" + + gpg.py + + + Lukas Puehringer + + + Jan 26, 2023 + + + See LICENSE for licensing information. + + + Provides in-toto flavored GPGSigner, GPGSignature and GPGKey. + +""" + +from dataclasses import dataclass +from typing import Any, Dict, List, Optional + +import securesystemslib.gpg.functions as gpg +from securesystemslib.signer import Key, Signature, Signer, SecretsHandler + + +class _LegacyGPGSignature(Signature): + """A container class containing information about a gpg signature. + Besides the signature, it also contains other meta information + needed to uniquely identify the key used to generate the signature. + + Attributes: + keyid: HEX string used as a unique identifier of the key. + signature: HEX string representing the signature. + other_headers: HEX representation of additional GPG headers. + """ + + def __init__( + self, + keyid: str, + signature: str, + other_headers: str, + ): + super().__init__(keyid, signature) + self.other_headers = other_headers + + @classmethod + def from_dict(cls, signature_dict: Dict) -> "_LegacyGPGSignature": + """Creates a ``_LegacyGPGSignature`` object from its JSON/dict + representation. + + Arguments: + signature_dict: Dict containing valid "keyid", "signature" and + "other_fields" fields. + Raises: + KeyError: If any of the "keyid", "sig" or "other_headers" fields + are missing from the signature_dict. + Returns: + ``_LegacyGPGSignature`` instance. + """ + + return cls( + signature_dict["keyid"], + signature_dict["signature"], + signature_dict["other_headers"], + ) + + def to_dict(self) -> Dict: + """Returns the JSON-serializable dictionary representation of self.""" + return { + "keyid": self.keyid, + "signature": self.signature, + "other_headers": self.other_headers, + } + + +class _LegacyGPGSigner(Signer): + """A in-toto gpg implementation of the ``Signer`` interface. + Provides a sign method to generate a cryptographic signature with gpg, using + an RSA, DSA or EdDSA private key identified by the keyid on the instance. + + Arguments: + keyid: The keyid of the gpg signing keyid. If not passed the default + key in the keyring is used. + homedir: Path to the gpg keyring. If not passed the default keyring + is used. + """ + + def __init__( + self, + keyid: Optional[str] = None, + homedir: Optional[str] = None + ): + self.keyid = keyid + self.homedir = homedir + + @classmethod + def from_priv_key_uri( + cls, + priv_key_uri: str, + public_key: Key, + secrets_handler: Optional[SecretsHandler] = None + ) -> "_LegacyGPGSigner": + + raise NotImplementedError("Incompatible with private key URIs") + + def sign(self, payload: bytes) -> _LegacyGPGSignature: + """Signs a given payload by the key assigned to the ``_LegacyGPGSigner`` + instance. Calls the gpg command line utility to sign the passed content + with the key identified by the passed keyid from the gpg keyring at the + passed homedir. + + The executed base command is defined in + securesystemslib.gpg.constants.GPG_SIGN_COMMAND. + + Arguments: + payload: The bytes to be signed. + Raises: + securesystemslib.exceptions.FormatError: + If the keyid was passed and does not match + securesystemslib.formats.KEYID_SCHEMA. + ValueError: the gpg command failed to create a valid signature. + OSError: the gpg command is not present or non-executable. + securesystemslib.exceptions.UnsupportedLibraryError: the gpg command is + not available, or the cryptography library is not installed. + securesystemslib.gpg.exceptions.CommandError: the gpg command returned a + non-zero exit code. + securesystemslib.gpg.exceptions.KeyNotFoundError: the used gpg version is + not fully supported and no public key can be found for short keyid. + Returns: + Returns a ``_LegacyGPGSignature`` class instance. + """ + + sig_dict = gpg.create_signature(payload, self.keyid, self.homedir) + return _LegacyGPGSignature(**sig_dict) + + +@dataclass +class _LegacyGPGKey(Key): + """A container class representing public key portion of a GPG key. + Provides a verify method to verify a cryptographic signature with a + gpg-style rsa, dsa or ecdsa public key on the instance. + + Attributes: + type: Key type, e.g. "rsa", "dsa" or "ecdsa". + method: GPG Key Scheme, For example: + "pgp+rsa-pkcsv1.5", "pgp+dsa-fips-180-2", and "pgp+eddsa-ed25519". + hashes: list of GPG Hash Algorithms, e.g. "pgp+SHA2". + keyval: Opaque key content. + keyid: Key identifier that is unique within the metadata it is used in. + Keyid is not verified to be the hash of a specific representation + of the key. + creation_time: Unix timestamp when GPG key was created. + validity_period: Validity of the GPG Keys in days. + subkeys: A dictionary containing keyid and GPG subkey. + """ + + type: str + method: str + hashes: List[str] + keyval: Dict[str, str] + keyid: str + creation_time: Optional[int] = None + validity_period: Optional[int] = None + subkeys: Optional[Dict[str, "_LegacyGPGKey"]] = None + + @classmethod + def from_dict(cls, keyid: str, key_dict: Dict[str, Any]): + """Creates ``_LegacyGPGKey`` object from its json/dict representation. + Raises: + KeyError, TypeError: Invalid arguments. + """ + subkeys_dict = key_dict.get("subkeys") + + gpg_subkeys = None + if subkeys_dict: + gpg_subkeys = { + _keyid: _LegacyGPGKey.from_dict(_keyid, subkey_dict) + for (_keyid, subkey_dict) in subkeys_dict.items() + } + + return cls( + key_dict["type"], + key_dict["method"], + key_dict["hashes"], + key_dict["keyval"], + keyid, + key_dict.get("creation_time"), + key_dict.get("validity_period"), + gpg_subkeys, + ) + + def to_dict(self): + """Returns the dictionary representation of self.""" + + key_dict = { + "method": self.method, + "type": self.type, + "hashes": self.hashes, + "keyid": self.keyid, + "keyval": self.keyval, + } + + if self.creation_time: + key_dict["creation_time"] = self.creation_time + if self.validity_period: + key_dict["validity_period"] = self.validity_period + if self.subkeys: + subkeys_dict = { + keyid: subkey.to_dict() + for (keyid, subkey) in self.subkeys.items() + } + key_dict["subkeys"] = subkeys_dict + + return key_dict + + @classmethod + def from_keyring(cls, keyid, homedir=None): + """Creates ``_LegacyGPGKey`` object from GnuPG Keyring.""" + + pubkey_dict = gpg.export_pubkey(keyid, homedir) + return cls.from_dict(keyid, pubkey_dict) + + def verify_signature( + self, + signature: _LegacyGPGSignature, + data: bytes + ) -> bool: + """Verifies a given payload by the key assigned to the _LegacyGPGKey + instance. + + Arguments: + signature: A ``_LegacyGPGSignature`` class instance. + payload: The bytes to be verified. + Returns: + Boolean. True if the signature is valid, False otherwise. + """ + + return gpg.verify_signature(signature.to_dict(), self.to_dict(), data) From f5a559310b0c6280f0c49d1889f8ae791668fee5 Mon Sep 17 00:00:00 2001 From: Pradyumna Krishna Date: Sun, 29 Jan 2023 14:09:17 +0530 Subject: [PATCH 2/4] Add Legacy GPGSigner and GPGKey testcase Adds testcase to validate the working of _LegacyGPGSigner, _LegacyGPGKey and _LegacyGPGSignature. Signed-off-by: Pradyumna Krishna --- in_toto/models/gpg.py | 11 ++- tests/models/test_gpg.py | 144 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 tests/models/test_gpg.py diff --git a/in_toto/models/gpg.py b/in_toto/models/gpg.py index eca879e26..b36ac2c20 100644 --- a/in_toto/models/gpg.py +++ b/in_toto/models/gpg.py @@ -104,7 +104,9 @@ def from_priv_key_uri( secrets_handler: Optional[SecretsHandler] = None ) -> "_LegacyGPGSigner": - raise NotImplementedError("Incompatible with private key URIs") + raise NotImplementedError( + "Incompatible with private key URIs") # pragma: no cover + def sign(self, payload: bytes) -> _LegacyGPGSignature: """Signs a given payload by the key assigned to the ``_LegacyGPGSigner`` @@ -192,6 +194,13 @@ def from_dict(cls, keyid: str, key_dict: Dict[str, Any]): gpg_subkeys, ) + @classmethod + def from_legacy_dict(cls, key_dict: Dict[str, Any]): + """Create GPGKey from legacy dictionary representation.""" + + keyid = key_dict["keyid"] + return cls.from_dict(keyid, key_dict) + def to_dict(self): """Returns the dictionary representation of self.""" diff --git a/tests/models/test_gpg.py b/tests/models/test_gpg.py new file mode 100644 index 000000000..203f588d1 --- /dev/null +++ b/tests/models/test_gpg.py @@ -0,0 +1,144 @@ +#!/usr/bin/env python + +# Copyright New York University and the in-toto contributors +# SPDX-License-Identifier: Apache-2.0 + +""" + + test_gpg.py + + + Lukas Puehringer + + + Jan 28, 2023 + + + See LICENSE for licensing information. + + + Test _LegacyGPGKey, _LegacyGPGSigner and _LegacyGPGSignature class methods. +""" + +import unittest + +from securesystemslib.gpg.functions import export_pubkey +from securesystemslib.gpg.constants import have_gpg + +from in_toto.models.gpg import (_LegacyGPGKey, _LegacyGPGSignature, + _LegacyGPGSigner) + +from tests.common import GPGKeysMixin, TmpDirMixin + + +@unittest.skipIf(not have_gpg(), "gpg not found") +class TestLegacyGPGKeyAndSigner(unittest.TestCase, TmpDirMixin, GPGKeysMixin): + """Test RSA gpg signature creation and verification.""" + + @classmethod + def setUpClass(cls): + cls.set_up_test_dir() + cls.set_up_gpg_keys() + + cls.test_data = b"test_data" + cls.wrong_data = b"something malicious" + + cls.default_keyid = cls.gpg_key_0C8A17 + cls.signing_subkey_keyid = cls.gpg_key_D924E9 + + cls.default_key_dict = export_pubkey(cls.default_keyid, cls.gnupg_home) + + @classmethod + def tearDownClass(self): + self.tear_down_test_dir() + + def test_gpg_sign_and_verify_object_with_default_key(self): + """Create and verify a signature using the default key on the keyring.""" + + # Create a signature. + signer = _LegacyGPGSigner(homedir=self.gnupg_home) + signature = signer.sign(self.test_data) + + # Generate Key from gnupg keyring. + key = _LegacyGPGKey.from_keyring(self.default_keyid, self.gnupg_home) + + self.assertTrue(key.verify_signature(signature, self.test_data)) + self.assertFalse(key.verify_signature(signature, self.wrong_data)) + + # Generate Key from dict. + key = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + + self.assertTrue(key.verify_signature(signature, self.test_data)) + self.assertFalse(key.verify_signature(signature, self.wrong_data)) + + def test_gpg_sign_and_verify_object(self): + """Create and verify a signature using the specific key on the keyring.""" + + # Create a signature. + signer = _LegacyGPGSigner(self.signing_subkey_keyid, self.gnupg_home) + signature = signer.sign(self.test_data) + + # Generate Key from gnupg keyring. + key = _LegacyGPGKey.from_keyring(self.signing_subkey_keyid, self.gnupg_home) + + self.assertTrue(key.verify_signature(signature, self.test_data)) + self.assertFalse(key.verify_signature(signature, self.wrong_data)) + + # Generate Key from dict. + key_dict = export_pubkey(self.signing_subkey_keyid, self.gnupg_home) + key = _LegacyGPGKey.from_dict(key_dict["keyid"], key_dict) + + self.assertTrue(key.verify_signature(signature, self.test_data)) + self.assertFalse(key.verify_signature(signature, self.wrong_data)) + + def test_gpg_signer_serialization(self): + """Tests from_dict and to_dict methods of GPGSignature.""" + + sig_dict = { + "keyid": "f4f90403af58eef6", + "signature": "c39f86e70e12e70e11d87eb7e3ab7d3b", + "other_headers": "d8f8a89b5d71f07b842a", + } + + signature = _LegacyGPGSignature.from_dict(sig_dict) + self.assertEqual(sig_dict, signature.to_dict()) + + def test_gpg_key_serialization(self): + """Test to check serialization methods of GPGKey.""" + + # Test loading and dumping of GPGKey. + key = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + self.assertEqual(key.to_dict(), self.default_key_dict) + + # Test loading and dumping of GPGKey from keyring. + key = _LegacyGPGKey.from_keyring(self.default_keyid, self.gnupg_home) + self.assertEqual(key.to_dict(), self.default_key_dict) + + def test_gpg_key_equality(self): + """Test to check equality between two GPGKey.""" + + # Generate two GPGkey. + key1 = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + key2 = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + + self.assertNotEqual(self.default_key_dict, key1) + self.assertEqual(key2, key1) + + # Assert equality of key created from dict of first GPGKey. + key2 = _LegacyGPGKey.from_legacy_dict(key1.to_dict()) + self.assertEqual(key2, key1) + + # Assert Inequalities. + key2.type = "invalid" + self.assertNotEqual(key2, key1) + key2.type = key1.type + + key2.subkeys = {} + self.assertNotEqual(key2, key1) + key2.subkeys = key1.subkeys + + key2.keyval = {} + self.assertNotEqual(key2, key1) + key2.keyval = key1.keyval + + self.assertEqual(key2, key1) From da9cd0d2de7e1a2d5f6bbb7b1fc2f6578fd61d22 Mon Sep 17 00:00:00 2001 From: Pradyumna Krishna Date: Thu, 2 Feb 2023 14:11:05 +0530 Subject: [PATCH 3/4] Raise exception in verify_signature method _LegacyGPGKey verify_signature method returns True if verification succeeds and False otherwise. This method lacks information behind the failure in verification. Hence, the method now returns None if verification passes, and raises an exception accordingly for the verification failure. * Tests updated accordingly. Signed-off-by: Pradyumna Krishna --- in_toto/models/gpg.py | 23 ++++++++++++++++------ tests/models/test_gpg.py | 41 +++++++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/in_toto/models/gpg.py b/in_toto/models/gpg.py index b36ac2c20..f23328393 100644 --- a/in_toto/models/gpg.py +++ b/in_toto/models/gpg.py @@ -6,7 +6,7 @@ gpg.py - Lukas Puehringer + Pradyumna Krishna Jan 26, 2023 @@ -22,7 +22,9 @@ from dataclasses import dataclass from typing import Any, Dict, List, Optional +import securesystemslib.gpg.exceptions as gpg_exceptions import securesystemslib.gpg.functions as gpg +from securesystemslib import exceptions from securesystemslib.signer import Key, Signature, Signer, SecretsHandler @@ -236,15 +238,24 @@ def verify_signature( self, signature: _LegacyGPGSignature, data: bytes - ) -> bool: + ) -> None: """Verifies a given payload by the key assigned to the _LegacyGPGKey instance. Arguments: signature: A ``_LegacyGPGSignature`` class instance. - payload: The bytes to be verified. - Returns: - Boolean. True if the signature is valid, False otherwise. + data: The bytes to be verified. """ - return gpg.verify_signature(signature.to_dict(), self.to_dict(), data) + try: + if not gpg.verify_signature(signature.to_dict(), self.to_dict(), data): + raise exceptions.UnverifiedSignatureError( + f"Failed to verify signature by {self.keyid}") + except ( + exceptions.FormatError, + exceptions.UnsupportedLibraryError, + gpg_exceptions.KeyExpirationError, + ) as e: + raise exceptions.VerificationError( + f"Unknown failure to verify signature by {self.keyid}" + ) from e diff --git a/tests/models/test_gpg.py b/tests/models/test_gpg.py index 203f588d1..e027c7d90 100644 --- a/tests/models/test_gpg.py +++ b/tests/models/test_gpg.py @@ -8,7 +8,7 @@ test_gpg.py - Lukas Puehringer + Pradyumna Krishna Jan 28, 2023 @@ -24,6 +24,8 @@ from securesystemslib.gpg.functions import export_pubkey from securesystemslib.gpg.constants import have_gpg +from securesystemslib.exceptions import ( + UnverifiedSignatureError, VerificationError) from in_toto.models.gpg import (_LegacyGPGKey, _LegacyGPGSignature, _LegacyGPGSigner) @@ -45,12 +47,13 @@ def setUpClass(cls): cls.default_keyid = cls.gpg_key_0C8A17 cls.signing_subkey_keyid = cls.gpg_key_D924E9 + cls.expired_keyid = "e8ac80c924116dabb51d4b987cb07d6d2c199c7c" cls.default_key_dict = export_pubkey(cls.default_keyid, cls.gnupg_home) @classmethod - def tearDownClass(self): - self.tear_down_test_dir() + def tearDownClass(cls): + cls.tear_down_test_dir() def test_gpg_sign_and_verify_object_with_default_key(self): """Create and verify a signature using the default key on the keyring.""" @@ -62,14 +65,16 @@ def test_gpg_sign_and_verify_object_with_default_key(self): # Generate Key from gnupg keyring. key = _LegacyGPGKey.from_keyring(self.default_keyid, self.gnupg_home) - self.assertTrue(key.verify_signature(signature, self.test_data)) - self.assertFalse(key.verify_signature(signature, self.wrong_data)) + key.verify_signature(signature, self.test_data) + with self.assertRaises(UnverifiedSignatureError): + key.verify_signature(signature, self.wrong_data) # Generate Key from dict. key = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) - self.assertTrue(key.verify_signature(signature, self.test_data)) - self.assertFalse(key.verify_signature(signature, self.wrong_data)) + key.verify_signature(signature, self.test_data) + with self.assertRaises(UnverifiedSignatureError): + key.verify_signature(signature, self.wrong_data) def test_gpg_sign_and_verify_object(self): """Create and verify a signature using the specific key on the keyring.""" @@ -81,15 +86,29 @@ def test_gpg_sign_and_verify_object(self): # Generate Key from gnupg keyring. key = _LegacyGPGKey.from_keyring(self.signing_subkey_keyid, self.gnupg_home) - self.assertTrue(key.verify_signature(signature, self.test_data)) - self.assertFalse(key.verify_signature(signature, self.wrong_data)) + key.verify_signature(signature, self.test_data) + with self.assertRaises(UnverifiedSignatureError): + key.verify_signature(signature, self.wrong_data) # Generate Key from dict. key_dict = export_pubkey(self.signing_subkey_keyid, self.gnupg_home) key = _LegacyGPGKey.from_dict(key_dict["keyid"], key_dict) - self.assertTrue(key.verify_signature(signature, self.test_data)) - self.assertFalse(key.verify_signature(signature, self.wrong_data)) + key.verify_signature(signature, self.test_data) + with self.assertRaises(UnverifiedSignatureError): + key.verify_signature(signature, self.wrong_data) + + def test_verify_using_expired_keyid(self): + """Creates and verifies a signature using expired key on the keyring.""" + + # Create a signature. + signer = _LegacyGPGSigner(self.signing_subkey_keyid, self.gnupg_home) + signature = signer.sign(self.test_data) + + # Verify signature using expired key. + key = _LegacyGPGKey.from_keyring(self.expired_keyid, self.gnupg_home) + with self.assertRaises(VerificationError): + key.verify_signature(signature, self.test_data) def test_gpg_signer_serialization(self): """Tests from_dict and to_dict methods of GPGSignature.""" From 71d7b150482af20b23322e485abf8edcff04b47e Mon Sep 17 00:00:00 2001 From: Pradyumna Krishna Date: Sat, 4 Feb 2023 18:53:09 +0530 Subject: [PATCH 4/4] Refactor models.gpg api models.gpg apis made non-public and moved to models._signer All ``_LegacyGPGSignature``, ``_LegacyGPGSigner``, and ``_LegacyGPGKey`` are renamed ``GPGSignature``, ``GPGSigner`` and ``GPGKey`` respectively. Tests are updated accordingly. Signed-off-by: Pradyumna Krishna --- in_toto/models/{gpg.py => _signer.py} | 41 +++++++++----------- tests/models/{test_gpg.py => test_signer.py} | 38 +++++++++--------- 2 files changed, 38 insertions(+), 41 deletions(-) rename in_toto/models/{gpg.py => _signer.py} (86%) rename tests/models/{test_gpg.py => test_signer.py} (77%) diff --git a/in_toto/models/gpg.py b/in_toto/models/_signer.py similarity index 86% rename from in_toto/models/gpg.py rename to in_toto/models/_signer.py index f23328393..5f8b5800e 100644 --- a/in_toto/models/gpg.py +++ b/in_toto/models/_signer.py @@ -3,7 +3,7 @@ """ - gpg.py + _signer.py Pradyumna Krishna @@ -28,7 +28,7 @@ from securesystemslib.signer import Key, Signature, Signer, SecretsHandler -class _LegacyGPGSignature(Signature): +class GPGSignature(Signature): """A container class containing information about a gpg signature. Besides the signature, it also contains other meta information needed to uniquely identify the key used to generate the signature. @@ -49,8 +49,8 @@ def __init__( self.other_headers = other_headers @classmethod - def from_dict(cls, signature_dict: Dict) -> "_LegacyGPGSignature": - """Creates a ``_LegacyGPGSignature`` object from its JSON/dict + def from_dict(cls, signature_dict: Dict) -> "GPGSignature": + """Creates a ``GPGSignature`` object from its JSON/dict representation. Arguments: @@ -60,7 +60,7 @@ def from_dict(cls, signature_dict: Dict) -> "_LegacyGPGSignature": KeyError: If any of the "keyid", "sig" or "other_headers" fields are missing from the signature_dict. Returns: - ``_LegacyGPGSignature`` instance. + ``GPGSignature`` instance. """ return cls( @@ -78,7 +78,7 @@ def to_dict(self) -> Dict: } -class _LegacyGPGSigner(Signer): +class GPGSigner(Signer): """A in-toto gpg implementation of the ``Signer`` interface. Provides a sign method to generate a cryptographic signature with gpg, using an RSA, DSA or EdDSA private key identified by the keyid on the instance. @@ -104,21 +104,18 @@ def from_priv_key_uri( priv_key_uri: str, public_key: Key, secrets_handler: Optional[SecretsHandler] = None - ) -> "_LegacyGPGSigner": + ) -> "GPGSigner": raise NotImplementedError( "Incompatible with private key URIs") # pragma: no cover - def sign(self, payload: bytes) -> _LegacyGPGSignature: - """Signs a given payload by the key assigned to the ``_LegacyGPGSigner`` + def sign(self, payload: bytes) -> GPGSignature: + """Signs a given payload by the key assigned to the ``GPGSigner`` instance. Calls the gpg command line utility to sign the passed content with the key identified by the passed keyid from the gpg keyring at the passed homedir. - The executed base command is defined in - securesystemslib.gpg.constants.GPG_SIGN_COMMAND. - Arguments: payload: The bytes to be signed. Raises: @@ -134,15 +131,15 @@ def sign(self, payload: bytes) -> _LegacyGPGSignature: securesystemslib.gpg.exceptions.KeyNotFoundError: the used gpg version is not fully supported and no public key can be found for short keyid. Returns: - Returns a ``_LegacyGPGSignature`` class instance. + Returns a ``GPGSignature`` class instance. """ sig_dict = gpg.create_signature(payload, self.keyid, self.homedir) - return _LegacyGPGSignature(**sig_dict) + return GPGSignature(**sig_dict) @dataclass -class _LegacyGPGKey(Key): +class GPGKey(Key): """A container class representing public key portion of a GPG key. Provides a verify method to verify a cryptographic signature with a gpg-style rsa, dsa or ecdsa public key on the instance. @@ -168,11 +165,11 @@ class _LegacyGPGKey(Key): keyid: str creation_time: Optional[int] = None validity_period: Optional[int] = None - subkeys: Optional[Dict[str, "_LegacyGPGKey"]] = None + subkeys: Optional[Dict[str, "GPGKey"]] = None @classmethod def from_dict(cls, keyid: str, key_dict: Dict[str, Any]): - """Creates ``_LegacyGPGKey`` object from its json/dict representation. + """Creates ``GPGKey`` object from its json/dict representation. Raises: KeyError, TypeError: Invalid arguments. """ @@ -181,7 +178,7 @@ def from_dict(cls, keyid: str, key_dict: Dict[str, Any]): gpg_subkeys = None if subkeys_dict: gpg_subkeys = { - _keyid: _LegacyGPGKey.from_dict(_keyid, subkey_dict) + _keyid: GPGKey.from_dict(_keyid, subkey_dict) for (_keyid, subkey_dict) in subkeys_dict.items() } @@ -229,21 +226,21 @@ def to_dict(self): @classmethod def from_keyring(cls, keyid, homedir=None): - """Creates ``_LegacyGPGKey`` object from GnuPG Keyring.""" + """Creates ``GPGKey`` object from GnuPG Keyring.""" pubkey_dict = gpg.export_pubkey(keyid, homedir) return cls.from_dict(keyid, pubkey_dict) def verify_signature( self, - signature: _LegacyGPGSignature, + signature: GPGSignature, data: bytes ) -> None: - """Verifies a given payload by the key assigned to the _LegacyGPGKey + """Verifies a given payload by the key assigned to the GPGKey instance. Arguments: - signature: A ``_LegacyGPGSignature`` class instance. + signature: A ``GPGSignature`` class instance. data: The bytes to be verified. """ diff --git a/tests/models/test_gpg.py b/tests/models/test_signer.py similarity index 77% rename from tests/models/test_gpg.py rename to tests/models/test_signer.py index e027c7d90..bdab7915e 100644 --- a/tests/models/test_gpg.py +++ b/tests/models/test_signer.py @@ -5,7 +5,7 @@ """ - test_gpg.py + test_signer.py Pradyumna Krishna @@ -17,7 +17,7 @@ See LICENSE for licensing information. - Test _LegacyGPGKey, _LegacyGPGSigner and _LegacyGPGSignature class methods. + Test GPGKey, GPGSigner and GPGSignature class methods. """ import unittest @@ -27,8 +27,8 @@ from securesystemslib.exceptions import ( UnverifiedSignatureError, VerificationError) -from in_toto.models.gpg import (_LegacyGPGKey, _LegacyGPGSignature, - _LegacyGPGSigner) +from in_toto.models._signer import (GPGKey, GPGSignature, + GPGSigner) from tests.common import GPGKeysMixin, TmpDirMixin @@ -59,18 +59,18 @@ def test_gpg_sign_and_verify_object_with_default_key(self): """Create and verify a signature using the default key on the keyring.""" # Create a signature. - signer = _LegacyGPGSigner(homedir=self.gnupg_home) + signer = GPGSigner(homedir=self.gnupg_home) signature = signer.sign(self.test_data) # Generate Key from gnupg keyring. - key = _LegacyGPGKey.from_keyring(self.default_keyid, self.gnupg_home) + key = GPGKey.from_keyring(self.default_keyid, self.gnupg_home) key.verify_signature(signature, self.test_data) with self.assertRaises(UnverifiedSignatureError): key.verify_signature(signature, self.wrong_data) # Generate Key from dict. - key = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + key = GPGKey.from_legacy_dict(self.default_key_dict) key.verify_signature(signature, self.test_data) with self.assertRaises(UnverifiedSignatureError): @@ -80,11 +80,11 @@ def test_gpg_sign_and_verify_object(self): """Create and verify a signature using the specific key on the keyring.""" # Create a signature. - signer = _LegacyGPGSigner(self.signing_subkey_keyid, self.gnupg_home) + signer = GPGSigner(self.signing_subkey_keyid, self.gnupg_home) signature = signer.sign(self.test_data) # Generate Key from gnupg keyring. - key = _LegacyGPGKey.from_keyring(self.signing_subkey_keyid, self.gnupg_home) + key = GPGKey.from_keyring(self.signing_subkey_keyid, self.gnupg_home) key.verify_signature(signature, self.test_data) with self.assertRaises(UnverifiedSignatureError): @@ -92,7 +92,7 @@ def test_gpg_sign_and_verify_object(self): # Generate Key from dict. key_dict = export_pubkey(self.signing_subkey_keyid, self.gnupg_home) - key = _LegacyGPGKey.from_dict(key_dict["keyid"], key_dict) + key = GPGKey.from_dict(key_dict["keyid"], key_dict) key.verify_signature(signature, self.test_data) with self.assertRaises(UnverifiedSignatureError): @@ -102,15 +102,15 @@ def test_verify_using_expired_keyid(self): """Creates and verifies a signature using expired key on the keyring.""" # Create a signature. - signer = _LegacyGPGSigner(self.signing_subkey_keyid, self.gnupg_home) + signer = GPGSigner(self.signing_subkey_keyid, self.gnupg_home) signature = signer.sign(self.test_data) # Verify signature using expired key. - key = _LegacyGPGKey.from_keyring(self.expired_keyid, self.gnupg_home) + key = GPGKey.from_keyring(self.expired_keyid, self.gnupg_home) with self.assertRaises(VerificationError): key.verify_signature(signature, self.test_data) - def test_gpg_signer_serialization(self): + def test_gpg_signature_serialization(self): """Tests from_dict and to_dict methods of GPGSignature.""" sig_dict = { @@ -119,32 +119,32 @@ def test_gpg_signer_serialization(self): "other_headers": "d8f8a89b5d71f07b842a", } - signature = _LegacyGPGSignature.from_dict(sig_dict) + signature = GPGSignature.from_dict(sig_dict) self.assertEqual(sig_dict, signature.to_dict()) def test_gpg_key_serialization(self): """Test to check serialization methods of GPGKey.""" # Test loading and dumping of GPGKey. - key = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + key = GPGKey.from_legacy_dict(self.default_key_dict) self.assertEqual(key.to_dict(), self.default_key_dict) # Test loading and dumping of GPGKey from keyring. - key = _LegacyGPGKey.from_keyring(self.default_keyid, self.gnupg_home) + key = GPGKey.from_keyring(self.default_keyid, self.gnupg_home) self.assertEqual(key.to_dict(), self.default_key_dict) def test_gpg_key_equality(self): """Test to check equality between two GPGKey.""" # Generate two GPGkey. - key1 = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) - key2 = _LegacyGPGKey.from_legacy_dict(self.default_key_dict) + key1 = GPGKey.from_legacy_dict(self.default_key_dict) + key2 = GPGKey.from_legacy_dict(self.default_key_dict) self.assertNotEqual(self.default_key_dict, key1) self.assertEqual(key2, key1) # Assert equality of key created from dict of first GPGKey. - key2 = _LegacyGPGKey.from_legacy_dict(key1.to_dict()) + key2 = GPGKey.from_legacy_dict(key1.to_dict()) self.assertEqual(key2, key1) # Assert Inequalities.