launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27225
[Merge] ~ilasc/lp-signing:add-android-kernel into lp-signing:master
Ioana Lasc has proposed merging ~ilasc/lp-signing:add-android-kernel into lp-signing:master.
Commit message:
Sign Android kernel boot images
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~ilasc/lp-signing/+git/lp-signing/+merge/404686
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/lp-signing:add-android-kernel into lp-signing:master.
diff --git a/lp_signing/enums.py b/lp_signing/enums.py
index e76b7cf..43828c0 100644
--- a/lp_signing/enums.py
+++ b/lp_signing/enums.py
@@ -59,6 +59,12 @@ class KeyType(DBEnumeratedType):
An Ambarella CV2 kernel signing key.
""")
+ ANDROID_KERNEL = DBItem(8, """
+ Android Kernel
+
+ An Android kernel signing key.
+ """)
+
class OpenPGPKeyAlgorithm(EnumeratedType):
diff --git a/lp_signing/model/key.py b/lp_signing/model/key.py
index 1dc70f8..7ba629d 100644
--- a/lp_signing/model/key.py
+++ b/lp_signing/model/key.py
@@ -350,6 +350,26 @@ class Key(Storm):
return key.read_bytes(), cert.read_bytes()
@classmethod
+ def _generateAndroidKernelKeys(cls, tmp, key_type, common_name):
+ """Generate a new key/certificate pair.
+
+ :param tmp: A `Path` to a temporary directory.
+ :param key_type: The `KeyType` to generate.
+ :param common_name: The common name for the new key.
+ :return: A tuple of (private key, public key).
+ """
+ cls._generateKeyCertPair(tmp, key_type, common_name)
+ key_file_name = tmp / f"{key_type.name.lower()}.key"
+ cert_file_name = tmp / f"{key_type.name.lower()}.crt"
+ # Convert private key from PKCS#8 to PKCS#1 format
+ PKCS1_key = tmp / f"{key_type.name.lower()}.key"
+ _log_subprocess_run([
+ "openssl", "rsa", "-in", str(key_file_name),
+ "-out", str(PKCS1_key),
+ ], check=True)
+ return PKCS1_key.read_bytes(), cert_file_name.read_bytes()
+
+ @classmethod
def _generatePEMX509(cls, tmp, key_type, common_name):
"""Generate a new PEM/X509 key pair.
@@ -449,7 +469,8 @@ class Key(Storm):
output = subprocess.run([
"openssl", "x509",
"-inform",
- "PEM" if key_type in (KeyType.UEFI, KeyType.FIT) else "DER",
+ "PEM" if key_type in (
+ KeyType.UEFI, KeyType.FIT, KeyType.ANDROID_KERNEL) else "DER",
"-noout", "-fingerprint",
], input=public_key, stdout=subprocess.PIPE, check=True).stdout
return (
@@ -513,6 +534,9 @@ class Key(Storm):
description))
elif key_type == KeyType.CV2_KERNEL:
private_key, public_key = cls._generateRSA(tmp, key_type)
+ elif key_type == KeyType.ANDROID_KERNEL:
+ private_key, public_key = cls._generateAndroidKernelKeys(
+ tmp, key_type, common_name)
else:
raise KeyGenerationError.single(
f"Unknown key type {key_type.name}")
@@ -523,7 +547,7 @@ class Key(Storm):
raise KeyGenerationError.single(f"Failed to generate key: {e}")
if key_type in (
KeyType.UEFI, KeyType.KMOD, KeyType.OPAL, KeyType.SIPL,
- KeyType.FIT):
+ KeyType.FIT, KeyType.ANDROID_KERNEL):
try:
fingerprint = cls._getX509Fingerprint(
key_type, public_key)
@@ -558,7 +582,7 @@ class Key(Storm):
_log.info("Injecting %s key for %s", key_type, description)
if key_type in (
KeyType.UEFI, KeyType.KMOD, KeyType.OPAL, KeyType.SIPL,
- KeyType.FIT):
+ KeyType.FIT, KeyType.ANDROID_KERNEL):
try:
fingerprint = cls._getX509Fingerprint(key_type, public_key)
except subprocess.CalledProcessError as e:
@@ -723,7 +747,7 @@ class Key(Storm):
cmd = [
"mkimage", "-F", "-k", str(tmp), "-r", str(sig_path),
]
- elif self.key_type == KeyType.CV2_KERNEL:
+ elif self.key_type in (KeyType.CV2_KERNEL, KeyType.ANDROID_KERNEL):
if mode == SignatureMode.DETACHED:
cmd = [
"openssl", "dgst", "-sha256", "-sign", str(key),
diff --git a/lp_signing/model/tests/test_key.py b/lp_signing/model/tests/test_key.py
index 38d5269..7ce5ceb 100644
--- a/lp_signing/model/tests/test_key.py
+++ b/lp_signing/model/tests/test_key.py
@@ -414,6 +414,54 @@ class TestKey(TestCase):
Equals(public_key))),
]))
+ def test_generate_android_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.ANDROID_KERNEL,
+ "~signing-owner/ubuntu/testing")
+ now = get_transaction_timestamp(store)
+ self.assertThat(key, MatchesStructure.byEquality(
+ key_type=KeyType.ANDROID_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.ANDROID_KERNEL, fingerprint))
+
+ genpkey_args = [
+ "openssl", "req", "-new", "-x509", "-newkey", "rsa:2048",
+ "-subj", r"/CN=~signing-owner\/ubuntu\/testing Android Kernel/",
+ "-keyout", EndsWith("android_kernel.key"),
+ "-out", EndsWith("android_kernel.crt"), "-days", "10956",
+ "-nodes", "-sha256",
+ ]
+
+ pkey_args = [
+ "openssl", "rsa", "-in", EndsWith("android_kernel.key"),
+ "-out", EndsWith("android_kernel.key"),
+ ]
+ pkey_der_args = ["openssl", "x509", "-inform", "PEM",
+ "-noout", "-fingerprint"]
+
+ 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)]
@@ -658,6 +706,36 @@ class TestKey(TestCase):
UnsupportedSignatureMode,
key.sign, "t.cv2_kernel", b"test data", SignatureMode.CLEAR)
+ def test_sign_android_kernel_attached_unsupported(self):
+ key = factory.create_key(KeyType.ANDROID_KERNEL)
+ self.assertRaises(
+ UnsupportedSignatureMode,
+ key.sign, "t.android_kernel", b"test data", SignatureMode.ATTACHED)
+
+ def test_sign_android_kernel_detached(self):
+ key = factory.create_key(KeyType.ANDROID_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.android_kernel", b"test data", SignatureMode.DETACHED))
+ self.assertEqual(b"test data", fake_openssl_sign.message_bytes)
+ openssl_sign_args = [
+ "openssl", "dgst", "-sha256", "-sign",
+ EndsWith("android_kernel.key"),
+ "-out", EndsWith("t.android_kernel.sig"),
+ EndsWith("t.android_kernel"),
+ ]
+ self.assertThat(
+ self.processes_fixture.procs,
+ MatchesListwise([RanCommand(openssl_sign_args)]))
+
+ def test_sign_android_kernel_clear_unsupported(self):
+ key = factory.create_key(KeyType.ANDROID_KERNEL)
+ self.assertRaises(
+ UnsupportedSignatureMode,
+ key.sign, "t.android_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)
@@ -859,3 +937,31 @@ class TestKey(TestCase):
self.assertThat(
self.processes_fixture.procs,
MatchesListwise([RanCommand(pkey_der_args)]))
+
+ def test_inject_android_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.ANDROID_KERNEL, private_key, public_key, description,
+ created_at)
+ now = get_transaction_timestamp(store)
+ self.assertThat(key, MatchesStructure.byEquality(
+ key_type=KeyType.ANDROID_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.ANDROID_KERNEL, fingerprint))
+ pkey_der_args = ["openssl", "x509", "-inform",
+ "PEM", "-noout", "-fingerprint"]
+ 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 3cb32bb..c53decd 100644
--- a/lp_signing/tests/test_webapi.py
+++ b/lp_signing/tests/test_webapi.py
@@ -720,6 +720,84 @@ class TestGenerateView(TestCase):
self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
self.assertNonceConsumed()
+ def test_generate_android_kernel(self):
+ # Integration test: generate and return a real Android kernel key.
+ resp = self.post_generate(
+ {
+ "key-type": "ANDROID_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+
+ self.assertThat(resp, IsJSONResponse(
+ MatchesDict({
+ "fingerprint": HasLength(40),
+ "public-key": AfterPreprocessing(
+ lambda data: load_pem_x509_certificate(
+ base64.b64decode(data.encode("UTF-8")),
+ default_backend()),
+ MatchesStructure(
+ subject=AfterPreprocessing(
+ lambda subject: subject.rfc4514_string(),
+ Equals("CN=PPA test-owner test-archive "
+ "Android Kernel")))),
+ }),
+ expected_status=201))
+
+ self.assertNonceConsumed()
+ # The new key was committed to the database.
+ key = Key.getByTypeAndFingerprint(
+ KeyType.ANDROID_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_android_kernel_req_error(self):
+ processes_fixture = self.useFixture(FakeProcesses())
+ processes_fixture.add(lambda _: {"returncode": 1}, name="openssl")
+ resp = self.post_generate(
+ {
+ "key-type": "ANDROID_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to generate key: "
+ r"Command .*'openssl', 'req'.* returned non-zero exit status 1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
+ def test_generate_android_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": "ANDROID_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to get fingerprint of new key: "
+ r"Command .*'-fingerprint'.* returned non-zero exit status 1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
+ def test_generate_android_kernel_genpkey_error(self):
+ processes_fixture = self.useFixture(FakeProcesses())
+ processes_fixture.add(lambda _: {"returncode": 1}, name="openssl")
+ resp = self.post_generate({
+ "key-type": "ANDROID_KERNEL",
+ "description": "PPA test-owner test-archive",
+ })
+ error_re = (
+ r"Failed to generate key: "
+ r"Command .*'req', '-new'.* returned non-zero exit status "
+ r"1")
+ self.assertThat(resp, HasAPIError(MatchesRegex(error_re), 500))
+ self.assertNonceConsumed()
+
class TestSignView(TestCase):
@@ -821,7 +899,7 @@ class TestSignView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL', 'ANDROID_KERNEL']"))
self.assertNonceConsumed()
def test_missing_fingerprint(self):
@@ -1643,7 +1721,7 @@ class TestInjectView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL', 'ANDROID_KERNEL']"))
self.assertNonceConsumed()
def test_inject_uefi(self):
@@ -2313,7 +2391,7 @@ class TestAddAuthorizationView(TestCase):
resp,
HasAPIError(
"'nonsense' is not one of ['UEFI', 'KMOD', 'OPAL', 'SIPL', "
- "'FIT', 'OPENPGP', 'CV2_KERNEL']"))
+ "'FIT', 'OPENPGP', 'CV2_KERNEL', 'ANDROID_KERNEL']"))
self.assertNonceConsumed()
def test_unknown_key(self):