Skip to content

Commit

Permalink
add migrate_key cli script
Browse files Browse the repository at this point in the history
This is in preparation for the removal of legacy key modules and
formats, in favor of the new Signer API. It allows users to convert
their old rsa, ed25519 and ecdsa key files, generated with the
`interface` or `keys` module, and using an outdated standard or sslib
proprietary format (see secure-systems-lab#309), to a consistent new standard format,
which can be used with the file-based signer (`CryptoSigner`) of the new
Signer API.

NOTE: The script uses legacy code and should thus be removed with them,
from the repo tree, while remaining available to users of
securesystemslib for some time.

We could keep pointing to it in docs after its removal (users would need
to check-out the repo at a specified tag), or move it to a different
place.

*Change details*
* Add cli script to convert key files.

* Add private/private encrypted/public test key files for each supported
  algorithm in the legacy format. **The key pairs were generated with
  `interface`, minimally modified to allow writing an encrypted and
  non-encrypted version of the same private key.

* Add comprehensive tests, includes backwards/forward compatibility of
  signatures.

Signed-off-by: Lukas Puehringer <lukas.puehringer@nyu.edu>
  • Loading branch information
lukpueh committed Oct 16, 2023
1 parent fe0cf39 commit f2508fe
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 0 deletions.
127 changes: 127 additions & 0 deletions securesystemslib/migrate_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
#!/usr/bin/env python
"""CLI script to migrate legacy keys to standard format
Convert legacy key files created via `securesystemslib.interface` or
`securesystemslib.keys` to a standard format, e.g. for use with `CryptoSigner`
of the Signer API (see CRYPTO_SIGNER.md).
Standard format for all algorithms
----------------------------------
* private: PEM/PKCS8
* public: PEM/subjectPublicKeyInfo
NOTE: Auto-generated keyids are likely to change after migration. Make sure to
set keyids of new signers explicitly, by passing a public key with the desired
keyid, or adopt changes in any delegations in TUF or in-toto.
"""
import argparse

from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import (
BestAvailableEncryption,
Encoding,
NoEncryption,
PrivateFormat,
PublicFormat,
load_pem_public_key,
)

from securesystemslib import interface as legacy
from securesystemslib.signer import CryptoSigner


def migrate_private(path_in, algo, password):
"""Migrate private key"""
legacy_key = legacy.import_privatekey_from_file(path_in, algo, password)
crypto_signer = CryptoSigner.from_securesystemslib_key(legacy_key)

if password:
encryption_algorithm = BestAvailableEncryption(password.encode())
else:
encryption_algorithm = NoEncryption()

private_key = crypto_signer._private_key # pylint: disable=protected-access

return private_key.private_bytes(
encoding=Encoding.PEM,
format=PrivateFormat.PKCS8,
encryption_algorithm=encryption_algorithm,
)


def migrate_public(path_in, algo):
"""Migrate public key"""
legacy_keys = legacy.import_publickeys_from_file([path_in], [algo])
legacy_key = list(legacy_keys.values())[0]

if algo in ["rsa", "ecdsa"]:
public_key = load_pem_public_key(
legacy_key["keyval"]["public"].encode()
)
else: # ed25519
public_bytes = bytes.fromhex(legacy_key["keyval"]["public"])
public_key = Ed25519PublicKey.from_public_bytes(public_bytes)

return public_key.public_bytes(
encoding=Encoding.PEM,
format=PublicFormat.SubjectPublicKeyInfo,
)


def main():
parser = argparse.ArgumentParser(
description=(
"Migrate legacy keys to standard format "
"(PEM/PKCS8/subjectPublicKeyInfo)."
)
)

parser.add_argument(
"--type",
choices=["private", "public"],
required=True,
help="key type",
)
parser.add_argument(
"--password",
help="password to decrypt legacy and encrypt new private key",
)
parser.add_argument(
"--algo",
choices=["rsa", "ecdsa", "ed25519"],
required=True,
help="key algorithm",
)
parser.add_argument(
"--in",
dest="path_in",
metavar="PATH",
required=True,
help="file path to legacy key",
)
parser.add_argument(
"--out",
dest="path_out",
metavar="PATH",
required=True,
help="file path to new key",
)

args = parser.parse_args()

if args.type == "private":
new_key_bytes = migrate_private(args.path_in, args.algo, args.password)

else: # public
if args.password:
parser.print_usage()
parser.error("use password with --type private only")
new_key_bytes = migrate_public(args.path_in, args.algo)

with open(args.path_out, "wb+") as output_file:
output_file.write(new_key_bytes)


if __name__ == "__main__":
main()
1 change: 1 addition & 0 deletions tests/data/legacy/ecdsa_private_encrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8fbf611c59332aebc82bdcf026fe4119@@@@100000@@@@307a043b7e82a3bf4421f6971e3c5af13570d6824e52e096ab653b877fc52e9f@@@@835eccefffb6b5492c53027a5a92e445@@@@c6513cceb8cb00fd117787a4a8e0331b55b24f7467e7ed881910954deecd23611fd09238a425052ee0fe5647cedaf42ee31e04a9233d368616f8f0668ebd2cc63bf8aa466f438d2aa671ebab9dc4fdacdd5600672480161f43318dd76410145fea99eaa4eb4ef36a4d903007461ac1f123beb72fbb2793ea1d0f7bf9199894d8af43dc880968e882ff5cc342bdebfb827e4158e74aa93b70164324c510aef94389ee4bac90c39277da0ff6a56d41d28e2f019ac90d7008a58e792064ad8e8800925a10bdf161159cc8169bc6bde07bce5b57ffbdaae3f90c825b2f4af9f8a58617f4fcd020922d5b1f6d8b6fe273e59627698acc80ccac5574a5254b1d4e2faae67daeee9dc36c673724d1663ada890a9176d2754eb75c6245f429df773b19b80ef0e18c076f159572acff26a51b15f302473638e08230d137246ab6cada40b26717dda3bd295d9df675f5d025230f9d67d79e70f94cd766abae8afff13f11bb2137e28bb0ed2b4e3675b6a3df707fd89a780ebf6c3c93834eecf7d2ff80240abf7d04957feb613554afb1fd5517320b422ef228608b87740ac2801bd4f9907ac018a9aeb51f36317736c54cc593712219dcf00af3a009ac5a0c762c69cc4d579c425f642f0053668bec77d65bb3f046ed090ebdc502c09fe83e68cc94355be96ec22bd3019043dc6a9e0a3acbb245e710caebc5df7b9ed1c81ec983b6b828f84f8487ac1b068ee8c2cbd91c411041a7a48a4fe59d10e55a7ee0bb276ddde9a3bbfe21bedfd50c094dc161034e3100dfa0309cca8bbe8178a8d5504741e1953075cc25922bca7f19284c51e85669be52687c9c005892c3331057964733b4d5c5b85bfb7bc31bc4a75fcd1f9cd121b19dd48cf5dc
1 change: 1 addition & 0 deletions tests/data/legacy/ecdsa_private_unencrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid": "57b7afab61dfd16b96619bb8af6c55483eeade3aa68cf20ff8f0aa69a8bcc8d8", "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+6/aDen+X60RXLETPYz/H4U4qAY\neD/faCdpHBBmyip7xRiyWIrWljDmqcwLfv5wswrqdLF8M6hAdgYjIQZU/A==\n-----END PUBLIC KEY-----", "private": "-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIEm0tgzxA8OHiudMGqscqR4QpaJfxwwREqAD3rlSfXGJoAoGCCqGSM49\nAwEHoUQDQgAEx+6/aDen+X60RXLETPYz/H4U4qAYeD/faCdpHBBmyip7xRiyWIrW\nljDmqcwLfv5wswrqdLF8M6hAdgYjIQZU/A==\n-----END EC PRIVATE KEY-----"}, "keyid_hash_algorithms": ["sha256", "sha512"]}
1 change: 1 addition & 0 deletions tests/data/legacy/ecdsa_public
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"keytype": "ecdsa", "scheme": "ecdsa-sha2-nistp256", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+6/aDen+X60RXLETPYz/H4U4qAY\neD/faCdpHBBmyip7xRiyWIrWljDmqcwLfv5wswrqdLF8M6hAdgYjIQZU/A==\n-----END PUBLIC KEY-----"}}
1 change: 1 addition & 0 deletions tests/data/legacy/ed25519_private_encrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9ad267c7c10c74fb754b2d0811cadb6a@@@@100000@@@@06e3a7f38f26fc7c08d28e112fb6b53ab56b84c6214ac040bfcfdafb2fd7d221@@@@52d11d61e2e4ce57f39b3db799e72d24@@@@a5dc0ef8f4c7b1a87ab91c71eac39453004b36fea1284526bc109ae97f179bed1efd5d2a39f69c4a9f246b5ca6cc2e53366f830c11e5d2032c792dfd7f64bc87562b791ca1c1af1cedd245d438033d81688414c1ceaf0883f5cd604311978a60a5239b96fe2ff64ca1cc96becb8aefafe8600dda3b268249bc265cdbd086edbaaf53fd016109125e1dec784390cb8eb1501ccfcdbc4a4608ed8ffbbffd56ec7c97badea762b4a37f00dc7998da8c22c13b0125ffa91465262004a6ed1747a22074a3b32aac64e773f104538fa5032b7667a76f6963bd86d1ec7d3e83e2d59a070d3f558e27e6c8228080f0cc81d07dff2907ef617571ba1b18836743327579285844b1cc6e8ce809a092af8f6e1f7c8d27621348cc35698dec7e1dd82d9d5f07e89bc6d3e192fb1f7822d057265a30f36f18fe376c3c729335605e7d0e0fdbc3f30352f3a2ed94cba2f01f4df015957d
1 change: 1 addition & 0 deletions tests/data/legacy/ed25519_private_unencrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"keytype": "ed25519", "scheme": "ed25519", "keyid": "cb2eea1134dac06c1ca2e94b1ffbd15c0bf9f0f541458f0a1df6968a900392f9", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "167ced64cc9908b0bebb92df124d8d7fbe4298d41407524e8d238d0bcdd76c79", "private": "71fe1138357bf15b08723fd01af86deb5b58e4f469eb0acc9892e3c4cf9f4504"}}
1 change: 1 addition & 0 deletions tests/data/legacy/ed25519_public
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"keytype": "ed25519", "scheme": "ed25519", "keyid_hash_algorithms": ["sha256", "sha512"], "keyval": {"public": "167ced64cc9908b0bebb92df124d8d7fbe4298d41407524e8d238d0bcdd76c79"}}
42 changes: 42 additions & 0 deletions tests/data/legacy/rsa_private_encrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,5196BEDDC2BA3ECB973C1B15CF8BA8F0

