launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #26096
[Merge] ~cjwatson/lp-signing:cv2-kernel into lp-signing:master
Colin Watson has proposed merging ~cjwatson/lp-signing:cv2-kernel into lp-signing:master.
Commit message:
Add support for Ambarella CV2 kernel keys
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/lp-signing/+git/lp-signing/+merge/396849
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/lp-signing:cv2-kernel into lp-signing:master.
diff --git a/lp_signing/enums.py b/lp_signing/enums.py
index 8d020d4..e76b7cf 100644
--- a/lp_signing/enums.py
+++ b/lp_signing/enums.py
@@ -53,6 +53,12 @@ class KeyType(DBEnumeratedType):
An OpenPGP signing key.
""")
+ CV2_KERNEL = DBItem(7, """
+ CV2 Kernel
+
+ An Ambarella CV2 kernel signing key.
+ """)
+
class OpenPGPKeyAlgorithm(EnumeratedType):
diff --git a/lp_signing/model/key.py b/lp_signing/model/key.py
index 6b0673c..1dc70f8 100644
--- a/lp_signing/model/key.py
+++ b/lp_signing/model/key.py
@@ -9,6 +9,7 @@ __all__ = [
import base64
from contextlib import contextmanager
+import hashlib
import json
import logging
import os
@@ -416,6 +417,28 @@ class Key(Storm):
return export_secret_result, export_public_result, key.fpr
@classmethod
+ def _generateRSA(cls, tmp, key_type):
+ """Generate a new RSA key.
+
+ :param tmp: A `Path` to a temporary directory.
+ :param key_type: The `KeyType` to generate.
+ :return: A tuple of (private key, public key).
+ """
+ private_key = tmp / f"{key_type.name.lower()}.priv"
+ public_key = tmp / f"{key_type.name.lower()}.pub"
+ _log_subprocess_run([
+ "openssl", "genpkey", "-algorithm", "RSA",
+ "-out", str(private_key),
+ "-pkeyopt", "rsa_keygen_bits:2048",
+ "-pkeyopt", "rsa_keygen_pubexp:65537",
+ ], check=True)
+ _log_subprocess_run([
+ "openssl", "pkey", "-in", str(private_key), "-pubout",
+ "-out", str(public_key),
+ ], check=True)
+ return private_key.read_bytes(), public_key.read_bytes()
+
+ @classmethod
def _getX509Fingerprint(cls, key_type, public_key):
"""Get the fingerprint of an X509 "public key" (i.e. certificate).
@@ -448,6 +471,18 @@ class Key(Storm):
return result.imports[0].fpr
@classmethod
+ def _getRSAFingerprint(cls, public_key):
+ """Get the fingerprint of an RSA public key.
+
+ :param public_key: The public key (`bytes`).
+ :return: The fingerprint (`str`).
+ """
+ output = subprocess.run([
+ "openssl", "pkey", "-pubin", "-outform", "DER",
+ ], input=public_key, stdout=subprocess.PIPE, check=True).stdout
+ return hashlib.sha1(output).hexdigest().upper()
+
+ @classmethod
def generate(cls, key_type, description, openpgp_key_algorithm=None,
length=None):
"""Generate a new key for an archive.
@@ -476,6 +511,8 @@ class Key(Storm):
cls._generateOpenPGP(
ctx, openpgp_key_algorithm, length,
description))
+ elif key_type == KeyType.CV2_KERNEL:
+ private_key, public_key = cls._generateRSA(tmp, key_type)
else:
raise KeyGenerationError.single(
f"Unknown key type {key_type.name}")
@@ -484,7 +521,9 @@ class Key(Storm):
except Exception as e:
_log.error("Failed to generate key: %s", e)
raise KeyGenerationError.single(f"Failed to generate key: {e}")
- if key_type != KeyType.OPENPGP:
+ if key_type in (
+ KeyType.UEFI, KeyType.KMOD, KeyType.OPAL, KeyType.SIPL,
+ KeyType.FIT):
try:
fingerprint = cls._getX509Fingerprint(
key_type, public_key)
@@ -492,6 +531,13 @@ class Key(Storm):
_log.error("Failed to get fingerprint of new key: %s", e)
raise KeyGenerationError.single(
f"Failed to get fingerprint of new key: {e}")
+ elif key_type == KeyType.CV2_KERNEL:
+ try:
+ fingerprint = cls._getRSAFingerprint(public_key)
+ except subprocess.CalledProcessError as e:
+ _log.error("Failed to get fingerprint of new key: %s", e)
+ raise KeyGenerationError.single(
+ f"Failed to get fingerprint of new key: {e}")
_log.info("Generated new key with fingerprint %s", fingerprint)
return cls.new(key_type, fingerprint, private_key, public_key)
@@ -510,16 +556,27 @@ class Key(Storm):
:return: The injected `Key`.
"""
_log.info("Injecting %s key for %s", key_type, description)
- if key_type == KeyType.OPENPGP:
+ if key_type in (
+ KeyType.UEFI, KeyType.KMOD, KeyType.OPAL, KeyType.SIPL,
+ KeyType.FIT):
+ try:
+ fingerprint = cls._getX509Fingerprint(key_type, public_key)
+ except subprocess.CalledProcessError as e:
+ _log.error("Failed to get fingerprint of new key: %s", e)
+ raise KeyImportError.single(
+ f"Failed to get fingerprint of new key: {e}")
+ elif key_type == KeyType.OPENPGP:
with _temporary_path() as tmp, _gpg_context(Path(tmp)) as ctx:
fingerprint = cls._getOpenPGPFingerprint(ctx, public_key)
- else:
+ elif key_type == KeyType.CV2_KERNEL:
try:
- fingerprint = cls._getX509Fingerprint(key_type, public_key)
+ fingerprint = cls._getRSAFingerprint(public_key)
except subprocess.CalledProcessError as e:
_log.error("Failed to get fingerprint of new key: %s", e)
raise KeyImportError.single(
f"Failed to get fingerprint of new key: {e}")
+ else:
+ raise KeyImportError.single(f"Unknown key type {key_type.name}")
_log.info("Injecting new key with fingerprint %s", fingerprint)
try:
key = Key.getByTypeAndFingerprint(key_type, fingerprint)
@@ -627,9 +684,15 @@ class Key(Storm):
# that we handle it in a separate method.
return self._signOpenPGP(tmp, message_name, message, mode)
- key = tmp / f"{self.key_type.name.lower()}.key"
+ if self.key_type == KeyType.CV2_KERNEL:
+ key = tmp / f"{self.key_type.name.lower()}.priv"
+ else:
+ key = tmp / f"{self.key_type.name.lower()}.key"
key.write_bytes(self.getPrivateKey())
- cert = tmp / f"{self.key_type.name.lower()}.crt"
+ if self.key_type == KeyType.CV2_KERNEL:
+ cert = tmp / f"{self.key_type.name.lower()}.pub"
+ else:
+ cert = tmp / f"{self.key_type.name.lower()}.crt"
cert.write_bytes(self.public_key)
message_path = tmp / message_name
message_path.write_bytes(message)
@@ -660,6 +723,12 @@ class Key(Storm):
cmd = [
"mkimage", "-F", "-k", str(tmp), "-r", str(sig_path),
]
+ elif self.key_type == KeyType.CV2_KERNEL:
+ if mode == SignatureMode.DETACHED:
+ cmd = [
+ "openssl", "dgst", "-sha256", "-sign", str(key),
+ "-out", str(sig_path), str(message_path),
+ ]
if cmd is None:
raise UnsupportedSignatureMode.single(
f"Signature mode {mode.name} not supported with "
diff --git a/lp_signing/model/tests/test_key.py b/lp_signing/model/tests/test_key.py
index 00115d7..38d5269 100644
--- a/lp_signing/model/tests/test_key.py
+++ b/lp_signing/model/tests/test_key.py
@@ -3,9 +3,13 @@
"""Test the database model for signing keys."""
-import re
-from datetime import datetime, timezone
+from datetime import (
+ datetime,
+ timezone,
+ )
+import hashlib
from pathlib import Path
+import re
from tempfile import TemporaryDirectory
from fixtures import MockPatch
@@ -49,6 +53,7 @@ from lp_signing.tests.testfixtures import (
FakeKmodSign,
FakeMkimage,
FakeOpenSSL,
+ FakeOpenSSLSign,
FakeProcesses,
FakeSBSign,
)
@@ -369,6 +374,46 @@ class TestKey(TestCase):
self.assertEqual(
key, Key.getByTypeAndFingerprint(KeyType.OPENPGP, key.fingerprint))
+ def test_generate_cv2_kernel(self):
+ private_key = factory.generate_random_bytes(size=64)
+ public_key = factory.generate_random_bytes(size=64)
+ fingerprint = hashlib.sha1(public_key).hexdigest().upper()
+ fake_openssl = FakeOpenSSL(private_key, public_key, fingerprint)
+ self.processes_fixture.add(fake_openssl)
+ key = Key.generate(KeyType.CV2_KERNEL, "~signing-owner/ubuntu/testing")
+ now = get_transaction_timestamp(store)
+ self.assertThat(key, MatchesStructure.byEquality(
+ key_type=KeyType.CV2_KERNEL,
+ fingerprint=fingerprint,
+ public_key=public_key,
+ created_at=now,
+ updated_at=now))
+ self.assertEqual(private_key, key.getPrivateKey())
+ self.assertEqual(
+ key, Key.getByTypeAndFingerprint(KeyType.CV2_KERNEL, fingerprint))
+ genpkey_args = [
+ "openssl", "genpkey", "-algorithm", "RSA",
+ "-out", EndsWith("cv2_kernel.priv"),
+ "-pkeyopt", "rsa_keygen_bits:2048",
+ "-pkeyopt", "rsa_keygen_pubexp:65537",
+ ]
+ pkey_args = [
+ "openssl", "pkey", "-in", EndsWith("cv2_kernel.priv"),
+ "-pubout", "-out", EndsWith("cv2_kernel.pub"),
+ ]
+ pkey_der_args = ["openssl", "pkey", "-pubin", "-outform", "DER"]
+ self.assertThat(
+ self.processes_fixture.procs,
+ MatchesListwise([
+ RanCommand(genpkey_args, stdin=Is(None)),
+ RanCommand(pkey_args, stdin=Is(None)),
+ RanCommand(
+ pkey_der_args,
+ stdin=AfterPreprocessing(
+ lambda f: f.getvalue(),
+ Equals(public_key))),
+ ]))
+
def test_addAuthorization(self):
key = factory.create_key()
clients = [factory.create_client() for _ in range(2)]
@@ -585,6 +630,34 @@ class TestKey(TestCase):
mock_sign.assert_called_once_with(
b"test data", mode=gpg.constants.SIG_MODE_CLEAR)
+ def test_sign_cv2_kernel_attached_unsupported(self):
+ key = factory.create_key(KeyType.CV2_KERNEL)
+ self.assertRaises(
+ UnsupportedSignatureMode,
+ key.sign, "t.cv2_kernel", b"test data", SignatureMode.ATTACHED)
+
+ def test_sign_cv2_kernel_detached(self):
+ key = factory.create_key(KeyType.CV2_KERNEL)
+ fake_openssl_sign = FakeOpenSSLSign(b"test signed data")
+ self.processes_fixture.add(fake_openssl_sign)
+ self.assertEqual(
+ b"test signed data",
+ key.sign("t.cv2_kernel", b"test data", SignatureMode.DETACHED))
+ self.assertEqual(b"test data", fake_openssl_sign.message_bytes)
+ openssl_sign_args = [
+ "openssl", "dgst", "-sha256", "-sign", EndsWith("cv2_kernel.priv"),
+ "-out", EndsWith("t.cv2_kernel.sig"), EndsWith("t.cv2_kernel"),
+ ]
+ self.assertThat(
+ self.processes_fixture.procs,
+ MatchesListwise([RanCommand(openssl_sign_args)]))
+
+ def test_sign_cv2_kernel_clear_unsupported(self):
+ key = factory.create_key(KeyType.CV2_KERNEL)
+ self.assertRaises(
+ UnsupportedSignatureMode,
+ key.sign, "t.cv2_kernel", b"test data", SignatureMode.CLEAR)
+
def test_inject_uefi(self):
private_key = factory.generate_random_bytes(size=64)
public_key = factory.generate_random_bytes(size=64)
@@ -760,3 +833,29 @@ class TestKey(TestCase):
self.assertEqual(private_key, key.getPrivateKey())
self.assertEqual(
key, Key.getByTypeAndFingerprint(KeyType.OPENPGP, fingerprint))
+
+ def test_inject_cv2_kernel(self):
+ private_key = factory.generate_random_bytes(size=64)
+ public_key = factory.generate_random_bytes(size=64)
+ fingerprint = hashlib.sha1(public_key).hexdigest().upper()
+ fake_openssl = FakeOpenSSL(private_key, public_key, fingerprint)
+ self.processes_fixture.add(fake_openssl)
+ description = "PPA signing-owner testing"
+ created_at = datetime.utcnow().replace(tzinfo=timezone.utc)
+ key = Key.inject(
+ KeyType.CV2_KERNEL, private_key, public_key, description,
+ created_at)
+ now = get_transaction_timestamp(store)
+ self.assertThat(key, MatchesStructure.byEquality(
+ key_type=KeyType.CV2_KERNEL,
+ fingerprint=fingerprint,
+ public_key=public_key,
+ created_at=created_at,
+ updated_at=now))
+ self.assertEqual(private_key, key.getPrivateKey())
+ self.assertEqual(
+ key, Key.getByTypeAndFingerprint(KeyType.CV2_KERNEL, fingerprint))
+ pkey_der_args = ["openssl", "pkey", "-pubin", "-outform", "DER"]
+ self.assertThat(
+ self.processes_fixture.procs,
+ MatchesListwise([RanCommand(pkey_der_args)]))
diff --git a/lp_signing/tests/test_webapi.py b/lp_signing/tests/test_webapi.py
index c4ad4ac..3cb32bb 100644
--- a/lp_signing/tests/test_webapi.py
+++ b/lp_signing/tests/test_webapi.py
@@ -9,6 +9,8 @@ import pytz
from tempfile import TemporaryDirectory
from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
+from cryptography.hazmat.primitives.serialization import load_pem_public_key
from cryptography.x509 import (
load_der_x509_certificate,
load_pem_x509_certificate,
@@ -30,7 +32,9 @@ from testtools.matchers import (
AnyMatch,
Equals,
HasLength,
+ IsInstance,
Matcher,
+ MatchesAll,
MatchesDict,
MatchesListwise,
MatchesRegex,
@@ -63,6 +67,7 @@ from lp_signing.tests.testfixtures import (
FakeKmodSign,
FakeMkimage,
FakeOpenSSL,
+ FakeOpenSSLSign,
FakeProcesses,
FakeSBSign,
)
@@ -657,6 +662,64 @@ class TestGenerateView(TestCase):
self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
self.assertNonceConsumed()
+ def test_generate_cv2_kernel(self):
+ # Integration test: generate and return a real CV2 kernel key.
+ resp = self.post_generate({
+ "key-type": "CV2_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ self.assertThat(resp, IsJSONResponse(
+ MatchesDict({
+ "fingerprint": HasLength(40),
+ "public-key": AfterPreprocessing(
+ lambda data: load_pem_public_key(
+ base64.b64decode(data.encode("UTF-8")),
+ default_backend()),
+ MatchesAll(
+ IsInstance(RSAPublicKey),
+ MatchesStructure(key_size=Equals(2048)))),
+ }),
+ expected_status=201))
+ self.assertNonceConsumed()
+ # The new key was committed to the database.
+ key = Key.getByTypeAndFingerprint(
+ KeyType.CV2_KERNEL, resp.json["fingerprint"])
+ self.assertThat(key, MatchesStructure.byEquality(
+ fingerprint=resp.json["fingerprint"],
+ public_key=base64.b64decode(
+ resp.json["public-key"].encode("UTF-8")),
+ authorizations=[self.client]))
+
+ def test_generate_cv2_kernel_genpkey_error(self):
+ processes_fixture = self.useFixture(FakeProcesses())
+ processes_fixture.add(lambda _: {"returncode": 1}, name="openssl")
+ resp = self.post_generate({
+ "key-type": "CV2_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to generate key: "
+ r"Command .*'openssl', 'genpkey'.* returned non-zero exit status "
+ r"1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
+ def test_generate_cv2_kernel_fingerprint_error(self):
+ processes_fixture = self.useFixture(FakeProcesses())
+ private_key = factory.generate_random_bytes(size=64)
+ public_key = factory.generate_random_bytes(size=64)
+ fake_openssl = FakeOpenSSL(private_key, public_key, None)
+ processes_fixture.add(fake_openssl)
+ resp = self.post_generate({
+ "key-type": "CV2_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to get fingerprint of new key: "
+ r"Command .*'-outform', 'DER'.* returned non-zero exit status 1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
class TestSignView(TestCase):
@@ -758,7 +821,7 @@ class TestSignView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
self.assertNonceConsumed()
def test_missing_fingerprint(self):
@@ -1355,6 +1418,85 @@ class TestSignView(TestCase):
self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
self.assertNonceConsumed()
+ def test_sign_cv2_kernel_attached_unsupported(self):
+ key = factory.create_key(key_type=KeyType.CV2_KERNEL)
+ key.addAuthorization(self.client)
+ store.commit()
+ resp = self.post_sign(
+ {
+ "key-type": "CV2_KERNEL",
+ "fingerprint": key.fingerprint,
+ "message-name": "t.cv2_kernel",
+ "message": base64.b64encode(b"test data").decode("UTF-8"),
+ "mode": "ATTACHED",
+ })
+ self.assertThat(
+ resp,
+ HasAPIError(
+ "Signature mode ATTACHED not supported with CV2_KERNEL"))
+
+ def test_sign_cv2_kernel_detached(self):
+ key = factory.create_key(key_type=KeyType.CV2_KERNEL)
+ key.addAuthorization(self.client)
+ store.commit()
+ fake_openssl_sign = FakeOpenSSLSign(b"test signed data")
+ processes_fixture = self.useFixture(FakeProcesses())
+ processes_fixture.add(fake_openssl_sign)
+ resp = self.post_sign(
+ {
+ "key-type": "CV2_KERNEL",
+ "fingerprint": key.fingerprint,
+ "message-name": "t.cv2_kernel",
+ "message": base64.b64encode(b"test data").decode("UTF-8"),
+ "mode": "DETACHED",
+ })
+ self.assertThat(resp, IsJSONResponse(
+ MatchesDict({
+ "public-key": AfterPreprocessing(
+ lambda data: base64.b64decode(data.encode("UTF-8")),
+ Equals(key.public_key)),
+ "signed-message": AfterPreprocessing(
+ lambda data: base64.b64decode(data.encode("UTF-8")),
+ Equals(b"test signed data")),
+ })))
+ self.assertNonceConsumed()
+
+ def test_sign_cv2_kernel_detached_openssl_sign_error(self):
+ key = factory.create_key(key_type=KeyType.CV2_KERNEL)
+ key.addAuthorization(self.client)
+ store.commit()
+ processes_fixture = self.useFixture(FakeProcesses())
+ processes_fixture.add(lambda _: {"returncode": 1}, name="openssl")
+ resp = self.post_sign(
+ {
+ "key-type": "CV2_KERNEL",
+ "fingerprint": key.fingerprint,
+ "message-name": "t.cv2_kernel",
+ "message": base64.b64encode(b"test data").decode("UTF-8"),
+ "mode": "DETACHED",
+ })
+ error_re = (
+ r"Failed to sign message: "
+ r"Command .*'openssl', 'dgst'.* returned non-zero exit status 1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
+ def test_sign_cv2_kernel_clear_unsupported(self):
+ key = factory.create_key(key_type=KeyType.CV2_KERNEL)
+ key.addAuthorization(self.client)
+ store.commit()
+ resp = self.post_sign(
+ {
+ "key-type": "CV2_KERNEL",
+ "fingerprint": key.fingerprint,
+ "message-name": "t.cv2_kernel",
+ "message": base64.b64encode(b"test data").decode("UTF-8"),
+ "mode": "CLEAR",
+ })
+ self.assertThat(
+ resp,
+ HasAPIError("Signature mode CLEAR not supported with CV2_KERNEL"))
+
class TestInjectView(TestCase):
@@ -1501,7 +1643,7 @@ class TestInjectView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
self.assertNonceConsumed()
def test_inject_uefi(self):
@@ -1814,6 +1956,50 @@ class TestInjectView(TestCase):
self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
self.assertNonceConsumed()
+ def test_inject_cv2_kernel(self):
+ # Integration test: inject a real CV2 kernel key and return its
+ # fingerprint.
+ with TemporaryDirectory() as tmp:
+ private_key, public_key = Key._generateRSA(
+ Path(tmp), KeyType.CV2_KERNEL)
+
+ resp = self.post_inject({
+ "key-type": "CV2_KERNEL",
+ "private-key": base64.b64encode(private_key).decode("UTF-8"),
+ "public-key": base64.b64encode(public_key).decode("UTF-8"),
+ "created-at": datetime.utcnow().isoformat(),
+ "description": "PPA test-owner test-archive",
+ })
+ self.assertThat(resp, IsJSONResponse(
+ MatchesDict({"fingerprint": HasLength(40)}),
+ expected_status=200))
+ self.assertNonceConsumed()
+ # The new key was committed to the database.
+ key = Key.getByTypeAndFingerprint(
+ KeyType.CV2_KERNEL, resp.json["fingerprint"])
+ self.assertThat(key, MatchesStructure.byEquality(
+ fingerprint=resp.json["fingerprint"],
+ authorizations=[self.clients[0]]))
+
+ def test_inject_cv2_kernel_fingerprint_error(self):
+ processes_fixture = self.useFixture(FakeProcesses())
+ private_key = factory.generate_random_bytes(size=64)
+ public_key = factory.generate_random_bytes(size=64)
+ fake_openssl = FakeOpenSSL(private_key, public_key, None)
+ processes_fixture.add(fake_openssl)
+ resp = self.post_inject({
+ "key-type": "CV2_KERNEL",
+ "private-key": "",
+ "public-key": "",
+ "created-at": datetime.utcnow().isoformat(),
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to get fingerprint of new key: "
+ r"Command .*'-outform', 'DER'.* returned non-zero exit status 1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
def test_inject_duplicate_key_different_clients(self):
common_name = Key._generateKeyCommonName(
"PPA test-owner test-archive", 'FIT')
@@ -2127,7 +2313,7 @@ class TestAddAuthorizationView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
self.assertNonceConsumed()
def test_unknown_key(self):
diff --git a/lp_signing/tests/testfixtures.py b/lp_signing/tests/testfixtures.py
index ccaec93..c012c42 100644
--- a/lp_signing/tests/testfixtures.py
+++ b/lp_signing/tests/testfixtures.py
@@ -3,6 +3,7 @@
"""Test fixtures."""
+import hashlib
import io
import json
from pathlib import Path
@@ -207,6 +208,31 @@ class FakeOpenSSL:
info["stdout"] = io.BytesIO(output.encode("UTF-8"))
else:
info["returncode"] = 1
+ elif args[1] == "genpkey":
+ if "-out" in args:
+ private_key_path = args[args.index("-out") + 1]
+ Path(private_key_path).write_bytes(self.private_key)
+ elif args[1] == "pkey":
+ if "-out" in args:
+ public_key_path = args[args.index("-out") + 1]
+ Path(public_key_path).write_bytes(self.public_key)
+ if ("-outform" in args and
+ args[args.index("-outform") + 1] == "DER"):
+ if self.fingerprint is not None:
+ # For plain RSA keys, Key._getRSAFingerprint computes
+ # the fingerprint in Python based on the DER encoding of
+ # the public key. Check that this matches
+ # self.fingerprint to avoid confusion.
+ public_key_fingerprint = hashlib.sha1(
+ self.public_key).hexdigest().upper()
+ if public_key_fingerprint != self.fingerprint:
+ raise AssertionError(
+ f"Fingerprint of self.public_key does not match "
+ f"self.fingerprint ({public_key_fingerprint} != "
+ f"{self.fingerprint}")
+ info["stdout"] = io.BytesIO(self.public_key)
+ else:
+ info["returncode"] = 1
return info
@@ -256,3 +282,21 @@ class FakeMkimage:
# mkimage signs in place.
Path(message_path).write_bytes(self.sig_data)
return {}
+
+
+class FakeOpenSSLSign:
+
+ name = "openssl"
+
+ def __init__(self, sig_data):
+ self.sig_data = sig_data
+ self.message_bytes = None
+
+ def __call__(self, proc_args):
+ args = proc_args["args"]
+ if args[1] == "dgst":
+ message_path = args[-1]
+ self.message_bytes = Path(message_path).read_bytes()
+ sig_path = args[args.index("-out") + 1]
+ Path(sig_path).write_bytes(self.sig_data)
+ return {}