Factor out the RSA verification in payload verifier

Right now, the payload is always signed with a RSA key, and the payload
verifier takes the public key as a PEM encoded string.

As we want to support payload signing with EC keys, we need to figure
out the key types first in the verifier. So, add an overload function in
payload verifier to accept EVP_PKEY as the public key.

Bug: 141244025
Test: unittests pass
Change-Id: Ibbdac5a7a3de48347100861aeac0013bff43da6f
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 3ff98ca..8049af7 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -1794,8 +1794,12 @@
   TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadPubKeyVerificationError,
                       hash_data.size() == kSHA256Size);
 
-  if (!PayloadVerifier::VerifySignature(
-          signatures_message_data_, public_key, hash_data)) {
+  auto payload_verifier = PayloadVerifier::CreateInstance(public_key);
+  if (!payload_verifier) {
+    LOG(ERROR) << "Failed to create the payload verifier from " << public_key;
+    return ErrorCode::kDownloadPayloadPubKeyVerificationError;
+  }
+  if (!payload_verifier->VerifySignature(signatures_message_data_, hash_data)) {
     // The autoupdate_CatchBadSignatures test checks for this string
     // in log-files. Keep in sync.
     LOG(ERROR) << "Public key verification failed, thus update failed.";
diff --git a/payload_consumer/payload_metadata.cc b/payload_consumer/payload_metadata.cc
index 3739767..c81d3a9 100644
--- a/payload_consumer/payload_metadata.cc
+++ b/payload_consumer/payload_metadata.cc
@@ -201,32 +201,26 @@
     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 expected_metadata_hash;
-    if (!PayloadVerifier::GetRawHashFromSignature(
-            metadata_signature_blob, pem_public_key, &expected_metadata_hash)) {
-      LOG(ERROR) << "Unable to compute expected hash from metadata signature";
-      return ErrorCode::kDownloadMetadataSignatureError;
-    }
-
-    brillo::Blob padded_metadata_hash = metadata_hash;
-    if (!PayloadVerifier::PadRSASHA256Hash(&padded_metadata_hash,
-                                           expected_metadata_hash.size())) {
-      LOG(ERROR) << "Failed to pad the SHA256 hash to "
-                 << expected_metadata_hash.size() << " bytes.";
-      return ErrorCode::kDownloadMetadataSignatureVerificationError;
-    }
-
-    if (padded_metadata_hash != expected_metadata_hash) {
-      LOG(ERROR) << "Manifest hash verification failed. Expected hash = ";
-      utils::HexDumpVector(expected_metadata_hash);
-      LOG(ERROR) << "Calculated hash = ";
-      utils::HexDumpVector(padded_metadata_hash);
+    brillo::Blob decrypted_signature;
+    if (!payload_verifier->VerifyRawSignature(
+            metadata_signature_blob, metadata_hash, &decrypted_signature)) {
+      LOG(ERROR) << "Manifest hash verification failed. Decrypted hash = ";
+      utils::HexDumpVector(decrypted_signature);
+      LOG(ERROR) << "Calculated hash before padding = ";
+      utils::HexDumpVector(metadata_hash);
       return ErrorCode::kDownloadMetadataSignatureMismatch;
     }
   } else {
-    if (!PayloadVerifier::VerifySignature(
-            metadata_signature_protobuf, pem_public_key, 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_verifier.cc b/payload_consumer/payload_verifier.cc
index 3a3ccbf..b2c5be4 100644
--- a/payload_consumer/payload_verifier.cc
+++ b/payload_consumer/payload_verifier.cc
@@ -51,9 +51,30 @@
 
 }  // namespace
 
-bool PayloadVerifier::VerifySignature(const string& signature_proto,
-                                      const string& pem_public_key,
-                                      const brillo::Blob& sha256_hash_data) {
+std::unique_ptr<PayloadVerifier> PayloadVerifier::CreateInstance(
+    const std::string& pem_public_key) {
+  std::unique_ptr<BIO, decltype(&BIO_free)> bp(
+      BIO_new_mem_buf(pem_public_key.data(), pem_public_key.size()), BIO_free);
+  if (!bp) {
+    LOG(ERROR) << "Failed to read " << pem_public_key << " into buffer.";
+    return nullptr;
+  }
+
+  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;
+    return nullptr;
+  }
+
+  return std::unique_ptr<PayloadVerifier>(
+      new PayloadVerifier(std::move(pub_key)));
+}
+
+bool PayloadVerifier::VerifySignature(
+    const string& signature_proto, const brillo::Blob& sha256_hash_data) const {
+  TEST_AND_RETURN_FALSE(public_key_ != nullptr);
+
   Signatures signatures;
   LOG(INFO) << "signature blob size = " << signature_proto.size();
   TEST_AND_RETURN_FALSE(signatures.ParseFromString(signature_proto));
@@ -69,17 +90,14 @@
     const Signatures::Signature& signature = signatures.signatures(i);
     brillo::Blob sig_data(signature.data().begin(), signature.data().end());
     brillo::Blob sig_hash_data;
-    if (!GetRawHashFromSignature(sig_data, pem_public_key, &sig_hash_data))
-      continue;
-
-    brillo::Blob padded_hash_data = sha256_hash_data;
-    if (PadRSASHA256Hash(&padded_hash_data, sig_hash_data.size()) &&
-        padded_hash_data == sig_hash_data) {
+    if (VerifyRawSignature(sig_data, sha256_hash_data, &sig_hash_data)) {
       LOG(INFO) << "Verified correct signature " << i + 1 << " out of "
                 << signatures.signatures_size() << " signatures.";
       return true;
     }
-    tested_hashes.push_back(sig_hash_data);
+    if (!sig_hash_data.empty()) {
+      tested_hashes.push_back(sig_hash_data);
+    }
   }
   LOG(ERROR) << "None of the " << signatures.signatures_size()
              << " signatures is correct. Expected hash before padding:";
@@ -91,24 +109,43 @@
   return false;
 }
 
-bool PayloadVerifier::GetRawHashFromSignature(const brillo::Blob& sig_data,
-                                              const string& pem_public_key,
-                                              brillo::Blob* out_hash_data) {
+bool PayloadVerifier::VerifyRawSignature(
+    const brillo::Blob& sig_data,
+    const brillo::Blob& sha256_hash_data,
+    brillo::Blob* decrypted_sig_data) const {
+  TEST_AND_RETURN_FALSE(public_key_ != nullptr);
+
+  int key_type = EVP_PKEY_id(public_key_.get());
+  TEST_AND_RETURN_FALSE(key_type == EVP_PKEY_RSA);
+  brillo::Blob sig_hash_data;
+  TEST_AND_RETURN_FALSE(
+      GetRawHashFromSignature(sig_data, public_key_.get(), &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()));
+
+  return padded_hash_data == sig_hash_data;
+}
+
+bool PayloadVerifier::GetRawHashFromSignature(
+    const brillo::Blob& sig_data,
+    const EVP_PKEY* public_key,
+    brillo::Blob* out_hash_data) const {
   // The code below executes the equivalent of:
   //
-  // openssl rsautl -verify -pubin -inkey <(echo |pem_public_key|)
+  // openssl rsautl -verify -pubin -inkey <(echo pem_public_key)
   //   -in |sig_data| -out |out_hash_data|
-
-  BIO* bp = BIO_new_mem_buf(pem_public_key.data(), pem_public_key.size());
-  char dummy_password[] = {' ', 0};  // Ensure no password is read from stdin.
-  RSA* rsa = PEM_read_bio_RSA_PUBKEY(bp, nullptr, nullptr, dummy_password);
-  BIO_free(bp);
+  RSA* rsa = EVP_PKEY_get0_RSA(public_key);
 
   TEST_AND_RETURN_FALSE(rsa != nullptr);
   unsigned int keysize = RSA_size(rsa);
   if (sig_data.size() > 2 * keysize) {
     LOG(ERROR) << "Signature size is too big for public key size.";
-    RSA_free(rsa);
     return false;
   }
 
@@ -116,7 +153,6 @@
   brillo::Blob hash_data(keysize);
   int decrypt_size = RSA_public_decrypt(
       sig_data.size(), sig_data.data(), hash_data.data(), rsa, RSA_NO_PADDING);
-  RSA_free(rsa);
   TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
                         decrypt_size <= static_cast<int>(hash_data.size()));
   hash_data.resize(decrypt_size);
diff --git a/payload_consumer/payload_verifier.h b/payload_consumer/payload_verifier.h
index af8e05f..b5d5457 100644
--- a/payload_consumer/payload_verifier.h
+++ b/payload_consumer/payload_verifier.h
@@ -17,38 +17,23 @@
 #ifndef UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
 #define UPDATE_ENGINE_PAYLOAD_CONSUMER_PAYLOAD_VERIFIER_H_
 
+#include <memory>
 #include <string>
+#include <utility>
 
-#include <base/macros.h>
 #include <brillo/secure_blob.h>
+#include <openssl/evp.h>
 
 #include "update_engine/update_metadata.pb.h"
 
-// This class encapsulates methods used for payload signature verification.
-// See payload_generator/payload_signer.h for payload signing.
+// This class holds the public key and implements methods used for payload
+// signature verification. See payload_generator/payload_signer.h for payload
+// signing.
 
 namespace chromeos_update_engine {
 
 class PayloadVerifier {
  public:
-  // Interprets |signature_proto| as a protocol buffer containing the Signatures
-  // message and decrypts each signature data using the |pem_public_key|.
-  // |pem_public_key| should be a PEM format RSA public key data.
-  // Pads the 32 bytes |sha256_hash_data| to 256 or 512 bytes according to the
-  // PKCS#1 v1.5 standard; and returns whether *any* of the decrypted hashes
-  // matches the padded hash data. In case of any error parsing the signatures
-  // or the public key, returns false.
-  static bool VerifySignature(const std::string& signature_proto,
-                              const std::string& pem_public_key,
-                              const brillo::Blob& sha256_hash_data);
-
-  // Decrypts |sig_data| with the given |pem_public_key| and populates
-  // |out_hash_data| with the decoded raw hash. |pem_public_key| should be a PEM
-  // format RSA public key data. Returns true if successful, false otherwise.
-  static bool GetRawHashFromSignature(const brillo::Blob& sig_data,
-                                      const std::string& pem_public_key,
-                                      brillo::Blob* out_hash_data);
-
   // Pads a SHA256 hash so that it may be encrypted/signed with RSA2048 or
   // RSA4096 using the PKCS#1 v1.5 scheme.
   // hash should be a pointer to vector of exactly 256 bits. |rsa_size| must be
@@ -57,9 +42,41 @@
   // Returns true on success, false otherwise.
   static bool PadRSASHA256Hash(brillo::Blob* hash, size_t rsa_size);
 
+  // Parses the input as a PEM encoded public string. And creates a
+  // PayloadVerifier with that public key for signature verification.
+  static std::unique_ptr<PayloadVerifier> CreateInstance(
+      const std::string& pem_public_key);
+
+  // 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
+  // according to the PKCS#1 v1.5 standard; and returns whether *any* of the
+  // decrypted hashes matches the padded hash data. In case of any error parsing
+  // the signatures, returns false.
+  bool VerifySignature(const std::string& signature_proto,
+                       const brillo::Blob& sha256_hash_data) const;
+
+  // Verifies if |sig_data| is a raw signature of the hash |sha256_hash_data|.
+  // If PayloadVerifier is using RSA as the public key, further puts the
+  // decrypted data of |sig_data| into |decrypted_sig_data|.
+  bool VerifyRawSignature(const brillo::Blob& sig_data,
+                          const brillo::Blob& sha256_hash_data,
+                          brillo::Blob* decrypted_sig_data) const;
+
  private:
-  // This should never be constructed
-  DISALLOW_IMPLICIT_CONSTRUCTORS(PayloadVerifier);
+  explicit PayloadVerifier(
+      std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)>&& public_key)
+      : public_key_(std::move(public_key)) {}
+
+  // Decrypts |sig_data| with the given |public_key| and populates
+  // |out_hash_data| with the decoded raw hash. Returns true if successful,
+  // false otherwise.
+  bool GetRawHashFromSignature(const brillo::Blob& sig_data,
+                               const EVP_PKEY* public_key,
+                               brillo::Blob* out_hash_data) const;
+
+  std::unique_ptr<EVP_PKEY, decltype(&EVP_PKEY_free)> public_key_{nullptr,
+                                                                  nullptr};
 };
 
 }  // namespace chromeos_update_engine