x0B78cwIeX7UjVqxUPaCdRhx+QhXduEVUL970GNYOQpLwnYAjBgDCXXVwyxIinnC
Ho0Qyd1bMAmlfAttlbPBN1Bf3lWUaLj3Rc51Sscj4pVWla9Q24LRN+87QCF6D9W+
TpfAtwByGFVZarhLE+g3Nuknd6zS73N3fzfxlluQcVN+NvAYmECZTqhb12F83mhY
0nsNmQrhC5zw3XKb+Fe4CD2Ds3VAfMaZR+r+D6CjpYfznmhKX73cHEtvzamoSCA+
qiTWwOhfOTPO78SZBVcEPPgNVIX/cUs2kDQAkgPSGNtyj5ELKcarXg6zyu5Y95q2
K3ZdurduJYM5S7y9JHjx2HZfRvF5p3o4biWzYUlaETnVzkCTts8TppJU3BiCaYXQ
CF5dYCrhF5CosANdOiL8Nq7hvJX+4YMkIO1GwbRMKhM1DaFXWzimP3cUENWPblll
T5FUF5Jg62BpFSXlp1a6ypJEZzAIQJIGzBwJIx5kWkeDYkNL2+9z6kByarS0Dsr+
n1xtq2Gbx4k+4GPanbh4FycjxAoXFZQQxYa4AACHl0A4j2MzGdIRQQapgtyQJ8Hj
7cZ4OKOooZ393NCZRQ8dc3kST0IvOptLzzk/COZCkt2gmcgL/6eblp4fqpWc3rt7
9V9iMi7HZnA2lZjLBqT39A/QQX9J9F3CZTpLsc0D9inlUogC6s3JCIi00x+5TYlL
uJZjk26CX39nqKFFMeNRYessGDDltSFMrNbiGsOWhZmAILT7oYsCCtlCeSIvBSll
bA5pzw7xn+b1fOW1MSVqc6wQS035u/qu4hGG9/kFLyuHcjwKEXrcrV1iPvyRPLq0
MuZG4Z74QkNNNwtZ97wZTynmuezayuIncqcCSsCP9bbCFEKJ9XMYFL7GUxD+rnzc
7twnWLnbjpL/qr4KP0y1Ydm8GUDYvYhQ0Ecd+nj7Xl8T3oV9S71WOVyWbSFpFSDV
VnB+kEV4edZ7gyhEo9lwdVV+8Ap+VI/Wg1jhV32jq3534wNX9DEMI7X8cKEW/JQO
kaMOz+21eYPiTMz3qm2NXyL4sC3LhJBz9YZpitJZ1K6cxvPRqokWZ31RXR8il4Ik
AsPUdIXLYZJ7jJ3JxdIX8NMDpw/hCUpqWfgkf5Vr4ZTp9bvKUiHTZurua1Av/ZMp
S/qhYo4x8RurtaXj0LbWx95eQLhzeoftIXyqH6uUOY6yAZjk162Egcs+ObJ57l3O
vvOsU6kbC0Fvf1JQjXLfZZ+RMRCn+f8umBAcO6QoF5ntz8Xmw8xJyLAh5ayJ18k2
bcMX54YvkqO5wbmPH7cQx3vpeMpLWP9P3e+PCcTvsMAqzDTfmzhMmR6GeCOsE/eE
i/ZKDEac0VzZsSfvWGDkE/qXs//3HvdooRTWhaPihUxLmwUxTeMcnKO+Ct2axrM5
5g4R1+iNwSGzx1Rq3LomGO22xlW0B0lk7Ah6CO/Nc/tCSm8MXlMgVB9wR/54v9Be
DLhKXRg03Pgm54lFdLLgxGL64i6eJ+JhVOdNqjZiAuu0ZULO6UVlaYpGIqMV5qe2
9hhlwwm5jOZkH5h18J/Tz1dB4bzHEt9QG1a4/ESaHNWF7tPudWdqV6cAmvzLWyWc
fbPv6r0OHYdHta+qB5kbo0knYiBEenvV+5LPE1xOpuUVhJ9sxq8O+1Jj0z/pmsQB
nW0cLjPT/CQQBq9T3n2mdVMBwTupgkW7h+MvPe+cWaKvdv/pJ1KsxxolqXDsYOH2
bw01qryYhaRmbSMvDudrR4ixHrAMAMrcfGkjsXsxXGWM1tNapcBbHMvZBrnYKEOe
PyhkGxvNAQLihcZAX5IXQqCn1nGqhJNcQ50X/Bc2RnM05CA67z3w8aBgalnzVKAq
b4HEJkGnmJn48zqcy5n1DZQ0Ov+xrO8vLm8ycDnb8f+miTlgPj9ehm0hUgKMJTH4
JY21dBNpmbn6+n8q1s9Odli0vnSwTo5ZrCLSz8XXAoy1QYcVl/a26/aBuNH877AR
aajmD563wBUpq8YuqdHeT/K7QjrSBqmtUgI5Twqc59MfTMUBHnWvw/wPFO1pw8bm
cb38NI4oC4eJhpq6w0pr06e55go6WZLrxhOB0TaqOcFQUQxaeFhvH9a4d0AEfh1y
AS/mrR2ydLyhTr7yH4DZyce+ql565rKbrbznn5Uy9YdLSEJSnh2vYcs6EoUHZ6yY
OMeqou0B6g3JlrMWrpefRwo6repcnzBK5axfwwMLHwC0zljY8VP3sMpMn7gTtyKO
FbiMcLYhD9L19ud3xbd4azaJ5zkjxjkIqIEQmDsbzppLuKqjWpmvgCVRKnV2WVbG
pzvH365UnfkZd0lElil5EGxHtId1g1RiDRZMZvz5DeEPlt/BzmA+X/OJt0J83td/
-----END RSA PRIVATE KEY-----
39 changes: 39 additions & 0 deletions tests/data/legacy/rsa_private_unencrypted
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
11 changes: 11 additions & 0 deletions tests/data/legacy/rsa_public
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-----BEGIN PUBLIC KEY-----
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAwYSU3rVOgJV1uXPdkBK+
Wkx09xMMEQE/xTQG0wX4tLOSeHSK+/MwXeYw83DGYAsHEtEb57j/iNrVSESMEJfB
9CT6YBLh6DfnZQnG5GS5f997t8c3zyGM1fYzoBWaVakMVO2DoM8vxst/c2MU1BJX
aG7hW+Tu5Kuz856YGmX8fV6eGvetWaQM9iN6jRmSS6Wmt/2WXcnvzjV5OyetpkH2
s8kT60x1zElKiZtmGvz2lnxBZWK3uMBiBL8I52KZF+s9G65+shPJScYFKJ/NLVcM
EaVDCghPay5bZ9pv6vqhylisLIs+alJoxWriz8a7Pc1tW0pBsWrk53adtBaxscFe
3kjiJblQWE19xDwjEVCGA1MmslY0LFi2+R6SVcDDnNK7m0wcaqy1OG1bQXSpZLn/
bNKE0NvT1SzrlRUojkcneiH6sJSWucXmoo5tL2+uzjEFM/xppW/leo72Umtgahlq
jhvFboJB0slG7ppwcrkuWYg1SejTHBt51jdvzsq6tVtdAgMBAAE=
-----END PUBLIC KEY-----
128 changes: 128 additions & 0 deletions tests/test_migrate_key.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
"""Test key migration script"""

