Merge "Move back to openssl::X509 in tests" into main
diff --git a/fsverity/fsverity_manifest_generator.py b/fsverity/fsverity_manifest_generator.py
index ca7ac5c..1a2fba2 100644
--- a/fsverity/fsverity_manifest_generator.py
+++ b/fsverity/fsverity_manifest_generator.py
@@ -31,7 +31,7 @@
   cmd = [fsverity_path, 'digest', input_file]
   cmd.extend(['--compact'])
   cmd.extend(['--hash-alg', HASH_ALGORITHM])
-  out = subprocess.check_output(cmd, universal_newlines=True).strip()
+  out = subprocess.check_output(cmd, text=True).strip()
   return bytes(bytearray.fromhex(out))
 
 if __name__ == '__main__':
@@ -46,20 +46,46 @@
       required=True)
   p.add_argument(
       '--base-dir',
-      help='directory to use as a relative root for the inputs',
-      required=True)
+      help='directory to use as a relative root for the inputs. Also see the documentation of '
+      'inputs')
   p.add_argument(
       'inputs',
       nargs='*',
-      help='input file for the build manifest')
+      help='input file for the build manifest. It can be in either of two forms: <file> or '
+      '<file>,<path_on_device>. If the first form is used, --base-dir must be provided, and the '
+      'path on device will be the filepath relative to the base dir')
   args = p.parse_args()
 
+  links = {}
   digests = FSVerityDigests()
   for f in sorted(args.inputs):
-    # f is a full path for now; make it relative so it starts with {mount_point}/
-    digest = digests.digests[os.path.relpath(f, args.base_dir)]
-    digest.digest = _digest(args.fsverity_path, f)
-    digest.hash_alg = HASH_ALGORITHM
+    if args.base_dir:
+      # f is a full path for now; make it relative so it starts with {mount_point}/
+      rel = os.path.relpath(f, args.base_dir)
+    else:
+      parts = f.split(',')
+      if len(parts) != 2 or not parts[0] or not parts[1]:
+        sys.exit("Since --base-path wasn't provided, all inputs must be pairs separated by commas "
+          "but this input wasn't: " + f)
+      f, rel = parts
+
+    # Some fsv_meta files are links to other ones. Don't read through the link, because the
+    # layout of files in the build system may not match the layout of files on the device.
+    # Instead, read its target and use it to copy the digest from the real file after all files
+    # are processed.
+    if os.path.islink(f):
+      links[rel] = os.path.normpath(os.path.join(os.path.dirname(rel), os.readlink(f)))
+    else:
+      digest = digests.digests[rel]
+      digest.digest = _digest(args.fsverity_path, f)
+      digest.hash_alg = HASH_ALGORITHM
+
+  for link_rel, real_rel in links.items():
+    if real_rel not in digests.digests:
+      sys.exit(f'There was a fsv_meta symlink to {real_rel}, but that file was not a fsv_meta file')
+    link_digest = digests.digests[link_rel]
+    real_digest = digests.digests[real_rel]
+    link_digest.CopyFrom(real_digest)
 
   manifest = digests.SerializeToString()
 
diff --git a/keystore2/postprocessor_client/src/lib.rs b/keystore2/postprocessor_client/src/lib.rs
index 8b347f9..beeb5f5 100644
--- a/keystore2/postprocessor_client/src/lib.rs
+++ b/keystore2/postprocessor_client/src/lib.rs
@@ -77,7 +77,7 @@
             ]
         }
         Err(err) => {
-            error!("Failed to replace certificates ({err:#?}), falling back to original chain.");
+            warn!("Failed to replace certificates ({err:#?}), falling back to original chain.");
             certificates.push(Certificate { encodedCertificate: attestation_certs });
             certificates
         }
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 72bbfe2..30c5973 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -48,6 +48,9 @@
 use std::collections::HashMap;
 use std::sync::{LazyLock, Mutex};
 
