Allow update engine read public keys from otacerts.zip am: 7a78d630e1 am: de863bc026
am: 5527f8ea8d
Change-Id: I230b4e3184d05d1d95088d0de5b0a462f06af973
diff --git a/Android.bp b/Android.bp
index e5e592c..a691e7e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -128,6 +128,7 @@
"libverity_tree",
],
shared_libs: [
+ "libziparchive",
"libbase",
"libcrypto",
"libfec",
@@ -164,6 +165,7 @@
"common/utils.cc",
"payload_consumer/bzip_extent_writer.cc",
"payload_consumer/cached_file_descriptor.cc",
+ "payload_consumer/certificate_parser_android.cc",
"payload_consumer/delta_performer.cc",
"payload_consumer/download_action.cc",
"payload_consumer/extent_reader.cc",
@@ -659,6 +661,7 @@
":ue_unittest_delta_generator",
":ue_unittest_disk_imgs",
":ue_unittest_keys",
+ "otacerts.zip",
"unittest_key.pem",
"unittest_key2.pem",
"unittest_key_RSA4096.pem",
@@ -693,6 +696,7 @@
"dynamic_partition_control_android_unittest.cc",
"payload_consumer/bzip_extent_writer_unittest.cc",
"payload_consumer/cached_file_descriptor_unittest.cc",
+ "payload_consumer/certificate_parser_android_unittest.cc",
"payload_consumer/delta_performer_integration_test.cc",
"payload_consumer/delta_performer_unittest.cc",
"payload_consumer/extent_reader_unittest.cc",
diff --git a/common/platform_constants.h b/common/platform_constants.h
index 6eaa940..243af69 100644
--- a/common/platform_constants.h
+++ b/common/platform_constants.h
@@ -38,6 +38,10 @@
// whole payload.
extern const char kUpdatePayloadPublicKeyPath[];
+// Path to the location of the zip archive file that contains PEM encoded X509
+// certificates. e.g. 'system/etc/security/otacerts.zip'.
+extern const char kUpdateCertificatesPath[];
+
// Path to the directory containing all the SSL certificates accepted by
// update_engine when sending requests to Omaha and the download server (if
// HTTPS is used for that as well).
diff --git a/common/platform_constants_android.cc b/common/platform_constants_android.cc
index 9d8d30e..f468c3b 100644
--- a/common/platform_constants_android.cc
+++ b/common/platform_constants_android.cc
@@ -25,8 +25,8 @@
"https://clients2.google.com/service/update2/brillo";
const char kOmahaUpdaterID[] = "Brillo";
const char kOmahaPlatformName[] = "Brillo";
-const char kUpdatePayloadPublicKeyPath[] =
- "/etc/update_engine/update-payload-key.pub.pem";
+const char kUpdatePayloadPublicKeyPath[] = "";
+const char kUpdateCertificatesPath[] = "/system/etc/security/otacerts.zip";
const char kCACertificatesPath[] = "/system/etc/security/cacerts_google";
// No deadline file API support on Android.
const char kOmahaResponseDeadlineFile[] = "";
diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc
index f1ac490..fe94a45 100644
--- a/common/platform_constants_chromeos.cc
+++ b/common/platform_constants_chromeos.cc
@@ -27,6 +27,7 @@
const char kOmahaPlatformName[] = "Chrome OS";
const char kUpdatePayloadPublicKeyPath[] =
"/usr/share/update_engine/update-payload-key.pub.pem";
+const char kUpdateCertificatesPath[] = "";
const char kCACertificatesPath[] = "/usr/share/chromeos-ca-certificates";
const char kOmahaResponseDeadlineFile[] = "/tmp/update-check-response-deadline";
// This directory is wiped during powerwash.
diff --git a/otacerts.zip b/otacerts.zip
new file mode 100644
index 0000000..00a5a51
--- /dev/null
+++ b/otacerts.zip
Binary files differ
diff --git a/payload_consumer/certificate_parser_android.cc b/payload_consumer/certificate_parser_android.cc
new file mode 100644
index 0000000..4a20547
--- /dev/null
+++ b/payload_consumer/certificate_parser_android.cc
@@ -0,0 +1,121 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/certificate_parser_android.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/logging.h>
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <ziparchive/zip_archive.h>
+
+#include "update_engine/payload_consumer/certificate_parser_interface.h"
+
+namespace {
+bool IterateZipEntriesAndSearchForKeys(
+ const ZipArchiveHandle& handle, std::vector<std::vector<uint8_t>>* result) {
+ void* cookie;
+ int32_t iter_status = StartIteration(handle, &cookie, "", "x509.pem");
+ if (iter_status != 0) {
+ LOG(ERROR) << "Failed to iterate over entries in the certificate zipfile: "
+ << ErrorCodeString(iter_status);
+ return false;
+ }
+ std::unique_ptr<void, decltype(&EndIteration)> guard(cookie, EndIteration);
+
+ std::vector<std::vector<uint8_t>> pem_keys;
+ std::string_view name;
+ ZipEntry entry;
+ while ((iter_status = Next(cookie, &entry, &name)) == 0) {
+ std::vector<uint8_t> pem_content(entry.uncompressed_length);
+ if (int32_t extract_status = ExtractToMemory(
+ handle, &entry, pem_content.data(), pem_content.size());
+ extract_status != 0) {
+ LOG(ERROR) << "Failed to extract " << name << ": "
+ << ErrorCodeString(extract_status);
+ return false;
+ }
+ pem_keys.push_back(pem_content);
+ }
+
+ if (iter_status != -1) {
+ LOG(ERROR) << "Error while iterating over zip entries: "
+ << ErrorCodeString(iter_status);
+ return false;
+ }
+
+ *result = std::move(pem_keys);
+ return true;
+}
+
+} // namespace
+
+namespace chromeos_update_engine {
+bool CertificateParserAndroid::ReadPublicKeysFromCertificates(
+ const std::string& path,
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>*
+ out_public_keys) {
+ out_public_keys->clear();
+
+ ZipArchiveHandle handle;
+ if (int32_t open_status = OpenArchive(path.c_str(), &handle);
+ open_status != 0) {
+ LOG(ERROR) << "Failed to open " << path << ": "
+ << ErrorCodeString(open_status);
+ return false;
+ }
+
+ std::vector<std::vector<uint8_t>> pem_certs;
+ if (!IterateZipEntriesAndSearchForKeys(handle, &pem_certs)) {
+ CloseArchive(handle);
+ return false;
+ }
+ CloseArchive(handle);
+
+ // Convert the certificates into public keys. Stop and return false if we
+ // encounter an error.
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> result;
+ for (const auto& cert : pem_certs) {
+ std::unique_ptr<BIO, decltype(&BIO_free)> input(
+ BIO_new_mem_buf(cert.data(), cert.size()), BIO_free);
+
+ std::unique_ptr<X509, decltype(&X509_free)> x509(
+ PEM_read_bio_X509(input.get(), nullptr, nullptr, nullptr), X509_free);
+ if (!x509) {
+ LOG(ERROR) << "Failed to read x509 certificate";
+ return false;
+ }
+
+ std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> 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;
+ }
+ result.push_back(std::move(public_key));
+ }
+
+ *out_public_keys = std::move(result);
+ return true;
+}
+
+std::unique_ptr<CertificateParserInterface> CreateCertificateParser() {
+ return std::make_unique<CertificateParserAndroid>();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/certificate_parser_android.h b/payload_consumer/certificate_parser_android.h
new file mode 100644
index 0000000..ccb9293
--- /dev/null
+++ b/payload_consumer/certificate_parser_android.h
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_ANDROID_H_
+#define UPDATE_ENGINE_CERTIFICATE_PARSER_ANDROID_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "payload_consumer/certificate_parser_interface.h"
+
+namespace chromeos_update_engine {
+// This class parses the certificates from a zip file. Because the Android
+// build system stores the certs in otacerts.zip.
+class CertificateParserAndroid : public CertificateParserInterface {
+ public:
+ CertificateParserAndroid() = default;
+
+ bool ReadPublicKeysFromCertificates(
+ const std::string& path,
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>*
+ out_public_keys) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CertificateParserAndroid);
+};
+
+} // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/certificate_parser_android_unittest.cc b/payload_consumer/certificate_parser_android_unittest.cc
new file mode 100644
index 0000000..e300414
--- /dev/null
+++ b/payload_consumer/certificate_parser_android_unittest.cc
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/payload_consumer/certificate_parser_interface.h"
+
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/hash_calculator.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
+#include "update_engine/payload_generator/payload_signer.h"
+
+namespace chromeos_update_engine {
+
+extern const char* kUnittestPrivateKeyPath;
+const char* kUnittestOtacertsPath = "otacerts.zip";
+
+TEST(CertificateParserAndroidTest, ParseZipArchive) {
+ std::string ota_cert =
+ test_utils::GetBuildArtifactsPath(kUnittestOtacertsPath);
+ ASSERT_TRUE(utils::FileExists(ota_cert.c_str()));
+
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> keys;
+ auto parser = CreateCertificateParser();
+ ASSERT_TRUE(parser->ReadPublicKeysFromCertificates(ota_cert, &keys));
+ ASSERT_EQ(1u, keys.size());
+}
+
+TEST(CertificateParserAndroidTest, VerifySignature) {
+ brillo::Blob hash_blob;
+ ASSERT_TRUE(HashCalculator::RawHashOfData({'x'}, &hash_blob));
+ brillo::Blob sig_blob;
+ ASSERT_TRUE(PayloadSigner::SignHash(
+ hash_blob,
+ test_utils::GetBuildArtifactsPath(kUnittestPrivateKeyPath),
+ &sig_blob));
+
+ auto verifier = PayloadVerifier::CreateInstanceFromZipPath(
+ test_utils::GetBuildArtifactsPath(kUnittestOtacertsPath));
+ ASSERT_TRUE(verifier != nullptr);
+ ASSERT_TRUE(verifier->VerifyRawSignature(sig_blob, hash_blob, nullptr));
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/certificate_parser_interface.h b/payload_consumer/certificate_parser_interface.h
new file mode 100644
index 0000000..dad23d2
--- /dev/null
+++ b/payload_consumer/certificate_parser_interface.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_INTERFACE_H_
+#define UPDATE_ENGINE_CERTIFICATE_PARSER_INTERFACE_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <openssl/pem.h>
+
+namespace chromeos_update_engine {
+
+// This class parses the PEM encoded X509 certificates from |path|; and
+// passes the parsed public keys to the caller.
+class CertificateParserInterface {
+ public:
+ virtual ~CertificateParserInterface() = default;
+
+ virtual bool ReadPublicKeysFromCertificates(
+ const std::string& path,
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>*
+ out_public_keys) = 0;
+};
+
+std::unique_ptr<CertificateParserInterface> CreateCertificateParser();
+
+} // namespace chromeos_update_engine
+
+#endif
diff --git a/payload_consumer/certificate_parser_stub.cc b/payload_consumer/certificate_parser_stub.cc
new file mode 100644
index 0000000..95fd6e8
--- /dev/null
+++ b/payload_consumer/certificate_parser_stub.cc
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <payload_consumer/certificate_parser_stub.h>
+
+namespace chromeos_update_engine {
+bool CertificateParserStub::ReadPublicKeysFromCertificates(
+ const std::string& path,
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>*
+ out_public_keys) {
+ return true;
+}
+
+std::unique_ptr<CertificateParserInterface> CreateCertificateParser() {
+ return std::make_unique<CertificateParserStub>();
+}
+
+} // namespace chromeos_update_engine
diff --git a/payload_consumer/certificate_parser_stub.h b/payload_consumer/certificate_parser_stub.h
new file mode 100644
index 0000000..f4f8825
--- /dev/null
+++ b/payload_consumer/certificate_parser_stub.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_
+#define UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+#include "payload_consumer/certificate_parser_interface.h"
+
+namespace chromeos_update_engine {
+class CertificateParserStub : public CertificateParserInterface {
+ public:
+ CertificateParserStub() = default;
+
+ bool ReadPublicKeysFromCertificates(
+ const std::string& path,
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>*
+ out_public_keys) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(CertificateParserStub);
+};
+
+} // namespace chromeos_update_engine
+
+#endif // UPDATE_ENGINE_CERTIFICATE_PARSER_STUB_H_
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 8049af7..4b80ae6 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -46,6 +46,7 @@
#include "update_engine/common/terminator.h"
#include "update_engine/payload_consumer/bzip_extent_writer.h"
#include "update_engine/payload_consumer/cached_file_descriptor.h"
+#include "update_engine/payload_consumer/certificate_parser_interface.h"
#include "update_engine/payload_consumer/download_action.h"
#include "update_engine/payload_consumer/extent_reader.h"
#include "update_engine/payload_consumer/extent_writer.h"
@@ -526,9 +527,10 @@
<< "Trusting metadata size in payload = " << metadata_size_;
}
- string public_key;
- if (!GetPublicKey(&public_key)) {
- LOG(ERROR) << "Failed to get public key.";
+ // Perform the verification unconditionally.
+ auto [payload_verifier, perform_verification] = CreatePayloadVerifier();
+ if (!payload_verifier) {
+ LOG(ERROR) << "Failed to create payload verifier.";
*error = ErrorCode::kDownloadMetadataSignatureVerificationError;
return MetadataParseResult::kError;
}
@@ -536,7 +538,7 @@
// We have the full metadata in |payload|. Verify its integrity
// and authenticity based on the information we have in Omaha response.
*error = payload_metadata_.ValidateMetadataSignature(
- payload, payload_->metadata_signature, public_key);
+ payload, payload_->metadata_signature, *payload_verifier);
if (*error != ErrorCode::kSuccess) {
if (install_plan_->hash_checks_mandatory) {
// The autoupdate_CatchBadSignatures test checks for this string
@@ -1596,10 +1598,32 @@
return brillo::data_encoding::Base64Decode(install_plan_->public_key_rsa,
out_public_key);
}
-
+ LOG(INFO) << "No public keys found for verification.";
return true;
}
+std::pair<std::unique_ptr<PayloadVerifier>, bool>
+DeltaPerformer::CreatePayloadVerifier() {
+ if (utils::FileExists(update_certificates_path_.c_str())) {
+ LOG(INFO) << "Verifying using certificates: " << update_certificates_path_;
+ return {
+ PayloadVerifier::CreateInstanceFromZipPath(update_certificates_path_),
+ true};
+ }
+
+ string public_key;
+ if (!GetPublicKey(&public_key)) {
+ LOG(ERROR) << "Failed to read public key";
+ return {nullptr, true};
+ }
+
+ // Skips the verification if the public key is empty.
+ if (public_key.empty()) {
+ return {nullptr, false};
+ }
+ return {PayloadVerifier::CreateInstance(public_key), true};
+}
+
ErrorCode DeltaPerformer::ValidateManifest() {
// Perform assorted checks to sanity check the manifest, make sure it
// matches data from other sources, and that it is a supported version.
@@ -1760,12 +1784,6 @@
ErrorCode DeltaPerformer::VerifyPayload(
const brillo::Blob& update_check_response_hash,
const uint64_t update_check_response_size) {
- string public_key;
- if (!GetPublicKey(&public_key)) {
- LOG(ERROR) << "Failed to get public key.";
- return ErrorCode::kDownloadPayloadPubKeyVerificationError;
- }
-
// Verifies the download size.
if (update_check_response_size !=
metadata_size_ + metadata_signature_size_ + buffer_offset_) {
@@ -1783,20 +1801,19 @@
ErrorCode::kPayloadHashMismatchError,
payload_hash_calculator_.raw_hash() == update_check_response_hash);
- // Verifies the signed payload hash.
- if (public_key.empty()) {
- LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
- return ErrorCode::kSuccess;
- }
TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
!signatures_message_data_.empty());
brillo::Blob hash_data = signed_hash_calculator_.raw_hash();
TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
hash_data.size() == kSHA256Size);
- auto payload_verifier = PayloadVerifier::CreateInstance(public_key);
+ auto [payload_verifier, perform_verification] = CreatePayloadVerifier();
+ if (!perform_verification) {
+ LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
+ return ErrorCode::kSuccess;
+ }
if (!payload_verifier) {
- LOG(ERROR) << "Failed to create the payload verifier from " << public_key;
+ LOG(ERROR) << "Failed to create the payload verifier.";
return ErrorCode::kDownloadPayloadPubKeyVerificationError;
}
if (!payload_verifier->VerifySignature(signatures_message_data_, hash_data)) {
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index 25c348c..4c64dfa 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -20,7 +20,9 @@
#include <inttypes.h>
#include <limits>
+#include <memory>
#include <string>
+#include <utility>
#include <vector>
#include <base/time/time.h>
@@ -34,6 +36,7 @@
#include "update_engine/payload_consumer/file_writer.h"
#include "update_engine/payload_consumer/install_plan.h"
#include "update_engine/payload_consumer/payload_metadata.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
@@ -156,6 +159,11 @@
public_key_path_ = public_key_path;
}
+ void set_update_certificates_path(
+ const std::string& update_certificates_path) {
+ update_certificates_path_ = update_certificates_path;
+ }
+
// Return true if header parsing is finished and no errors occurred.
bool IsHeaderParsed() const;
@@ -273,6 +281,12 @@
// |out_public_key|. Returns false on failures.
bool GetPublicKey(std::string* out_public_key);
+ // Creates a PayloadVerifier from the zip file containing certificates. If the
+ // path to the zip file doesn't exist, falls back to use the public key.
+ // Returns a tuple with the created PayloadVerifier and if we should perform
+ // the verification.
+ std::pair<std::unique_ptr<PayloadVerifier>, bool> CreatePayloadVerifier();
+
// After install_plan_ is filled with partition names and sizes, initialize
// metadata of partitions and map necessary devices before opening devices.
bool PreparePartitionsForUpdate();
@@ -383,6 +397,9 @@
// override with test keys.
std::string public_key_path_{constants::kUpdatePayloadPublicKeyPath};
+ // The path to the zip file with X509 certificates.
+ std::string update_certificates_path_{constants::kUpdateCertificatesPath};
+
// The number of bytes received so far, used for progress tracking.
size_t total_bytes_received_{0};
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
index 28c11b6..a2ad77b 100644
--- a/payload_consumer/delta_performer_integration_test.cc
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -777,6 +777,7 @@
: GetBuildArtifactsPath(kUnittestPublicKeyPath);
EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
(*performer)->set_public_key_path(public_key_path);
+ (*performer)->set_update_certificates_path("");
EXPECT_EQ(static_cast<off_t>(state->image_size),
HashCalculator::RawHashOfFile(
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index b7a38cc..e9022ba 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -159,6 +159,11 @@
install_plan_.target_slot = 1;
EXPECT_CALL(mock_delegate_, ShouldCancel(_))
.WillRepeatedly(testing::Return(false));
+ performer_.set_update_certificates_path("");
+ // Set the public key corresponding to the unittest private key.
+ string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath);
+ EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
+ performer_.set_public_key_path(public_key_path);
}
// Test helper placed where it can easily be friended from DeltaPerformer.
@@ -388,12 +393,6 @@
expected_error = ErrorCode::kSuccess;
}
- // Use the public key corresponding to the private key used above to
- // sign the metadata.
- string public_key_path = GetBuildArtifactsPath(kUnittestPublicKeyPath);
- EXPECT_TRUE(utils::FileExists(public_key_path.c_str()));
- performer_.set_public_key_path(public_key_path);
-
// Init actual_error with an invalid value so that we make sure
// ParsePayloadMetadata properly populates it in all cases.
actual_error = ErrorCode::kUmaReportedMax;
@@ -920,7 +919,6 @@
brillo::Blob payload_data = GeneratePayload(
{}, {}, true, kBrilloMajorPayloadVersion, kSourceMinorPayloadVersion);
install_plan_.hash_checks_mandatory = true;
- performer_.set_public_key_path(GetBuildArtifactsPath(kUnittestPublicKeyPath));
ErrorCode error;
EXPECT_EQ(MetadataParseResult::kSuccess,
performer_.ParsePayloadMetadata(payload_data, &error));
diff --git a/payload_consumer/payload_metadata.cc b/payload_consumer/payload_metadata.cc
index c81d3a9..0952646 100644
--- a/payload_consumer/payload_metadata.cc
+++ b/payload_consumer/payload_metadata.cc
@@ -159,7 +159,7 @@
ErrorCode PayloadMetadata::ValidateMetadataSignature(
const brillo::Blob& payload,
const string& metadata_signature,
- const string& pem_public_key) const {
+ const PayloadVerifier& payload_verifier) const {
if (payload.size() < metadata_size_ + metadata_signature_size_)
return ErrorCode::kDownloadMetadataSignatureError;
@@ -201,16 +201,9 @@
return ErrorCode::kDownloadMetadataSignatureVerificationError;
}
- auto payload_verifier = PayloadVerifier::CreateInstance(pem_public_key);
- if (!payload_verifier) {
- LOG(ERROR) << "Failed to create the payload verifier from "
- << pem_public_key;
- return ErrorCode::kDownloadMetadataSignatureVerificationError;
- }
-
if (!metadata_signature_blob.empty()) {
brillo::Blob decrypted_signature;
- if (!payload_verifier->VerifyRawSignature(
+ if (!payload_verifier.VerifyRawSignature(
metadata_signature_blob, metadata_hash, &decrypted_signature)) {
LOG(ERROR) << "Manifest hash verification failed. Decrypted hash = ";
utils::HexDumpVector(decrypted_signature);
@@ -219,8 +212,8 @@
return ErrorCode::kDownloadMetadataSignatureMismatch;
}
} else {
- if (!payload_verifier->VerifySignature(metadata_signature_protobuf,
- metadata_hash)) {
+ if (!payload_verifier.VerifySignature(metadata_signature_protobuf,
+ metadata_hash)) {
LOG(ERROR) << "Manifest hash verification failed.";
return ErrorCode::kDownloadMetadataSignatureMismatch;
}
diff --git a/payload_consumer/payload_metadata.h b/payload_consumer/payload_metadata.h
index 1b4c5c8..75ef8f9 100644
--- a/payload_consumer/payload_metadata.h
+++ b/payload_consumer/payload_metadata.h
@@ -27,6 +27,7 @@
#include "update_engine/common/error_code.h"
#include "update_engine/common/platform_constants.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
#include "update_engine/update_metadata.pb.h"
namespace chromeos_update_engine {
@@ -65,9 +66,10 @@
// metadata is parsed so that a man-in-the-middle attack on the SSL connection
// to the payload server doesn't exploit any vulnerability in the code that
// parses the protocol buffer.
- ErrorCode ValidateMetadataSignature(const brillo::Blob& payload,
- const std::string& metadata_signature,
- const std::string& pem_public_key) const;
+ ErrorCode ValidateMetadataSignature(
+ const brillo::Blob& payload,
+ const std::string& metadata_signature,
+ const PayloadVerifier& payload_verifier) const;
// Returns the major payload version. If the version was not yet parsed,
// returns zero.
diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc
index 02eeb76..24e337e 100644
--- a/payload_consumer/payload_verifier.cc
+++ b/payload_consumer/payload_verifier.cc
@@ -25,6 +25,7 @@
#include "update_engine/common/constants.h"
#include "update_engine/common/hash_calculator.h"
#include "update_engine/common/utils.h"
+#include "update_engine/payload_consumer/certificate_parser_interface.h"
#include "update_engine/update_metadata.pb.h"
using std::string;
@@ -63,17 +64,39 @@
auto pub_key = std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>(
PEM_read_bio_PUBKEY(bp.get(), nullptr, nullptr, nullptr), EVP_PKEY_free);
if (!pub_key) {
- LOG(ERROR) << "Failed to parse the public key in " << pem_public_key;
+ LOG(ERROR) << "Failed to parse the public key in: " << pem_public_key;
+ return nullptr;
+ }
+
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> keys;
+ keys.emplace_back(std::move(pub_key));
+ return std::unique_ptr<PayloadVerifier>(new PayloadVerifier(std::move(keys)));
+}
+
+std::unique_ptr<PayloadVerifier> PayloadVerifier::CreateInstanceFromZipPath(
+ const std::string& certificate_zip_path) {
+ auto parser = CreateCertificateParser();
+ if (!parser) {
+ LOG(ERROR) << "Failed to create certificate parser from "
+ << certificate_zip_path;
+ return nullptr;
+ }
+
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> public_keys;
+ if (!parser->ReadPublicKeysFromCertificates(certificate_zip_path,
+ &public_keys) ||
+ public_keys.empty()) {
+ LOG(ERROR) << "Failed to parse public keys in: " << certificate_zip_path;
return nullptr;
}
return std::unique_ptr<PayloadVerifier>(
- new PayloadVerifier(std::move(pub_key)));
+ new PayloadVerifier(std::move(public_keys)));
}
bool PayloadVerifier::VerifySignature(
const string& signature_proto, const brillo::Blob& sha256_hash_data) const {
- TEST_AND_RETURN_FALSE(public_key_ != nullptr);
+ TEST_AND_RETURN_FALSE(!public_keys_.empty());
Signatures signatures;
LOG(INFO) << "signature blob size = " << signature_proto.size();
@@ -125,37 +148,50 @@
const brillo::Blob& sig_data,
const brillo::Blob& sha256_hash_data,
brillo::Blob* decrypted_sig_data) const {
- TEST_AND_RETURN_FALSE(public_key_ != nullptr);
+ TEST_AND_RETURN_FALSE(!public_keys_.empty());
- int key_type = EVP_PKEY_id(public_key_.get());
- if (key_type == EVP_PKEY_RSA) {
- brillo::Blob sig_hash_data;
- TEST_AND_RETURN_FALSE(
- GetRawHashFromSignature(sig_data, public_key_.get(), &sig_hash_data));
+ for (const auto& public_key : public_keys_) {
+ int key_type = EVP_PKEY_id(public_key.get());
+ if (key_type == EVP_PKEY_RSA) {
+ brillo::Blob sig_hash_data;
+ if (!GetRawHashFromSignature(
+ sig_data, public_key.get(), &sig_hash_data)) {
+ LOG(WARNING)
+ << "Failed to get the raw hash with RSA key. Trying other keys.";
+ continue;
+ }
- if (decrypted_sig_data != nullptr) {
- *decrypted_sig_data = sig_hash_data;
+ if (decrypted_sig_data != nullptr) {
+ *decrypted_sig_data = sig_hash_data;
+ }
+
+ brillo::Blob padded_hash_data = sha256_hash_data;
+ TEST_AND_RETURN_FALSE(
+ PadRSASHA256Hash(&padded_hash_data, sig_hash_data.size()));
+
+ if (padded_hash_data == sig_hash_data) {
+ return true;
+ }
}
- brillo::Blob padded_hash_data = sha256_hash_data;
- TEST_AND_RETURN_FALSE(
- PadRSASHA256Hash(&padded_hash_data, sig_hash_data.size()));
+ if (key_type == EVP_PKEY_EC) {
+ EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(public_key.get());
+ TEST_AND_RETURN_FALSE(ec_key != nullptr);
+ if (ECDSA_verify(0,
+ sha256_hash_data.data(),
+ sha256_hash_data.size(),
+ sig_data.data(),
+ sig_data.size(),
+ ec_key) == 1) {
+ return true;
+ }
+ }
- return padded_hash_data == sig_hash_data;
+ LOG(ERROR) << "Unsupported key type " << key_type;
+ return false;
}
-
- if (key_type == EVP_PKEY_EC) {
- EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(public_key_.get());
- TEST_AND_RETURN_FALSE(ec_key != nullptr);
- return ECDSA_verify(0,
- sha256_hash_data.data(),
- sha256_hash_data.size(),
- sig_data.data(),
- sig_data.size(),
- ec_key) == 1;
- }
-
- LOG(ERROR) << "Unsupported key type " << key_type;
+ LOG(INFO) << "Failed to verify the signature with " << public_keys_.size()
+ << " keys.";
return false;
}
diff --git a/payload_consumer/payload_verifier.h b/payload_consumer/payload_verifier.h
index b5d5457..bc5231f 100644
--- a/payload_consumer/payload_verifier.h
+++ b/payload_consumer/payload_verifier.h
@@ -20,13 +20,14 @@
#include <memory>
#include <string>
#include <utility>
+#include <vector>
#include <brillo/secure_blob.h>
#include <openssl/evp.h>
#include "update_engine/update_metadata.pb.h"
-// This class holds the public key and implements methods used for payload
+// This class holds the public keys and implements methods used for payload
// signature verification. See payload_generator/payload_signer.h for payload
// signing.
@@ -47,6 +48,11 @@
static std::unique_ptr<PayloadVerifier> CreateInstance(
const std::string& pem_public_key);
+ // Extracts the public keys from the certificates contained in the input
+ // zip file. And creates a PayloadVerifier with these public keys.
+ static std::unique_ptr<PayloadVerifier> CreateInstanceFromZipPath(
+ const std::string& certificate_zip_path);
+
// Interprets |signature_proto| as a protocol buffer containing the
// |Signatures| message and decrypts each signature data using the stored
// public key. Pads the 32 bytes |sha256_hash_data| to 256 or 512 bytes
@@ -65,8 +71,9 @@
private:
explicit PayloadVerifier(
- std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>&& public_key)
- : public_key_(std::move(public_key)) {}
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>>&&
+ public_keys)
+ : public_keys_(std::move(public_keys)) {}
// Decrypts |sig_data| with the given |public_key| and populates
// |out_hash_data| with the decoded raw hash. Returns true if successful,
@@ -75,8 +82,7 @@
const EVP_PKEY* public_key,
brillo::Blob* out_hash_data) const;
- std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key_{nullptr,
- nullptr};
+ std::vector<std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>> public_keys_;
};
} // namespace chromeos_update_engine
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 08f6c20..5bffc42 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -39,6 +39,7 @@
#include "update_engine/metrics_reporter_interface.h"
#include "update_engine/metrics_utils.h"
#include "update_engine/network_selector.h"
+#include "update_engine/payload_consumer/certificate_parser_interface.h"
#include "update_engine/payload_consumer/delta_performer.h"
#include "update_engine/payload_consumer/download_action.h"
#include "update_engine/payload_consumer/file_descriptor.h"
@@ -46,6 +47,7 @@
#include "update_engine/payload_consumer/filesystem_verifier_action.h"
#include "update_engine/payload_consumer/payload_constants.h"
#include "update_engine/payload_consumer/payload_metadata.h"
+#include "update_engine/payload_consumer/payload_verifier.h"
#include "update_engine/payload_consumer/postinstall_runner_action.h"
#include "update_engine/update_boot_flags_action.h"
#include "update_engine/update_status_utils.h"
@@ -410,12 +412,16 @@
}
fd->Close();
- string public_key;
- if (!utils::ReadFile(constants::kUpdatePayloadPublicKeyPath, &public_key)) {
- return LogAndSetError(error, FROM_HERE, "Failed to read public key.");
+ auto payload_verifier = PayloadVerifier::CreateInstanceFromZipPath(
+ constants::kUpdateCertificatesPath);
+ if (!payload_verifier) {
+ return LogAndSetError(error,
+ FROM_HERE,
+ "Failed to create the payload verifier from " +
+ std::string(constants::kUpdateCertificatesPath));
}
- errorcode =
- payload_metadata.ValidateMetadataSignature(metadata, "", public_key);
+ errorcode = payload_metadata.ValidateMetadataSignature(
+ metadata, "", *payload_verifier);
if (errorcode != ErrorCode::kSuccess) {
return LogAndSetError(error,
FROM_HERE,