import shutil
import sys
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch

from securesystemslib.exceptions import UnverifiedSignatureError
from securesystemslib.interface import (
import_privatekey_from_file,
import_publickeys_from_file,
)
from securesystemslib.migrate_key import main as migrate_key_cli
from securesystemslib.signer import CryptoSigner, SSlibKey, SSlibSigner


class TestMigrateKey(unittest.TestCase):
"""Test key migration and backwards compatibility of signatures."""

@classmethod
def setUpClass(cls):
cls.old_keys = Path(__file__).parent / "data" / "legacy"
cls.new_keys = Path(tempfile.mkdtemp())

# Migrate private, private encrypted and public keys for each algo
for algo in ["rsa", "ecdsa", "ed25519"]:
for type_, name_suffix, has_password in [
("private", "_encrypted", True),
("private", "_unencrypted", False),
("public", "", False),
]:
args = [
"migrate_key.py",
"--type",
type_,
"--algo",
algo,
"--in",
str(cls.old_keys / f"{algo}_{type_}{name_suffix}"),
"--out",
str(cls.new_keys / f"{algo}_{type_}{name_suffix}"),
]

if has_password:
args += ["--password", "password"]

with patch.object(sys, "argv", args):
migrate_key_cli()

@classmethod
def tearDownClass(cls):
shutil.rmtree(cls.new_keys)