+#[cfg(test)]
+mod tests;
+
 // Note: Crash events are recorded at keystore restarts, based on the assumption that keystore only
 // gets restarted after a crash, during a boot cycle.
 const KEYSTORE_CRASH_COUNT_PROPERTY: &str = "keystore.crash_count";
@@ -980,31 +983,3 @@
         }
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_enum_show() {
-        let algo = MetricsAlgorithm::RSA;
-        assert_eq!("RSA ", algo.show());
-        let algo = MetricsAlgorithm(42);
-        assert_eq!("Unknown(42)", algo.show());
-    }
-
-    #[test]
-    fn test_enum_bitmask_show() {
-        let mut modes = 0i32;
-        compute_block_mode_bitmap(&mut modes, BlockMode::ECB);
-        compute_block_mode_bitmap(&mut modes, BlockMode::CTR);
-
-        assert_eq!(show_blockmode(modes), "-T-E");
-
-        // Add some bits not covered by the enum of valid bit positions.
-        modes |= 0xa0;
-        assert_eq!(show_blockmode(modes), "-T-E(full:0x000000aa)");
-        modes |= 0x300;
-        assert_eq!(show_blockmode(modes), "-T-E(full:0x000003aa)");
-    }
-}
diff --git a/keystore2/src/metrics_store/tests.rs b/keystore2/src/metrics_store/tests.rs
new file mode 100644
index 0000000..95d4a01
--- /dev/null
+++ b/keystore2/src/metrics_store/tests.rs
@@ -0,0 +1,160 @@
+// Copyright 2020, 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.
+
+use crate::metrics_store::*;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+    HardwareAuthenticatorType::HardwareAuthenticatorType as AuthType, KeyParameter::KeyParameter,
+    KeyParameterValue::KeyParameterValue, SecurityLevel::SecurityLevel, Tag::Tag,
+};
+use android_security_metrics::aidl::android::security::metrics::{
+    HardwareAuthenticatorType::HardwareAuthenticatorType as MetricsAuthType,
+    SecurityLevel::SecurityLevel as MetricsSecurityLevel,
+};
+
+#[test]
+fn test_enum_show() {
+    let algo = MetricsAlgorithm::RSA;
+    assert_eq!("RSA ", algo.show());
+    let algo = MetricsAlgorithm(42);
+    assert_eq!("Unknown(42)", algo.show());
+}
+
+#[test]
+fn test_enum_bitmask_show() {
+    let mut modes = 0i32;
+    compute_block_mode_bitmap(&mut modes, BlockMode::ECB);
+    compute_block_mode_bitmap(&mut modes, BlockMode::CTR);
+
+    assert_eq!(show_blockmode(modes), "-T-E");
+
+    // Add some bits not covered by the enum of valid bit positions.
+    modes |= 0xa0;
+    assert_eq!(show_blockmode(modes), "-T-E(full:0x000000aa)");
+    modes |= 0x300;
+    assert_eq!(show_blockmode(modes), "-T-E(full:0x000003aa)");
+}
+
+fn create_key_param_with_auth_type(auth_type: AuthType) -> KeyParameter {
+    KeyParameter {
+        tag: Tag::USER_AUTH_TYPE,
+        value: KeyParameterValue::HardwareAuthenticatorType(auth_type),
+    }
+}
+
+#[test]
+fn test_user_auth_type() {
+    let test_cases = [
+        (vec![], MetricsAuthType::NO_AUTH_TYPE),
+        (vec![AuthType::NONE], MetricsAuthType::NONE),
+        (vec![AuthType::PASSWORD], MetricsAuthType::PASSWORD),
+        (vec![AuthType::FINGERPRINT], MetricsAuthType::FINGERPRINT),
+        (
+            vec![AuthType(AuthType::PASSWORD.0 | AuthType::FINGERPRINT.0)],
+            MetricsAuthType::PASSWORD_OR_FINGERPRINT,
+        ),
+        (vec![AuthType::ANY], MetricsAuthType::ANY),
+        // 7 is the "next" undefined HardwareAuthenticatorType enum tag number, so
+        // force this test to fail and be updated if someone adds a new enum value.
+        (vec![AuthType(7)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+        (vec![AuthType(123)], MetricsAuthType::AUTH_TYPE_UNSPECIFIED),
+        (
+            // In practice, Tag::USER_AUTH_TYPE isn't a repeatable tag. It's allowed
+            // to appear once for auth-bound keys and contains the binary OR of the
+            // applicable auth types. However, this test case repeats the tag more
+            // than once in order to unit test the logic that constructs the atom.
+            vec![AuthType::ANY, AuthType(123), AuthType::PASSWORD],
+            // The last auth type wins.
+            MetricsAuthType::PASSWORD,
+        ),
+    ];
+    for (auth_types, expected) in test_cases {
+        let key_params: Vec<_> =
+            auth_types.iter().map(|a| create_key_param_with_auth_type(*a)).collect();
+        let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            &key_params,
+            &Ok(()),
+        );
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a) if a.user_auth_type == expected
+        ));
+    }
+}
+
+fn create_key_param_with_auth_timeout(timeout: i32) -> KeyParameter {
+    KeyParameter { tag: Tag::AUTH_TIMEOUT, value: KeyParameterValue::Integer(timeout) }
+}
+
+#[test]
+fn test_log_auth_timeout_seconds() {
+    let test_cases = [
+        (vec![], -1),
+        (vec![-1], 0),
+        // The metrics code computes the value of this field for a timeout `t` with
+        // `f32::log10(t as f32) as i32`. The result of f32::log10(0 as f32) is `-inf`.
+        // Casting this to i32 means it gets "rounded" to i32::MIN, which is -2147483648.
+        (vec![0], -2147483648),
+        (vec![1], 0),
+        (vec![9], 0),
+        (vec![10], 1),
+        (vec![999], 2),
+        (
+            // In practice, Tag::AUTH_TIMEOUT isn't a repeatable tag. It's allowed to
+            // appear once for auth-bound keys. However, this test case repeats the
+            // tag more than once in order to unit test the logic that constructs the
+            // atom.
+            vec![1, 0, 10],
+            // The last timeout wins.
+            1,
+        ),
+    ];
+    for (timeouts, expected) in test_cases {
+        let key_params: Vec<_> =
+            timeouts.iter().map(|t| create_key_param_with_auth_timeout(*t)).collect();
+        let (_, atom_with_auth_info, _) = process_key_creation_event_stats(
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            &key_params,
+            &Ok(()),
+        );
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+                if a.log10_auth_key_timeout_seconds == expected
+        ));
+    }
+}
+
+#[test]
+fn test_security_level() {
+    let test_cases = [
+        (SecurityLevel::SOFTWARE, MetricsSecurityLevel::SECURITY_LEVEL_SOFTWARE),
+        (
+            SecurityLevel::TRUSTED_ENVIRONMENT,
+            MetricsSecurityLevel::SECURITY_LEVEL_TRUSTED_ENVIRONMENT,
+        ),
+        (SecurityLevel::STRONGBOX, MetricsSecurityLevel::SECURITY_LEVEL_STRONGBOX),
+        (SecurityLevel::KEYSTORE, MetricsSecurityLevel::SECURITY_LEVEL_KEYSTORE),
+        (SecurityLevel(123), MetricsSecurityLevel::SECURITY_LEVEL_UNSPECIFIED),
+    ];
+    for (security_level, expected) in test_cases {
+        let (_, atom_with_auth_info, _) =
+            process_key_creation_event_stats(security_level, &[], &Ok(()));
+        assert!(matches!(
+            atom_with_auth_info,
+            KeystoreAtomPayload::KeyCreationWithAuthInfo(a)
+                if a.security_level == expected
+        ));
+    }
+}