From 825669802315fe11508f0e962490b77cfdfc6184 Mon Sep 17 00:00:00 2001 From: Tianjie Xu Date: Wed, 10 Oct 2018 15:44:17 -0700 Subject: Add function to load the key from x509.pem file We used to convert a pem certificate file to some intermediate plain text format; and parse that format under recovery mode. This is uncessary since the x509.pem can be directly parsed with openssl functions. Add the function to load the public key from one x509.pem file and corresponding unit tests. And we will add more cls to extract the pem files from otacert.zip later. Bug: 116655889 Test: verify package with 5 supported certficate versions Change-Id: Ibc6c696c534567f005db75143cc4ef8d4bdea6a0 --- tests/component/verifier_test.cpp | 84 +++++++++++++++++++++++++++++++++++++++ verifier.cpp | 68 +++++++++++++++++++++++++++++++ verifier.h | 6 +++ 3 files changed, 158 insertions(+) diff --git a/tests/component/verifier_test.cpp b/tests/component/verifier_test.cpp index 3246ecdbc..c460cbe6f 100644 --- a/tests/component/verifier_test.cpp +++ b/tests/component/verifier_test.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include "common/test_constants.h" @@ -35,6 +36,89 @@ using namespace std::string_literals; +static void LoadKeyFromFile(const std::string& file_name, Certificate* cert) { + std::string testkey_string; + ASSERT_TRUE(android::base::ReadFileToString(file_name, &testkey_string)); + ASSERT_TRUE(LoadCertificateFromBuffer( + std::vector(testkey_string.begin(), testkey_string.end()), cert)); +} + +static void VerifyPackageWithCertificate(const std::string& name, Certificate&& cert) { + std::string package = from_testdata_base(name); + MemMapping memmap; + if (!memmap.MapFile(package)) { + FAIL() << "Failed to mmap " << package << ": " << strerror(errno) << "\n"; + } + + std::vector certs; + certs.emplace_back(std::move(cert)); + ASSERT_EQ(VERIFY_SUCCESS, verify_file(memmap.addr, memmap.length, certs)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_failure) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + std::string testkey_string; + ASSERT_TRUE( + android::base::ReadFileToString(from_testdata_base("testkey_v1.txt"), &testkey_string)); + ASSERT_FALSE(LoadCertificateFromBuffer( + std::vector(testkey_string.begin(), testkey_string.end()), &cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent3) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v1.x509.pem"), &cert); + + ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithCertificate("otasigned_v1.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha1_exponent65537) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v2.x509.pem"), &cert); + + ASSERT_EQ(SHA_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithCertificate("otasigned_v2.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent3) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v3.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithCertificate("otasigned_v3.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_exponent65537) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v4.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_RSA, cert.key_type); + ASSERT_EQ(nullptr, cert.ec); + + VerifyPackageWithCertificate("otasigned_v4.zip", std::move(cert)); +} + +TEST(VerifierTest, LoadCertificateFromBuffer_sha256_ec256bits) { + Certificate cert(0, Certificate::KEY_TYPE_RSA, nullptr, nullptr); + LoadKeyFromFile(from_testdata_base("testkey_v5.x509.pem"), &cert); + + ASSERT_EQ(SHA256_DIGEST_LENGTH, cert.hash_len); + ASSERT_EQ(Certificate::KEY_TYPE_EC, cert.key_type); + ASSERT_EQ(nullptr, cert.rsa); + + VerifyPackageWithCertificate("otasigned_v5.zip", std::move(cert)); +} + class VerifierTest : public testing::TestWithParam> { protected: void SetUp() override { diff --git a/verifier.cpp b/verifier.cpp index 283e04300..1dc52a0ef 100644 --- a/verifier.cpp +++ b/verifier.cpp @@ -27,9 +27,13 @@ #include #include +#include #include #include +#include #include +#include +#include #include "asn1_decoder.h" #include "otautil/print_sha1.h" @@ -441,6 +445,70 @@ std::unique_ptr parse_ec_key(FILE* file) { return key; } +bool LoadCertificateFromBuffer(const std::vector& pem_content, Certificate* cert) { + std::unique_ptr content( + BIO_new_mem_buf(pem_content.data(), pem_content.size()), BIO_free); + + std::unique_ptr x509( + PEM_read_bio_X509(content.get(), nullptr, nullptr, nullptr), X509_free); + if (!x509) { + LOG(ERROR) << "Failed to read x509 certificate"; + return false; + } + + int nid = X509_get_signature_nid(x509.get()); + switch (nid) { + // SignApk has historically accepted md5WithRSA certificates, but treated them as + // sha1WithRSA anyway. Continue to do so for backwards compatibility. + case NID_md5WithRSA: + case NID_md5WithRSAEncryption: + case NID_sha1WithRSA: + case NID_sha1WithRSAEncryption: + cert->hash_len = SHA_DIGEST_LENGTH; + break; + case NID_sha256WithRSAEncryption: + case NID_ecdsa_with_SHA256: + cert->hash_len = SHA256_DIGEST_LENGTH; + break; + default: + LOG(ERROR) << "Unrecognized signature nid " << OBJ_nid2ln(nid); + return false; + } + + std::unique_ptr public_key(X509_get_pubkey(x509.get()), + EVP_PKEY_free); + if (!public_key) { + LOG(ERROR) << "Failed to extract the public key from x509 certificate"; + return false; + } + + int key_type = EVP_PKEY_id(public_key.get()); + // TODO(xunchang) check the rsa key has exponent 3 or 65537 with RSA_get0_key; and ec key is + // 256 bits. + if (key_type == EVP_PKEY_RSA) { + cert->key_type = Certificate::KEY_TYPE_RSA; + cert->ec.reset(); + cert->rsa.reset(EVP_PKEY_get1_RSA(public_key.get())); + if (!cert->rsa) { + LOG(ERROR) << "Failed to get the rsa key info from public key"; + return false; + } + } else if (key_type == EVP_PKEY_EC) { + cert->key_type = Certificate::KEY_TYPE_EC; + cert->rsa.reset(); + cert->ec.reset(EVP_PKEY_get1_EC_KEY(public_key.get())); + if (!cert->ec) { + LOG(ERROR) << "Failed to get the ec key info from the public key"; + return false; + } + } else { + LOG(ERROR) << "Unrecognized public key type " << OBJ_nid2ln(key_type); + return false; + } + + return true; +} + // Reads a file containing one or more public keys as produced by // DumpPublicKey: this is an RSAPublicKey struct as it would appear // as a C source literal, eg: diff --git a/verifier.h b/verifier.h index 6fa8f2b0a..b13424126 100644 --- a/verifier.h +++ b/verifier.h @@ -17,6 +17,8 @@ #ifndef _RECOVERY_VERIFIER_H #define _RECOVERY_VERIFIER_H +#include + #include #include #include @@ -70,6 +72,10 @@ int verify_file(const unsigned char* addr, size_t length, const std::vector& certs); +// Parses a PEM-encoded x509 certificate from the given buffer and saves it into |cert|. Returns +// false if there is a parsing failure or the signature's encryption algorithm is not supported. +bool LoadCertificateFromBuffer(const std::vector& pem_content, Certificate* cert); + #define VERIFY_SUCCESS 0 #define VERIFY_FAILURE 1 -- cgit v1.2.3