def test_migrated_keys(self):
for algo in ["rsa", "ecdsa", "ed25519"]:
# Load public key
with open(self.new_keys / f"{algo}_public", "rb") as f:
public_key = SSlibKey.from_pem(f.read())

# Load unencrypted private key
path = self.new_keys / f"{algo}_private_unencrypted"
uri = f"file:{path}?encrypted=false"
signer_unenc = CryptoSigner.from_priv_key_uri(uri, public_key)

# Load encrypted private key
path = self.new_keys / f"{algo}_private_encrypted"
uri = f"file:{path}?encrypted=true"
signer_enc = CryptoSigner.from_priv_key_uri(
uri, public_key, lambda sec: "password"
)

# Sign and test signatures
for signer in [signer_unenc, signer_enc]:
sig = signer.sign(b"data")
self.assertIsNone(public_key.verify_signature(sig, b"data"))
with self.assertRaises(UnverifiedSignatureError):
public_key.verify_signature(sig, b"not data")

def test_new_signature_verifies_with_old_key(self):
for algo in ["rsa", "ecdsa", "ed25519"]:
# Load old public key
key_dicts = import_publickeys_from_file(
[str(self.old_keys / f"{algo}_public")], [algo]
)
key_dict = list(key_dicts.values())[0]
public_key = SSlibKey.from_securesystemslib_key(key_dict)

