apkverify: verify cert/public key match

Bug: 190343842
Test: atest libapkverify.integration_test
Change-Id: If7da64ea6ff3f5fc25d204cbcf30d22122ee45c7
diff --git a/apkverify/src/v3.rs b/apkverify/src/v3.rs
index 0ef035c..5ec3d07 100644
--- a/apkverify/src/v3.rs
+++ b/apkverify/src/v3.rs
@@ -19,12 +19,13 @@
 // TODO(jooyung) remove this
 #![allow(dead_code)]
 
-use anyhow::{anyhow, bail, Result};
+use anyhow::{anyhow, bail, Context, Result};
 use bytes::Bytes;
 use std::fs::File;
 use std::io::{Read, Seek};
 use std::ops::Range;
 use std::path::Path;
+use x509_parser::x509;
 
 use crate::bytes_ext::{BytesExt, LengthPrefixed, ReadFromBytes};
 use crate::sigutil::*;
@@ -127,7 +128,8 @@
 
         // 2. Verify the corresponding signature from signatures against signed data using public key.
         //    (It is now safe to parse signed data.)
-        verify_signed_data(&self.signed_data, strongest, &self.public_key)?;
+        let (_, key_info) = x509::SubjectPublicKeyInfo::from_der(self.public_key.as_ref())?;
+        verify_signed_data(&self.signed_data, strongest, &key_info)?;
 
         // It is now safe to parse signed data.
         let signed_data: SignedData = self.signed_data.slice(..).read()?;
@@ -161,13 +163,20 @@
         // 6. Verify that the computed digest is identical to the corresponding digest from digests.
         if computed != digest.digest.as_ref() {
             bail!(
-                "digest mismatch: computed={:?} vs expected={:?}",
+                "Digest mismatch: computed={:?} vs expected={:?}",
                 to_hex_string(&computed),
                 to_hex_string(&digest.digest),
             );
         }
 
-        // TODO(jooyung) 7. Verify that SubjectPublicKeyInfo of the first certificate of certificates is identical to public key.
+        // 7. Verify that SubjectPublicKeyInfo of the first certificate of certificates is identical
+        //    to public key.
+        let cert = signed_data.certificates.first().context("No certificates listed")?;
+        let (_, cert) = x509_parser::parse_x509_certificate(cert.as_ref())?;
+        if cert.tbs_certificate.subject_pki != key_info {
+            bail!("Public key mismatch between certificate and signature record");
+        }
+
         // TODO(jooyung) 8. If the proof-of-rotation attribute exists for the signer verify that the struct is valid and this signer is the last certificate in the list.
         Ok(())
     }
@@ -176,10 +185,9 @@
 fn verify_signed_data(
     data: &Bytes,
     signature: &Signature,
-    public_key: &SubjectPublicKeyInfo,
+    key_info: &x509::SubjectPublicKeyInfo,
 ) -> Result<()> {
     use ring::signature;
-    let (_, key_info) = x509_parser::x509::SubjectPublicKeyInfo::from_der(public_key.as_ref())?;
     let verification_alg: &dyn signature::VerificationAlgorithm =
         match signature.signature_algorithm_id {
             SIGNATURE_RSA_PSS_WITH_SHA256 => &signature::RSA_PSS_2048_8192_SHA256,
@@ -202,7 +210,7 @@
             }
             _ => bail!("Unsupported signature algorithm: {:#x}", signature.signature_algorithm_id),
         };
-    let key = signature::UnparsedPublicKey::new(verification_alg, key_info.subject_public_key.data);
+    let key = signature::UnparsedPublicKey::new(verification_alg, &key_info.subject_public_key);
     key.verify(data.as_ref(), signature.signature.as_ref())?;
     Ok(())
 }
diff --git a/apkverify/tests/apkverify_test.rs b/apkverify/tests/apkverify_test.rs
index b40c435..cad5ef2 100644
--- a/apkverify/tests/apkverify_test.rs
+++ b/apkverify/tests/apkverify_test.rs
@@ -40,5 +40,12 @@
 fn test_verify_v3_digest_mismatch() {
     let res = verify("tests/data/v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk");
     assert!(res.is_err());
-    assert_contains!(res.err().unwrap().to_string(), "digest mismatch");
+    assert_contains!(res.err().unwrap().to_string(), "Digest mismatch");
+}
+
+#[test]
+fn test_verify_v3_cert_and_publick_key_mismatch() {
+    let res = verify("tests/data/v3-only-cert-and-public-key-mismatch.apk");
+    assert!(res.is_err());
+    assert_contains!(res.err().unwrap().to_string(), "Public key mismatch");
 }
diff --git a/apkverify/tests/data/README.md b/apkverify/tests/data/README.md
index 581a522..953ecdb 100644
--- a/apkverify/tests/data/README.md
+++ b/apkverify/tests/data/README.md
@@ -12,4 +12,5 @@
 ```
 
 Some test APKs are copied from tools/apksig/src/test/resources/com/android/apksig/.
+- v3-only-cert-and-public-key-mismatch.apk
 - v3-only-with-rsa-pkcs1-sha512-8192-digest-mismatch.apk
diff --git a/apkverify/tests/data/v3-only-cert-and-public-key-mismatch.apk b/apkverify/tests/data/v3-only-cert-and-public-key-mismatch.apk
new file mode 100644
index 0000000..2291e7e
--- /dev/null
+++ b/apkverify/tests/data/v3-only-cert-and-public-key-mismatch.apk
Binary files differ