# Load new private key
# NOTE: The signer is loaded with the old public key, thus the old
# keyid will be assigned to any new signatures.
path = self.new_keys / f"{algo}_private_unencrypted"
uri = f"file:{path}?encrypted=false"
signer = CryptoSigner.from_priv_key_uri(uri, public_key)

# Sign and test signatures
sig = signer.sign(b"data")
self.assertIsNone(public_key.verify_signature(sig, b"data"))
with self.assertRaises(UnverifiedSignatureError):
public_key.verify_signature(sig, b"not data")

def test_old_signature_verifies_with_new_key(self):
for algo in ["rsa", "ecdsa", "ed25519"]:
# Load old private key
private_key = import_privatekey_from_file(
str(self.old_keys / f"{algo}_private_unencrypted"), algo
)
signer = SSlibSigner(private_key)

# Load new public key
with open(self.new_keys / f"{algo}_public", "rb") as f:
# NOTE: The new auto-keyid would differ from the old keyid.
# Set it explicitly, to verify signatures with old keyid below
public_key = SSlibKey.from_pem(
f.read(), keyid=private_key["keyid"]
)

# Sign and test signature
sig = signer.sign(b"data")
self.assertIsNone(public_key.verify_signature(sig, b"data"))
with self.assertRaises(UnverifiedSignatureError):
public_key.verify_signature(sig, b"not data")


# Run the unit tests.
if __name__ == "__main__":
unittest.main()

0 comments on commit f2508fe

Please sign in to comment.