[automerger skipped] Merge "Keystore 2.0: Add CREATE_DATETIME unconditionally." into sc-v2-dev am: e2dac5e297 -s ours
am skip reason: Merged-In I5b6eb8647d2595e575c72476010ccabf0a1a4527 with SHA-1 5aca5d6947 is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/system/security/+/16150313
Change-Id: Ie9339ad4c7cf5926ef5df6ee0694f469f5366366
diff --git a/OWNERS b/OWNERS
index bb51005..563a78c 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,10 @@
-swillden@google.com
+alanstokes@google.com
cbrubaker@google.com
-jdanis@google.com
hasinitg@google.com
+jbires@google.com
+jdanis@google.com
+jeffv@google.com
kroot@google.com
+sethmo@google.com
+swillden@google.com
zeuthen@google.com
diff --git a/fsverity/Android.bp b/fsverity/Android.bp
new file mode 100644
index 0000000..5fb38ae
--- /dev/null
+++ b/fsverity/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 2021 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+python_library_host {
+ name: "fsverity_digests_proto_python",
+ srcs: [
+ "fsverity_digests.proto",
+ ],
+ required: [
+ "fsverity",
+ ],
+ proto: {
+ canonical_path_from_root: false,
+ },
+ version: {
+ py2: {
+ enabled: true,
+ },
+ py3: {
+ enabled: true,
+ },
+ },
+}
diff --git a/fsverity/AndroidManifest.xml b/fsverity/AndroidManifest.xml
new file mode 100644
index 0000000..434955c
--- /dev/null
+++ b/fsverity/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.security.fsverity_metadata" />
diff --git a/fsverity/OWNERS b/fsverity/OWNERS
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/fsverity/OWNERS
@@ -0,0 +1,5 @@
+alanstokes@google.com
+ebiggers@google.com
+jeffv@google.com
+jiyong@google.com
+victorhsieh@google.com
diff --git a/keystore2/system_property/system_property_bindgen.hpp b/fsverity/fsverity_digests.proto
similarity index 75%
rename from keystore2/system_property/system_property_bindgen.hpp
rename to fsverity/fsverity_digests.proto
index e3c1ade..816ae61 100644
--- a/keystore2/system_property/system_property_bindgen.hpp
+++ b/fsverity/fsverity_digests.proto
@@ -13,6 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#pragma once
-#include "sys/system_properties.h"
+syntax = "proto3";
+
+package android.security.fsverity;
+
+message FSVerityDigests {
+ message Digest {
+ bytes digest = 1;
+ string hash_alg = 2;
+ }
+ map<string, Digest> digests = 1;
+}
diff --git a/fsverity_init/Android.bp b/fsverity_init/Android.bp
index 39d4e6b..83c5945 100644
--- a/fsverity_init/Android.bp
+++ b/fsverity_init/Android.bp
@@ -10,17 +10,34 @@
cc_binary {
name: "fsverity_init",
srcs: [
- "fsverity_init.cpp",
+ "main.cpp",
],
static_libs: [
"libc++fs",
+ "libfsverity_init",
+ "libmini_keyctl_static",
+ ],
+ shared_libs: [
+ "libbase",
+ "libkeyutils",
+ "liblog",
+ ],
+ cflags: ["-Werror", "-Wall", "-Wextra"],
+}
+
+cc_library {
+ name: "libfsverity_init",
+ srcs: ["fsverity_init.cpp"],
+ static_libs: [
+ "libc++fs",
"libmini_keyctl_static",
],
shared_libs: [
"libbase",
"libkeyutils",
"liblog",
- "liblogwrap",
],
cflags: ["-Werror", "-Wall", "-Wextra"],
+ export_include_dirs: ["include"],
+ recovery_available: true,
}
diff --git a/fsverity_init/OWNERS b/fsverity_init/OWNERS
new file mode 100644
index 0000000..f9e7b25
--- /dev/null
+++ b/fsverity_init/OWNERS
@@ -0,0 +1,5 @@
+alanstokes@google.com
+ebiggers@google.com
+jeffv@google.com
+jiyong@google.com
+victorhsieh@google.com
diff --git a/fsverity_init/fsverity_init.cpp b/fsverity_init/fsverity_init.cpp
index 7ab4097..61f84dd 100644
--- a/fsverity_init/fsverity_init.cpp
+++ b/fsverity_init/fsverity_init.cpp
@@ -37,15 +37,17 @@
return true;
}
-void LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname) {
+bool LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname) {
std::string content;
if (!android::base::ReadFdToString(STDIN_FILENO, &content)) {
LOG(ERROR) << "Failed to read key from stdin";
- return;
+ return false;
}
if (!LoadKeyToKeyring(keyring_id, keyname, content.c_str(), content.size())) {
LOG(ERROR) << "Failed to load key from stdin";
+ return false;
}
+ return true;
}
void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path) {
@@ -79,45 +81,3 @@
LoadKeyFromDirectory(keyring_id, "fsv_system_", "/system/etc/security/fsverity");
LoadKeyFromDirectory(keyring_id, "fsv_product_", "/product/etc/security/fsverity");
}
-
-int main(int argc, const char** argv) {
- if (argc < 2) {
- LOG(ERROR) << "Not enough arguments";
- return -1;
- }
-
- key_serial_t keyring_id = android::GetKeyringId(".fs-verity");
- if (keyring_id < 0) {
- LOG(ERROR) << "Failed to find .fs-verity keyring id";
- return -1;
- }
-
- const std::string_view command = argv[1];
-
- if (command == "--load-verified-keys") {
- LoadKeyFromVerifiedPartitions(keyring_id);
- } else if (command == "--load-extra-key") {
- if (argc != 3) {
- LOG(ERROR) << "--load-extra-key requires <key_name> argument.";
- return -1;
- }
- LoadKeyFromStdin(keyring_id, argv[2]);
- } else if (command == "--lock") {
- // Requires files backed by fs-verity to be verified with a key in .fs-verity
- // keyring.
- if (!android::base::WriteStringToFile("1", "/proc/sys/fs/verity/require_signatures")) {
- PLOG(ERROR) << "Failed to enforce fs-verity signature";
- }
-
- if (!android::base::GetBoolProperty("ro.debuggable", false)) {
- if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) {
- PLOG(ERROR) << "Cannot restrict .fs-verity keyring";
- }
- }
- } else {
- LOG(ERROR) << "Unknown argument(s).";
- return -1;
- }
-
- return 0;
-}
diff --git a/keystore2/system_property/system_property_bindgen.hpp b/fsverity_init/include/fsverity_init.h
similarity index 70%
copy from keystore2/system_property/system_property_bindgen.hpp
copy to fsverity_init/include/fsverity_init.h
index e3c1ade..c3bc93b 100644
--- a/keystore2/system_property/system_property_bindgen.hpp
+++ b/fsverity_init/include/fsverity_init.h
@@ -13,6 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#pragma once
-#include "sys/system_properties.h"
+#include <mini_keyctl_utils.h>
+
+bool LoadKeyFromStdin(key_serial_t keyring_id, const char* keyname);
+void LoadKeyFromFile(key_serial_t keyring_id, const char* keyname, const std::string& path);
+void LoadKeyFromVerifiedPartitions(key_serial_t keyring_id);
diff --git a/fsverity_init/main.cpp b/fsverity_init/main.cpp
new file mode 100644
index 0000000..3f75dca
--- /dev/null
+++ b/fsverity_init/main.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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 <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <fsverity_init.h>
+#include <log/log.h>
+#include <mini_keyctl_utils.h>
+
+int main(int argc, const char** argv) {
+ if (argc < 2) {
+ LOG(ERROR) << "Not enough arguments";
+ return -1;
+ }
+
+ key_serial_t keyring_id = android::GetKeyringId(".fs-verity");
+ if (keyring_id < 0) {
+ LOG(ERROR) << "Failed to find .fs-verity keyring id";
+ return -1;
+ }
+
+ const std::string_view command = argv[1];
+
+ if (command == "--load-verified-keys") {
+ LoadKeyFromVerifiedPartitions(keyring_id);
+ } else if (command == "--load-extra-key") {
+ if (argc != 3) {
+ LOG(ERROR) << "--load-extra-key requires <key_name> argument.";
+ return -1;
+ }
+ if (!LoadKeyFromStdin(keyring_id, argv[2])) {
+ return -1;
+ }
+ } else if (command == "--lock") {
+ // Requires files backed by fs-verity to be verified with a key in .fs-verity
+ // keyring.
+ if (!android::base::WriteStringToFile("1", "/proc/sys/fs/verity/require_signatures")) {
+ PLOG(ERROR) << "Failed to enforce fs-verity signature";
+ }
+
+ if (!android::base::GetBoolProperty("ro.debuggable", false)) {
+ if (keyctl_restrict_keyring(keyring_id, nullptr, nullptr) < 0) {
+ PLOG(ERROR) << "Cannot restrict .fs-verity keyring";
+ }
+ }
+ } else {
+ LOG(ERROR) << "Unknown argument(s).";
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/identity/Android.bp b/identity/Android.bp
index 8267a6b..ddb4335 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -24,7 +24,10 @@
cc_binary {
name: "credstore",
- defaults: ["identity_defaults"],
+ defaults: [
+ "identity_defaults",
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
srcs: [
"main.cpp",
@@ -48,14 +51,13 @@
"android.hardware.identity-support-lib",
"libkeymaster4support",
"libkeystore-attestation-application-id",
- "android.hardware.security.keymint-V1-ndk_platform",
- "android.security.authorization-ndk_platform",
+ "android.security.authorization-ndk",
],
static_libs: [
"android.hardware.identity-V3-cpp",
"android.hardware.keymaster-V3-cpp",
"libcppbor_external",
- ]
+ ],
}
filegroup {
diff --git a/identity/TEST_MAPPING b/identity/TEST_MAPPING
index 87707a8..6444c56 100644
--- a/identity/TEST_MAPPING
+++ b/identity/TEST_MAPPING
@@ -2,6 +2,9 @@
"presubmit": [
{
"name": "CtsIdentityTestCases"
+ },
+ {
+ "name": "identity-credential-util-tests"
}
]
}
diff --git a/identity/util/Android.bp b/identity/util/Android.bp
new file mode 100644
index 0000000..71d7718
--- /dev/null
+++ b/identity/util/Android.bp
@@ -0,0 +1,42 @@
+// Copyright 2021 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+ name: "identity-credential-util",
+ srcs: [
+ "src/java/**/*.java",
+ ],
+ static_libs: [
+ "androidx.annotation_annotation",
+ "bouncycastle-unbundled",
+ "cbor-java",
+ ],
+}
+
+android_test {
+ name: "identity-credential-util-tests",
+ test_suites: ["general-tests"],
+ srcs: [
+ "test/java/**/*.java",
+ ],
+ static_libs: [
+ "androidx.test.rules",
+ "identity-credential-util",
+ "junit",
+ ],
+}
diff --git a/identity/util/AndroidManifest.xml b/identity/util/AndroidManifest.xml
new file mode 100644
index 0000000..eece4dc
--- /dev/null
+++ b/identity/util/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright 2021 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.security.identity.internal">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.security.identity.internal"
+ android:label="Unit tests for com.android.security.identity.internal"/>
+
+</manifest>
+
diff --git a/identity/util/AndroidTest.xml b/identity/util/AndroidTest.xml
new file mode 100644
index 0000000..345460f
--- /dev/null
+++ b/identity/util/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Config for identity cred support library tests">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="identity-credential-util-tests.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.InstrumentationTest" >
+ <option name="package" value="com.android.security.identity.internal" />
+ </test>
+</configuration>
diff --git a/identity/util/src/java/com/android/security/identity/internal/Iso18013.java b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
new file mode 100644
index 0000000..6da90e5
--- /dev/null
+++ b/identity/util/src/java/com/android/security/identity/internal/Iso18013.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package com.android.security.identity.internal;
+
+import static com.android.security.identity.internal.Util.CBOR_SEMANTIC_TAG_ENCODED_CBOR;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.security.InvalidKeyException;
+import java.security.InvalidParameterException;
+import java.security.KeyPair;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.builder.MapBuilder;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+
+/**
+ * Various utilities for working with the ISO mobile driving license (mDL)
+ * application specification (ISO 18013-5).
+ */
+public class Iso18013 {
+ /**
+ * Each version of the spec is namespaced, and all namespace-specific constants
+ * are thus collected into a namespace-specific nested class.
+ */
+ public static class V1 {
+ public static final String NAMESPACE = "org.iso.18013.5.1";
+ public static final String DOC_TYPE = "org.iso.18013.5.1.mdl";
+
+ public static final String FAMILY_NAME = "family_name";
+ public static final String GIVEN_NAME = "given_name";
+ public static final String BIRTH_DATE = "birth_date";
+ public static final String ISSUE_DATE = "issue_date";
+ public static final String EXPIRY = "expiry_date";
+ public static final String ISSUING_COUNTRY = "issuing_country";
+ public static final String ISSUING_AUTHORITY = "issuing_authority";
+ public static final String DOCUMENT_NUMBER = "document_number";
+ public static final String PORTRAIT = "portrait";
+ public static final String DRIVING_PRIVILEGES = "driving_privileges";
+ public static final String UN_DISTINGUISHING_SIGN = "un_distinguishing_sign";
+ public static final String HEIGHT = "height";
+ public static final String BIO_FACE = "biometric_template_face";
+
+ public static String ageOver(int age) {
+ if (age < 0 || age > 99) {
+ throw new InvalidParameterException("age must be between 0 and 99, inclusive");
+ }
+ return String.format("age_over_%02d", age);
+ }
+ }
+
+ public static byte[] buildDeviceAuthenticationCbor(String docType,
+ byte[] encodedSessionTranscript,
+ byte[] deviceNameSpacesBytes) {
+ ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
+ try {
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
+ List<DataItem> dataItems = null;
+ dataItems = new CborDecoder(bais).decode();
+ DataItem sessionTranscript = dataItems.get(0);
+ ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes);
+ deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ new CborEncoder(daBaos).encode(new CborBuilder()
+ .addArray()
+ .add("DeviceAuthentication")
+ .add(sessionTranscript)
+ .add(docType)
+ .add(deviceNameSpacesBytesItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding DeviceAuthentication", e);
+ }
+ return daBaos.toByteArray();
+ }
+
+ public static byte[] buildReaderAuthenticationBytesCbor(
+ byte[] encodedSessionTranscript,
+ byte[] requestMessageBytes) {
+
+ ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
+ try {
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
+ List<DataItem> dataItems = null;
+ dataItems = new CborDecoder(bais).decode();
+ DataItem sessionTranscript = dataItems.get(0);
+ ByteString requestMessageBytesItem = new ByteString(requestMessageBytes);
+ requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ new CborEncoder(daBaos).encode(new CborBuilder()
+ .addArray()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript)
+ .add(requestMessageBytesItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding ReaderAuthentication", e);
+ }
+ byte[] readerAuthentication = daBaos.toByteArray();
+ return Util.prependSemanticTagForEncodedCbor(readerAuthentication);
+ }
+
+ // This returns a SessionTranscript which satisfy the requirement
+ // that the uncompressed X and Y coordinates of the public key for the
+ // mDL's ephemeral key-pair appear somewhere in the encoded
+ // DeviceEngagement.
+ public static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) {
+ // Make the coordinates appear in an already encoded bstr - this
+ // mimics how the mDL COSE_Key appear as encoded data inside the
+ // encoded DeviceEngagement
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+ // X and Y are always positive so for interop we remove any leading zeroes
+ // inserted by the BigInteger encoder.
+ byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
+ byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
+ baos.write(new byte[]{41});
+ baos.write(x);
+ baos.write(y);
+ baos.write(new byte[]{42, 44});
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ byte[] blobWithCoords = baos.toByteArray();
+
+ baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray()
+ .add(blobWithCoords)
+ .end()
+ .build());
+ } catch (CborException e) {
+ e.printStackTrace();
+ return null;
+ }
+ ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray());
+ ByteString encodedEReaderKeyItem = new ByteString(Util.cborEncodeString("doesn't matter"));
+ encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray()
+ .add(encodedDeviceEngagementItem)
+ .add(encodedEReaderKeyItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return baos.toByteArray();
+ }
+
+ /*
+ * Helper function to create a CBOR data for requesting data items. The IntentToRetain
+ * value will be set to false for all elements.
+ *
+ * <p>The returned CBOR data conforms to the following CDDL schema:</p>
+ *
+ * <pre>
+ * ItemsRequest = {
+ * ? "docType" : DocType,
+ * "nameSpaces" : NameSpaces,
+ * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
+ * }
+ *
+ * NameSpaces = {
+ * + NameSpace => DataElements ; Requested data elements for each NameSpace
+ * }
+ *
+ * DataElements = {
+ * + DataElement => IntentToRetain
+ * }
+ *
+ * DocType = tstr
+ *
+ * DataElement = tstr
+ * IntentToRetain = bool
+ * NameSpace = tstr
+ * </pre>
+ *
+ * @param entriesToRequest The entries to request, organized as a map of namespace
+ * names with each value being a collection of data elements
+ * in the given namespace.
+ * @param docType The document type or {@code null} if there is no document
+ * type.
+ * @return CBOR data conforming to the CDDL mentioned above.
+ */
+ public static @NonNull
+ byte[] createItemsRequest(
+ @NonNull Map<String, Collection<String>> entriesToRequest,
+ @Nullable String docType) {
+ CborBuilder builder = new CborBuilder();
+ MapBuilder<CborBuilder> mapBuilder = builder.addMap();
+ if (docType != null) {
+ mapBuilder.put("docType", docType);
+ }
+
+ MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces");
+ for (String namespaceName : entriesToRequest.keySet()) {
+ Collection<String> entryNames = entriesToRequest.get(namespaceName);
+ MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder =
+ nsMapBuilder.putMap(namespaceName);
+ for (String entryName : entryNames) {
+ entryNameMapBuilder.put(entryName, false);
+ }
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CborEncoder encoder = new CborEncoder(baos);
+ try {
+ encoder.encode(builder.build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding CBOR", e);
+ }
+ return baos.toByteArray();
+ }
+
+ public static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
+ PrivateKey ephemeralReaderPrivateKey,
+ byte[] encodedSessionTranscript) {
+ try {
+ KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+ ka.init(ephemeralReaderPrivateKey);
+ ka.doPhase(authenticationPublicKey, true);
+ byte[] sharedSecret = ka.generateSecret();
+
+ byte[] sessionTranscriptBytes =
+ Util.cborEncode(Util.buildCborTaggedByteString(encodedSessionTranscript));
+
+ byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
+ byte[] info = new byte[]{'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+
+ SecretKey secretKey = new SecretKeySpec(derivedKey, "");
+ return secretKey;
+ } catch (InvalidKeyException
+ | NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Error performing key agreement", e);
+ }
+ }
+
+ private static byte[] stripLeadingZeroes(byte[] value) {
+ int n = 0;
+ while (n < value.length && value[n] == 0) {
+ n++;
+ }
+ int newLen = value.length - n;
+ byte[] ret = new byte[newLen];
+ int m = 0;
+ while (n < value.length) {
+ ret[m++] = value[n++];
+ }
+ return ret;
+ }
+}
diff --git a/identity/util/src/java/com/android/security/identity/internal/Util.java b/identity/util/src/java/com/android/security/identity/internal/Util.java
new file mode 100644
index 0000000..4ec54a7
--- /dev/null
+++ b/identity/util/src/java/com/android/security/identity/internal/Util.java
@@ -0,0 +1,1316 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.security.identity.internal;
+
+import android.security.identity.ResultData;
+import android.security.identity.IdentityCredentialStore;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.FeatureInfo;
+import android.os.SystemProperties;
+import android.security.keystore.KeyProperties;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.ECGenParameterSpec;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Formatter;
+import java.util.Map;
+
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECPoint;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.builder.ArrayBuilder;
+import co.nstant.in.cbor.builder.MapBuilder;
+import co.nstant.in.cbor.model.AbstractFloat;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.MajorType;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SimpleValueType;
+import co.nstant.in.cbor.model.SpecialType;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+public class Util {
+ private static final String TAG = "Util";
+
+ public static byte[] canonicalizeCbor(byte[] encodedCbor) throws CborException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedCbor);
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ for(DataItem dataItem : dataItems) {
+ CborEncoder encoder = new CborEncoder(baos);
+ encoder.encode(dataItem);
+ }
+ return baos.toByteArray();
+ }
+
+
+ public static String cborPrettyPrint(byte[] encodedBytes) throws CborException {
+ StringBuilder sb = new StringBuilder();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedBytes);
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ int count = 0;
+ for (DataItem dataItem : dataItems) {
+ if (count > 0) {
+ sb.append(",\n");
+ }
+ cborPrettyPrintDataItem(sb, 0, dataItem);
+ count++;
+ }
+
+ return sb.toString();
+ }
+
+ // Returns true iff all elements in |items| are not compound (e.g. an array or a map).
+ static boolean cborAreAllDataItemsNonCompound(List<DataItem> items) {
+ for (DataItem item : items) {
+ switch (item.getMajorType()) {
+ case ARRAY:
+ case MAP:
+ return false;
+ default:
+ // continue inspecting other data items
+ }
+ }
+ return true;
+ }
+
+ public static void cborPrettyPrintDataItem(StringBuilder sb, int indent, DataItem dataItem) {
+ StringBuilder indentBuilder = new StringBuilder();
+ for (int n = 0; n < indent; n++) {
+ indentBuilder.append(' ');
+ }
+ String indentString = indentBuilder.toString();
+
+ if (dataItem.hasTag()) {
+ sb.append(String.format("tag %d ", dataItem.getTag().getValue()));
+ }
+
+ switch (dataItem.getMajorType()) {
+ case INVALID:
+ // TODO: throw
+ sb.append("<invalid>");
+ break;
+ case UNSIGNED_INTEGER: {
+ // Major type 0: an unsigned integer.
+ BigInteger value = ((UnsignedInteger) dataItem).getValue();
+ sb.append(value);
+ }
+ break;
+ case NEGATIVE_INTEGER: {
+ // Major type 1: a negative integer.
+ BigInteger value = ((NegativeInteger) dataItem).getValue();
+ sb.append(value);
+ }
+ break;
+ case BYTE_STRING: {
+ // Major type 2: a byte string.
+ byte[] value = ((ByteString) dataItem).getBytes();
+ sb.append("[");
+ int count = 0;
+ for (byte b : value) {
+ if (count > 0) {
+ sb.append(", ");
+ }
+ sb.append(String.format("0x%02x", b));
+ count++;
+ }
+ sb.append("]");
+ }
+ break;
+ case UNICODE_STRING: {
+ // Major type 3: string of Unicode characters that is encoded as UTF-8 [RFC3629].
+ String value = ((UnicodeString) dataItem).getString();
+ // TODO: escape ' in |value|
+ sb.append("'" + value + "'");
+ }
+ break;
+ case ARRAY: {
+ // Major type 4: an array of data items.
+ List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
+ if (items.size() == 0) {
+ sb.append("[]");
+ } else if (cborAreAllDataItemsNonCompound(items)) {
+ // The case where everything fits on one line.
+ sb.append("[");
+ int count = 0;
+ for (DataItem item : items) {
+ cborPrettyPrintDataItem(sb, indent, item);
+ if (++count < items.size()) {
+ sb.append(", ");
+ }
+ }
+ sb.append("]");
+ } else {
+ sb.append("[\n" + indentString);
+ int count = 0;
+ for (DataItem item : items) {
+ sb.append(" ");
+ cborPrettyPrintDataItem(sb, indent + 2, item);
+ if (++count < items.size()) {
+ sb.append(",");
+ }
+ sb.append("\n" + indentString);
+ }
+ sb.append("]");
+ }
+ }
+ break;
+ case MAP: {
+ // Major type 5: a map of pairs of data items.
+ Collection<DataItem> keys = ((co.nstant.in.cbor.model.Map) dataItem).getKeys();
+ if (keys.size() == 0) {
+ sb.append("{}");
+ } else {
+ sb.append("{\n" + indentString);
+ int count = 0;
+ for (DataItem key : keys) {
+ sb.append(" ");
+ DataItem value = ((co.nstant.in.cbor.model.Map) dataItem).get(key);
+ cborPrettyPrintDataItem(sb, indent + 2, key);
+ sb.append(" : ");
+ cborPrettyPrintDataItem(sb, indent + 2, value);
+ if (++count < keys.size()) {
+ sb.append(",");
+ }
+ sb.append("\n" + indentString);
+ }
+ sb.append("}");
+ }
+ }
+ break;
+ case TAG:
+ // Major type 6: optional semantic tagging of other major types
+ //
+ // We never encounter this one since it's automatically handled via the
+ // DataItem that is tagged.
+ throw new RuntimeException("Semantic tag data item not expected");
+
+ case SPECIAL:
+ // Major type 7: floating point numbers and simple data types that need no
+ // content, as well as the "break" stop code.
+ if (dataItem instanceof SimpleValue) {
+ switch (((SimpleValue) dataItem).getSimpleValueType()) {
+ case FALSE:
+ sb.append("false");
+ break;
+ case TRUE:
+ sb.append("true");
+ break;
+ case NULL:
+ sb.append("null");
+ break;
+ case UNDEFINED:
+ sb.append("undefined");
+ break;
+ case RESERVED:
+ sb.append("reserved");
+ break;
+ case UNALLOCATED:
+ sb.append("unallocated");
+ break;
+ }
+ } else if (dataItem instanceof DoublePrecisionFloat) {
+ DecimalFormat df = new DecimalFormat("0",
+ DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+ df.setMaximumFractionDigits(340);
+ sb.append(df.format(((DoublePrecisionFloat) dataItem).getValue()));
+ } else if (dataItem instanceof AbstractFloat) {
+ DecimalFormat df = new DecimalFormat("0",
+ DecimalFormatSymbols.getInstance(Locale.ENGLISH));
+ df.setMaximumFractionDigits(340);
+ sb.append(df.format(((AbstractFloat) dataItem).getValue()));
+ } else {
+ sb.append("break");
+ }
+ break;
+ }
+ }
+
+ public static byte[] encodeCbor(List<DataItem> dataItems) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CborEncoder encoder = new CborEncoder(baos);
+ try {
+ encoder.encode(dataItems);
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding data", e);
+ }
+ return baos.toByteArray();
+ }
+
+ public static byte[] coseBuildToBeSigned(byte[] encodedProtectedHeaders,
+ byte[] payload,
+ byte[] detachedContent) {
+ CborBuilder sigStructure = new CborBuilder();
+ ArrayBuilder<CborBuilder> array = sigStructure.addArray();
+
+ array.add("Signature1");
+ array.add(encodedProtectedHeaders);
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ byte emptyExternalAad[] = new byte[0];
+ array.add(emptyExternalAad);
+
+ // Next field is the payload, independently of how it's transported (RFC
+ // 8152 section 4.4). Since our API specifies only one of |data| and
+ // |detachedContent| can be non-empty, it's simply just the non-empty one.
+ if (payload != null && payload.length > 0) {
+ array.add(payload);
+ } else {
+ array.add(detachedContent);
+ }
+ array.end();
+ return encodeCbor(sigStructure.build());
+ }
+
+ private static final int COSE_LABEL_ALG = 1;
+ private static final int COSE_LABEL_X5CHAIN = 33; // temporary identifier
+
+ // From "COSE Algorithms" registry
+ private static final int COSE_ALG_ECDSA_256 = -7;
+ private static final int COSE_ALG_HMAC_256_256 = 5;
+
+ private static byte[] signatureDerToCose(byte[] signature) {
+ if (signature.length > 128) {
+ throw new RuntimeException("Unexpected length " + signature.length
+ + ", expected less than 128");
+ }
+ if (signature[0] != 0x30) {
+ throw new RuntimeException("Unexpected first byte " + signature[0]
+ + ", expected 0x30");
+ }
+ if ((signature[1] & 0x80) != 0x00) {
+ throw new RuntimeException("Unexpected second byte " + signature[1]
+ + ", bit 7 shouldn't be set");
+ }
+ int rOffset = 2;
+ int rSize = signature[rOffset + 1];
+ byte[] rBytes = stripLeadingZeroes(
+ Arrays.copyOfRange(signature,rOffset + 2, rOffset + rSize + 2));
+
+ int sOffset = rOffset + 2 + rSize;
+ int sSize = signature[sOffset + 1];
+ byte[] sBytes = stripLeadingZeroes(
+ Arrays.copyOfRange(signature, sOffset + 2, sOffset + sSize + 2));
+
+ if (rBytes.length > 32) {
+ throw new RuntimeException("rBytes.length is " + rBytes.length + " which is > 32");
+ }
+ if (sBytes.length > 32) {
+ throw new RuntimeException("sBytes.length is " + sBytes.length + " which is > 32");
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ for (int n = 0; n < 32 - rBytes.length; n++) {
+ baos.write(0x00);
+ }
+ baos.write(rBytes);
+ for (int n = 0; n < 32 - sBytes.length; n++) {
+ baos.write(0x00);
+ }
+ baos.write(sBytes);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return baos.toByteArray();
+ }
+
+ // Adds leading 0x00 if the first encoded byte MSB is set.
+ private static byte[] encodePositiveBigInteger(BigInteger i) {
+ byte[] bytes = i.toByteArray();
+ if ((bytes[0] & 0x80) != 0) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ baos.write(0x00);
+ baos.write(bytes);
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new RuntimeException("Failed writing data", e);
+ }
+ bytes = baos.toByteArray();
+ }
+ return bytes;
+ }
+
+ private static byte[] signatureCoseToDer(byte[] signature) {
+ if (signature.length != 64) {
+ throw new RuntimeException("signature.length is " + signature.length + ", expected 64");
+ }
+ // r and s are always positive and may use all 256 bits so use the constructor which
+ // parses them as unsigned.
+ BigInteger r = new BigInteger(1, Arrays.copyOfRange(signature, 0, 32));
+ BigInteger s = new BigInteger(1, Arrays.copyOfRange(signature, 32, 64));
+ byte[] rBytes = encodePositiveBigInteger(r);
+ byte[] sBytes = encodePositiveBigInteger(s);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ baos.write(0x30);
+ baos.write(2 + rBytes.length + 2 + sBytes.length);
+ baos.write(0x02);
+ baos.write(rBytes.length);
+ baos.write(rBytes);
+ baos.write(0x02);
+ baos.write(sBytes.length);
+ baos.write(sBytes);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return baos.toByteArray();
+ }
+
+ public static byte[] coseSign1Sign(PrivateKey key,
+ @Nullable byte[] data,
+ byte[] detachedContent,
+ @Nullable Collection<X509Certificate> certificateChain)
+ throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
+
+ int dataLen = (data != null ? data.length : 0);
+ int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
+ if (dataLen > 0 && detachedContentLen > 0) {
+ throw new RuntimeException("data and detachedContent cannot both be non-empty");
+ }
+
+ CborBuilder protectedHeaders = new CborBuilder();
+ MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
+ protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_ECDSA_256);
+ byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
+
+ byte[] toBeSigned = coseBuildToBeSigned(protectedHeadersBytes, data, detachedContent);
+
+ byte[] coseSignature = null;
+ try {
+ Signature s = Signature.getInstance("SHA256withECDSA");
+ s.initSign(key);
+ s.update(toBeSigned);
+ byte[] derSignature = s.sign();
+ coseSignature = signatureDerToCose(derSignature);
+ } catch (SignatureException e) {
+ throw new RuntimeException("Error signing data");
+ }
+
+ CborBuilder builder = new CborBuilder();
+ ArrayBuilder<CborBuilder> array = builder.addArray();
+ array.add(protectedHeadersBytes);
+ MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
+ if (certificateChain != null && certificateChain.size() > 0) {
+ if (certificateChain.size() == 1) {
+ X509Certificate cert = certificateChain.iterator().next();
+ unprotectedHeaders.put(COSE_LABEL_X5CHAIN, cert.getEncoded());
+ } else {
+ ArrayBuilder<MapBuilder<ArrayBuilder<CborBuilder>>> x5chainsArray =
+ unprotectedHeaders.putArray(COSE_LABEL_X5CHAIN);
+ for (X509Certificate cert : certificateChain) {
+ x5chainsArray.add(cert.getEncoded());
+ }
+ }
+ }
+ if (data == null || data.length == 0) {
+ array.add(new SimpleValue(SimpleValueType.NULL));
+ } else {
+ array.add(data);
+ }
+ array.add(coseSignature);
+
+ return encodeCbor(builder.build());
+ }
+
+ public static boolean coseSign1CheckSignature(byte[] signatureCose1,
+ byte[] detachedContent,
+ PublicKey publicKey) throws NoSuchAlgorithmException, InvalidKeyException {
+ ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
+ List<DataItem> dataItems = null;
+ try {
+ dataItems = new CborDecoder(bais).decode();
+ } catch (CborException e) {
+ throw new RuntimeException("Given signature is not valid CBOR", e);
+ }
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected just one data item");
+ }
+ DataItem dataItem = dataItems.get(0);
+ if (dataItem.getMajorType() != MajorType.ARRAY) {
+ throw new RuntimeException("Data item is not an array");
+ }
+ List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
+ if (items.size() < 4) {
+ throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
+ }
+ if (items.get(0).getMajorType() != MajorType.BYTE_STRING) {
+ throw new RuntimeException("Item 0 (protected headers) is not a byte-string");
+ }
+ byte[] encodedProtectedHeaders =
+ ((co.nstant.in.cbor.model.ByteString) items.get(0)).getBytes();
+ byte[] payload = new byte[0];
+ if (items.get(2).getMajorType() == MajorType.SPECIAL) {
+ if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
+ != SpecialType.SIMPLE_VALUE) {
+ throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
+ }
+ SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
+ if (simple.getSimpleValueType() != SimpleValueType.NULL) {
+ throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
+ }
+ } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
+ payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
+ } else {
+ throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
+ }
+ if (items.get(3).getMajorType() != MajorType.BYTE_STRING) {
+ throw new RuntimeException("Item 3 (signature) is not a byte-string");
+ }
+ byte[] coseSignature = ((co.nstant.in.cbor.model.ByteString) items.get(3)).getBytes();
+
+ byte[] derSignature = signatureCoseToDer(coseSignature);
+
+ int dataLen = payload.length;
+ int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
+ if (dataLen > 0 && detachedContentLen > 0) {
+ throw new RuntimeException("data and detachedContent cannot both be non-empty");
+ }
+
+ byte[] toBeSigned = Util.coseBuildToBeSigned(encodedProtectedHeaders,
+ payload, detachedContent);
+
+ try {
+ Signature verifier = Signature.getInstance("SHA256withECDSA");
+ verifier.initVerify(publicKey);
+ verifier.update(toBeSigned);
+ return verifier.verify(derSignature);
+ } catch (SignatureException e) {
+ throw new RuntimeException("Error verifying signature");
+ }
+ }
+
+ // Returns the empty byte-array if no data is included in the structure.
+ //
+ // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
+ //
+ public static byte[] coseSign1GetData(byte[] signatureCose1) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
+ List<DataItem> dataItems = null;
+ try {
+ dataItems = new CborDecoder(bais).decode();
+ } catch (CborException e) {
+ throw new RuntimeException("Given signature is not valid CBOR", e);
+ }
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected just one data item");
+ }
+ DataItem dataItem = dataItems.get(0);
+ if (dataItem.getMajorType() != MajorType.ARRAY) {
+ throw new RuntimeException("Data item is not an array");
+ }
+ List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
+ if (items.size() < 4) {
+ throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
+ }
+ byte[] payload = new byte[0];
+ if (items.get(2).getMajorType() == MajorType.SPECIAL) {
+ if (((co.nstant.in.cbor.model.Special) items.get(2)).getSpecialType()
+ != SpecialType.SIMPLE_VALUE) {
+ throw new RuntimeException("Item 2 (payload) is a special but not a simple value");
+ }
+ SimpleValue simple = (co.nstant.in.cbor.model.SimpleValue) items.get(2);
+ if (simple.getSimpleValueType() != SimpleValueType.NULL) {
+ throw new RuntimeException("Item 2 (payload) is a simple but not the value null");
+ }
+ } else if (items.get(2).getMajorType() == MajorType.BYTE_STRING) {
+ payload = ((co.nstant.in.cbor.model.ByteString) items.get(2)).getBytes();
+ } else {
+ throw new RuntimeException("Item 2 (payload) is not nil or byte-string");
+ }
+ return payload;
+ }
+
+ // Returns the empty collection if no x5chain is included in the structure.
+ //
+ // Throws RuntimeException if the given bytes aren't valid COSE_Sign1.
+ //
+ public static Collection<X509Certificate> coseSign1GetX5Chain(byte[] signatureCose1)
+ throws CertificateException {
+ ArrayList<X509Certificate> ret = new ArrayList<>();
+ ByteArrayInputStream bais = new ByteArrayInputStream(signatureCose1);
+ List<DataItem> dataItems = null;
+ try {
+ dataItems = new CborDecoder(bais).decode();
+ } catch (CborException e) {
+ throw new RuntimeException("Given signature is not valid CBOR", e);
+ }
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected just one data item");
+ }
+ DataItem dataItem = dataItems.get(0);
+ if (dataItem.getMajorType() != MajorType.ARRAY) {
+ throw new RuntimeException("Data item is not an array");
+ }
+ List<DataItem> items = ((co.nstant.in.cbor.model.Array) dataItem).getDataItems();
+ if (items.size() < 4) {
+ throw new RuntimeException("Expected at least four items in COSE_Sign1 array");
+ }
+ if (items.get(1).getMajorType() != MajorType.MAP) {
+ throw new RuntimeException("Item 1 (unprocted headers) is not a map");
+ }
+ co.nstant.in.cbor.model.Map map = (co.nstant.in.cbor.model.Map) items.get(1);
+ DataItem x5chainItem = map.get(new UnsignedInteger(COSE_LABEL_X5CHAIN));
+ if (x5chainItem != null) {
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ if (x5chainItem instanceof ByteString) {
+ ByteArrayInputStream certBais =
+ new ByteArrayInputStream(((ByteString) x5chainItem).getBytes());
+ ret.add((X509Certificate) factory.generateCertificate(certBais));
+ } else if (x5chainItem instanceof Array) {
+ for (DataItem certItem : ((Array) x5chainItem).getDataItems()) {
+ if (!(certItem instanceof ByteString)) {
+ throw new RuntimeException(
+ "Unexpected type for array item in x5chain value");
+ }
+ ByteArrayInputStream certBais =
+ new ByteArrayInputStream(((ByteString) certItem).getBytes());
+ ret.add((X509Certificate) factory.generateCertificate(certBais));
+ }
+ } else {
+ throw new RuntimeException("Unexpected type for x5chain value");
+ }
+ }
+ return ret;
+ }
+
+ public static byte[] coseBuildToBeMACed(byte[] encodedProtectedHeaders,
+ byte[] payload,
+ byte[] detachedContent) {
+ CborBuilder macStructure = new CborBuilder();
+ ArrayBuilder<CborBuilder> array = macStructure.addArray();
+
+ array.add("MAC0");
+ array.add(encodedProtectedHeaders);
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ byte emptyExternalAad[] = new byte[0];
+ array.add(emptyExternalAad);
+
+ // Next field is the payload, independently of how it's transported (RFC
+ // 8152 section 4.4). Since our API specifies only one of |data| and
+ // |detachedContent| can be non-empty, it's simply just the non-empty one.
+ if (payload != null && payload.length > 0) {
+ array.add(payload);
+ } else {
+ array.add(detachedContent);
+ }
+
+ return encodeCbor(macStructure.build());
+ }
+
+ public static byte[] coseMac0(SecretKey key,
+ @Nullable byte[] data,
+ byte[] detachedContent)
+ throws NoSuchAlgorithmException, InvalidKeyException, CertificateEncodingException {
+
+ int dataLen = (data != null ? data.length : 0);
+ int detachedContentLen = (detachedContent != null ? detachedContent.length : 0);
+ if (dataLen > 0 && detachedContentLen > 0) {
+ throw new RuntimeException("data and detachedContent cannot both be non-empty");
+ }
+
+ CborBuilder protectedHeaders = new CborBuilder();
+ MapBuilder<CborBuilder> protectedHeadersMap = protectedHeaders.addMap();
+ protectedHeadersMap.put(COSE_LABEL_ALG, COSE_ALG_HMAC_256_256);
+ byte[] protectedHeadersBytes = encodeCbor(protectedHeaders.build());
+
+ byte[] toBeMACed = coseBuildToBeMACed(protectedHeadersBytes, data, detachedContent);
+
+ byte[] mac = null;
+ Mac m = Mac.getInstance("HmacSHA256");
+ m.init(key);
+ m.update(toBeMACed);
+ mac = m.doFinal();
+
+ CborBuilder builder = new CborBuilder();
+ ArrayBuilder<CborBuilder> array = builder.addArray();
+ array.add(protectedHeadersBytes);
+ MapBuilder<ArrayBuilder<CborBuilder>> unprotectedHeaders = array.addMap();
+ if (data == null || data.length == 0) {
+ array.add(new SimpleValue(SimpleValueType.NULL));
+ } else {
+ array.add(data);
+ }
+ array.add(mac);
+
+ return encodeCbor(builder.build());
+ }
+
+ public static String replaceLine(String text, int lineNumber, String replacementLine) {
+ String[] lines = text.split("\n");
+ int numLines = lines.length;
+ if (lineNumber < 0) {
+ lineNumber = numLines - (-lineNumber);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (int n = 0; n < numLines; n++) {
+ if (n == lineNumber) {
+ sb.append(replacementLine);
+ } else {
+ sb.append(lines[n]);
+ }
+ // Only add terminating newline if passed-in string ends in a newline.
+ if (n == numLines - 1) {
+ if (text.endsWith(("\n"))) {
+ sb.append('\n');
+ }
+ } else {
+ sb.append('\n');
+ }
+ }
+ return sb.toString();
+ }
+
+ public static byte[] cborEncode(DataItem dataItem) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(dataItem);
+ } catch (CborException e) {
+ // This should never happen and we don't want cborEncode() to throw since that
+ // would complicate all callers. Log it instead.
+ e.printStackTrace();
+ Log.e(TAG, "Error encoding DataItem");
+ }
+ return baos.toByteArray();
+ }
+
+ public static byte[] cborEncodeBoolean(boolean value) {
+ return cborEncode(new CborBuilder().add(value).build().get(0));
+ }
+
+ public static byte[] cborEncodeString(@NonNull String value) {
+ return cborEncode(new CborBuilder().add(value).build().get(0));
+ }
+
+ public static byte[] cborEncodeBytestring(@NonNull byte[] value) {
+ return cborEncode(new CborBuilder().add(value).build().get(0));
+ }
+
+ public static byte[] cborEncodeInt(long value) {
+ return cborEncode(new CborBuilder().add(value).build().get(0));
+ }
+
+ static final int CBOR_SEMANTIC_TAG_ENCODED_CBOR = 24;
+
+ public static DataItem cborToDataItem(byte[] data) {
+ ByteArrayInputStream bais = new ByteArrayInputStream(data);
+ try {
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected 1 item, found " + dataItems.size());
+ }
+ return dataItems.get(0);
+ } catch (CborException e) {
+ throw new RuntimeException("Error decoding data", e);
+ }
+ }
+
+ public static boolean cborDecodeBoolean(@NonNull byte[] data) {
+ return cborToDataItem(data) == SimpleValue.TRUE;
+ }
+
+ public static String cborDecodeString(@NonNull byte[] data) {
+ return ((co.nstant.in.cbor.model.UnicodeString) cborToDataItem(data)).getString();
+ }
+
+ public static long cborDecodeInt(@NonNull byte[] data) {
+ return ((co.nstant.in.cbor.model.Number) cborToDataItem(data)).getValue().longValue();
+ }
+
+ public static byte[] cborDecodeBytestring(@NonNull byte[] data) {
+ return ((co.nstant.in.cbor.model.ByteString) cborToDataItem(data)).getBytes();
+ }
+
+ public static String getStringEntry(ResultData data, String namespaceName, String name) {
+ return Util.cborDecodeString(data.getEntry(namespaceName, name));
+ }
+
+ public static boolean getBooleanEntry(ResultData data, String namespaceName, String name) {
+ return Util.cborDecodeBoolean(data.getEntry(namespaceName, name));
+ }
+
+ public static long getIntegerEntry(ResultData data, String namespaceName, String name) {
+ return Util.cborDecodeInt(data.getEntry(namespaceName, name));
+ }
+
+ public static byte[] getBytestringEntry(ResultData data, String namespaceName, String name) {
+ return Util.cborDecodeBytestring(data.getEntry(namespaceName, name));
+ }
+
+ /*
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: ecdsa-with-SHA256
+ Issuer: CN=fake
+ Validity
+ Not Before: Jan 1 00:00:00 1970 GMT
+ Not After : Jan 1 00:00:00 2048 GMT
+ Subject: CN=fake
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ Public-Key: (256 bit)
+ 00000000 04 9b 60 70 8a 99 b6 bf e3 b8 17 02 9e 93 eb 48 |..`p...........H|
+ 00000010 23 b9 39 89 d1 00 bf a0 0f d0 2f bd 6b 11 bc d1 |#.9......./.k...|
+ 00000020 19 53 54 28 31 00 f5 49 db 31 fb 9f 7d 99 bf 23 |.ST(1..I.1..}..#|
+ 00000030 fb 92 04 6b 23 63 55 98 ad 24 d2 68 c4 83 bf 99 |...k#cU..$.h....|
+ 00000040 62 |b|
+ Signature Algorithm: ecdsa-with-SHA256
+ 30:45:02:20:67:ad:d1:34:ed:a5:68:3f:5b:33:ee:b3:18:a2:
+ eb:03:61:74:0f:21:64:4a:a3:2e:82:b3:92:5c:21:0f:88:3f:
+ 02:21:00:b7:38:5c:9b:f2:9c:b1:27:86:37:44:df:eb:4a:b2:
+ 6c:11:9a:c1:ff:b2:80:95:ce:fc:5f:26:b4:20:6e:9b:0d
+ */
+
+
+ public static @NonNull X509Certificate signPublicKeyWithPrivateKey(String keyToSignAlias,
+ String keyToSignWithAlias) {
+
+ KeyStore ks = null;
+ try {
+ ks = KeyStore.getInstance("AndroidKeyStore");
+ ks.load(null);
+
+ /* First note that KeyStore.getCertificate() returns a self-signed X.509 certificate
+ * for the key in question. As per RFC 5280, section 4.1 an X.509 certificate has the
+ * following structure:
+ *
+ * Certificate ::= SEQUENCE {
+ * tbsCertificate TBSCertificate,
+ * signatureAlgorithm AlgorithmIdentifier,
+ * signatureValue BIT STRING }
+ *
+ * Conveniently, the X509Certificate class has a getTBSCertificate() method which
+ * returns the tbsCertificate blob. So all we need to do is just sign that and build
+ * signatureAlgorithm and signatureValue and combine it with tbsCertificate. We don't
+ * need a full-blown ASN.1/DER encoder to do this.
+ */
+ X509Certificate selfSignedCert = (X509Certificate) ks.getCertificate(keyToSignAlias);
+ byte[] tbsCertificate = selfSignedCert.getTBSCertificate();
+
+ KeyStore.Entry keyToSignWithEntry = ks.getEntry(keyToSignWithAlias, null);
+ Signature s = Signature.getInstance("SHA256withECDSA");
+ s.initSign(((KeyStore.PrivateKeyEntry) keyToSignWithEntry).getPrivateKey());
+ s.update(tbsCertificate);
+ byte[] signatureValue = s.sign();
+
+ /* The DER encoding for a SEQUENCE of length 128-65536 - the length is updated below.
+ *
+ * We assume - and test for below - that the final length is always going to be in
+ * this range. This is a sound assumption given we're using 256-bit EC keys.
+ */
+ byte[] sequence = new byte[]{
+ 0x30, (byte) 0x82, 0x00, 0x00
+ };
+
+ /* The DER encoding for the ECDSA with SHA-256 signature algorithm:
+ *
+ * SEQUENCE (1 elem)
+ * OBJECT IDENTIFIER 1.2.840.10045.4.3.2 ecdsaWithSHA256 (ANSI X9.62 ECDSA
+ * algorithm with SHA256)
+ */
+ byte[] signatureAlgorithm = new byte[]{
+ 0x30, 0x0a, 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x04, 0x03,
+ 0x02
+ };
+
+ /* The DER encoding for a BIT STRING with one element - the length is updated below.
+ *
+ * We assume the length of signatureValue is always going to be less than 128. This
+ * assumption works since we know ecdsaWithSHA256 signatures are always 69, 70, or
+ * 71 bytes long when DER encoded.
+ */
+ byte[] bitStringForSignature = new byte[]{0x03, 0x00, 0x00};
+
+ // Calculate sequence length and set it in |sequence|.
+ int sequenceLength = tbsCertificate.length
+ + signatureAlgorithm.length
+ + bitStringForSignature.length
+ + signatureValue.length;
+ if (sequenceLength < 128 || sequenceLength > 65535) {
+ throw new Exception("Unexpected sequenceLength " + sequenceLength);
+ }
+ sequence[2] = (byte) (sequenceLength >> 8);
+ sequence[3] = (byte) (sequenceLength & 0xff);
+
+ // Calculate signatureValue length and set it in |bitStringForSignature|.
+ int signatureValueLength = signatureValue.length + 1;
+ if (signatureValueLength >= 128) {
+ throw new Exception("Unexpected signatureValueLength " + signatureValueLength);
+ }
+ bitStringForSignature[1] = (byte) signatureValueLength;
+
+ // Finally concatenate everything together.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ baos.write(sequence);
+ baos.write(tbsCertificate);
+ baos.write(signatureAlgorithm);
+ baos.write(bitStringForSignature);
+ baos.write(signatureValue);
+ byte[] resultingCertBytes = baos.toByteArray();
+
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ ByteArrayInputStream bais = new ByteArrayInputStream(resultingCertBytes);
+ X509Certificate result = (X509Certificate) cf.generateCertificate(bais);
+ return result;
+ } catch (Exception e) {
+ throw new RuntimeException("Error signing public key with private key", e);
+ }
+ }
+
+ public static byte[] buildDeviceAuthenticationCbor(String docType,
+ byte[] encodedSessionTranscript,
+ byte[] deviceNameSpacesBytes) {
+ ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
+ try {
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
+ List<DataItem> dataItems = null;
+ dataItems = new CborDecoder(bais).decode();
+ DataItem sessionTranscript = dataItems.get(0);
+ ByteString deviceNameSpacesBytesItem = new ByteString(deviceNameSpacesBytes);
+ deviceNameSpacesBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ new CborEncoder(daBaos).encode(new CborBuilder()
+ .addArray()
+ .add("DeviceAuthentication")
+ .add(sessionTranscript)
+ .add(docType)
+ .add(deviceNameSpacesBytesItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding DeviceAuthentication", e);
+ }
+ return daBaos.toByteArray();
+ }
+
+ public static byte[] buildReaderAuthenticationBytesCbor(
+ byte[] encodedSessionTranscript,
+ byte[] requestMessageBytes) {
+
+ ByteArrayOutputStream daBaos = new ByteArrayOutputStream();
+ try {
+ ByteArrayInputStream bais = new ByteArrayInputStream(encodedSessionTranscript);
+ List<DataItem> dataItems = null;
+ dataItems = new CborDecoder(bais).decode();
+ DataItem sessionTranscript = dataItems.get(0);
+ ByteString requestMessageBytesItem = new ByteString(requestMessageBytes);
+ requestMessageBytesItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ new CborEncoder(daBaos).encode(new CborBuilder()
+ .addArray()
+ .add("ReaderAuthentication")
+ .add(sessionTranscript)
+ .add(requestMessageBytesItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding ReaderAuthentication", e);
+ }
+ byte[] readerAuthentication = daBaos.toByteArray();
+ return Util.prependSemanticTagForEncodedCbor(readerAuthentication);
+ }
+
+ // Returns #6.24(bstr) of the given already encoded CBOR
+ //
+ public static @NonNull DataItem buildCborTaggedByteString(@NonNull byte[] encodedCbor) {
+ DataItem item = new ByteString(encodedCbor);
+ item.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ return item;
+ }
+
+ public static byte[] prependSemanticTagForEncodedCbor(byte[] encodedCbor) {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(buildCborTaggedByteString(encodedCbor));
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding with semantic tag for CBOR encoding", e);
+ }
+ return baos.toByteArray();
+ }
+
+ public static byte[] concatArrays(byte[] a, byte[] b) {
+ byte[] ret = new byte[a.length + b.length];
+ System.arraycopy(a, 0, ret, 0, a.length);
+ System.arraycopy(b, 0, ret, a.length, b.length);
+ return ret;
+ }
+
+ public static SecretKey calcEMacKeyForReader(PublicKey authenticationPublicKey,
+ PrivateKey ephemeralReaderPrivateKey,
+ byte[] encodedSessionTranscript) {
+ try {
+ KeyAgreement ka = KeyAgreement.getInstance("ECDH");
+ ka.init(ephemeralReaderPrivateKey);
+ ka.doPhase(authenticationPublicKey, true);
+ byte[] sharedSecret = ka.generateSecret();
+
+ byte[] sessionTranscriptBytes =
+ Util.prependSemanticTagForEncodedCbor(encodedSessionTranscript);
+
+ byte[] salt = MessageDigest.getInstance("SHA-256").digest(sessionTranscriptBytes);
+ byte[] info = new byte[] {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ byte[] derivedKey = Util.computeHkdf("HmacSha256", sharedSecret, salt, info, 32);
+ SecretKey secretKey = new SecretKeySpec(derivedKey, "");
+ return secretKey;
+ } catch (InvalidKeyException
+ | NoSuchAlgorithmException e) {
+ throw new RuntimeException("Error performing key agreement", e);
+ }
+ }
+
+ /**
+ * Computes an HKDF.
+ *
+ * This is based on https://github.com/google/tink/blob/master/java/src/main/java/com/google
+ * /crypto/tink/subtle/Hkdf.java
+ * which is also Copyright (c) Google and also licensed under the Apache 2 license.
+ *
+ * @param macAlgorithm the MAC algorithm used for computing the Hkdf. I.e., "HMACSHA1" or
+ * "HMACSHA256".
+ * @param ikm the input keying material.
+ * @param salt optional salt. A possibly non-secret random value. If no salt is
+ * provided (i.e. if
+ * salt has length 0) then an array of 0s of the same size as the hash
+ * digest is used as salt.
+ * @param info optional context and application specific information.
+ * @param size The length of the generated pseudorandom string in bytes. The maximal
+ * size is
+ * 255.DigestSize, where DigestSize is the size of the underlying HMAC.
+ * @return size pseudorandom bytes.
+ */
+ public static byte[] computeHkdf(
+ String macAlgorithm, final byte[] ikm, final byte[] salt, final byte[] info, int size) {
+ Mac mac = null;
+ try {
+ mac = Mac.getInstance(macAlgorithm);
+ } catch (NoSuchAlgorithmException e) {
+ throw new RuntimeException("No such algorithm: " + macAlgorithm, e);
+ }
+ if (size > 255 * mac.getMacLength()) {
+ throw new RuntimeException("size too large");
+ }
+ try {
+ if (salt == null || salt.length == 0) {
+ // According to RFC 5869, Section 2.2 the salt is optional. If no salt is provided
+ // then HKDF uses a salt that is an array of zeros of the same length as the hash
+ // digest.
+ mac.init(new SecretKeySpec(new byte[mac.getMacLength()], macAlgorithm));
+ } else {
+ mac.init(new SecretKeySpec(salt, macAlgorithm));
+ }
+ byte[] prk = mac.doFinal(ikm);
+ byte[] result = new byte[size];
+ int ctr = 1;
+ int pos = 0;
+ mac.init(new SecretKeySpec(prk, macAlgorithm));
+ byte[] digest = new byte[0];
+ while (true) {
+ mac.update(digest);
+ mac.update(info);
+ mac.update((byte) ctr);
+ digest = mac.doFinal();
+ if (pos + digest.length < size) {
+ System.arraycopy(digest, 0, result, pos, digest.length);
+ pos += digest.length;
+ ctr++;
+ } else {
+ System.arraycopy(digest, 0, result, pos, size - pos);
+ break;
+ }
+ }
+ return result;
+ } catch (InvalidKeyException e) {
+ throw new RuntimeException("Error MACing", e);
+ }
+ }
+
+ static byte[] stripLeadingZeroes(byte[] value) {
+ int n = 0;
+ while (n < value.length && value[n] == 0) {
+ n++;
+ }
+ int newLen = value.length - n;
+ byte[] ret = new byte[newLen];
+ int m = 0;
+ while (n < value.length) {
+ ret[m++] = value[n++];
+ }
+ return ret;
+ }
+
+ public static void hexdump(String name, byte[] data) {
+ int n, m, o;
+ StringBuilder sb = new StringBuilder();
+ Formatter fmt = new Formatter(sb);
+ for (n = 0; n < data.length; n += 16) {
+ fmt.format("%04x ", n);
+ for (m = 0; m < 16 && n + m < data.length; m++) {
+ fmt.format("%02x ", data[n + m]);
+ }
+ for (o = m; o < 16; o++) {
+ sb.append(" ");
+ }
+ sb.append(" ");
+ for (m = 0; m < 16 && n + m < data.length; m++) {
+ int c = data[n + m] & 0xff;
+ fmt.format("%c", Character.isISOControl(c) ? '.' : c);
+ }
+ sb.append("\n");
+ }
+ sb.append("\n");
+ Log.e(TAG, name + ": dumping " + data.length + " bytes\n" + fmt.toString());
+ }
+
+
+ // This returns a SessionTranscript which satisfy the requirement
+ // that the uncompressed X and Y coordinates of the public key for the
+ // mDL's ephemeral key-pair appear somewhere in the encoded
+ // DeviceEngagement.
+ public static byte[] buildSessionTranscript(KeyPair ephemeralKeyPair) {
+ // Make the coordinates appear in an already encoded bstr - this
+ // mimics how the mDL COSE_Key appear as encoded data inside the
+ // encoded DeviceEngagement
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try {
+ ECPoint w = ((ECPublicKey) ephemeralKeyPair.getPublic()).getW();
+ // X and Y are always positive so for interop we remove any leading zeroes
+ // inserted by the BigInteger encoder.
+ byte[] x = stripLeadingZeroes(w.getAffineX().toByteArray());
+ byte[] y = stripLeadingZeroes(w.getAffineY().toByteArray());
+ baos.write(new byte[]{42});
+ baos.write(x);
+ baos.write(y);
+ baos.write(new byte[]{43, 44});
+ } catch (IOException e) {
+ e.printStackTrace();
+ return null;
+ }
+ byte[] blobWithCoords = baos.toByteArray();
+
+ baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray()
+ .add(blobWithCoords)
+ .end()
+ .build());
+ } catch (CborException e) {
+ e.printStackTrace();
+ return null;
+ }
+ ByteString encodedDeviceEngagementItem = new ByteString(baos.toByteArray());
+ ByteString encodedEReaderKeyItem = new ByteString(cborEncodeString("doesn't matter"));
+ encodedDeviceEngagementItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ encodedEReaderKeyItem.setTag(CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ baos = new ByteArrayOutputStream();
+ try {
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray()
+ .add(encodedDeviceEngagementItem)
+ .add(encodedEReaderKeyItem)
+ .end()
+ .build());
+ } catch (CborException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return baos.toByteArray();
+ }
+
+ /*
+ * Helper function to create a CBOR data for requesting data items. The IntentToRetain
+ * value will be set to false for all elements.
+ *
+ * <p>The returned CBOR data conforms to the following CDDL schema:</p>
+ *
+ * <pre>
+ * ItemsRequest = {
+ * ? "docType" : DocType,
+ * "nameSpaces" : NameSpaces,
+ * ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
+ * }
+ *
+ * NameSpaces = {
+ * + NameSpace => DataElements ; Requested data elements for each NameSpace
+ * }
+ *
+ * DataElements = {
+ * + DataElement => IntentToRetain
+ * }
+ *
+ * DocType = tstr
+ *
+ * DataElement = tstr
+ * IntentToRetain = bool
+ * NameSpace = tstr
+ * </pre>
+ *
+ * @param entriesToRequest The entries to request, organized as a map of namespace
+ * names with each value being a collection of data elements
+ * in the given namespace.
+ * @param docType The document type or {@code null} if there is no document
+ * type.
+ * @return CBOR data conforming to the CDDL mentioned above.
+ */
+ public static @NonNull byte[] createItemsRequest(
+ @NonNull Map<String, Collection<String>> entriesToRequest,
+ @Nullable String docType) {
+ CborBuilder builder = new CborBuilder();
+ MapBuilder<CborBuilder> mapBuilder = builder.addMap();
+ if (docType != null) {
+ mapBuilder.put("docType", docType);
+ }
+
+ MapBuilder<MapBuilder<CborBuilder>> nsMapBuilder = mapBuilder.putMap("nameSpaces");
+ for (String namespaceName : entriesToRequest.keySet()) {
+ Collection<String> entryNames = entriesToRequest.get(namespaceName);
+ MapBuilder<MapBuilder<MapBuilder<CborBuilder>>> entryNameMapBuilder =
+ nsMapBuilder.putMap(namespaceName);
+ for (String entryName : entryNames) {
+ entryNameMapBuilder.put(entryName, false);
+ }
+ }
+
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ CborEncoder encoder = new CborEncoder(baos);
+ try {
+ encoder.encode(builder.build());
+ } catch (CborException e) {
+ throw new RuntimeException("Error encoding CBOR", e);
+ }
+ return baos.toByteArray();
+ }
+
+ public static KeyPair createEphemeralKeyPair() {
+ try {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
+ ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
+ kpg.initialize(ecSpec);
+ KeyPair keyPair = kpg.generateKeyPair();
+ return keyPair;
+ } catch (NoSuchAlgorithmException
+ | InvalidAlgorithmParameterException e) {
+ throw new RuntimeException("Error generating ephemeral key-pair", e);
+ }
+ }
+
+ public static byte[] getPopSha256FromAuthKeyCert(X509Certificate cert) {
+ byte[] octetString = cert.getExtensionValue("1.3.6.1.4.1.11129.2.1.26");
+ if (octetString == null) {
+ return null;
+ }
+ Util.hexdump("octetString", octetString);
+
+ try {
+ ASN1InputStream asn1InputStream = new ASN1InputStream(octetString);
+ byte[] cborBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+ Util.hexdump("cborBytes", cborBytes);
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(cborBytes);
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ if (dataItems.size() != 1) {
+ throw new RuntimeException("Expected 1 item, found " + dataItems.size());
+ }
+ if (!(dataItems.get(0) instanceof co.nstant.in.cbor.model.Array)) {
+ throw new RuntimeException("Item is not a map");
+ }
+ co.nstant.in.cbor.model.Array array = (co.nstant.in.cbor.model.Array) dataItems.get(0);
+ List<DataItem> items = array.getDataItems();
+ if (items.size() < 2) {
+ throw new RuntimeException(
+ "Expected at least 2 array items, found " + items.size());
+ }
+ if (!(items.get(0) instanceof UnicodeString)) {
+ throw new RuntimeException("First array item is not a string");
+ }
+ String id = ((UnicodeString) items.get(0)).getString();
+ if (!id.equals("ProofOfBinding")) {
+ throw new RuntimeException("Expected ProofOfBinding, got " + id);
+ }
+ if (!(items.get(1) instanceof ByteString)) {
+ throw new RuntimeException("Second array item is not a bytestring");
+ }
+ byte[] popSha256 = ((ByteString) items.get(1)).getBytes();
+ if (popSha256.length != 32) {
+ throw new RuntimeException(
+ "Expected bstr to be 32 bytes, it is " + popSha256.length);
+ }
+ return popSha256;
+ } catch (IOException e) {
+ throw new RuntimeException("Error decoding extension data", e);
+ } catch (CborException e) {
+ throw new RuntimeException("Error decoding data", e);
+ }
+ }
+
+}
diff --git a/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java b/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java
new file mode 100644
index 0000000..6a75090
--- /dev/null
+++ b/identity/util/test/java/com/android/security/identity/internal/HkdfTest.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.security.identity.internal;
+
+import androidx.test.runner.AndroidJUnit4;
+import com.android.security.identity.internal.Util;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.GeneralSecurityException;
+import java.util.Random;
+
+/*
+ * This is based on https://github.com/google/tink/blob/master/java/src/test/java/com/google
+ * /crypto/tink/subtle/HkdfTest.java
+ * which is also Copyright (c) Google and licensed under the Apache 2 license.
+ */
+@RunWith(AndroidJUnit4.class)
+public class HkdfTest {
+
+ static Random sRandom = new Random();
+
+ /** Encodes a byte array to hex. */
+ static String hexEncode(final byte[] bytes) {
+ String chars = "0123456789abcdef";
+ StringBuilder result = new StringBuilder(2 * bytes.length);
+ for (byte b : bytes) {
+ // convert to unsigned
+ int val = b & 0xff;
+ result.append(chars.charAt(val / 16));
+ result.append(chars.charAt(val % 16));
+ }
+ return result.toString();
+ }
+
+ /** Decodes a hex string to a byte array. */
+ static byte[] hexDecode(String hex) {
+ if (hex.length() % 2 != 0) {
+ throw new IllegalArgumentException("Expected a string of even length");
+ }
+ int size = hex.length() / 2;
+ byte[] result = new byte[size];
+ for (int i = 0; i < size; i++) {
+ int hi = Character.digit(hex.charAt(2 * i), 16);
+ int lo = Character.digit(hex.charAt(2 * i + 1), 16);
+ if ((hi == -1) || (lo == -1)) {
+ throw new IllegalArgumentException("input is not hexadecimal");
+ }
+ result[i] = (byte) (16 * hi + lo);
+ }
+ return result;
+ }
+
+ static byte[] randBytes(int numBytes) {
+ byte[] bytes = new byte[numBytes];
+ sRandom.nextBytes(bytes);
+ return bytes;
+ }
+
+ @Test
+ public void testNullSaltOrInfo() throws Exception {
+ byte[] ikm = randBytes(20);
+ byte[] info = randBytes(20);
+ int size = 40;
+
+ byte[] hkdfWithNullSalt = Util.computeHkdf("HmacSha256", ikm, null, info, size);
+ byte[] hkdfWithEmptySalt = Util.computeHkdf("HmacSha256", ikm, new byte[0], info, size);
+ assertArrayEquals(hkdfWithNullSalt, hkdfWithEmptySalt);
+
+ byte[] salt = randBytes(20);
+ byte[] hkdfWithNullInfo = Util.computeHkdf("HmacSha256", ikm, salt, null, size);
+ byte[] hkdfWithEmptyInfo = Util.computeHkdf("HmacSha256", ikm, salt, new byte[0], size);
+ assertArrayEquals(hkdfWithNullInfo, hkdfWithEmptyInfo);
+ }
+
+ @Test
+ public void testInvalidCodeSize() throws Exception {
+ try {
+ Util.computeHkdf("HmacSha256", new byte[0], new byte[0], new byte[0], 32 * 256);
+ fail("Invalid size, should have thrown exception");
+ } catch (RuntimeException expected) {
+
+ // Expected
+ }
+ }
+
+ /**
+ * Tests the implementation against the test vectors from RFC 5869.
+ */
+ @Test
+ public void testVectors() throws Exception {
+ // Test case 1
+ assertEquals(
+ "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf"
+ + "1a5a4c5db02d56ecc4c5bf34007208d5b887185865",
+ computeHkdfHex("HmacSha256",
+ "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b",
+ "000102030405060708090a0b0c",
+ "f0f1f2f3f4f5f6f7f8f9",
+ 42));
+
+ // Test case 2
+ assertEquals(
+ "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c"
+ + "59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71"
+ + "cc30c58179ec3e87c14c01d5c1f3434f1d87",
+ computeHkdfHex("HmacSha256",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
+ + "404142434445464748494a4b4c4d4e4f",
+ "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
+ + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+ + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+ + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+ 82));
+
+ // Test case 3: salt is empty
+ assertEquals(
+ "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d"
+ + "9d201395faa4b61a96c8",
+ computeHkdfHex("HmacSha256",
+ "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
+ 42));
+
+ // Test Case 4
+ assertEquals(
+ "085a01ea1b10f36933068b56efa5ad81a4f14b822f"
+ + "5b091568a9cdd4f155fda2c22e422478d305f3f896",
+ computeHkdfHex(
+ "HmacSha1",
+ "0b0b0b0b0b0b0b0b0b0b0b",
+ "000102030405060708090a0b0c",
+ "f0f1f2f3f4f5f6f7f8f9",
+ 42));
+
+ // Test Case 5
+ assertEquals(
+ "0bd770a74d1160f7c9f12cd5912a06ebff6adcae899d92191fe4305673ba2ffe"
+ + "8fa3f1a4e5ad79f3f334b3b202b2173c486ea37ce3d397ed034c7f9dfeb15c5e"
+ + "927336d0441f4c4300e2cff0d0900b52d3b4",
+ computeHkdfHex(
+ "HmacSha1",
+ "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
+ + "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
+ + "404142434445464748494a4b4c4d4e4f",
+ "606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"
+ + "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"
+ + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf",
+ "b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecf"
+ + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeef"
+ + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff",
+ 82));
+
+ // Test Case 6: salt is empty
+ assertEquals(
+ "0ac1af7002b3d761d1e55298da9d0506b9ae52057220a306e07b6b87e8df21d0"
+ + "ea00033de03984d34918",
+ computeHkdfHex("HmacSha1", "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b", "", "",
+ 42));
+
+ // Test Case 7
+ assertEquals(
+ "2c91117204d745f3500d636a62f64f0ab3bae548aa53d423b0d1f27ebba6f5e5"
+ + "673a081d70cce7acfc48",
+ computeHkdfHex("HmacSha1", "0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c0c", "", "",
+ 42));
+ }
+
+ /**
+ * Test version of Hkdf where all inputs and outputs are hexadecimal.
+ */
+ private String computeHkdfHex(String macAlgorithm, String ikmHex, String saltHex,
+ String infoHex,
+ int size) throws GeneralSecurityException {
+ return hexEncode(
+ Util.computeHkdf(macAlgorithm, hexDecode(ikmHex), hexDecode(saltHex),
+ hexDecode(infoHex), size));
+ }
+
+}
diff --git a/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java b/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java
new file mode 100644
index 0000000..9c27c14
--- /dev/null
+++ b/identity/util/test/java/com/android/security/identity/internal/UtilUnitTests.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 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.
+ */
+
+package com.android.security.identity.internal;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import com.android.security.identity.internal.Util;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import java.security.cert.X509Certificate;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import co.nstant.in.cbor.CborBuilder;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborEncoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.builder.ArrayBuilder;
+import co.nstant.in.cbor.model.ByteString;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.DoublePrecisionFloat;
+import co.nstant.in.cbor.model.HalfPrecisionFloat;
+import co.nstant.in.cbor.model.NegativeInteger;
+import co.nstant.in.cbor.model.SimpleValue;
+import co.nstant.in.cbor.model.SimpleValueType;
+import co.nstant.in.cbor.model.SinglePrecisionFloat;
+import co.nstant.in.cbor.model.UnicodeString;
+import co.nstant.in.cbor.model.UnsignedInteger;
+
+@RunWith(AndroidJUnit4.class)
+public class UtilUnitTests {
+ @Test
+ public void prettyPrintMultipleCompleteTypes() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new CborBuilder()
+ .add("text") // add string
+ .add(1234) // add integer
+ .add(new byte[]{0x10}) // add byte array
+ .addArray() // add array
+ .add(1)
+ .add("text")
+ .end()
+ .build());
+ assertEquals("'text',\n"
+ + "1234,\n"
+ + "[0x10],\n"
+ + "[1, 'text']", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintString() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new UnicodeString("foobar"));
+ assertEquals("'foobar'", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintBytestring() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new ByteString(new byte[]{1, 2, 33, (byte) 254}));
+ assertEquals("[0x01, 0x02, 0x21, 0xfe]", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintUnsignedInteger() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new UnsignedInteger(42));
+ assertEquals("42", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintNegativeInteger() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new NegativeInteger(-42));
+ assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintDouble() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new DoublePrecisionFloat(1.1));
+ assertEquals("1.1", Util.cborPrettyPrint(baos.toByteArray()));
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new DoublePrecisionFloat(-42.0000000001));
+ assertEquals("-42.0000000001", Util.cborPrettyPrint(baos.toByteArray()));
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new DoublePrecisionFloat(-5));
+ assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintFloat() throws CborException {
+ ByteArrayOutputStream baos;
+
+ // TODO: These two tests yield different results on different devices, disable for now
+ /*
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SinglePrecisionFloat(1.1f));
+ assertEquals("1.100000023841858", Util.cborPrettyPrint(baos.toByteArray()));
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SinglePrecisionFloat(-42.0001f));
+ assertEquals("-42.000099182128906", Util.cborPrettyPrint(baos.toByteArray()));
+ */
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SinglePrecisionFloat(-5f));
+ assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintHalfFloat() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new HalfPrecisionFloat(1.1f));
+ assertEquals("1.099609375", Util.cborPrettyPrint(baos.toByteArray()));
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new HalfPrecisionFloat(-42.0001f));
+ assertEquals("-42", Util.cborPrettyPrint(baos.toByteArray()));
+
+ baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new HalfPrecisionFloat(-5f));
+ assertEquals("-5", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintFalse() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.FALSE));
+ assertEquals("false", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintTrue() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.TRUE));
+ assertEquals("true", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintNull() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.NULL));
+ assertEquals("null", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintUndefined() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new SimpleValue(SimpleValueType.UNDEFINED));
+ assertEquals("undefined", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintTag() throws CborException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addTag(0)
+ .add("ABC")
+ .build());
+ byte[] data = baos.toByteArray();
+ assertEquals("tag 0 'ABC'", Util.cborPrettyPrint(data));
+ }
+
+ @Test
+ public void prettyPrintArrayNoCompounds() throws CborException {
+ // If an array has no compound elements, no newlines are used.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray() // add array
+ .add(1)
+ .add("text")
+ .add(new ByteString(new byte[]{1, 2, 3}))
+ .end()
+ .build());
+ assertEquals("[1, 'text', [0x01, 0x02, 0x03]]", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintArray() throws CborException {
+ // This array contains a compound value so will use newlines
+ CborBuilder array = new CborBuilder();
+ ArrayBuilder<CborBuilder> arrayBuilder = array.addArray();
+ arrayBuilder.add(2);
+ arrayBuilder.add(3);
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addArray() // add array
+ .add(1)
+ .add("text")
+ .add(new ByteString(new byte[]{1, 2, 3}))
+ .add(array.build().get(0))
+ .end()
+ .build());
+ assertEquals("[\n"
+ + " 1,\n"
+ + " 'text',\n"
+ + " [0x01, 0x02, 0x03],\n"
+ + " [2, 3]\n"
+ + "]", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void prettyPrintMap() throws CborException {
+ // If an array has no compound elements, no newlines are used.
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ new CborEncoder(baos).encode(new CborBuilder()
+ .addMap()
+ .put("Foo", 42)
+ .put("Bar", "baz")
+ .put(43, 44)
+ .put(new UnicodeString("bstr"), new ByteString(new byte[]{1, 2, 3}))
+ .put(new ByteString(new byte[]{1, 2, 3}), new UnicodeString("other way"))
+ .end()
+ .build());
+ assertEquals("{\n"
+ + " 43 : 44,\n"
+ + " [0x01, 0x02, 0x03] : 'other way',\n"
+ + " 'Bar' : 'baz',\n"
+ + " 'Foo' : 42,\n"
+ + " 'bstr' : [0x01, 0x02, 0x03]\n"
+ + "}", Util.cborPrettyPrint(baos.toByteArray()));
+ }
+
+ @Test
+ public void testCanonicalizeCbor() throws Exception {
+ // {"one":1, 2:"two"}
+ byte[] first =
+ new byte[]{(byte) 0xA2, 0x63, 0x6F, 0x6E, 0x65, 0x01, 0x02, 0x63, 0x74, 0x77, 0x6F};
+
+ // {2: "two", "one": 1}
+ byte[] second =
+ new byte[]{(byte) 0xA2, 0x02, 0x63, 0x74, 0x77, 0x6F, 0x63, 0x6F, 0x6E, 0x65, 0x01};
+
+ assertArrayEquals(Util.canonicalizeCbor(first), Util.canonicalizeCbor(second));
+ }
+
+ @Test
+ public void cborEncodeDecodeSingle() throws Exception {
+ List<DataItem> items = new CborBuilder()
+ .addMap().put(1,"one").put("one", 1).end()
+ .addArray().add(42).add(true).addMap().end().end()
+ .add("STRING")
+ .build();
+ for (DataItem item: items) {
+ assertEquals(item, Util.cborToDataItem(Util.cborEncode(item)));
+ }
+ }
+
+ @Test
+ public void cborEncodeDecodeBoolean() {
+ assertEquals(true, Util.cborDecodeBoolean(Util.cborEncodeBoolean(true)));
+ assertEquals(false, Util.cborDecodeBoolean(Util.cborEncodeBoolean(false)));
+ }
+
+ @Test
+ public void cborEncodeDecodeString() {
+ assertEquals("foo bar", Util.cborDecodeString(Util.cborEncodeString("foo bar")));
+ }
+
+ @Test
+ public void cborEncodeDecodeBytestring() {
+ byte[] bits = new byte[256];
+ for (int i = 0; i < bits.length; ++i) {
+ bits[i] = (byte)i;
+ }
+ assertArrayEquals(bits, Util.cborDecodeBytestring(Util.cborEncodeBytestring(bits)));
+ }
+
+ @Test
+ public void cborEncodeDecodeInt() {
+ assertEquals(0, Util.cborDecodeInt(Util.cborEncodeInt(0)));
+ assertEquals(Integer.MAX_VALUE, Util.cborDecodeInt(Util.cborEncodeInt(Integer.MAX_VALUE)));
+ assertEquals(Integer.MIN_VALUE, Util.cborDecodeInt(Util.cborEncodeInt(Integer.MIN_VALUE)));
+ }
+
+ @Test
+ public void prependSemanticTagForEncodedCbor() throws Exception {
+ byte[] inputBytes = new byte[] {1, 2, 3, 4};
+ byte[] encodedInput = Util.cborEncodeBytestring(inputBytes);
+ byte[] encodedWithTag = Util.prependSemanticTagForEncodedCbor(encodedInput);
+
+ ByteString decodedWithTag = (ByteString)Util.cborToDataItem(encodedWithTag);
+ assertEquals(decodedWithTag.getTag().getValue(), 24); // RFC 8949 defines 24
+
+ byte[] decodedBytes = Util.cborDecodeBytestring(decodedWithTag.getBytes());
+ assertArrayEquals(inputBytes, decodedBytes);
+ }
+
+ private KeyPair coseGenerateKeyPair() throws Exception {
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance(
+ KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
+ KeyGenParameterSpec.Builder builder =
+ new KeyGenParameterSpec.Builder(
+ "coseTestKeyPair",
+ KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+ .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512);
+ kpg.initialize(builder.build());
+ return kpg.generateKeyPair();
+ }
+
+ @Test
+ public void coseSignAndVerify() throws Exception {
+ KeyPair keyPair = coseGenerateKeyPair();
+ byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
+ byte[] detachedContent = new byte[] {};
+ byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
+ assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
+ assertArrayEquals(data, Util.coseSign1GetData(sig));
+ assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
+ }
+
+ @Test
+ public void coseSignAndVerifyDetachedContent() throws Exception {
+ KeyPair keyPair = coseGenerateKeyPair();
+ byte[] data = new byte[] {};
+ byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
+ byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, null);
+ assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
+ assertArrayEquals(data, Util.coseSign1GetData(sig));
+ assertEquals(new ArrayList() {}, Util.coseSign1GetX5Chain(sig));
+ }
+
+ @Test
+ public void coseSignAndVerifySingleCertificate() throws Exception {
+ KeyPair keyPair = coseGenerateKeyPair();
+ byte[] data = new byte[] {};
+ byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
+ ArrayList<X509Certificate> certs = new ArrayList() {};
+ certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
+ byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
+ assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
+ assertArrayEquals(data, Util.coseSign1GetData(sig));
+ assertEquals(certs, Util.coseSign1GetX5Chain(sig));
+ }
+
+ @Test
+ public void coseSignAndVerifyMultipleCertificates() throws Exception {
+ KeyPair keyPair = coseGenerateKeyPair();
+ byte[] data = new byte[] {};
+ byte[] detachedContent = new byte[] {0x20, 0x21, 0x22, 0x23, 0x24};
+ ArrayList<X509Certificate> certs = new ArrayList() {};
+ certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
+ certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
+ certs.add(Util.signPublicKeyWithPrivateKey("coseTestKeyPair", "coseTestKeyPair"));
+ byte[] sig = Util.coseSign1Sign(keyPair.getPrivate(), data, detachedContent, certs);
+ assertTrue(Util.coseSign1CheckSignature(sig, detachedContent, keyPair.getPublic()));
+ assertArrayEquals(data, Util.coseSign1GetData(sig));
+ assertEquals(certs, Util.coseSign1GetX5Chain(sig));
+ }
+
+ @Test
+ public void coseMac0() throws Exception {
+ SecretKey secretKey = new SecretKeySpec(new byte[32], "");
+ byte[] data = new byte[] {0x10, 0x11, 0x12, 0x13};
+ byte[] detachedContent = new byte[] {};
+ byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
+ assertEquals("[\n"
+ + " [0xa1, 0x01, 0x05],\n"
+ + " {},\n"
+ + " [0x10, 0x11, 0x12, 0x13],\n"
+ + " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
+ + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
+ + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
+ + "]", Util.cborPrettyPrint(mac));
+ }
+
+ @Test
+ public void coseMac0DetachedContent() throws Exception {
+ SecretKey secretKey = new SecretKeySpec(new byte[32], "");
+ byte[] data = new byte[] {};
+ byte[] detachedContent = new byte[] {0x10, 0x11, 0x12, 0x13};
+ byte[] mac = Util.coseMac0(secretKey, data, detachedContent);
+ // Same HMAC as in coseMac0 test, only difference is that payload is null.
+ assertEquals("[\n"
+ + " [0xa1, 0x01, 0x05],\n"
+ + " {},\n"
+ + " null,\n"
+ + " [0x6c, 0xec, 0xb5, 0x6a, 0xc9, 0x5c, 0xae, 0x3b, 0x41, 0x13, 0xde, 0xa4, "
+ + "0xd8, 0x86, 0x5c, 0x28, 0x2c, 0xd5, 0xa5, 0x13, 0xff, 0x3b, 0xd1, 0xde, 0x70, "
+ + "0x5e, 0xbb, 0xe2, 0x2d, 0x42, 0xbe, 0x53]\n"
+ + "]", Util.cborPrettyPrint(mac));
+ }
+
+ @Test
+ public void replaceLineTest() {
+ assertEquals("foo",
+ Util.replaceLine("Hello World", 0, "foo"));
+ assertEquals("foo\n",
+ Util.replaceLine("Hello World\n", 0, "foo"));
+ assertEquals("Hello World",
+ Util.replaceLine("Hello World", 1, "foo"));
+ assertEquals("Hello World\n",
+ Util.replaceLine("Hello World\n", 1, "foo"));
+ assertEquals("foo\ntwo\nthree",
+ Util.replaceLine("one\ntwo\nthree", 0, "foo"));
+ assertEquals("one\nfoo\nthree",
+ Util.replaceLine("one\ntwo\nthree", 1, "foo"));
+ assertEquals("one\ntwo\nfoo",
+ Util.replaceLine("one\ntwo\nthree", 2, "foo"));
+ assertEquals("one\ntwo\nfoo",
+ Util.replaceLine("one\ntwo\nthree", -1, "foo"));
+ assertEquals("one\ntwo\nthree\nfoo",
+ Util.replaceLine("one\ntwo\nthree\nfour", -1, "foo"));
+ assertEquals("one\ntwo\nfoo\nfour",
+ Util.replaceLine("one\ntwo\nthree\nfour", -2, "foo"));
+ }
+
+}
diff --git a/keystore-engine/Android.bp b/keystore-engine/Android.bp
index 0cecfd8..cb75cde 100644
--- a/keystore-engine/Android.bp
+++ b/keystore-engine/Android.bp
@@ -36,7 +36,7 @@
],
shared_libs: [
- "android.system.keystore2-V1-ndk_platform",
+ "android.system.keystore2-V1-ndk",
"libbinder_ndk",
"libcrypto",
"libcutils",
@@ -66,7 +66,7 @@
],
shared_libs: [
- "android.system.keystore2-V1-ndk_platform",
+ "android.system.keystore2-V1-ndk",
"libbase",
"libbinder_ndk",
"libcrypto",
diff --git a/keystore-engine/keystore2_engine.cpp b/keystore-engine/keystore2_engine.cpp
index 49c5e9a..69caf51 100644
--- a/keystore-engine/keystore2_engine.cpp
+++ b/keystore-engine/keystore2_engine.cpp
@@ -384,7 +384,7 @@
// If the key_id starts with the grant id prefix, we parse the following string as numeric
// grant id. We can then use the grant domain without alias to load the designated key.
- if (alias.find(keystore2_grant_id_prefix) == 0) {
+ if (android::base::StartsWith(alias, keystore2_grant_id_prefix)) {
std::stringstream s(alias.substr(keystore2_grant_id_prefix.size()));
s >> std::hex >> reinterpret_cast<uint64_t&>(descriptor.nspace);
descriptor.domain = ks2::Domain::GRANT;
diff --git a/keystore/Android.bp b/keystore/Android.bp
index 0f2000c..892c5b4 100644
--- a/keystore/Android.bp
+++ b/keystore/Android.bp
@@ -36,7 +36,10 @@
cc_binary {
name: "keystore_cli_v2",
- defaults: ["keystore_defaults"],
+ defaults: [
+ "keystore_defaults",
+ "keystore2_use_latest_aidl_ndk_shared",
+ ],
cflags: [
"-DKEYMASTER_NAME_TAGS",
@@ -47,8 +50,7 @@
"keystore_client.proto",
],
shared_libs: [
- "android.security.apc-ndk_platform",
- "android.system.keystore2-V1-ndk_platform",
+ "android.security.apc-ndk",
"libbinder",
"libbinder_ndk",
"libchrome",
@@ -63,7 +65,7 @@
// Library used by both keystore and credstore for generating the ASN.1 stored
// in Tag::ATTESTATION_APPLICATION_ID
-cc_library_shared {
+cc_library {
name: "libkeystore-attestation-application-id",
defaults: ["keystore_defaults"],
@@ -87,7 +89,7 @@
}
// Library for keystore clients using the WiFi HIDL interface
-cc_library_shared {
+cc_library {
name: "libkeystore-wifi-hidl",
defaults: ["keystore_defaults"],
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 43f72a9..1e9126d 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -19,6 +19,7 @@
#include <iostream>
#include <memory>
#include <string>
+#include <variant>
#include <vector>
#include <base/command_line.h>
@@ -616,9 +617,9 @@
return std::move(parameters);
}
-keymint::AuthorizationSet GetECDSAParameters(uint32_t key_size, bool sha256_only) {
+keymint::AuthorizationSet GetECDSAParameters(keymint::EcCurve curve, bool sha256_only) {
keymint::AuthorizationSetBuilder parameters;
- parameters.EcdsaSigningKey(key_size)
+ parameters.EcdsaSigningKey(curve)
.Digest(keymint::Digest::SHA_2_256)
.Authorization(keymint::TAG_NO_AUTH_REQUIRED);
if (!sha256_only) {
@@ -662,11 +663,12 @@
{"RSA-2048 Encrypt", true, GetRSAEncryptParameters(2048)},
{"RSA-3072 Encrypt", false, GetRSAEncryptParameters(3072)},
{"RSA-4096 Encrypt", false, GetRSAEncryptParameters(4096)},
- {"ECDSA-P256 Sign", true, GetECDSAParameters(256, true)},
- {"ECDSA-P256 Sign (more digests)", false, GetECDSAParameters(256, false)},
- {"ECDSA-P224 Sign", false, GetECDSAParameters(224, false)},
- {"ECDSA-P384 Sign", false, GetECDSAParameters(384, false)},
- {"ECDSA-P521 Sign", false, GetECDSAParameters(521, false)},
+ {"ECDSA-P256 Sign", true, GetECDSAParameters(keymint::EcCurve::P_256, true)},
+ {"ECDSA-P256 Sign (more digests)", false,
+ GetECDSAParameters(keymint::EcCurve::P_256, false)},
+ {"ECDSA-P224 Sign", false, GetECDSAParameters(keymint::EcCurve::P_224, false)},
+ {"ECDSA-P384 Sign", false, GetECDSAParameters(keymint::EcCurve::P_384, false)},
+ {"ECDSA-P521 Sign", false, GetECDSAParameters(keymint::EcCurve::P_521, false)},
{"AES-128", true, GetAESParameters(128, false)},
{"AES-256", true, GetAESParameters(256, false)},
{"AES-128-GCM", false, GetAESParameters(128, true)},
diff --git a/keystore/tests/Android.bp b/keystore/tests/Android.bp
index 39601eb..f51cc2f 100644
--- a/keystore/tests/Android.bp
+++ b/keystore/tests/Android.bp
@@ -62,7 +62,7 @@
"libgtest_main",
"libutils",
"liblog",
- "android.security.apc-ndk_platform",
+ "android.security.apc-ndk",
],
shared_libs: [
"libbinder_ndk",
diff --git a/keystore/tests/fuzzer/Android.bp b/keystore/tests/fuzzer/Android.bp
new file mode 100644
index 0000000..589cef7
--- /dev/null
+++ b/keystore/tests/fuzzer/Android.bp
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+cc_fuzz {
+ name: "keystoreGetWifiHidl_fuzzer",
+ vendor: true,
+ srcs: [
+ "keystoreGetWifiHidl_fuzzer.cpp",
+ ],
+ static_libs: [
+ "libkeystore-wifi-hidl",
+ "libutils",
+ ],
+ shared_libs: [
+ "android.system.wifi.keystore@1.0",
+ "libhidlbase",
+ "liblog",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_defaults {
+ name: "keystoreAttestation_defaults",
+ static_libs: [
+ "libkeystore-attestation-application-id",
+ "liblog",
+ "libutils",
+ "libbase",
+ "libhidlbase",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libcrypto",
+ ],
+ fuzz_config: {
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
+ },
+}
+
+cc_fuzz {
+ name: "keystoreSignature_fuzzer",
+ srcs: [
+ "keystoreSignature_fuzzer.cpp",
+ ],
+ defaults: [
+ "keystoreAttestation_defaults",
+ ],
+}
+
+cc_fuzz {
+ name: "keystorePackageInfo_fuzzer",
+ srcs: [
+ "keystorePackageInfo_fuzzer.cpp",
+ ],
+ defaults: [
+ "keystoreAttestation_defaults",
+ ],
+}
+
+cc_fuzz {
+ name: "keystoreApplicationId_fuzzer",
+ srcs: [
+ "keystoreApplicationId_fuzzer.cpp",
+ ],
+ defaults: [
+ "keystoreAttestation_defaults",
+ ],
+}
+
+cc_fuzz {
+ name: "keystoreAttestationId_fuzzer",
+ srcs: [
+ "keystoreAttestationId_fuzzer.cpp",
+ ],
+ defaults: [
+ "keystoreAttestation_defaults",
+ ],
+}
diff --git a/keystore/tests/fuzzer/README.md b/keystore/tests/fuzzer/README.md
new file mode 100644
index 0000000..25d53ab
--- /dev/null
+++ b/keystore/tests/fuzzer/README.md
@@ -0,0 +1,103 @@
+# Fuzzer for libkeystore
+## Table of contents
++ [libkeystore-get-wifi-hidl](#libkeystore-get-wifi-hidl)
++ [libkeystore_attestation_application_id](#libkeystore_attestation_application_id)
+
+# <a name="libkeystore-get-wifi-hidl"></a> Fuzzer for libkeystore-get-wifi-hidl
+## Plugin Design Considerations
+The fuzzer plugin for libkeystore-get-wifi-hidl is designed based on the understanding of the library and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libkeystore-get-wifi-hidl supports the following parameters:
+1. Key (parameter name: `key`)
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `key` | `String` | Value obtained from FuzzedDataProvider|
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugin feeds the entire input data to the libkeystore-get-wifi-hidl module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build keystoreGetWifiHidl_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) keystoreGetWifiHidl_fuzzer
+```
+#### Steps to run
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreGetWifiHidl_fuzzer/keystoreGetWifiHidl_fuzzer
+```
+
+# <a name="libkeystore_attestation_application_id"></a> Fuzzer for libkeystore_attestation_application_id
+## Plugin Design Considerations
+The fuzzer plugin for libkeystore-attestation-application-id are designed based on the understanding of the library and tries to achieve the following:
+
+##### Maximize code coverage
+The configuration parameters are not hardcoded, but instead selected based on
+incoming data. This ensures more code paths are reached by the fuzzer.
+
+libkeystore-attestation-application-id supports the following parameters:
+1. Package Name (parameter name: `packageName`)
+2. Version Code (parameter name: `versionCode`)
+3. Uid (parameter name: `uid`)
+
+
+| Parameter| Valid Values| Configured Value|
+|------------- |-------------| ----- |
+| `packageName` | `String` | Value obtained from FuzzedDataProvider|
+| `versionCode` | `INT64_MIN` to `INT64_MAX` | Value obtained from FuzzedDataProvider|
+| `uid` | `0` to `1000` | Value obtained from FuzzedDataProvider|
+
+This also ensures that the plugin is always deterministic for any given input.
+
+##### Maximize utilization of input data
+The plugins feed the entire input data to the libkeystore_attestation_application_id module.
+This ensures that the plugin tolerates any kind of input (empty, huge,
+malformed, etc) and doesnt `exit()` on any input and thereby increasing the
+chance of identifying vulnerabilities.
+
+## Build
+
+This describes steps to build keystoreSignature_fuzzer, keystorePackageInfo_fuzzer, keystoreApplicationId_fuzzer and keystoreAttestationId_fuzzer binary.
+
+### Android
+
+#### Steps to build
+Build the fuzzer
+```
+ $ mm -j$(nproc) keystoreSignature_fuzzer
+ $ mm -j$(nproc) keystorePackageInfo_fuzzer
+ $ mm -j$(nproc) keystoreApplicationId_fuzzer
+ $ mm -j$(nproc) keystoreAttestationId_fuzzer
+```
+#### Steps to run
+
+To run on device
+```
+ $ adb sync data
+ $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreSignature_fuzzer/keystoreSignature_fuzzer
+ $ adb shell /data/fuzz/${TARGET_ARCH}/keystorePackageInfo_fuzzer/keystorePackageInfo_fuzzer
+ $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreApplicationId_fuzzer/keystoreApplicationId_fuzzer
+ $ adb shell /data/fuzz/${TARGET_ARCH}/keystoreAttestationId_fuzzer/keystoreAttestationId_fuzzer
+```
+
+## References:
+ * http://llvm.org/docs/LibFuzzer.html
+ * https://github.com/google/oss-fuzz
diff --git a/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp
new file mode 100644
index 0000000..0eddb9a
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreApplicationId_fuzzer.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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 "keystoreCommon.h"
+#include <keystore/KeyAttestationApplicationId.h>
+
+using ::security::keymaster::KeyAttestationApplicationId;
+
+constexpr size_t kPackageVectorSizeMin = 1;
+constexpr size_t kPackageVectorSizeMax = 10;
+
+class KeystoreApplicationId {
+ public:
+ void process(const uint8_t* data, size_t size);
+ ~KeystoreApplicationId() {}
+
+ private:
+ void invokeApplicationId();
+ std::unique_ptr<FuzzedDataProvider> mFdp;
+};
+
+void KeystoreApplicationId::invokeApplicationId() {
+ std::optional<KeyAttestationApplicationId> applicationId;
+ bool shouldUsePackageInfoVector = mFdp->ConsumeBool();
+ if (shouldUsePackageInfoVector) {
+ KeyAttestationApplicationId::PackageInfoVector packageInfoVector;
+ int32_t packageVectorSize =
+ mFdp->ConsumeIntegralInRange<int32_t>(kPackageVectorSizeMin, kPackageVectorSizeMax);
+ for (int32_t packageSize = 0; packageSize < packageVectorSize; ++packageSize) {
+ auto packageInfoData = initPackageInfoData(mFdp.get());
+ packageInfoVector.push_back(make_optional<KeyAttestationPackageInfo>(
+ String16((packageInfoData.packageName).c_str()), packageInfoData.versionCode,
+ packageInfoData.sharedSignaturesVector));
+ }
+ applicationId = KeyAttestationApplicationId(std::move(packageInfoVector));
+ } else {
+ auto packageInfoData = initPackageInfoData(mFdp.get());
+ applicationId = KeyAttestationApplicationId(make_optional<KeyAttestationPackageInfo>(
+ String16((packageInfoData.packageName).c_str()), packageInfoData.versionCode,
+ packageInfoData.sharedSignaturesVector));
+ }
+ invokeReadWriteParcel(&applicationId.value());
+}
+
+void KeystoreApplicationId::process(const uint8_t* data, size_t size) {
+ mFdp = std::make_unique<FuzzedDataProvider>(data, size);
+ invokeApplicationId();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ KeystoreApplicationId keystoreApplicationId;
+ keystoreApplicationId.process(data, size);
+ return 0;
+}
diff --git a/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp b/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp
new file mode 100644
index 0000000..581da46
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreAttestationId_fuzzer.cpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 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 <keystore/keystore_attestation_id.h>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+ uint32_t uid = fdp.ConsumeIntegral<uint32_t>();
+ auto result = android::security::gather_attestation_application_id(uid);
+ result.isOk();
+ result.status();
+ result.value();
+ return 0;
+}
diff --git a/keystore/tests/fuzzer/keystoreCommon.h b/keystore/tests/fuzzer/keystoreCommon.h
new file mode 100644
index 0000000..7af3ba8
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreCommon.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2021 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 KEYSTORECOMMON_H
+#define KEYSTORECOMMON_H
+
+#include <binder/Parcel.h>
+#include <binder/Parcelable.h>
+#include <keystore/KeyAttestationPackageInfo.h>
+#include <keystore/Signature.h>
+#include <vector>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+using namespace android;
+using namespace std;
+using ::content::pm::Signature;
+using ::security::keymaster::KeyAttestationPackageInfo;
+
+constexpr size_t kSignatureSizeMin = 1;
+constexpr size_t kSignatureSizeMax = 1000;
+constexpr size_t kRandomStringLength = 256;
+constexpr size_t kSignatureVectorSizeMin = 1;
+constexpr size_t kSignatureVectorSizeMax = 1000;
+
+struct PackageInfoData {
+ string packageName;
+ int64_t versionCode;
+ KeyAttestationPackageInfo::SharedSignaturesVector sharedSignaturesVector;
+};
+
+inline void invokeReadWriteParcel(Parcelable* obj) {
+ Parcel parcel;
+ obj->writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ obj->readFromParcel(&parcel);
+}
+
+inline vector<uint8_t> initSignatureData(FuzzedDataProvider* fdp) {
+ size_t signatureSize = fdp->ConsumeIntegralInRange(kSignatureSizeMin, kSignatureSizeMax);
+ vector<uint8_t> signatureData = fdp->ConsumeBytes<uint8_t>(signatureSize);
+ return signatureData;
+}
+
+inline PackageInfoData initPackageInfoData(FuzzedDataProvider* fdp) {
+ PackageInfoData packageInfoData;
+ packageInfoData.packageName = fdp->ConsumeRandomLengthString(kRandomStringLength);
+ packageInfoData.versionCode = fdp->ConsumeIntegral<int64_t>();
+ size_t signatureVectorSize =
+ fdp->ConsumeIntegralInRange(kSignatureVectorSizeMin, kSignatureVectorSizeMax);
+ KeyAttestationPackageInfo::SignaturesVector signatureVector;
+ for (size_t size = 0; size < signatureVectorSize; ++size) {
+ bool shouldUseParameterizedConstructor = fdp->ConsumeBool();
+ if (shouldUseParameterizedConstructor) {
+ vector<uint8_t> signatureData = initSignatureData(fdp);
+ signatureVector.push_back(make_optional<Signature>(signatureData));
+ } else {
+ signatureVector.push_back(std::nullopt);
+ }
+ }
+ packageInfoData.sharedSignaturesVector =
+ make_shared<KeyAttestationPackageInfo::SignaturesVector>(move(signatureVector));
+ return packageInfoData;
+}
+#endif // KEYSTORECOMMON_H
diff --git a/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp b/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp
new file mode 100644
index 0000000..1e033c8
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreGetWifiHidl_fuzzer.cpp
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 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 "fuzzer/FuzzedDataProvider.h"
+#include <inttypes.h>
+#include <keystore/keystore_get.h>
+
+using namespace std;
+
+constexpr int32_t kMaxKeySize = 256;
+const string kValidStrKeyPrefix[] = {"USRSKEY_",
+ "PLATFORM_VPN_",
+ "USRPKEY_",
+ "CACERT_",
+ "VPN_"
+ "USRCERT_",
+ "WIFI_"};
+constexpr char kStrGrantKeyPrefix[] = "ks2_keystore-engine_grant_id:";
+constexpr char kStrKeySuffix[] = "LOCKDOWN_VPN";
+constexpr size_t kGrantIdSize = 20;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp = FuzzedDataProvider(data, size);
+ size_t keyLength = fdp.ConsumeIntegralInRange<size_t>(0, kMaxKeySize);
+ bool usePrefix = fdp.ConsumeBool();
+ string strKeyPrefix;
+ size_t strKeyPrefixLength = 0;
+ size_t strKeySuffixLength = min(fdp.remaining_bytes(), keyLength);
+ if (usePrefix) {
+ strKeyPrefix = fdp.PickValueInArray(kValidStrKeyPrefix);
+ strKeyPrefixLength = sizeof(strKeyPrefix);
+ strKeySuffixLength =
+ (strKeySuffixLength > strKeyPrefixLength) ? strKeySuffixLength - strKeyPrefixLength : 0;
+ }
+ string strKeySuffix =
+ fdp.ConsumeBool() ? string(kStrKeySuffix) : fdp.ConsumeBytesAsString(strKeySuffixLength);
+ string strKey;
+ strKey = usePrefix ? strKeyPrefix + strKeySuffix : strKeySuffix;
+ if (fdp.ConsumeBool()) {
+ uint64_t grant = fdp.ConsumeIntegral<uint64_t>();
+ char grantId[kGrantIdSize] = "";
+ snprintf(grantId, kGrantIdSize, "%" PRIx64, grant);
+ strKey = strKey + string(kStrGrantKeyPrefix) + grantId;
+ }
+ const char* key = strKey.c_str();
+ uint8_t* value = nullptr;
+ keystore_get(key, strlen(key), &value);
+ free(value);
+ return 0;
+}
diff --git a/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp
new file mode 100644
index 0000000..63899ff
--- /dev/null
+++ b/keystore/tests/fuzzer/keystorePackageInfo_fuzzer.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2021 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 "keystoreCommon.h"
+
+class KeystorePackageInfoFuzzer {
+ public:
+ void process(const uint8_t* data, size_t size);
+ ~KeystorePackageInfoFuzzer() {}
+
+ private:
+ void invokePackageInfo();
+ std::unique_ptr<FuzzedDataProvider> mFdp;
+};
+
+void KeystorePackageInfoFuzzer::invokePackageInfo() {
+ auto packageInfoData = initPackageInfoData(mFdp.get());
+ KeyAttestationPackageInfo packageInfo(String16((packageInfoData.packageName).c_str()),
+ packageInfoData.versionCode,
+ packageInfoData.sharedSignaturesVector);
+ invokeReadWriteParcel(&packageInfo);
+}
+
+void KeystorePackageInfoFuzzer::process(const uint8_t* data, size_t size) {
+ mFdp = std::make_unique<FuzzedDataProvider>(data, size);
+ invokePackageInfo();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ KeystorePackageInfoFuzzer keystorePackageInfoFuzzer;
+ keystorePackageInfoFuzzer.process(data, size);
+ return 0;
+}
diff --git a/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp
new file mode 100644
index 0000000..b8f8a73
--- /dev/null
+++ b/keystore/tests/fuzzer/keystoreSignature_fuzzer.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2021 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 "keystoreCommon.h"
+#include <keystore/Signature.h>
+
+class KeystoreSignatureFuzzer {
+ public:
+ void process(const uint8_t* data, size_t size);
+ ~KeystoreSignatureFuzzer() {}
+
+ private:
+ void invokeSignature();
+ std::unique_ptr<FuzzedDataProvider> mFdp;
+};
+
+void KeystoreSignatureFuzzer::invokeSignature() {
+ std::optional<Signature> signature;
+ bool shouldUseParameterizedConstructor = mFdp->ConsumeBool();
+ if (shouldUseParameterizedConstructor) {
+ std::vector<uint8_t> signatureData = initSignatureData(mFdp.get());
+ signature = Signature(signatureData);
+ } else {
+ signature = Signature();
+ }
+ invokeReadWriteParcel(&signature.value());
+}
+
+void KeystoreSignatureFuzzer::process(const uint8_t* data, size_t size) {
+ mFdp = std::make_unique<FuzzedDataProvider>(data, size);
+ invokeSignature();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ KeystoreSignatureFuzzer keystoreSignatureFuzzer;
+ keystoreSignatureFuzzer.process(data, size);
+ return 0;
+}
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 0069f95..520237a 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -25,9 +25,11 @@
name: "libkeystore2_defaults",
crate_name: "keystore2",
srcs: ["src/lib.rs"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
rustlibs: [
- "android.hardware.security.keymint-V1-rust",
"android.hardware.security.secureclock-V1-rust",
"android.hardware.security.sharedsecret-V1-rust",
"android.os.permissions_aidl-rust",
@@ -37,24 +39,23 @@
"android.security.maintenance-rust",
"android.security.metrics-rust",
"android.security.remoteprovisioning-rust",
- "android.system.keystore2-V1-rust",
+ "android.system.keystore2-V2-rust",
"libanyhow",
"libbinder_rs",
- "libcutils_bindgen",
"libkeystore2_aaid-rust",
"libkeystore2_apc_compat-rust",
"libkeystore2_crypto_rust",
"libkeystore2_km_compat",
"libkeystore2_selinux",
- "libkeystore2_system_property-rust",
"libkeystore2_vintf_rust",
"liblazy_static",
"liblibc",
- "liblibsqlite3_sys",
"liblog_event_list",
"liblog_rust",
"librand",
- "librusqlite",
+ "librustutils",
+ "libserde",
+ "libserde_cbor",
"libthiserror",
],
shared_libs: [
@@ -68,6 +69,19 @@
rust_library {
name: "libkeystore2",
defaults: ["libkeystore2_defaults"],
+ rustlibs: [
+ "liblibsqlite3_sys",
+ "librusqlite",
+ ],
+}
+
+rust_library {
+ name: "libkeystore2_noicu",
+ defaults: ["libkeystore2_defaults"],
+ rustlibs: [
+ "liblibsqlite3_sys_noicu",
+ "librusqlite_noicu",
+ ],
}
rust_library {
@@ -75,8 +89,29 @@
crate_name: "keystore2_test_utils",
srcs: ["test_utils/lib.rs"],
rustlibs: [
+ "libkeystore2_selinux",
"liblog_rust",
+ "libnix",
"librand",
+ "libserde",
+ "libserde_cbor",
+ ],
+}
+
+rust_test {
+ name: "keystore2_test_utils_test",
+ srcs: ["test_utils/lib.rs"],
+ test_suites: ["general-tests"],
+ require_root: true,
+ auto_gen_config: true,
+ compile_multilib: "first",
+ rustlibs: [
+ "libkeystore2_selinux",
+ "liblog_rust",
+ "libnix",
+ "librand",
+ "libserde",
+ "libserde_cbor",
],
}
@@ -90,7 +125,9 @@
rustlibs: [
"libandroid_logger",
"libkeystore2_test_utils",
+ "liblibsqlite3_sys",
"libnix",
+ "librusqlite",
],
// The test should always include watchdog.
features: [
@@ -98,15 +135,13 @@
],
}
-rust_binary {
- name: "keystore2",
+rust_defaults {
+ name: "keystore2_defaults",
srcs: ["src/keystore2_main.rs"],
rustlibs: [
"libandroid_logger",
"libbinder_rs",
- "libkeystore2",
"liblog_rust",
- "liblegacykeystore-rust",
],
init_rc: ["keystore2.rc"],
@@ -118,31 +153,32 @@
// selection available in the build system.
prefer_rlib: true,
- // TODO(b/187412695)
- // This is a hack to work around the build system not installing
- // dynamic dependencies of rlibs to the device. This section should
- // be removed once that works correctly.
- shared_libs: [
- "android.hardware.confirmationui@1.0",
- "android.hardware.security.sharedsecret-V1-ndk_platform",
- "android.security.compat-ndk_platform",
- "libc",
- "libdl_android",
- "libdl",
- "libandroidicu",
- "libkeymint",
- "libkeystore2_aaid",
- "libkeystore2_apc_compat",
- "libkeystore2_crypto",
- "libkeystore2_vintf_cpp",
- "libkm_compat_service",
- "libkm_compat",
- "libm",
- "libstatspull",
- "libstatssocket",
- ],
-
vintf_fragments: ["android.system.keystore2-service.xml"],
required: ["keystore_cli_v2"],
}
+
+rust_binary {
+ name: "keystore2",
+ defaults: ["keystore2_defaults"],
+ rustlibs: [
+ "libkeystore2",
+ "liblegacykeystore-rust",
+ "librusqlite",
+ ],
+}
+
+// Variant of keystore2 for use in microdroid. It doesn't depend on the ICU-enabled sqlite.
+// This can be used also in Android, but we choose not to because it will bring two
+// variants of sqlite to the system causing more RAM usage and CPU cycles when loading.
+rust_binary {
+ name: "keystore2_microdroid",
+ stem: "keystore2",
+ defaults: ["keystore2_defaults"],
+ rustlibs: [
+ "libkeystore2_noicu",
+ "liblegacykeystore-rust_noicu",
+ "librusqlite_noicu",
+ ],
+ installable: false, // don't install this to Android
+}
diff --git a/keystore2/TEST_MAPPING b/keystore2/TEST_MAPPING
index 16b6f85..049adc7 100644
--- a/keystore2/TEST_MAPPING
+++ b/keystore2/TEST_MAPPING
@@ -10,7 +10,23 @@
"name": "keystore2_test"
},
{
+ "name": "keystore2_test_utils_test"
+ },
+ {
"name": "CtsIdentityTestCases"
+ },
+ {
+ "name": "CtsKeystoreTestCases",
+ "options": [
+ {
+ "include-annotation": "android.platform.test.annotations.RequiresDevice"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsKeystorePerformanceTestCases"
}
]
}
diff --git a/keystore2/aaid/Android.bp b/keystore2/aaid/Android.bp
index c04ce51..3417960 100644
--- a/keystore2/aaid/Android.bp
+++ b/keystore2/aaid/Android.bp
@@ -57,3 +57,13 @@
"libkeystore2_aaid",
],
}
+
+rust_test {
+ name: "libkeystore2_aaid_bindgen_test",
+ srcs: [":libkeystore2_aaid_bindgen"],
+ crate_name: "keystore2_aaid_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index 4a7b7b4..7eb2b83 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -24,7 +24,7 @@
aidl_interface {
name: "android.security.attestationmanager",
srcs: [ "android/security/attestationmanager/*.aidl", ],
- imports: [ "android.hardware.security.keymint-V1" ],
+ imports: [ "android.hardware.security.keymint-V2" ],
unstable: true,
backend: {
java: {
@@ -45,7 +45,7 @@
name: "android.security.authorization",
srcs: [ "android/security/authorization/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V2",
"android.hardware.security.secureclock-V1",
],
unstable: true,
@@ -86,7 +86,7 @@
name: "android.security.compat",
srcs: [ "android/security/compat/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V2",
"android.hardware.security.secureclock-V1",
"android.hardware.security.sharedsecret-V1",
],
@@ -110,7 +110,7 @@
name: "android.security.remoteprovisioning",
srcs: [ "android/security/remoteprovisioning/*.aidl" ],
imports: [
- "android.hardware.security.keymint-V1",
+ "android.hardware.security.keymint-V2",
],
unstable: true,
backend: {
@@ -132,7 +132,7 @@
name: "android.security.maintenance",
srcs: [ "android/security/maintenance/*.aidl" ],
imports: [
- "android.system.keystore2-V1",
+ "android.system.keystore2-V2",
],
unstable: true,
backend: {
@@ -173,7 +173,7 @@
name: "android.security.metrics",
srcs: [ "android/security/metrics/*.aidl" ],
imports: [
- "android.system.keystore2-V1",
+ "android.system.keystore2-V2",
],
unstable: true,
backend: {
@@ -191,3 +191,19 @@
},
}
+// cc_defaults that includes the latest Keystore2 AIDL library.
+// Modules that depend on KeyMint directly can include this cc_defaults to avoid
+// managing dependency versions explicitly.
+cc_defaults {
+ name: "keystore2_use_latest_aidl_ndk_static",
+ static_libs: [
+ "android.system.keystore2-V2-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "keystore2_use_latest_aidl_ndk_shared",
+ shared_libs: [
+ "android.system.keystore2-V2-ndk",
+ ],
+}
diff --git a/keystore2/aidl/android/security/metrics/EcCurve.aidl b/keystore2/aidl/android/security/metrics/EcCurve.aidl
index b190d83..7b1a5a2 100644
--- a/keystore2/aidl/android/security/metrics/EcCurve.aidl
+++ b/keystore2/aidl/android/security/metrics/EcCurve.aidl
@@ -29,4 +29,5 @@
P_256 = 2,
P_384 = 3,
P_521 = 4,
+ CURVE_25519 = 5,
}
\ No newline at end of file
diff --git a/keystore2/android.system.keystore2-service.xml b/keystore2/android.system.keystore2-service.xml
index 6b8d0cb..20c2fba 100644
--- a/keystore2/android.system.keystore2-service.xml
+++ b/keystore2/android.system.keystore2-service.xml
@@ -1,6 +1,7 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
<name>android.system.keystore2</name>
+ <version>2</version>
<interface>
<name>IKeystoreService</name>
<instance>default</instance>
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
index bf21675..df7521e 100644
--- a/keystore2/apc_compat/Android.bp
+++ b/keystore2/apc_compat/Android.bp
@@ -63,3 +63,13 @@
"libkeystore2_apc_compat",
],
}
+
+rust_test {
+ name: "libkeystore2_apc_compat_bindgen_test",
+ srcs: [":libkeystore2_apc_compat_bindgen"],
+ crate_name: "keystore2_apc_compat_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
diff --git a/keystore2/keystore2.rc b/keystore2/keystore2.rc
index 82bf3b8..6f88dd3 100644
--- a/keystore2/keystore2.rc
+++ b/keystore2/keystore2.rc
@@ -10,4 +10,4 @@
class early_hal
user keystore
group keystore readproc log
- writepid /dev/cpuset/foreground/tasks
+ task_profiles ProcessCapacityHigh
diff --git a/keystore2/legacykeystore/Android.bp b/keystore2/legacykeystore/Android.bp
index fb6f60f..d407569 100644
--- a/keystore2/legacykeystore/Android.bp
+++ b/keystore2/legacykeystore/Android.bp
@@ -21,8 +21,8 @@
default_applicable_licenses: ["system_security_license"],
}
-rust_library {
- name: "liblegacykeystore-rust",
+rust_defaults {
+ name: "liblegacykeystore-rust_defaults",
crate_name: "legacykeystore",
srcs: [
"lib.rs",
@@ -31,14 +31,31 @@
"android.security.legacykeystore-rust",
"libanyhow",
"libbinder_rs",
- "libcutils_bindgen",
- "libkeystore2",
"liblog_rust",
"librusqlite",
+ "librustutils",
"libthiserror",
],
}
+rust_library {
+ name: "liblegacykeystore-rust",
+ defaults: ["liblegacykeystore-rust_defaults"],
+ rustlibs: [
+ "libkeystore2",
+ "librusqlite",
+ ],
+}
+
+rust_library {
+ name: "liblegacykeystore-rust_noicu",
+ defaults: ["liblegacykeystore-rust_defaults"],
+ rustlibs: [
+ "libkeystore2_noicu",
+ "librusqlite_noicu",
+ ],
+}
+
rust_test {
name: "legacykeystore_test",
crate_name: "legacykeystore",
@@ -49,11 +66,11 @@
"android.security.legacykeystore-rust",
"libanyhow",
"libbinder_rs",
- "libcutils_bindgen",
"libkeystore2",
"libkeystore2_test_utils",
"liblog_rust",
"librusqlite",
+ "librustutils",
"libthiserror",
],
}
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index efa0870..da60297 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -161,7 +161,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
tx.execute(
"DELETE FROM profiles WHERE cast ( ( owner/? ) as int) = ?;",
- params![cutils_bindgen::AID_USER_OFFSET, user_id],
+ params![rustutils::users::AID_USER_OFFSET, user_id],
)
.context("In remove_uid: Failed to delete.")
})?;
@@ -526,7 +526,7 @@
use std::time::Duration;
use std::time::Instant;
- static TEST_ALIAS: &str = &"test_alias";
+ static TEST_ALIAS: &str = "test_alias";
static TEST_BLOB1: &[u8] = &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
static TEST_BLOB2: &[u8] = &[2, 2, 3, 4, 5, 6, 7, 8, 9, 0];
static TEST_BLOB3: &[u8] = &[3, 2, 3, 4, 5, 6, 7, 8, 9, 0];
@@ -600,9 +600,9 @@
.expect("Failed to open database.");
// Insert three entries for owner 2.
- db.put(2 + 2 * cutils_bindgen::AID_USER_OFFSET, "test1", TEST_BLOB1)
+ db.put(2 + 2 * rustutils::users::AID_USER_OFFSET, "test1", TEST_BLOB1)
.expect("Failed to insert test1.");
- db.put(4 + 2 * cutils_bindgen::AID_USER_OFFSET, "test2", TEST_BLOB2)
+ db.put(4 + 2 * rustutils::users::AID_USER_OFFSET, "test2", TEST_BLOB2)
.expect("Failed to insert test2.");
db.put(3, "test3", TEST_BLOB3).expect("Failed to insert test3.");
@@ -610,12 +610,12 @@
assert_eq!(
Vec::<String>::new(),
- db.list(2 + 2 * cutils_bindgen::AID_USER_OFFSET).expect("Failed to list entries.")
+ db.list(2 + 2 * rustutils::users::AID_USER_OFFSET).expect("Failed to list entries.")
);
assert_eq!(
Vec::<String>::new(),
- db.list(4 + 2 * cutils_bindgen::AID_USER_OFFSET).expect("Failed to list entries.")
+ db.list(4 + 2 * rustutils::users::AID_USER_OFFSET).expect("Failed to list entries.")
);
assert_eq!(vec!["test3".to_string(),], db.list(3).expect("Failed to list entries."));
@@ -694,9 +694,9 @@
}
let mut db = DB::new(&db_path3).expect("Failed to open database.");
- db.put(3, &TEST_ALIAS, TEST_BLOB3).expect("Failed to add entry (3).");
+ db.put(3, TEST_ALIAS, TEST_BLOB3).expect("Failed to add entry (3).");
- db.remove(3, &TEST_ALIAS).expect("Remove failed (3).");
+ db.remove(3, TEST_ALIAS).expect("Remove failed (3).");
}
});
@@ -710,7 +710,7 @@
let mut db = DB::new(&db_path).expect("Failed to open database.");
// This may return Some or None but it must not fail.
- db.get(3, &TEST_ALIAS).expect("Failed to get entry (4).");
+ db.get(3, TEST_ALIAS).expect("Failed to get entry (4).");
}
});
diff --git a/keystore2/selinux/Android.bp b/keystore2/selinux/Android.bp
index 748e406..254f95e 100644
--- a/keystore2/selinux/Android.bp
+++ b/keystore2/selinux/Android.bp
@@ -63,3 +63,24 @@
"libthiserror",
],
}
+
+rust_test {
+ name: "keystore2_selinux_concurrency_test",
+ srcs: [
+ "src/concurrency_test.rs",
+ ],
+ crate_name: "keystore2_selinux_concurrency_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+
+ rustlibs: [
+ "libandroid_logger",
+ "libanyhow",
+ "libkeystore2_selinux",
+ "liblazy_static",
+ "liblog_rust",
+ "libnix",
+ "libnum_cpus",
+ "libthiserror",
+ ],
+}
diff --git a/keystore2/selinux/src/concurrency_test.rs b/keystore2/selinux/src/concurrency_test.rs
new file mode 100644
index 0000000..a5d2df2
--- /dev/null
+++ b/keystore2/selinux/src/concurrency_test.rs
@@ -0,0 +1,190 @@
+// Copyright 2021, 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 keystore2_selinux::{check_access, Context};
+use nix::sched::sched_setaffinity;
+use nix::sched::CpuSet;
+use nix::unistd::getpid;
+use std::thread;
+use std::{
+ sync::{atomic::AtomicU8, atomic::Ordering, Arc},
+ time::{Duration, Instant},
+};
+
+#[derive(Clone, Copy)]
+struct CatCount(u8, u8, u8, u8);
+
+impl CatCount {
+ fn next(&mut self) -> CatCount {
+ let result = *self;
+ if self.3 == 255 {
+ if self.2 == 254 {
+ if self.1 == 253 {
+ if self.0 == 252 {
+ self.0 = 255;
+ }
+ self.0 += 1;
+ self.1 = self.0;
+ }
+ self.1 += 1;
+ self.2 = self.1;
+ }
+ self.2 += 1;
+ self.3 = self.2;
+ }
+ self.3 += 1;
+ result
+ }
+
+ fn make_string(&self) -> String {
+ format!("c{},c{},c{},c{}", self.0, self.1, self.2, self.3)
+ }
+}
+
+impl Default for CatCount {
+ fn default() -> Self {
+ Self(0, 1, 2, 3)
+ }
+}
+
+/// This test calls selinux_check_access concurrently causing access vector cache misses
+/// in libselinux avc. The test then checks if any of the threads fails to report back
+/// after a burst of access checks. The purpose of the test is to draw out a specific
+/// access vector cache corruption that sends a calling thread into an infinite loop.
+/// This was observed when keystore2 used libselinux concurrently in a non thread safe
+/// way. See b/184006658.
+#[test]
+fn test_concurrent_check_access() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_selinux_concurrency_test")
+ .with_min_level(log::Level::Debug),
+ );
+
+ let cpus = num_cpus::get();
+ let turnpike = Arc::new(AtomicU8::new(0));
+ let complete_count = Arc::new(AtomicU8::new(0));
+ let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
+
+ for i in 0..cpus {
+ log::info!("Spawning thread {}", i);
+ let turnpike_clone = turnpike.clone();
+ let complete_count_clone = complete_count.clone();
+ threads.push(thread::spawn(move || {
+ let mut cpu_set = CpuSet::new();
+ cpu_set.set(i).unwrap();
+ sched_setaffinity(getpid(), &cpu_set).unwrap();
+ let mut cat_count: CatCount = Default::default();
+
+ log::info!("Thread 0 reached turnpike");
+ loop {
+ turnpike_clone.fetch_add(1, Ordering::Relaxed);
+ loop {
+ match turnpike_clone.load(Ordering::Relaxed) {
+ 0 => break,
+ 255 => return,
+ _ => {}
+ }
+ }
+
+ for _ in 0..250 {
+ let (tctx, sctx, perm, class) = (
+ Context::new("u:object_r:keystore:s0").unwrap(),
+ Context::new(&format!(
+ "u:r:untrusted_app:s0:{}",
+ cat_count.next().make_string()
+ ))
+ .unwrap(),
+ "use",
+ "keystore2_key",
+ );
+
+ check_access(&sctx, &tctx, class, perm).unwrap();
+ }
+
+ complete_count_clone.fetch_add(1, Ordering::Relaxed);
+ while complete_count_clone.load(Ordering::Relaxed) as usize != cpus {
+ thread::sleep(Duration::from_millis(5));
+ }
+ }
+ }));
+ }
+
+ let mut i = 0;
+ let run_time = Instant::now();
+
+ loop {
+ const TEST_ITERATIONS: u32 = 500;
+ const MAX_SLEEPS: u64 = 500;
+ const SLEEP_MILLISECONDS: u64 = 5;
+ let mut sleep_count: u64 = 0;
+ while turnpike.load(Ordering::Relaxed) as usize != cpus {
+ thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS));
+ sleep_count += 1;
+ assert!(
+ sleep_count < MAX_SLEEPS,
+ "Waited too long to go ready on iteration {}, only {} are ready",
+ i,
+ turnpike.load(Ordering::Relaxed)
+ );
+ }
+
+ if i % 100 == 0 {
+ let elapsed = run_time.elapsed().as_secs();
+ println!("{:02}:{:02}: Iteration {}", elapsed / 60, elapsed % 60, i);
+ }
+
+ // Give the threads some time to reach and spin on the turn pike.
+ assert_eq!(turnpike.load(Ordering::Relaxed) as usize, cpus, "i = {}", i);
+ if i >= TEST_ITERATIONS {
+ turnpike.store(255, Ordering::Relaxed);
+ break;
+ }
+
+ // Now go.
+ complete_count.store(0, Ordering::Relaxed);
+ turnpike.store(0, Ordering::Relaxed);
+ i += 1;
+
+ // Wait for them to all complete.
+ sleep_count = 0;
+ while complete_count.load(Ordering::Relaxed) as usize != cpus {
+ thread::sleep(Duration::from_millis(SLEEP_MILLISECONDS));
+ sleep_count += 1;
+ if sleep_count >= MAX_SLEEPS {
+ // Enable the following block to park the thread to allow attaching a debugger.
+ if false {
+ println!(
+ "Waited {} seconds and we seem stuck. Going to sleep forever.",
+ (MAX_SLEEPS * SLEEP_MILLISECONDS) as f32 / 1000.0
+ );
+ loop {
+ thread::park();
+ }
+ } else {
+ assert!(
+ sleep_count < MAX_SLEEPS,
+ "Waited too long to complete on iteration {}, only {} are complete",
+ i,
+ complete_count.load(Ordering::Relaxed)
+ );
+ }
+ }
+ }
+ }
+
+ for t in threads {
+ t.join().unwrap();
+ }
+}
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index 5197cf6..c0593b7 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -130,7 +130,7 @@
fn deref(&self) -> &Self::Target {
match self {
Self::Raw(p) => unsafe { CStr::from_ptr(*p) },
- Self::CString(cstr) => &cstr,
+ Self::CString(cstr) => cstr,
}
}
}
@@ -321,6 +321,323 @@
}
}
+/// Safe wrapper around setcon.
+pub fn setcon(target: &CStr) -> std::io::Result<()> {
+ // SAFETY: `setcon` takes a const char* and only performs read accesses on it
+ // using strdup and strcmp. `setcon` does not retain a pointer to `target`
+ // and `target` outlives the call to `setcon`.
+ if unsafe { selinux::setcon(target.as_ptr()) } != 0 {
+ Err(std::io::Error::last_os_error())
+ } else {
+ Ok(())
+ }
+}
+
+/// Represents an SEPolicy permission belonging to a specific class.
+pub trait ClassPermission {
+ /// The permission string of the given instance as specified in the class vector.
+ fn name(&self) -> &'static str;
+ /// The class of the permission.
+ fn class_name(&self) -> &'static str;
+}
+
+/// This macro implements an enum with values mapped to SELinux permission names.
+/// The example below implements `enum MyPermission with public visibility:
+/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
+/// any variant not specified to the default `None` with value `0`.
+/// * `MyPermission` implements ClassPermission.
+/// * An implicit default values `MyPermission::None` is created with a numeric representation
+/// of `0` and a string representation of `"none"`.
+/// * Specifying a value is optional. If the value is omitted it is set to the value of the
+/// previous variant left shifted by 1.
+///
+/// ## Example
+/// ```
+/// implement_class!(
+/// /// MyPermission documentation.
+/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
+/// #[selinux(class_name = my_class)]
+/// pub enum MyPermission {
+/// #[selinux(name = foo)]
+/// Foo = 1,
+/// #[selinux(name = bar)]
+/// Bar = 2,
+/// #[selinux(name = snafu)]
+/// Snafu, // Implicit value: MyPermission::Bar << 1 -> 4
+/// }
+/// assert_eq!(MyPermission::Foo.name(), &"foo");
+/// assert_eq!(MyPermission::Foo.class_name(), &"my_class");
+/// assert_eq!(MyPermission::Snafu as i32, 4);
+/// );
+/// ```
+#[macro_export]
+macro_rules! implement_class {
+ // First rule: Public interface.
+ (
+ $(#[$($enum_meta:tt)+])*
+ $enum_vis:vis enum $enum_name:ident $body:tt
+ ) => {
+ implement_class! {
+ @extract_class
+ []
+ [$(#[$($enum_meta)+])*]
+ $enum_vis enum $enum_name $body
+ }
+ };
+
+ // The next two rules extract the #[selinux(class_name = <name>)] meta field from
+ // the types meta list.
+ // This first rule finds the field and terminates the recursion through the meta fields.
+ (
+ @extract_class
+ [$(#[$mout:meta])*]
+ [
+ #[selinux(class_name = $class_name:ident)]
+ $(#[$($mtail:tt)+])*
+ ]
+ $enum_vis:vis enum $enum_name:ident {
+ $(
+ $(#[$($emeta:tt)+])*
+ $vname:ident$( = $vval:expr)?
+ ),* $(,)?
+ }
+ ) => {
+ implement_class!{
+ @extract_perm_name
+ $class_name
+ $(#[$mout])*
+ $(#[$($mtail)+])*
+ $enum_vis enum $enum_name {
+ 1;
+ []
+ [$(
+ [] [$(#[$($emeta)+])*]
+ $vname$( = $vval)?,
+ )*]
+ }
+ }
+ };
+
+ // The second rule iterates through the type global meta fields.
+ (
+ @extract_class
+ [$(#[$mout:meta])*]
+ [
+ #[$front:meta]
+ $(#[$($mtail:tt)+])*
+ ]
+ $enum_vis:vis enum $enum_name:ident $body:tt
+ ) => {
+ implement_class!{
+ @extract_class
+ [
+ $(#[$mout])*
+ #[$front]
+ ]
+ [$(#[$($mtail)+])*]
+ $enum_vis enum $enum_name $body
+ }
+ };
+
+ // The next four rules implement two nested recursions. The outer iterates through
+ // the enum variants and the inner iterates through the meta fields of each variant.
+ // The first two rules find the #[selinux(name = <name>)] stanza, terminate the inner
+ // recursion and descend a level in the outer recursion.
+ // The first rule matches variants with explicit initializer $vval. And updates the next
+ // value to ($vval << 1).
+ (
+ @extract_perm_name
+ $class_name:ident
+ $(#[$enum_meta:meta])*
+ $enum_vis:vis enum $enum_name:ident {
+ $next_val:expr;
+ [$($out:tt)*]
+ [
+ [$(#[$mout:meta])*]
+ [
+ #[selinux(name = $selinux_name:ident)]
+ $(#[$($mtail:tt)+])*
+ ]
+ $vname:ident = $vval:expr,
+ $($tail:tt)*
+ ]
+ }
+ ) => {
+ implement_class!{
+ @extract_perm_name
+ $class_name
+ $(#[$enum_meta])*
+ $enum_vis enum $enum_name {
+ ($vval << 1);
+ [
+ $($out)*
+ $(#[$mout])*
+ $(#[$($mtail)+])*
+ $selinux_name $vname = $vval,
+ ]
+ [$($tail)*]
+ }
+ }
+ };
+
+ // The second rule differs form the previous in that there is no explicit initializer.
+ // Instead $next_val is used as initializer and the next value is set to (&next_val << 1).
+ (
+ @extract_perm_name
+ $class_name:ident
+ $(#[$enum_meta:meta])*
+ $enum_vis:vis enum $enum_name:ident {
+ $next_val:expr;
+ [$($out:tt)*]
+ [
+ [$(#[$mout:meta])*]
+ [
+ #[selinux(name = $selinux_name:ident)]
+ $(#[$($mtail:tt)+])*
+ ]
+ $vname:ident,
+ $($tail:tt)*
+ ]
+ }
+ ) => {
+ implement_class!{
+ @extract_perm_name
+ $class_name
+ $(#[$enum_meta])*
+ $enum_vis enum $enum_name {
+ ($next_val << 1);
+ [
+ $($out)*
+ $(#[$mout])*
+ $(#[$($mtail)+])*
+ $selinux_name $vname = $next_val,
+ ]
+ [$($tail)*]
+ }
+ }
+ };
+
+ // The third rule descends a step in the inner recursion.
+ (
+ @extract_perm_name
+ $class_name:ident
+ $(#[$enum_meta:meta])*
+ $enum_vis:vis enum $enum_name:ident {
+ $next_val:expr;
+ [$($out:tt)*]
+ [
+ [$(#[$mout:meta])*]
+ [
+ #[$front:meta]
+ $(#[$($mtail:tt)+])*
+ ]
+ $vname:ident$( = $vval:expr)?,
+ $($tail:tt)*
+ ]
+ }
+ ) => {
+ implement_class!{
+ @extract_perm_name
+ $class_name
+ $(#[$enum_meta])*
+ $enum_vis enum $enum_name {
+ $next_val;
+ [$($out)*]
+ [
+ [
+ $(#[$mout])*
+ #[$front]
+ ]
+ [$(#[$($mtail)+])*]
+ $vname$( = $vval)?,
+ $($tail)*
+ ]
+ }
+ }
+ };
+
+ // The fourth rule terminates the outer recursion and transitions to the
+ // implementation phase @spill.
+ (
+ @extract_perm_name
+ $class_name:ident
+ $(#[$enum_meta:meta])*
+ $enum_vis:vis enum $enum_name:ident {
+ $next_val:expr;
+ [$($out:tt)*]
+ []
+ }
+ ) => {
+ implement_class!{
+ @spill
+ $class_name
+ $(#[$enum_meta])*
+ $enum_vis enum $enum_name {
+ $($out)*
+ }
+ }
+ };
+
+ (
+ @spill
+ $class_name:ident
+ $(#[$enum_meta:meta])*
+ $enum_vis:vis enum $enum_name:ident {
+ $(
+ $(#[$emeta:meta])*
+ $selinux_name:ident $vname:ident = $vval:expr,
+ )*
+ }
+ ) => {
+ $(#[$enum_meta])*
+ $enum_vis enum $enum_name {
+ /// The default variant of the enum.
+ None = 0,
+ $(
+ $(#[$emeta])*
+ $vname = $vval,
+ )*
+ }
+
+ impl From<i32> for $enum_name {
+ #[allow(non_upper_case_globals)]
+ fn from (p: i32) -> Self {
+ // Creating constants forces the compiler to evaluate the value expressions
+ // so that they can be used in the match statement below.
+ $(const $vname: i32 = $vval;)*
+ match p {
+ 0 => Self::None,
+ $($vname => Self::$vname,)*
+ _ => Self::None,
+ }
+ }
+ }
+
+ impl From<$enum_name> for i32 {
+ fn from(p: $enum_name) -> i32 {
+ p as i32
+ }
+ }
+
+ impl ClassPermission for $enum_name {
+ fn name(&self) -> &'static str {
+ match self {
+ Self::None => &"none",
+ $(Self::$vname => stringify!($selinux_name),)*
+ }
+ }
+ fn class_name(&self) -> &'static str {
+ stringify!($class_name)
+ }
+ }
+ };
+}
+
+/// Calls `check_access` on the given class permission.
+pub fn check_permission<T: ClassPermission>(source: &CStr, target: &CStr, perm: T) -> Result<()> {
+ check_access(source, target, perm.class_name(), perm.name())
+}
+
#[cfg(test)]
mod tests {
use super::*;
diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs
index e130024..0515c8f 100644
--- a/keystore2/src/async_task.rs
+++ b/keystore2/src/async_task.rs
@@ -417,7 +417,9 @@
Err(RecvTimeoutError::Timeout)
);
done_receiver.recv().unwrap();
- idle_done_receiver.recv_timeout(Duration::from_millis(1)).unwrap();
+ // Now that the last low-priority job has completed, the idle task should
+ // fire pretty much immediately.
+ idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
// Idle callback not executed again even if we wait for a while.
assert_eq!(
@@ -438,7 +440,7 @@
Err(RecvTimeoutError::Timeout)
);
done_receiver.recv().unwrap();
- idle_done_receiver.recv_timeout(Duration::from_millis(1)).unwrap();
+ idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap();
}
#[test]
diff --git a/keystore2/src/attestation_key_utils.rs b/keystore2/src/attestation_key_utils.rs
index ca00539..a8c1ca9 100644
--- a/keystore2/src/attestation_key_utils.rs
+++ b/keystore2/src/attestation_key_utils.rs
@@ -60,7 +60,7 @@
let challenge_present = params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE);
match attest_key_descriptor {
None if challenge_present => rem_prov_state
- .get_remotely_provisioned_attestation_key_and_certs(&key, caller_uid, params, db)
+ .get_remotely_provisioned_attestation_key_and_certs(key, caller_uid, params, db)
.context(concat!(
"In get_attest_key_and_cert_chain: ",
"Trying to get remotely provisioned attestation key."
@@ -71,7 +71,7 @@
})
}),
None => Ok(None),
- Some(attest_key) => get_user_generated_attestation_key(&attest_key, caller_uid, db)
+ Some(attest_key) => get_user_generated_attestation_key(attest_key, caller_uid, db)
.context("In get_attest_key_and_cert_chain: Trying to load attest key")
.map(Some),
}
@@ -83,7 +83,7 @@
db: &mut KeystoreDB,
) -> Result<AttestationKeyInfo> {
let (key_id_guard, blob, cert, blob_metadata) =
- load_attest_key_blob_and_cert(&key, caller_uid, db)
+ load_attest_key_blob_and_cert(key, caller_uid, db)
.context("In get_user_generated_attestation_key: Failed to load blob and cert")?;
let issuer_subject: Vec<u8> = parse_subject_from_certificate(&cert).context(
@@ -105,11 +105,11 @@
_ => {
let (key_id_guard, mut key_entry) = db
.load_key_entry(
- &key,
+ key,
KeyType::Client,
KeyEntryLoadBits::BOTH,
caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ |k, av| check_key_permission(KeyPerm::Use, k, &av),
)
.context("In load_attest_key_blob_and_cert: Failed to load key.")?;
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 777089f..04626bc 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -119,7 +119,7 @@
fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> {
// Check keystore permission.
- check_keystore_permission(KeystorePerm::add_auth()).context("In add_auth_token.")?;
+ check_keystore_permission(KeystorePerm::AddAuth).context("In add_auth_token.")?;
ENFORCEMENTS.add_auth_token(auth_token.clone());
Ok(())
@@ -143,7 +143,7 @@
(LockScreenEvent::UNLOCK, Some(password)) => {
// This corresponds to the unlock() method in legacy keystore API.
// check permission
- check_keystore_permission(KeystorePerm::unlock())
+ check_keystore_permission(KeystorePerm::Unlock)
.context("In on_lock_screen_event: Unlock with password.")?;
ENFORCEMENTS.set_device_locked(user_id, false);
@@ -177,7 +177,7 @@
Ok(())
}
(LockScreenEvent::UNLOCK, None) => {
- check_keystore_permission(KeystorePerm::unlock())
+ check_keystore_permission(KeystorePerm::Unlock)
.context("In on_lock_screen_event: Unlock.")?;
ENFORCEMENTS.set_device_locked(user_id, false);
DB.with(|db| {
@@ -187,7 +187,7 @@
Ok(())
}
(LockScreenEvent::LOCK, None) => {
- check_keystore_permission(KeystorePerm::lock())
+ check_keystore_permission(KeystorePerm::Lock)
.context("In on_lock_screen_event: Lock")?;
ENFORCEMENTS.set_device_locked(user_id, true);
DB.with(|db| {
@@ -215,7 +215,7 @@
) -> Result<AuthorizationTokens> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::get_auth_token())
+ check_keystore_permission(KeystorePerm::GetAuthToken)
.context("In get_auth_tokens_for_credstore.")?;
// If the challenge is zero, return error
diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs
index 1110caf..08c52af 100644
--- a/keystore2/src/boot_level_keys.rs
+++ b/keystore2/src/boot_level_keys.rs
@@ -243,40 +243,40 @@
fn test_output_is_consistent() -> Result<()> {
let initial_key = b"initial key";
let mut blkc = BootLevelKeyCache::new(ZVec::try_from(initial_key as &[u8])?);
- assert_eq!(true, blkc.level_accessible(0));
- assert_eq!(true, blkc.level_accessible(9));
- assert_eq!(true, blkc.level_accessible(10));
- assert_eq!(true, blkc.level_accessible(100));
+ assert!(blkc.level_accessible(0));
+ assert!(blkc.level_accessible(9));
+ assert!(blkc.level_accessible(10));
+ assert!(blkc.level_accessible(100));
let v0 = blkc.aes_key(0).unwrap().unwrap();
let v10 = blkc.aes_key(10).unwrap().unwrap();
assert_eq!(Some(&v0), blkc.aes_key(0)?.as_ref());
assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
blkc.advance_boot_level(5)?;
- assert_eq!(false, blkc.level_accessible(0));
- assert_eq!(true, blkc.level_accessible(9));
- assert_eq!(true, blkc.level_accessible(10));
- assert_eq!(true, blkc.level_accessible(100));
+ assert!(!blkc.level_accessible(0));
+ assert!(blkc.level_accessible(9));
+ assert!(blkc.level_accessible(10));
+ assert!(blkc.level_accessible(100));
assert_eq!(None, blkc.aes_key(0)?);
assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
blkc.advance_boot_level(10)?;
- assert_eq!(false, blkc.level_accessible(0));
- assert_eq!(false, blkc.level_accessible(9));
- assert_eq!(true, blkc.level_accessible(10));
- assert_eq!(true, blkc.level_accessible(100));
+ assert!(!blkc.level_accessible(0));
+ assert!(!blkc.level_accessible(9));
+ assert!(blkc.level_accessible(10));
+ assert!(blkc.level_accessible(100));
assert_eq!(None, blkc.aes_key(0)?);
assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
blkc.advance_boot_level(0)?;
- assert_eq!(false, blkc.level_accessible(0));
- assert_eq!(false, blkc.level_accessible(9));
- assert_eq!(true, blkc.level_accessible(10));
- assert_eq!(true, blkc.level_accessible(100));
+ assert!(!blkc.level_accessible(0));
+ assert!(!blkc.level_accessible(9));
+ assert!(blkc.level_accessible(10));
+ assert!(blkc.level_accessible(100));
assert_eq!(None, blkc.aes_key(0)?);
assert_eq!(Some(v10), blkc.aes_key(10)?);
blkc.finish();
- assert_eq!(false, blkc.level_accessible(0));
- assert_eq!(false, blkc.level_accessible(9));
- assert_eq!(false, blkc.level_accessible(10));
- assert_eq!(false, blkc.level_accessible(100));
+ assert!(!blkc.level_accessible(0));
+ assert!(!blkc.level_accessible(9));
+ assert!(!blkc.level_accessible(10));
+ assert!(!blkc.level_accessible(100));
assert_eq!(None, blkc.aes_key(0)?);
assert_eq!(None, blkc.aes_key(10)?);
Ok(())
diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp
index 3ba47cd..4e76507 100644
--- a/keystore2/src/crypto/Android.bp
+++ b/keystore2/src/crypto/Android.bp
@@ -125,3 +125,13 @@
"libcrypto",
],
}
+
+rust_test {
+ name: "libkeystore2_crypto_bindgen_test",
+ srcs: [":libkeystore2_crypto_bindgen"],
+ crate_name: "keystore2_crypto_bindgen_test",
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
diff --git a/keystore2/src/crypto/certificate_utils.cpp b/keystore2/src/crypto/certificate_utils.cpp
index 24b3793..64bf1d0 100644
--- a/keystore2/src/crypto/certificate_utils.cpp
+++ b/keystore2/src/crypto/certificate_utils.cpp
@@ -19,6 +19,7 @@
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/mem.h>
+#include <openssl/ossl_typ.h>
#include <openssl/x509v3.h>
#include <functional>
@@ -517,10 +518,7 @@
return ASN1_STRING_Ptr(algo_str);
}
-CertUtilsError makeAndSetAlgo(X509_ALGOR* algo_field, Algo algo, Padding padding, Digest digest) {
- if (algo_field == nullptr) {
- return CertUtilsError::UnexpectedNullPointer;
- }
+std::variant<CertUtilsError, X509_ALGOR_Ptr> makeAlgo(Algo algo, Padding padding, Digest digest) {
ASN1_STRING_Ptr param;
int param_type = V_ASN1_UNDEF;
int nid = 0;
@@ -589,23 +587,29 @@
return CertUtilsError::InvalidArgument;
}
- if (!X509_ALGOR_set0(algo_field, OBJ_nid2obj(nid), param_type, param.get())) {
+ X509_ALGOR_Ptr result(X509_ALGOR_new());
+ if (!result) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ if (!X509_ALGOR_set0(result.get(), OBJ_nid2obj(nid), param_type, param.get())) {
return CertUtilsError::Encoding;
}
// The X509 struct took ownership.
param.release();
- return CertUtilsError::Ok;
+ return result;
}
// This function allows for signing a
CertUtilsError signCertWith(X509* certificate,
std::function<std::vector<uint8_t>(const uint8_t*, size_t)> sign,
Algo algo, Padding padding, Digest digest) {
- if (auto error = makeAndSetAlgo(certificate->sig_alg, algo, padding, digest)) {
- return error;
+ auto algo_objV = makeAlgo(algo, padding, digest);
+ if (auto error = std::get_if<CertUtilsError>(&algo_objV)) {
+ return *error;
}
- if (auto error = makeAndSetAlgo(certificate->cert_info->signature, algo, padding, digest)) {
- return error;
+ auto& algo_obj = std::get<X509_ALGOR_Ptr>(algo_objV);
+ if (!X509_set1_signature_algo(certificate, algo_obj.get())) {
+ return CertUtilsError::BoringSsl;
}
uint8_t* cert_buf = nullptr;
@@ -620,13 +624,10 @@
return CertUtilsError::SignatureFailed;
}
- if (!ASN1_STRING_set(certificate->signature, signature.data(), signature.size())) {
+ if (!X509_set1_signature_value(certificate, signature.data(), signature.size())) {
return CertUtilsError::BoringSsl;
}
- certificate->signature->flags &= ~(0x07);
- certificate->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT;
-
return CertUtilsError::Ok;
}
diff --git a/keystore2/src/crypto/error.rs b/keystore2/src/crypto/error.rs
index a369012..c6476f9 100644
--- a/keystore2/src/crypto/error.rs
+++ b/keystore2/src/crypto/error.rs
@@ -13,6 +13,7 @@
// limitations under the License.
//! This module implements Error for the keystore2_crypto library.
+use crate::zvec;
/// Crypto specific error codes.
#[derive(Debug, thiserror::Error, Eq, PartialEq)]
@@ -93,4 +94,8 @@
/// This is returned if the C implementation of extractSubjectFromCertificate failed.
#[error("Failed to extract certificate subject.")]
ExtractSubjectFailed,
+
+ /// Zvec error.
+ #[error(transparent)]
+ ZVec(#[from] zvec::Error),
}
diff --git a/keystore2/src/crypto/include/certificate_utils.h b/keystore2/src/crypto/include/certificate_utils.h
index b483d88..cad82b6 100644
--- a/keystore2/src/crypto/include/certificate_utils.h
+++ b/keystore2/src/crypto/include/certificate_utils.h
@@ -39,6 +39,7 @@
DEFINE_OPENSSL_OBJECT_POINTER(ASN1_TIME);
DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY);
DEFINE_OPENSSL_OBJECT_POINTER(X509);
+DEFINE_OPENSSL_OBJECT_POINTER(X509_ALGOR);
DEFINE_OPENSSL_OBJECT_POINTER(X509_EXTENSION);
DEFINE_OPENSSL_OBJECT_POINTER(X509_NAME);
DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY_CTX);
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 5f8a2ef..92da965 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -16,7 +16,7 @@
//! Keystore 2.0.
mod error;
-mod zvec;
+pub mod zvec;
pub use error::Error;
use keystore2_crypto_bindgen::{
extractSubjectFromCertificate, generateKeyFromPassword, randomBytes, AES_gcm_decrypt,
diff --git a/keystore2/src/crypto/tests/certificate_utils_test.cpp b/keystore2/src/crypto/tests/certificate_utils_test.cpp
index ebd6792..bd94928 100644
--- a/keystore2/src/crypto/tests/certificate_utils_test.cpp
+++ b/keystore2/src/crypto/tests/certificate_utils_test.cpp
@@ -334,4 +334,4 @@
// And one millisecond earlier must be GeneralizedTime.
// This also checks that the rounding direction does not flip when the input is negative.
ASSERT_EQ(std::string(toTimeString(-631152000001)->data()), "19491231235959Z");
-}
\ No newline at end of file
+}
diff --git a/keystore2/src/crypto/zvec.rs b/keystore2/src/crypto/zvec.rs
index 78b474e..5a173c3 100644
--- a/keystore2/src/crypto/zvec.rs
+++ b/keystore2/src/crypto/zvec.rs
@@ -12,7 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use crate::error::Error;
+//! Implements ZVec, a vector that is mlocked during its lifetime and zeroed
+//! when dropped.
+
use nix::sys::mman::{mlock, munlock};
use std::convert::TryFrom;
use std::fmt;
@@ -29,6 +31,14 @@
len: usize,
}
+/// ZVec specific error codes.
+#[derive(Debug, thiserror::Error, Eq, PartialEq)]
+pub enum Error {
+ /// Underlying libc error.
+ #[error(transparent)]
+ NixError(#[from] nix::Error),
+}
+
impl ZVec {
/// Create a ZVec with the given size.
pub fn new(size: usize) -> Result<Self, Error> {
@@ -48,6 +58,14 @@
self.len = len;
}
}
+
+ /// Attempts to make a clone of the Zvec. This may fail due trying to mlock
+ /// the new memory region.
+ pub fn try_clone(&self) -> Result<Self, Error> {
+ let mut result = Self::new(self.len())?;
+ result[..].copy_from_slice(&self[..]);
+ Ok(result)
+ }
}
impl Drop for ZVec {
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index c788720..d73cc8b 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -82,7 +82,7 @@
#[cfg(not(test))]
use rand::prelude::random;
use rusqlite::{
- params,
+ params, params_from_iter,
types::FromSql,
types::FromSqlResult,
types::ToSqlOutput,
@@ -142,7 +142,7 @@
let db_tag: i64 = row.get(0).context("Failed to read tag.")?;
metadata.insert(
db_tag,
- KeyMetaEntry::new_from_sql(db_tag, &SqlField::new(1, &row))
+ KeyMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row))
.context("Failed to read KeyMetaEntry.")?,
);
Ok(())
@@ -217,7 +217,7 @@
let db_tag: i64 = row.get(0).context("Failed to read tag.")?;
metadata.insert(
db_tag,
- BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, &row))
+ BlobMetaEntry::new_from_sql(db_tag, &SqlField::new(1, row))
.context("Failed to read BlobMetaEntry.")?,
);
Ok(())
@@ -388,12 +388,12 @@
}
/// Returns unix epoch time in milliseconds.
- pub fn to_millis_epoch(&self) -> i64 {
+ pub fn to_millis_epoch(self) -> i64 {
self.0
}
/// Returns unix epoch time in seconds.
- pub fn to_secs_epoch(&self) -> i64 {
+ pub fn to_secs_epoch(self) -> i64 {
self.0 / 1000
}
}
@@ -832,7 +832,7 @@
const UPGRADERS: &'static [fn(&Transaction) -> Result<u32>] = &[Self::from_0_to_1];
/// Name of the file that holds the cross-boot persistent database.
- pub const PERSISTENT_DB_FILENAME: &'static str = &"persistent.sqlite";
+ pub const PERSISTENT_DB_FILENAME: &'static str = "persistent.sqlite";
/// This will create a new database connection connecting the two
/// files persistent.sqlite and perboot.sqlite in the given directory.
@@ -842,7 +842,7 @@
pub fn new(db_root: &Path, gc: Option<Arc<Gc>>) -> Result<Self> {
let _wp = wd::watch_millis("KeystoreDB::new", 500);
- let persistent_path = Self::make_persistent_path(&db_root)?;
+ let persistent_path = Self::make_persistent_path(db_root)?;
let conn = Self::make_connection(&persistent_path)?;
let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() };
@@ -1029,7 +1029,7 @@
params: &[&str],
) -> Result<StorageStats> {
let (total, unused) = self.with_transaction(TransactionBehavior::Deferred, |tx| {
- tx.query_row(query, params, |row| Ok((row.get(0)?, row.get(1)?)))
+ tx.query_row(query, params_from_iter(params), |row| Ok((row.get(0)?, row.get(1)?)))
.with_context(|| {
format!("get_storage_stat: Error size of storage type {}", storage_type.0)
})
@@ -1244,7 +1244,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_descriptor =
KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None };
- let result = Self::load_key_entry_id(&tx, &key_descriptor, key_type);
+ let result = Self::load_key_entry_id(tx, &key_descriptor, key_type);
match result {
Ok(_) => Ok(true),
Err(error) => match error.root_cause().downcast_ref::<KsError>() {
@@ -1290,7 +1290,7 @@
key_metadata.store_in_db(key_id, tx).context("KeyMetaData::store_in_db failed")?;
Self::set_blob_internal(
- &tx,
+ tx,
key_id,
SubComponentType::KEY_BLOB,
Some(blob),
@@ -1320,10 +1320,10 @@
alias: Some(key_type.alias.into()),
blob: None,
};
- let id = Self::load_key_entry_id(&tx, &key_descriptor, KeyType::Super);
+ let id = Self::load_key_entry_id(tx, &key_descriptor, KeyType::Super);
match id {
Ok(id) => {
- let key_entry = Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
+ let key_entry = Self::load_key_components(tx, KeyEntryLoadBits::KM, id)
.context("In load_super_key. Failed to load key entry.")?;
Ok(Some((KEY_ID_LOCK.get(id), key_entry)))
}
@@ -1383,7 +1383,7 @@
let (id, entry) = match id {
Some(id) => (
id,
- Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
+ Self::load_key_components(tx, KeyEntryLoadBits::KM, id)
.context("In get_or_create_key_with.")?,
),
@@ -1409,7 +1409,7 @@
let (blob, metadata) =
create_new_key().context("In get_or_create_key_with.")?;
Self::set_blob_internal(
- &tx,
+ tx,
id,
SubComponentType::KEY_BLOB,
Some(&blob),
@@ -1560,7 +1560,7 @@
.context("In create_key_entry")?,
);
Self::set_blob_internal(
- &tx,
+ tx,
key_id.0,
SubComponentType::KEY_BLOB,
Some(private_key),
@@ -1569,7 +1569,7 @@
let mut metadata = KeyMetaData::new();
metadata.add(KeyMetaEntry::AttestationMacedPublicKey(maced_public_key.to_vec()));
metadata.add(KeyMetaEntry::AttestationRawPubKey(raw_public_key.to_vec()));
- metadata.store_in_db(key_id.0, &tx)?;
+ metadata.store_in_db(key_id.0, tx)?;
Ok(()).no_gc()
})
.context("In create_attestation_key_entry")
@@ -1592,7 +1592,7 @@
let _wp = wd::watch_millis("KeystoreDB::set_blob", 500);
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- Self::set_blob_internal(&tx, key_id.0, sc_type, blob, blob_metadata).need_gc()
+ Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc()
})
.context("In set_blob.")
}
@@ -1606,7 +1606,7 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
Self::set_blob_internal(
- &tx,
+ tx,
Self::UNASSIGNED_KEY_ID,
SubComponentType::KEY_BLOB,
Some(blob),
@@ -1699,7 +1699,7 @@
#[cfg(test)]
fn insert_key_metadata(&mut self, key_id: &KeyIdGuard, metadata: &KeyMetaData) -> Result<()> {
self.with_transaction(TransactionBehavior::Immediate, |tx| {
- metadata.store_in_db(key_id.0, &tx).no_gc()
+ metadata.store_in_db(key_id.0, tx).no_gc()
})
.context("In insert_key_metadata.")
}
@@ -1761,16 +1761,16 @@
metadata.add(KeyMetaEntry::AttestationExpirationDate(DateTime::from_millis_epoch(
expiration_date,
)));
- metadata.store_in_db(key_id, &tx).context("Failed to insert key metadata.")?;
+ metadata.store_in_db(key_id, tx).context("Failed to insert key metadata.")?;
Self::set_blob_internal(
- &tx,
+ tx,
key_id,
SubComponentType::CERT_CHAIN,
Some(cert_chain),
None,
)
.context("Failed to insert cert chain")?;
- Self::set_blob_internal(&tx, key_id, SubComponentType::CERT, Some(batch_cert), None)
+ Self::set_blob_internal(tx, key_id, SubComponentType::CERT, Some(batch_cert), None)
.context("Failed to insert cert")?;
Ok(()).no_gc()
})
@@ -1914,7 +1914,7 @@
);
let mut num_deleted = 0;
for id in key_ids_to_check.iter().filter(|kt| kt.1 < curr_time).map(|kt| kt.0) {
- if Self::mark_unreferenced(&tx, id)? {
+ if Self::mark_unreferenced(tx, id)? {
num_deleted += 1;
}
}
@@ -1941,7 +1941,7 @@
.context("Failed to execute statement")?;
let num_deleted = keys_to_delete
.iter()
- .map(|id| Self::mark_unreferenced(&tx, *id))
+ .map(|id| Self::mark_unreferenced(tx, *id))
.collect::<Result<Vec<bool>>>()
.context("Failed to execute mark_unreferenced on a keyid")?
.into_iter()
@@ -2227,7 +2227,7 @@
/// fields, and rebinds the given alias to the new key.
/// The boolean returned is a hint for the garbage collector. If true, a key was replaced,
/// is now unreferenced and needs to be collected.
- #[allow(clippy::clippy::too_many_arguments)]
+ #[allow(clippy::too_many_arguments)]
pub fn store_new_key(
&mut self,
key: &KeyDescriptor,
@@ -2259,11 +2259,11 @@
key_id.id(),
SubComponentType::KEY_BLOB,
Some(blob),
- Some(&blob_metadata),
+ Some(blob_metadata),
)
.context("Trying to insert the key blob.")?;
if let Some(cert) = &cert_info.cert {
- Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(&cert), None)
+ Self::set_blob_internal(tx, key_id.id(), SubComponentType::CERT, Some(cert), None)
.context("Trying to insert the certificate.")?;
}
if let Some(cert_chain) = &cert_info.cert_chain {
@@ -2271,7 +2271,7 @@
tx,
key_id.id(),
SubComponentType::CERT_CHAIN,
- Some(&cert_chain),
+ Some(cert_chain),
None,
)
.context("Trying to insert the certificate chain.")?;
@@ -2279,7 +2279,7 @@
Self::insert_keyparameter_internal(tx, &key_id, params)
.context("Trying to insert key parameters.")?;
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
- let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace, key_type)
+ let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type)
.context("Trying to rebind alias.")?;
Ok(key_id).do_gc(need_gc)
})
@@ -2329,7 +2329,7 @@
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
- let need_gc = Self::rebind_alias(tx, &key_id, &alias, &domain, namespace, key_type)
+ let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type)
.context("Trying to rebind alias.")?;
Ok(key_id).do_gc(need_gc)
})
@@ -2398,7 +2398,7 @@
if access_key.domain == Domain::APP {
access_key.nspace = caller_uid as i64;
}
- let key_id = Self::load_key_entry_id(&tx, &access_key, key_type)
+ let key_id = Self::load_key_entry_id(tx, &access_key, key_type)
.with_context(|| format!("With key.domain = {:?}.", access_key.domain))?;
Ok((key_id, access_key, None))
@@ -2563,7 +2563,7 @@
let tag = Tag(row.get(0).context("Failed to read tag.")?);
let sec_level = SecurityLevel(row.get(2).context("Failed to read sec_level.")?);
parameters.push(
- KeyParameter::new_from_sql(tag, &SqlField::new(1, &row), sec_level)
+ KeyParameter::new_from_sql(tag, &SqlField::new(1, row), sec_level)
.context("Failed to read KeyParameter.")?,
);
Ok(())
@@ -2941,7 +2941,7 @@
}
}
}
- notify_gc = Self::mark_unreferenced(&tx, key_id)
+ notify_gc = Self::mark_unreferenced(tx, key_id)
.context("In unbind_keys_for_user.")?
|| notify_gc;
}
@@ -2955,16 +2955,15 @@
load_bits: KeyEntryLoadBits,
key_id: i64,
) -> Result<KeyEntry> {
- let metadata = KeyMetaData::load_from_db(key_id, &tx).context("In load_key_components.")?;
+ let metadata = KeyMetaData::load_from_db(key_id, tx).context("In load_key_components.")?;
let (has_km_blob, key_blob_info, cert_blob, cert_chain_blob) =
- Self::load_blob_components(key_id, load_bits, &tx)
- .context("In load_key_components.")?;
+ Self::load_blob_components(key_id, load_bits, tx).context("In load_key_components.")?;
- let parameters = Self::load_key_parameters(key_id, &tx)
+ let parameters = Self::load_key_parameters(key_id, tx)
.context("In load_key_components: Trying to load key parameters.")?;
- let km_uuid = Self::get_key_km_uuid(&tx, key_id)
+ let km_uuid = Self::get_key_km_uuid(tx, key_id)
.context("In load_key_components: Trying to get KM uuid.")?;
Ok(KeyEntry {
@@ -3048,7 +3047,7 @@
// But even if we load the access tuple by grant here, the permission
// check denies the attempt to create a grant by grant descriptor.
let (key_id, access_key_descriptor, _) =
- Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid)
+ Self::load_access_tuple(tx, key, KeyType::Client, caller_uid)
.context("In grant")?;
// Perform access control. It is vital that we return here if the permission
@@ -3108,7 +3107,7 @@
// Load the key_id and complete the access control tuple.
// We ignore the access vector here because grants cannot be granted.
let (key_id, access_key_descriptor, _) =
- Self::load_access_tuple(&tx, key, KeyType::Client, caller_uid)
+ Self::load_access_tuple(tx, key, KeyType::Client, caller_uid)
.context("In ungrant.")?;
// Perform access control. We must return here if the permission
@@ -3466,7 +3465,7 @@
load_attestation_key_pool(&mut db, expiration_date, namespace, base_byte)?;
let chain =
db.retrieve_attestation_key_and_cert_chain(Domain::APP, namespace, &KEYSTORE_UUID)?;
- assert_eq!(true, chain.is_some());
+ assert!(chain.is_some());
let cert_chain = chain.unwrap();
assert_eq!(cert_chain.private_key.to_vec(), loaded_values.priv_key);
assert_eq!(cert_chain.batch_cert, loaded_values.batch_cert);
@@ -3705,8 +3704,8 @@
alias: Some("key".to_string()),
blob: None,
};
- const PVEC1: KeyPermSet = key_perm_set![KeyPerm::use_(), KeyPerm::get_info()];
- const PVEC2: KeyPermSet = key_perm_set![KeyPerm::use_()];
+ const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo];
+ const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use];
// Reset totally predictable random number generator in case we
// are not the first test running on this thread.
@@ -4182,7 +4181,7 @@
},
1,
2,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_k, _av| Ok(()),
)
.unwrap();
@@ -4192,7 +4191,7 @@
let (_key_guard, key_entry) = db
.load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| {
assert_eq!(Domain::GRANT, k.domain);
- assert!(av.unwrap().includes(KeyPerm::use_()));
+ assert!(av.unwrap().includes(KeyPerm::Use));
Ok(())
})
.unwrap();
@@ -4239,7 +4238,7 @@
},
OWNER_UID,
GRANTEE_UID,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_k, _av| Ok(()),
)
.unwrap();
@@ -4258,7 +4257,7 @@
|k, av| {
assert_eq!(Domain::APP, k.domain);
assert_eq!(OWNER_UID as i64, k.nspace);
- assert!(av.unwrap().includes(KeyPerm::use_()));
+ assert!(av.unwrap().includes(KeyPerm::Use));
Ok(())
},
)
@@ -4309,8 +4308,8 @@
let mut db = new_test_db()?;
const SOURCE_UID: u32 = 1u32;
const DESTINATION_UID: u32 = 2u32;
- static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
let key_id_guard =
make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
.context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
@@ -4378,8 +4377,8 @@
const SOURCE_UID: u32 = 1u32;
const DESTINATION_UID: u32 = 2u32;
const DESTINATION_NAMESPACE: i64 = 1000i64;
- static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
let key_id_guard =
make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
.context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
@@ -4446,8 +4445,8 @@
let mut db = new_test_db()?;
const SOURCE_UID: u32 = 1u32;
const DESTINATION_UID: u32 = 2u32;
- static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
- static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ static SOURCE_ALIAS: &str = "SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS";
let key_id_guard =
make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
.context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
@@ -4479,9 +4478,9 @@
#[test]
fn test_upgrade_0_to_1() {
- const ALIAS1: &str = &"test_upgrade_0_to_1_1";
- const ALIAS2: &str = &"test_upgrade_0_to_1_2";
- const ALIAS3: &str = &"test_upgrade_0_to_1_3";
+ const ALIAS1: &str = "test_upgrade_0_to_1_1";
+ const ALIAS2: &str = "test_upgrade_0_to_1_2";
+ const ALIAS3: &str = "test_upgrade_0_to_1_3";
const UID: u32 = 33;
let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap());
let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap();
@@ -4966,10 +4965,7 @@
Ok(KeyEntryRow {
id: row.get(0)?,
key_type: row.get(1)?,
- domain: match row.get(2)? {
- Some(i) => Some(Domain(i)),
- None => None,
- },
+ domain: row.get::<_, Option<_>>(2)?.map(Domain),
namespace: row.get(3)?,
alias: row.get(4)?,
state: row.get(5)?,
@@ -5479,7 +5475,7 @@
)?;
//check if super key exists
- assert!(db.key_exists(Domain::APP, 1, &USER_SUPER_KEY.alias, KeyType::Super)?);
+ assert!(db.key_exists(Domain::APP, 1, USER_SUPER_KEY.alias, KeyType::Super)?);
let (_, key_entry) = db.load_super_key(&USER_SUPER_KEY, 1)?.unwrap();
let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(
@@ -5585,7 +5581,7 @@
&& updated_stats[&k].unused_size == baseline[&k].unused_size,
"updated_stats:\n{}\nbaseline:\n{}",
stringify(&updated_stats),
- stringify(&baseline)
+ stringify(baseline)
);
}
}
@@ -5679,7 +5675,7 @@
},
OWNER as u32,
123,
- key_perm_set![KeyPerm::use_()],
+ key_perm_set![KeyPerm::Use],
|_, _| Ok(()),
)?;
diff --git a/keystore2/src/database/utils.rs b/keystore2/src/database/utils.rs
index 90f5616..b4590da 100644
--- a/keystore2/src/database/utils.rs
+++ b/keystore2/src/database/utils.rs
@@ -44,7 +44,7 @@
loop {
match rows.next().context("In with_rows_extract_all: Failed to unpack row")? {
Some(row) => {
- row_extractor(&row).context("In with_rows_extract_all.")?;
+ row_extractor(row).context("In with_rows_extract_all.")?;
}
None => break Ok(()),
}
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index b7b40f7..2407525 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -28,14 +28,13 @@
KeyParameter::KeyParameter as KmKeyParameter, KeyPurpose::KeyPurpose, Tag::Tag,
};
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
- ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
+ TimeStampToken::TimeStampToken,
};
use android_security_authorization::aidl::android::security::authorization::ResponseCode::ResponseCode as AuthzResponseCode;
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
OperationChallenge::OperationChallenge,
};
-use android_system_keystore2::binder::Strong;
use anyhow::{Context, Result};
use std::{
collections::{HashMap, HashSet},
@@ -219,13 +218,10 @@
}
fn get_timestamp_token(challenge: i64) -> Result<TimeStampToken, Error> {
- let dev: Strong<dyn ISecureClock> = get_timestamp_service()
- .expect(concat!(
- "Secure Clock service must be present ",
- "if TimeStampTokens are required."
- ))
- .get_interface()
- .expect("Fatal: Timestamp service does not implement ISecureClock.");
+ let dev = get_timestamp_service().expect(concat!(
+ "Secure Clock service must be present ",
+ "if TimeStampTokens are required."
+ ));
map_binder_status(dev.generateTimeStamp(challenge))
}
@@ -841,8 +837,12 @@
.context("In get_auth_tokens: No auth token found.");
}
} else {
- return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
- .context("In get_auth_tokens: Passed-in auth token max age is zero.");
+ return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)).context(
+ concat!(
+ "In get_auth_tokens: No auth token found for ",
+ "the given challenge and passed-in auth token max age is zero."
+ ),
+ );
}
};
// Wait and obtain the timestamp token from secure clock service.
diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp
new file mode 100644
index 0000000..384ab77
--- /dev/null
+++ b/keystore2/src/fuzzers/Android.bp
@@ -0,0 +1,29 @@
+// Copyright 2021, 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_fuzz {
+ name: "legacy_blob_fuzzer",
+ srcs: ["legacy_blob_fuzzer.rs"],
+ rustlibs: [
+ "libkeystore2",
+ ],
+ fuzz_config: {
+ fuzz_on_haiku_device: true,
+ fuzz_on_haiku_host: false,
+ },
+}
diff --git a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs b/keystore2/src/fuzzers/legacy_blob_fuzzer.rs
new file mode 100644
index 0000000..7e3e848
--- /dev/null
+++ b/keystore2/src/fuzzers/legacy_blob_fuzzer.rs
@@ -0,0 +1,26 @@
+// Copyright 2021, 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.
+
+#![allow(missing_docs)]
+#![no_main]
+#[macro_use]
+extern crate libfuzzer_sys;
+use keystore2::legacy_blob::LegacyBlobLoader;
+
+fuzz_target!(|data: &[u8]| {
+ if !data.is_empty() {
+ let string = data.iter().filter_map(|c| std::char::from_u32(*c as u32)).collect::<String>();
+ let _res = LegacyBlobLoader::decode_alias(&string);
+ }
+});
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index 2010c79..25f08c8 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -123,7 +123,7 @@
.super_key
.unwrap_key_if_required(&blob_metadata, &blob)
.context("In process_one_key: Trying to unwrap to-be-deleted blob.")?;
- (self.invalidate_key)(&uuid, &*blob)
+ (self.invalidate_key)(uuid, &*blob)
.context("In process_one_key: Trying to invalidate key.")?;
}
}
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 8212213..eae5ad0 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -21,7 +21,6 @@
use crate::legacy_migrator::LegacyMigrator;
use crate::super_key::SuperKeyManager;
use crate::utils::watchdog as wd;
-use crate::utils::Asp;
use crate::{async_task::AsyncTask, database::MonotonicRawTime};
use crate::{
database::KeystoreDB,
@@ -33,6 +32,9 @@
IKeyMintDevice::IKeyMintDevice, IRemotelyProvisionedComponent::IRemotelyProvisionedComponent,
KeyMintHardwareInfo::KeyMintHardwareInfo, SecurityLevel::SecurityLevel,
};
+use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
+ ISecureClock::ISecureClock,
+};
use android_hardware_security_keymint::binder::{StatusCode, Strong};
use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
use anyhow::{Context, Result};
@@ -85,34 +87,33 @@
RefCell::new(create_thread_local_db());
}
-#[derive(Default)]
-struct DevicesMap {
- devices_by_uuid: HashMap<Uuid, (Asp, KeyMintHardwareInfo)>,
+struct DevicesMap<T: FromIBinder + ?Sized> {
+ devices_by_uuid: HashMap<Uuid, (Strong<T>, KeyMintHardwareInfo)>,
uuid_by_sec_level: HashMap<SecurityLevel, Uuid>,
}
-impl DevicesMap {
+impl<T: FromIBinder + ?Sized> DevicesMap<T> {
fn dev_by_sec_level(
&self,
sec_level: &SecurityLevel,
- ) -> Option<(Asp, KeyMintHardwareInfo, Uuid)> {
+ ) -> Option<(Strong<T>, KeyMintHardwareInfo, Uuid)> {
self.uuid_by_sec_level.get(sec_level).and_then(|uuid| self.dev_by_uuid(uuid))
}
- fn dev_by_uuid(&self, uuid: &Uuid) -> Option<(Asp, KeyMintHardwareInfo, Uuid)> {
+ fn dev_by_uuid(&self, uuid: &Uuid) -> Option<(Strong<T>, KeyMintHardwareInfo, Uuid)> {
self.devices_by_uuid
.get(uuid)
.map(|(dev, hw_info)| ((*dev).clone(), (*hw_info).clone(), *uuid))
}
- fn devices<T: FromIBinder + ?Sized>(&self) -> Vec<Strong<T>> {
- self.devices_by_uuid.values().filter_map(|(asp, _)| asp.get_interface::<T>().ok()).collect()
+ fn devices(&self) -> Vec<Strong<T>> {
+ self.devices_by_uuid.values().map(|(dev, _)| dev.clone()).collect()
}
/// The requested security level and the security level of the actual implementation may
/// differ. So we map the requested security level to the uuid of the implementation
/// so that there cannot be any confusion as to which KeyMint instance is requested.
- fn insert(&mut self, sec_level: SecurityLevel, dev: Asp, hw_info: KeyMintHardwareInfo) {
+ fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>, hw_info: KeyMintHardwareInfo) {
// For now we use the reported security level of the KM instance as UUID.
// TODO update this section once UUID was added to the KM hardware info.
let uuid: Uuid = sec_level.into();
@@ -121,17 +122,31 @@
}
}
-#[derive(Default)]
-struct RemotelyProvisionedDevicesMap {
- devices_by_sec_level: HashMap<SecurityLevel, Asp>,
+impl<T: FromIBinder + ?Sized> Default for DevicesMap<T> {
+ fn default() -> Self {
+ Self {
+ devices_by_uuid: HashMap::<Uuid, (Strong<T>, KeyMintHardwareInfo)>::new(),
+ uuid_by_sec_level: Default::default(),
+ }
+ }
}
-impl RemotelyProvisionedDevicesMap {
- fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Asp> {
+struct RemotelyProvisionedDevicesMap<T: FromIBinder + ?Sized> {
+ devices_by_sec_level: HashMap<SecurityLevel, Strong<T>>,
+}
+
+impl<T: FromIBinder + ?Sized> Default for RemotelyProvisionedDevicesMap<T> {
+ fn default() -> Self {
+ Self { devices_by_sec_level: HashMap::<SecurityLevel, Strong<T>>::new() }
+ }
+}
+
+impl<T: FromIBinder + ?Sized> RemotelyProvisionedDevicesMap<T> {
+ fn dev_by_sec_level(&self, sec_level: &SecurityLevel) -> Option<Strong<T>> {
self.devices_by_sec_level.get(sec_level).map(|dev| (*dev).clone())
}
- fn insert(&mut self, sec_level: SecurityLevel, dev: Asp) {
+ fn insert(&mut self, sec_level: SecurityLevel, dev: Strong<T>) {
self.devices_by_sec_level.insert(sec_level, dev);
}
}
@@ -143,11 +158,13 @@
/// Runtime database of unwrapped super keys.
pub static ref SUPER_KEY: Arc<SuperKeyManager> = Default::default();
/// Map of KeyMint devices.
- static ref KEY_MINT_DEVICES: Mutex<DevicesMap> = Default::default();
+ static ref KEY_MINT_DEVICES: Mutex<DevicesMap<dyn IKeyMintDevice>> = Default::default();
/// Timestamp service.
- static ref TIME_STAMP_DEVICE: Mutex<Option<Asp>> = Default::default();
+ static ref TIME_STAMP_DEVICE: Mutex<Option<Strong<dyn ISecureClock>>> = Default::default();
/// RemotelyProvisionedComponent HAL devices.
- static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES: Mutex<RemotelyProvisionedDevicesMap> = Default::default();
+ static ref REMOTELY_PROVISIONED_COMPONENT_DEVICES:
+ Mutex<RemotelyProvisionedDevicesMap<dyn IRemotelyProvisionedComponent>> =
+ Default::default();
/// A single on-demand worker thread that handles deferred tasks with two different
/// priorities.
pub static ref ASYNC_TASK: Arc<AsyncTask> = Default::default();
@@ -166,8 +183,7 @@
static ref GC: Arc<Gc> = Arc::new(Gc::new_init_with(ASYNC_TASK.clone(), || {
(
Box::new(|uuid, blob| {
- let km_dev: Strong<dyn IKeyMintDevice> =
- get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?.get_interface()?;
+ let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?;
let _wp = wd::watch_millis("In invalidate key closure: calling deleteKey", 500);
map_km_error(km_dev.deleteKey(&*blob))
.context("In invalidate key closure: Trying to invalidate key blob.")
@@ -184,20 +200,22 @@
/// Make a new connection to a KeyMint device of the given security level.
/// If no native KeyMint device can be found this function also brings
/// up the compatibility service and attempts to connect to the legacy wrapper.
-fn connect_keymint(security_level: &SecurityLevel) -> Result<(Asp, KeyMintHardwareInfo)> {
+fn connect_keymint(
+ security_level: &SecurityLevel,
+) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> {
let keymint_instances =
get_aidl_instances("android.hardware.security.keymint", 1, "IKeyMintDevice");
let service_name = match *security_level {
SecurityLevel::TRUSTED_ENVIRONMENT => {
- if keymint_instances.as_vec()?.iter().any(|instance| *instance == "default") {
+ if keymint_instances.iter().any(|instance| *instance == "default") {
Some(format!("{}/default", KEYMINT_SERVICE_NAME))
} else {
None
}
}
SecurityLevel::STRONGBOX => {
- if keymint_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") {
+ if keymint_instances.iter().any(|instance| *instance == "strongbox") {
Some(format!("{}/strongbox", KEYMINT_SERVICE_NAME))
} else {
None
@@ -251,7 +269,7 @@
hw_info.versionNumber = hal_version;
}
- Ok((Asp::new(keymint.as_binder()), hw_info))
+ Ok((keymint, hw_info))
}
/// Get a keymint device for the given security level either from our cache or
@@ -259,9 +277,9 @@
/// TODO the latter can be removed when the uuid is part of the hardware info.
pub fn get_keymint_device(
security_level: &SecurityLevel,
-) -> Result<(Asp, KeyMintHardwareInfo, Uuid)> {
+) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo, Uuid)> {
let mut devices_map = KEY_MINT_DEVICES.lock().unwrap();
- if let Some((dev, hw_info, uuid)) = devices_map.dev_by_sec_level(&security_level) {
+ if let Some((dev, hw_info, uuid)) = devices_map.dev_by_sec_level(security_level) {
Ok((dev, hw_info, uuid))
} else {
let (dev, hw_info) = connect_keymint(security_level).context("In get_keymint_device.")?;
@@ -275,7 +293,9 @@
/// attempt to establish a new connection. It is assumed that the cache is already populated
/// when this is called. This is a fair assumption, because service.rs iterates through all
/// security levels when it gets instantiated.
-pub fn get_keymint_dev_by_uuid(uuid: &Uuid) -> Result<(Asp, KeyMintHardwareInfo)> {
+pub fn get_keymint_dev_by_uuid(
+ uuid: &Uuid,
+) -> Result<(Strong<dyn IKeyMintDevice>, KeyMintHardwareInfo)> {
let devices_map = KEY_MINT_DEVICES.lock().unwrap();
if let Some((dev, hw_info, _)) = devices_map.dev_by_uuid(uuid) {
Ok((dev, hw_info))
@@ -294,12 +314,12 @@
/// Make a new connection to a secure clock service.
/// If no native SecureClock device can be found brings up the compatibility service and attempts
/// to connect to the legacy wrapper.
-fn connect_secureclock() -> Result<Asp> {
+fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> {
let secureclock_instances =
get_aidl_instances("android.hardware.security.secureclock", 1, "ISecureClock");
let secure_clock_available =
- secureclock_instances.as_vec()?.iter().any(|instance| *instance == "default");
+ secureclock_instances.iter().any(|instance| *instance == "default");
let default_time_stamp_service_name = format!("{}/default", TIME_STAMP_SERVICE_NAME);
@@ -325,12 +345,12 @@
.context("In connect_secureclock: Trying to get Legacy wrapper.")
}?;
- Ok(Asp::new(secureclock.as_binder()))
+ Ok(secureclock)
}
/// Get the timestamp service that verifies auth token timeliness towards security levels with
/// different clocks.
-pub fn get_timestamp_service() -> Result<Asp> {
+pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> {
let mut ts_device = TIME_STAMP_DEVICE.lock().unwrap();
if let Some(dev) = &*ts_device {
Ok(dev.clone())
@@ -344,20 +364,22 @@
static REMOTE_PROVISIONING_HAL_SERVICE_NAME: &str =
"android.hardware.security.keymint.IRemotelyProvisionedComponent";
-fn connect_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> {
+fn connect_remotely_provisioned_component(
+ security_level: &SecurityLevel,
+) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
let remotely_prov_instances =
get_aidl_instances("android.hardware.security.keymint", 1, "IRemotelyProvisionedComponent");
let service_name = match *security_level {
SecurityLevel::TRUSTED_ENVIRONMENT => {
- if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "default") {
+ if remotely_prov_instances.iter().any(|instance| *instance == "default") {
Some(format!("{}/default", REMOTE_PROVISIONING_HAL_SERVICE_NAME))
} else {
None
}
}
SecurityLevel::STRONGBOX => {
- if remotely_prov_instances.as_vec()?.iter().any(|instance| *instance == "strongbox") {
+ if remotely_prov_instances.iter().any(|instance| *instance == "strongbox") {
Some(format!("{}/strongbox", REMOTE_PROVISIONING_HAL_SERVICE_NAME))
} else {
None
@@ -375,14 +397,16 @@
" RemotelyProvisionedComponent service."
))
.map_err(|e| e)?;
- Ok(Asp::new(rem_prov_hal.as_binder()))
+ Ok(rem_prov_hal)
}
/// Get a remote provisiong component device for the given security level either from the cache or
/// by making a new connection. Returns the device.
-pub fn get_remotely_provisioned_component(security_level: &SecurityLevel) -> Result<Asp> {
+pub fn get_remotely_provisioned_component(
+ security_level: &SecurityLevel,
+) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
let mut devices_map = REMOTELY_PROVISIONED_COMPONENT_DEVICES.lock().unwrap();
- if let Some(dev) = devices_map.dev_by_sec_level(&security_level) {
+ if let Some(dev) = devices_map.dev_by_sec_level(security_level) {
Ok(dev)
} else {
let dev = connect_remotely_provisioned_component(security_level)
diff --git a/keystore2/src/id_rotation.rs b/keystore2/src/id_rotation.rs
index dbf0fc9..e3992d8 100644
--- a/keystore2/src/id_rotation.rs
+++ b/keystore2/src/id_rotation.rs
@@ -27,7 +27,7 @@
use std::time::Duration;
const ID_ROTATION_PERIOD: Duration = Duration::from_secs(30 * 24 * 60 * 60); // Thirty days.
-static TIMESTAMP_FILE_NAME: &str = &"timestamp";
+static TIMESTAMP_FILE_NAME: &str = "timestamp";
/// The IdRotationState stores the path to the timestamp file for deferred usage. The data
/// partition is usually not available when Keystore 2.0 starts up. So this object is created
@@ -83,7 +83,7 @@
fn test_had_factory_reset_since_id_rotation() -> Result<()> {
let temp_dir = TempDir::new("test_had_factory_reset_since_id_rotation_")
.expect("Failed to create temp dir.");
- let id_rotation_state = IdRotationState::new(&temp_dir.path());
+ let id_rotation_state = IdRotationState::new(temp_dir.path());
let mut temp_file_path = temp_dir.path().to_owned();
temp_file_path.push(TIMESTAMP_FILE_NAME);
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index cf2ba04..abab4b6 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -25,7 +25,8 @@
use keystore2::{authorization::AuthorizationManager, id_rotation::IdRotationState};
use legacykeystore::LegacyKeystore;
use log::{error, info};
-use std::{panic, path::Path, sync::mpsc::channel};
+use rusqlite::trace as sqlite_trace;
+use std::{os::raw::c_int, panic, path::Path, sync::mpsc::channel};
static KS2_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
static APC_SERVICE_NAME: &str = "android.security.apc";
@@ -52,6 +53,14 @@
let mut args = std::env::args();
args.next().expect("That's odd. How is there not even a first argument?");
+ // This must happen early before any other sqlite operations.
+ log::info!("Setting up sqlite logging for keystore2");
+ fn sqlite_log_handler(err: c_int, message: &str) {
+ log::error!("[SQLITE3] {}: {}", err, message);
+ }
+ unsafe { sqlite_trace::config_log(Some(sqlite_log_handler)) }
+ .expect("Error setting sqlite log callback.");
+
// Write/update keystore.crash_count system property.
metrics_store::update_keystore_crash_sysprop();
@@ -63,7 +72,7 @@
let db_path = Path::new(&dir);
*keystore2::globals::DB_PATH.write().expect("Could not lock DB_PATH.") =
db_path.to_path_buf();
- IdRotationState::new(&db_path)
+ IdRotationState::new(db_path)
} else {
panic!("Must specify a database directory.");
};
diff --git a/keystore2/src/km_compat/Android.bp b/keystore2/src/km_compat/Android.bp
index 541788e..806f3dc 100644
--- a/keystore2/src/km_compat/Android.bp
+++ b/keystore2/src/km_compat/Android.bp
@@ -25,9 +25,10 @@
name: "libkeystore2_km_compat",
crate_name: "keystore2_km_compat",
srcs: ["lib.rs"],
-
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
rustlibs: [
- "android.hardware.security.keymint-V1-rust",
"android.security.compat-rust",
],
shared_libs: [
@@ -41,8 +42,10 @@
srcs: ["lib.rs"],
test_suites: ["general-tests"],
auto_gen_config: true,
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ ],
rustlibs: [
- "android.hardware.security.keymint-V1-rust",
"android.security.compat-rust",
],
shared_libs: [
@@ -53,15 +56,17 @@
cc_library {
name: "libkm_compat",
srcs: ["km_compat.cpp"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ "keystore2_use_latest_aidl_ndk_shared",
+ ],
shared_libs: [
"android.hardware.keymaster@3.0",
"android.hardware.keymaster@4.0",
"android.hardware.keymaster@4.1",
- "android.hardware.security.keymint-V1-ndk_platform",
- "android.hardware.security.secureclock-V1-ndk_platform",
- "android.hardware.security.sharedsecret-V1-ndk_platform",
- "android.security.compat-ndk_platform",
- "android.system.keystore2-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk",
+ "android.hardware.security.sharedsecret-V1-ndk",
+ "android.security.compat-ndk",
"libbase",
"libbinder_ndk",
"libcrypto",
@@ -77,11 +82,13 @@
cc_library {
name: "libkm_compat_service",
srcs: ["km_compat_service.cpp"],
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ ],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk_platform",
- "android.hardware.security.secureclock-V1-ndk_platform",
- "android.hardware.security.sharedsecret-V1-ndk_platform",
- "android.security.compat-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk",
+ "android.hardware.security.sharedsecret-V1-ndk",
+ "android.security.compat-ndk",
"libbinder_ndk",
"libcrypto",
"libkm_compat",
@@ -103,15 +110,17 @@
"parameter_conversion_test.cpp",
"slot_test.cpp",
],
+ defaults: [
+ "keymint_use_latest_hal_aidl_ndk_shared",
+ "keystore2_use_latest_aidl_ndk_shared",
+ ],
shared_libs: [
"android.hardware.keymaster@3.0",
"android.hardware.keymaster@4.0",
"android.hardware.keymaster@4.1",
- "android.hardware.security.keymint-V1-ndk_platform",
- "android.hardware.security.secureclock-V1-ndk_platform",
- "android.hardware.security.sharedsecret-V1-ndk_platform",
- "android.security.compat-ndk_platform",
- "android.system.keystore2-V1-ndk_platform",
+ "android.hardware.security.secureclock-V1-ndk",
+ "android.hardware.security.sharedsecret-V1-ndk",
+ "android.security.compat-ndk",
"libbase",
"libbinder_ndk",
"libcrypto",
diff --git a/keystore2/src/km_compat/km_compat.cpp b/keystore2/src/km_compat/km_compat.cpp
index 8d59a5a..bb60047 100644
--- a/keystore2/src/km_compat/km_compat.cpp
+++ b/keystore2/src/km_compat/km_compat.cpp
@@ -1340,7 +1340,7 @@
CHECK(serviceManager.get()) << "Failed to get ServiceManager";
auto result = enumerateKeymasterDevices<Keymaster4>(serviceManager.get());
auto softKeymaster = result[SecurityLevel::SOFTWARE];
- if (!result[SecurityLevel::TRUSTED_ENVIRONMENT]) {
+ if ((!result[SecurityLevel::TRUSTED_ENVIRONMENT]) && (!result[SecurityLevel::STRONGBOX])) {
result = enumerateKeymasterDevices<Keymaster3>(serviceManager.get());
}
if (softKeymaster) result[SecurityLevel::SOFTWARE] = softKeymaster;
diff --git a/keystore2/src/km_compat/km_compat_type_conversion.h b/keystore2/src/km_compat/km_compat_type_conversion.h
index de09477..33248a4 100644
--- a/keystore2/src/km_compat/km_compat_type_conversion.h
+++ b/keystore2/src/km_compat/km_compat_type_conversion.h
@@ -16,6 +16,9 @@
#pragma once
+#include <optional>
+
+#include <aidl/android/hardware/security/keymint/EcCurve.h>
#include <aidl/android/hardware/security/keymint/ErrorCode.h>
#include <keymasterV4_1/keymaster_tags.h>
#include <keymint_support/keymint_tags.h>
@@ -278,7 +281,7 @@
}
}
-static V4_0::EcCurve convert(KMV1::EcCurve e) {
+static std::optional<V4_0::EcCurve> convert(KMV1::EcCurve e) {
switch (e) {
case KMV1::EcCurve::P_224:
return V4_0::EcCurve::P_224;
@@ -288,7 +291,11 @@
return V4_0::EcCurve::P_384;
case KMV1::EcCurve::P_521:
return V4_0::EcCurve::P_521;
+ case KMV1::EcCurve::CURVE_25519:
+ // KeyMaster did not support curve 25519
+ return std::nullopt;
}
+ return std::nullopt;
}
static KMV1::EcCurve convert(V4_0::EcCurve e) {
@@ -490,7 +497,9 @@
break;
case KMV1::Tag::EC_CURVE:
if (auto v = KMV1::authorizationValue(KMV1::TAG_EC_CURVE, kp)) {
- return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, convert(v->get()));
+ if (auto curve = convert(v->get())) {
+ return V4_0::makeKeyParameter(V4_0::TAG_EC_CURVE, curve.value());
+ }
}
break;
case KMV1::Tag::RSA_PUBLIC_EXPONENT:
diff --git a/keystore2/src/km_compat/lib.rs b/keystore2/src/km_compat/lib.rs
index 56c35bf..8abf2ba 100644
--- a/keystore2/src/km_compat/lib.rs
+++ b/keystore2/src/km_compat/lib.rs
@@ -260,7 +260,7 @@
if let Some(mut extras) = extra_params {
kps.append(&mut extras);
}
- let result = legacy.begin(purpose, &blob, &kps, None);
+ let result = legacy.begin(purpose, blob, &kps, None);
assert!(result.is_ok(), "{:?}", result);
result.unwrap()
}
@@ -450,10 +450,6 @@
)));
assert!(sec_level_enforced.iter().any(|kp| matches!(
kp,
- KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KeyParameterValue::Integer(_) }
- )));
- assert!(sec_level_enforced.iter().any(|kp| matches!(
- kp,
KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KeyParameterValue::Integer(_) }
)));
}
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index 6b16d2e..7454cca 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -416,14 +416,14 @@
BlobValue::Encrypted { iv, tag, data } => Ok(Blob {
flags: blob.flags,
value: BlobValue::Decrypted(
- decrypt(&data, &iv, &tag, None, None)
+ decrypt(data, iv, tag, None, None)
.context("In new_from_stream_decrypt_with.")?,
),
}),
BlobValue::PwEncrypted { iv, tag, data, salt, key_size } => Ok(Blob {
flags: blob.flags,
value: BlobValue::Decrypted(
- decrypt(&data, &iv, &tag, Some(salt), Some(*key_size))
+ decrypt(data, iv, tag, Some(salt), Some(*key_size))
.context("In new_from_stream_decrypt_with.")?,
),
}),
@@ -836,7 +836,7 @@
// in are all in the printable range that don't get mangled.
for prefix in Self::KNOWN_KEYSTORE_PREFIXES {
if let Some(alias) = encoded_alias.strip_prefix(prefix) {
- return Self::decode_alias(&alias).ok();
+ return Self::decode_alias(alias).ok();
}
}
None
diff --git a/keystore2/src/legacy_migrator.rs b/keystore2/src/legacy_migrator.rs
index f92fd45..65f4b0b 100644
--- a/keystore2/src/legacy_migrator.rs
+++ b/keystore2/src/legacy_migrator.rs
@@ -567,7 +567,7 @@
if let Some(super_key) = self
.legacy_loader
- .load_super_key(user_id, &pw)
+ .load_super_key(user_id, pw)
.context("In check_and_migrate_super_key: Trying to load legacy super key.")?
{
let (blob, blob_metadata) =
@@ -724,8 +724,8 @@
fn deref(&self) -> &Self::Target {
match self {
- Self::Vec(v) => &v,
- Self::ZVec(v) => &v,
+ Self::Vec(v) => v,
+ Self::ZVec(v) => v,
}
}
}
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 3180e5d..57abc26 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -23,8 +23,9 @@
use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::UserState;
use crate::utils::{check_key_permission, check_keystore_permission, watchdog as wd};
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
+};
use android_security_maintenance::aidl::android::security::maintenance::{
IKeystoreMaintenance::{BnKeystoreMaintenance, IKeystoreMaintenance},
UserState::UserState as AidlUserState,
@@ -68,7 +69,7 @@
fn on_user_password_changed(user_id: i32, password: Option<Password>) -> Result<()> {
//Check permission. Function should return if this failed. Therefore having '?' at the end
//is very important.
- check_keystore_permission(KeystorePerm::change_password())
+ check_keystore_permission(KeystorePerm::ChangePassword)
.context("In on_user_password_changed.")?;
if let Some(pw) = password.as_ref() {
@@ -105,7 +106,7 @@
fn add_or_remove_user(&self, user_id: i32) -> Result<()> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::change_user()).context("In add_or_remove_user.")?;
+ check_keystore_permission(KeystorePerm::ChangeUser).context("In add_or_remove_user.")?;
DB.with(|db| {
UserState::reset_user(
&mut db.borrow_mut(),
@@ -123,7 +124,7 @@
fn clear_namespace(&self, domain: Domain, nspace: i64) -> Result<()> {
// Permission check. Must return on error. Do not touch the '?'.
- check_keystore_permission(KeystorePerm::clear_uid()).context("In clear_namespace.")?;
+ check_keystore_permission(KeystorePerm::ClearUID).context("In clear_namespace.")?;
LEGACY_MIGRATOR
.bulk_delete_uid(domain, nspace)
@@ -138,7 +139,7 @@
fn get_state(user_id: i32) -> Result<AidlUserState> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::get_state()).context("In get_state.")?;
+ check_keystore_permission(KeystorePerm::GetState).context("In get_state.")?;
let state = DB
.with(|db| {
UserState::get(&mut db.borrow_mut(), &LEGACY_MIGRATOR, &SUPER_KEY, user_id as u32)
@@ -156,11 +157,8 @@
where
F: Fn(Strong<dyn IKeyMintDevice>) -> binder::public_api::Result<()>,
{
- let (dev, _, _) = get_keymint_device(&sec_level)
+ let (km_dev, _, _) = get_keymint_device(&sec_level)
.context("In call_with_watchdog: getting keymint device")?;
- let km_dev: Strong<dyn IKeyMintDevice> = dev
- .get_interface()
- .context("In call_with_watchdog: getting keymint device interface")?;
let _wp = wd::watch_millis_with("In call_with_watchdog", 500, move || {
format!("Seclevel: {:?} Op: {}", sec_level, name)
@@ -197,7 +195,7 @@
}
fn early_boot_ended() -> Result<()> {
- check_keystore_permission(KeystorePerm::early_boot_ended())
+ check_keystore_permission(KeystorePerm::EarlyBootEnded)
.context("In early_boot_ended. Checking permission")?;
log::info!("In early_boot_ended.");
@@ -209,8 +207,7 @@
fn on_device_off_body() -> Result<()> {
// Security critical permission check. This statement must return on fail.
- check_keystore_permission(KeystorePerm::report_off_body())
- .context("In on_device_off_body.")?;
+ check_keystore_permission(KeystorePerm::ReportOffBody).context("In on_device_off_body.")?;
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()));
Ok(())
@@ -218,21 +215,26 @@
fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
+ let migrate_any_key_permission =
+ check_keystore_permission(KeystorePerm::MigrateAnyKey).is_ok();
DB.with(|db| {
let key_id_guard = match source.domain {
Domain::APP | Domain::SELINUX | Domain::KEY_ID => {
let (key_id_guard, _) = LEGACY_MIGRATOR
- .with_try_migrate(&source, caller_uid, || {
+ .with_try_migrate(source, caller_uid, || {
db.borrow_mut().load_key_entry(
- &source,
+ source,
KeyType::Client,
KeyEntryLoadBits::NONE,
caller_uid,
|k, av| {
- check_key_permission(KeyPerm::use_(), k, &av)?;
- check_key_permission(KeyPerm::delete(), k, &av)?;
- check_key_permission(KeyPerm::grant(), k, &av)
+ if !migrate_any_key_permission {
+ check_key_permission(KeyPerm::Use, k, &av)?;
+ check_key_permission(KeyPerm::Delete, k, &av)?;
+ check_key_permission(KeyPerm::Grant, k, &av)?;
+ }
+ Ok(())
},
)
})
@@ -248,14 +250,17 @@
};
db.borrow_mut().migrate_key_namespace(key_id_guard, destination, caller_uid, |k| {
- check_key_permission(KeyPerm::rebind(), k, &None)
+ if !migrate_any_key_permission {
+ check_key_permission(KeyPerm::Rebind, k, &None)?;
+ }
+ Ok(())
})
})
}
fn delete_all_keys() -> Result<()> {
// Security critical permission check. This statement must return on fail.
- check_keystore_permission(KeystorePerm::delete_all_keys())
+ check_keystore_permission(KeystorePerm::DeleteAllKeys)
.context("In delete_all_keys. Checking permission")?;
log::info!("In delete_all_keys.");
diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs
index 42295b7..3d8d6d3 100644
--- a/keystore2/src/metrics.rs
+++ b/keystore2/src/metrics.rs
@@ -41,7 +41,7 @@
fn pull_metrics(&self, atom_id: AtomID) -> Result<Vec<KeystoreAtom>> {
// Check permission. Function should return if this failed. Therefore having '?' at the end
// is very important.
- check_keystore_permission(KeystorePerm::pull_metrics()).context("In pull_metrics.")?;
+ check_keystore_permission(KeystorePerm::PullMetrics).context("In pull_metrics.")?;
METRICS_STORE.get_atoms(atom_id)
}
}
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index a064f65..b18d84c 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -46,8 +46,8 @@
};
use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
use anyhow::{Context, Result};
-use keystore2_system_property::{write, PropertyWatcher, PropertyWatcherError};
use lazy_static::lazy_static;
+use rustutils::system_properties::PropertyWatcherError;
use std::collections::HashMap;
use std::sync::Mutex;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
@@ -288,6 +288,7 @@
EcCurve::P_256 => MetricsEcCurve::P_256,
EcCurve::P_384 => MetricsEcCurve::P_384,
EcCurve::P_521 => MetricsEcCurve::P_521,
+ EcCurve::CURVE_25519 => MetricsEcCurve::CURVE_25519,
_ => MetricsEcCurve::EC_CURVE_UNSPECIFIED,
}
}
@@ -631,7 +632,9 @@
}
};
- if let Err(e) = write(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string()) {
+ if let Err(e) =
+ rustutils::system_properties::write(KEYSTORE_CRASH_COUNT_PROPERTY, &new_count.to_string())
+ {
log::error!(
concat!(
"In update_keystore_crash_sysprop:: ",
@@ -644,12 +647,10 @@
/// Read the system property: keystore.crash_count.
pub fn read_keystore_crash_count() -> Result<i32> {
- let mut prop_reader = PropertyWatcher::new("keystore.crash_count").context(concat!(
- "In read_keystore_crash_count: Failed to create reader a PropertyWatcher."
- ))?;
- prop_reader
- .read(|_n, v| v.parse::<i32>().map_err(std::convert::Into::into))
- .context("In read_keystore_crash_count: Failed to read the existing system property.")
+ rustutils::system_properties::read("keystore.crash_count")
+ .context("In read_keystore_crash_count: Failed read property.")?
+ .parse::<i32>()
+ .map_err(std::convert::Into::into)
}
/// Enum defining the bit position for each padding mode. Since padding mode can be repeatable, it
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 7171864..7e08f4e 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -128,12 +128,12 @@
use crate::enforcements::AuthInfo;
use crate::error::{map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, ResponseCode};
use crate::metrics_store::log_key_operation_event_stats;
-use crate::utils::{watchdog as wd, Asp};
+use crate::utils::watchdog as wd;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose,
SecurityLevel::SecurityLevel,
};
-use android_hardware_security_keymint::binder::BinderFeatures;
+use android_hardware_security_keymint::binder::{BinderFeatures, Strong};
use android_system_keystore2::aidl::android::system::keystore2::{
IKeystoreOperation::BnKeystoreOperation, IKeystoreOperation::IKeystoreOperation,
};
@@ -170,7 +170,7 @@
pub struct Operation {
// The index of this operation in the OperationDb.
index: usize,
- km_op: Asp,
+ km_op: Strong<dyn IKeyMintOperation>,
last_usage: Mutex<Instant>,
outcome: Mutex<Outcome>,
owner: u32, // Uid of the operation's owner.
@@ -222,7 +222,7 @@
) -> Self {
Self {
index,
- km_op: Asp::new(km_op.as_binder()),
+ km_op,
last_usage: Mutex::new(Instant::now()),
outcome: Mutex::new(Outcome::Unknown),
owner,
@@ -282,19 +282,10 @@
}
*locked_outcome = Outcome::Pruned;
- let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
- match self.km_op.get_interface() {
- Ok(km_op) => km_op,
- Err(e) => {
- log::error!("In prune: Failed to get KeyMintOperation interface.\n {:?}", e);
- return Err(Error::sys());
- }
- };
-
let _wp = wd::watch_millis("In Operation::prune: calling abort()", 500);
// We abort the operation. If there was an error we log it but ignore it.
- if let Err(e) = map_km_error(km_op.abort()) {
+ if let Err(e) = map_km_error(self.km_op.abort()) {
log::error!("In prune: KeyMint::abort failed with {:?}.", e);
}
@@ -362,9 +353,6 @@
Self::check_input_length(aad_input).context("In update_aad")?;
self.touch();
- let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
- self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?;
-
let (hat, tst) = self
.auth_info
.lock()
@@ -374,7 +362,7 @@
self.update_outcome(&mut *outcome, {
let _wp = wd::watch_millis("Operation::update_aad: calling updateAad", 500);
- map_km_error(km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref()))
+ map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref()))
})
.context("In update_aad: KeyMint::update failed.")?;
@@ -388,9 +376,6 @@
Self::check_input_length(input).context("In update")?;
self.touch();
- let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
- self.km_op.get_interface().context("In update: Failed to get KeyMintOperation.")?;
-
let (hat, tst) = self
.auth_info
.lock()
@@ -401,7 +386,7 @@
let output = self
.update_outcome(&mut *outcome, {
let _wp = wd::watch_millis("Operation::update: calling update", 500);
- map_km_error(km_op.update(input, hat.as_ref(), tst.as_ref()))
+ map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref()))
})
.context("In update: KeyMint::update failed.")?;
@@ -421,9 +406,6 @@
}
self.touch();
- let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
- self.km_op.get_interface().context("In finish: Failed to get KeyMintOperation.")?;
-
let (hat, tst, confirmation_token) = self
.auth_info
.lock()
@@ -434,7 +416,7 @@
let output = self
.update_outcome(&mut *outcome, {
let _wp = wd::watch_millis("Operation::finish: calling finish", 500);
- map_km_error(km_op.finish(
+ map_km_error(self.km_op.finish(
input,
signature,
hat.as_ref(),
@@ -462,12 +444,10 @@
fn abort(&self, outcome: Outcome) -> Result<()> {
let mut locked_outcome = self.check_active().context("In abort")?;
*locked_outcome = outcome;
- let km_op: binder::public_api::Strong<dyn IKeyMintOperation> =
- self.km_op.get_interface().context("In abort: Failed to get KeyMintOperation.")?;
{
let _wp = wd::watch_millis("Operation::abort: calling abort", 500);
- map_km_error(km_op.abort()).context("In abort: KeyMint::abort failed.")
+ map_km_error(self.km_op.abort()).context("In abort: KeyMint::abort failed.")
}
}
}
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 4add899..e6d61b0 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -18,23 +18,18 @@
//! It also provides KeystorePerm and KeyPerm as convenience wrappers for the SELinux permission
//! defined by keystore2 and keystore2_key respectively.
+use crate::error::Error as KsError;
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission,
};
-
+use anyhow::Context as AnyhowContext;
+use keystore2_selinux as selinux;
+use lazy_static::lazy_static;
+use selinux::{implement_class, Backend, ClassPermission};
use std::cmp::PartialEq;
use std::convert::From;
use std::ffi::CStr;
-use crate::error::Error as KsError;
-use keystore2_selinux as selinux;
-
-use anyhow::Context as AnyhowContext;
-
-use selinux::Backend;
-
-use lazy_static::lazy_static;
-
// Replace getcon with a mock in the test situation
#[cfg(not(test))]
use selinux::getcon;
@@ -52,273 +47,108 @@
KEYSTORE2_KEY_LABEL_BACKEND.lookup(&namespace.to_string())
}
-/// ## Background
-///
-/// AIDL enums are represented as constants of the form:
-/// ```
-/// mod EnumName {
-/// pub type EnumName = i32;
-/// pub const Variant1: EnumName = <value1>;
-/// pub const Variant2: EnumName = <value2>;
-/// ...
-/// }
-///```
-/// This macro wraps the enum in a new type, e.g., `MyPerm` and maps each variant to an SELinux
-/// permission while providing the following interface:
-/// * From<EnumName> and Into<EnumName> are implemented. Where the implementation of From maps
-/// any variant not specified to the default.
-/// * Every variant has a constructor with a name corresponding to its lower case SELinux string
-/// representation.
-/// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the
-/// represented permission.
-///
-/// ## Special behavior
-/// If the keyword `use` appears as an selinux name `use_` is used as identifier for the
-/// constructor function (e.g. `MePerm::use_()`) but the string returned by `to_selinux` will
-/// still be `"use"`.
-///
-/// ## Example
-/// ```
-///
-/// implement_permission!(
-/// /// MyPerm documentation.
-/// #[derive(Clone, Copy, Debug, PartialEq)]
-/// MyPerm from EnumName with default (None, none) {}
-/// Variant1, selinux name: variant1;
-/// Variant2, selinux name: variant1;
-/// }
-/// );
-/// ```
-macro_rules! implement_permission_aidl {
- // This rule provides the public interface of the macro. And starts the preprocessing
- // recursion (see below).
- ($(#[$m:meta])* $name:ident from $aidl_name:ident with default ($($def:tt)*)
- { $($element:tt)* })
- => {
- implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*), [],
- $($element)*);
- };
-
- // The following three rules recurse through the elements of the form
- // `<enum variant>, selinux name: <selinux_name>;`
- // preprocessing the input.
-
- // The first rule terminates the recursion and passes the processed arguments to the final
- // rule that spills out the implementation.
- (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*], ) => {
- implement_permission_aidl!(@end $($m)*, $name, $aidl_name, ($($def)*) { $($out)* } );
- };
-
- // The second rule is triggered if the selinux name of an element is literally `use`.
- // It produces the tuple `<enum variant>, use_, use;`
- // and appends it to the out list.
- (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*],
- $e_name:ident, selinux name: use; $($element:tt)*)
- => {
- implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*),
- [$($out)* $e_name, use_, use;], $($element)*);
- };
-
- // The third rule is the default rule which replaces every input tuple with
- // `<enum variant>, <selinux_name>, <selinux_name>;`
- // and appends the result to the out list.
- (@replace_use $($m:meta)*, $name:ident, $aidl_name:ident, ($($def:tt)*), [$($out:tt)*],
- $e_name:ident, selinux name: $e_str:ident; $($element:tt)*)
- => {
- implement_permission_aidl!(@replace_use $($m)*, $name, $aidl_name, ($($def)*),
- [$($out)* $e_name, $e_str, $e_str;], $($element)*);
- };
-
- (@end $($m:meta)*, $name:ident, $aidl_name:ident,
- ($def_name:ident, $def_selinux_name:ident) {
- $($element_name:ident, $element_identifier:ident,
- $selinux_name:ident;)*
- })
- =>
- {
- $(#[$m])*
- pub struct $name(pub $aidl_name);
-
- impl From<$aidl_name> for $name {
- fn from (p: $aidl_name) -> Self {
- match p {
- $aidl_name::$def_name => Self($aidl_name::$def_name),
- $($aidl_name::$element_name => Self($aidl_name::$element_name),)*
- _ => Self($aidl_name::$def_name),
- }
- }
- }
-
- impl From<$name> for $aidl_name {
- fn from(p: $name) -> $aidl_name {
- p.0
- }
- }
-
- impl $name {
- /// Returns a string representation of the permission as required by
- /// `selinux::check_access`.
- pub fn to_selinux(&self) -> &'static str {
- match self {
- Self($aidl_name::$def_name) => stringify!($def_selinux_name),
- $(Self($aidl_name::$element_name) => stringify!($selinux_name),)*
- _ => stringify!($def_selinux_name),
- }
- }
-
- /// Creates an instance representing a permission with the same name.
- pub const fn $def_selinux_name() -> Self { Self($aidl_name::$def_name) }
- $(
- /// Creates an instance representing a permission with the same name.
- pub const fn $element_identifier() -> Self { Self($aidl_name::$element_name) }
- )*
- }
- };
-}
-
-implement_permission_aidl!(
+implement_class!(
/// KeyPerm provides a convenient abstraction from the SELinux class `keystore2_key`.
/// At the same time it maps `KeyPermissions` from the Keystore 2.0 AIDL Grant interface to
- /// the SELinux permissions. With the implement_permission macro, we conveniently
- /// provide mappings between the wire type bit field values, the rust enum and the SELinux
- /// string representation.
- ///
- /// ## Example
- ///
- /// In this access check `KeyPerm::get_info().to_selinux()` would return the SELinux representation
- /// "info".
- /// ```
- /// selinux::check_access(source_context, target_context, "keystore2_key",
- /// KeyPerm::get_info().to_selinux());
- /// ```
- #[derive(Clone, Copy, Debug, Eq, PartialEq)]
- KeyPerm from KeyPermission with default (NONE, none) {
- CONVERT_STORAGE_KEY_TO_EPHEMERAL, selinux name: convert_storage_key_to_ephemeral;
- DELETE, selinux name: delete;
- GEN_UNIQUE_ID, selinux name: gen_unique_id;
- GET_INFO, selinux name: get_info;
- GRANT, selinux name: grant;
- MANAGE_BLOB, selinux name: manage_blob;
- REBIND, selinux name: rebind;
- REQ_FORCED_OP, selinux name: req_forced_op;
- UPDATE, selinux name: update;
- USE, selinux name: use;
- USE_DEV_ID, selinux name: use_dev_id;
+ /// the SELinux permissions.
+ #[repr(i32)]
+ #[selinux(class_name = keystore2_key)]
+ #[derive(Clone, Copy, Debug, PartialEq)]
+ pub enum KeyPerm {
+ /// Checked when convert_storage_key_to_ephemeral is called.
+ #[selinux(name = convert_storage_key_to_ephemeral)]
+ ConvertStorageKeyToEphemeral = KeyPermission::CONVERT_STORAGE_KEY_TO_EPHEMERAL.0,
+ /// Checked when the caller tries do delete a key.
+ #[selinux(name = delete)]
+ Delete = KeyPermission::DELETE.0,
+ /// Checked when the caller tries to use a unique id.
+ #[selinux(name = gen_unique_id)]
+ GenUniqueId = KeyPermission::GEN_UNIQUE_ID.0,
+ /// Checked when the caller tries to load a key.
+ #[selinux(name = get_info)]
+ GetInfo = KeyPermission::GET_INFO.0,
+ /// Checked when the caller attempts to grant a key to another uid.
+ /// Also used for gating key migration attempts.
+ #[selinux(name = grant)]
+ Grant = KeyPermission::GRANT.0,
+ /// Checked when the caller attempts to use Domain::BLOB.
+ #[selinux(name = manage_blob)]
+ ManageBlob = KeyPermission::MANAGE_BLOB.0,
+ /// Checked when the caller tries to create a key which implies rebinding
+ /// an alias to the new key.
+ #[selinux(name = rebind)]
+ Rebind = KeyPermission::REBIND.0,
+ /// Checked when the caller attempts to create a forced operation.
+ #[selinux(name = req_forced_op)]
+ ReqForcedOp = KeyPermission::REQ_FORCED_OP.0,
+ /// Checked when the caller attempts to update public key artifacts.
+ #[selinux(name = update)]
+ Update = KeyPermission::UPDATE.0,
+ /// Checked when the caller attempts to use a private or public key.
+ #[selinux(name = use)]
+ Use = KeyPermission::USE.0,
+ /// Checked when the caller attempts to use device ids for attestation.
+ #[selinux(name = use_dev_id)]
+ UseDevId = KeyPermission::USE_DEV_ID.0,
}
);
-/// This macro implements an enum with values mapped to SELinux permission names.
-/// The below example wraps the enum MyPermission in the tuple struct `MyPerm` and implements
-/// * From<i32> and Into<i32> are implemented. Where the implementation of From maps
-/// any variant not specified to the default.
-/// * Every variant has a constructor with a name corresponding to its lower case SELinux string
-/// representation.
-/// * `MyPerm.to_selinux(&self)` returns the SELinux string representation of the
-/// represented permission.
-///
-/// ## Example
-/// ```
-/// implement_permission!(
-/// /// MyPerm documentation.
-/// #[derive(Clone, Copy, Debug, Eq, PartialEq)]
-/// MyPerm with default (None = 0, none) {
-/// Foo = 1, selinux name: foo;
-/// Bar = 2, selinux name: bar;
-/// }
-/// );
-/// ```
-macro_rules! implement_permission {
- // This rule provides the public interface of the macro. And starts the preprocessing
- // recursion (see below).
- ($(#[$m:meta])* $name:ident with default
- ($def_name:ident = $def_val:expr, $def_selinux_name:ident)
- {
- $($(#[$element_meta:meta])*
- $element_name:ident = $element_val:expr, selinux name: $selinux_name:ident;)*
- })
- => {
- $(#[$m])*
- pub enum $name {
- /// The default variant of an enum.
- $def_name = $def_val,
- $(
- $(#[$element_meta])*
- $element_name = $element_val,
- )*
- }
-
- impl From<i32> for $name {
- fn from (p: i32) -> Self {
- match p {
- $def_val => Self::$def_name,
- $($element_val => Self::$element_name,)*
- _ => Self::$def_name,
- }
- }
- }
-
- impl From<$name> for i32 {
- fn from(p: $name) -> i32 {
- p as i32
- }
- }
-
- impl $name {
- /// Returns a string representation of the permission as required by
- /// `selinux::check_access`.
- pub fn to_selinux(&self) -> &'static str {
- match self {
- Self::$def_name => stringify!($def_selinux_name),
- $(Self::$element_name => stringify!($selinux_name),)*
- }
- }
-
- /// Creates an instance representing a permission with the same name.
- pub const fn $def_selinux_name() -> Self { Self::$def_name }
- $(
- /// Creates an instance representing a permission with the same name.
- pub const fn $selinux_name() -> Self { Self::$element_name }
- )*
- }
- };
-}
-
-implement_permission!(
+implement_class!(
/// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`.
/// Using the implement_permission macro we get the same features as `KeyPerm`.
+ #[selinux(class_name = keystore2)]
#[derive(Clone, Copy, Debug, PartialEq)]
- KeystorePerm with default (None = 0, none) {
+ pub enum KeystorePerm {
/// Checked when a new auth token is installed.
- AddAuth = 1, selinux name: add_auth;
+ #[selinux(name = add_auth)]
+ AddAuth,
/// Checked when an app is uninstalled or wiped.
- ClearNs = 2, selinux name: clear_ns;
+ #[selinux(name = clear_ns)]
+ ClearNs,
/// Checked when the user state is queried from Keystore 2.0.
- GetState = 4, selinux name: get_state;
+ #[selinux(name = get_state)]
+ GetState,
/// Checked when Keystore 2.0 is asked to list a namespace that the caller
/// does not have the get_info permission for.
- List = 8, selinux name: list;
+ #[selinux(name = list)]
+ List,
/// Checked when Keystore 2.0 gets locked.
- Lock = 0x10, selinux name: lock;
+ #[selinux(name = lock)]
+ Lock,
/// Checked when Keystore 2.0 shall be reset.
- Reset = 0x20, selinux name: reset;
+ #[selinux(name = reset)]
+ Reset,
/// Checked when Keystore 2.0 shall be unlocked.
- Unlock = 0x40, selinux name: unlock;
+ #[selinux(name = unlock)]
+ Unlock,
/// Checked when user is added or removed.
- ChangeUser = 0x80, selinux name: change_user;
+ #[selinux(name = change_user)]
+ ChangeUser,
/// Checked when password of the user is changed.
- ChangePassword = 0x100, selinux name: change_password;
+ #[selinux(name = change_password)]
+ ChangePassword,
/// Checked when a UID is cleared.
- ClearUID = 0x200, selinux name: clear_uid;
+ #[selinux(name = clear_uid)]
+ ClearUID,
/// Checked when Credstore calls IKeystoreAuthorization to obtain auth tokens.
- GetAuthToken = 0x400, selinux name: get_auth_token;
+ #[selinux(name = get_auth_token)]
+ GetAuthToken,
/// Checked when earlyBootEnded() is called.
- EarlyBootEnded = 0x800, selinux name: early_boot_ended;
+ #[selinux(name = early_boot_ended)]
+ EarlyBootEnded,
/// Checked when IKeystoreMaintenance::onDeviceOffBody is called.
- ReportOffBody = 0x1000, selinux name: report_off_body;
- /// Checked when IkeystoreMetrics::pullMetris is called.
- PullMetrics = 0x2000, selinux name: pull_metrics;
+ #[selinux(name = report_off_body)]
+ ReportOffBody,
+ /// Checked when IkeystoreMetrics::pullMetrics is called.
+ #[selinux(name = pull_metrics)]
+ PullMetrics,
/// Checked when IKeystoreMaintenance::deleteAllKeys is called.
- DeleteAllKeys = 0x4000, selinux name: delete_all_keys;
+ #[selinux(name = delete_all_keys)]
+ DeleteAllKeys,
+ /// Checked when migrating any key from any namespace to any other namespace. It was
+ /// introduced for migrating keys when an app leaves a sharedUserId.
+ #[selinux(name = migrate_any_key)]
+ MigrateAnyKey,
}
);
@@ -332,17 +162,17 @@
///
/// ## Example
/// ```
-/// let perms1 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob(), KeyPerm::grant()];
-/// let perms2 = key_perm_set![KeyPerm::use_(), KeyPerm::manage_blob()];
+/// let perms1 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob, KeyPerm::Grant];
+/// let perms2 = key_perm_set![KeyPerm::Use, KeyPerm::ManageBlob];
///
/// assert!(perms1.includes(perms2))
/// assert!(!perms2.includes(perms1))
///
/// let i = perms1.into_iter();
/// // iteration in ascending order of the permission's numeric representation.
-/// assert_eq(Some(KeyPerm::manage_blob()), i.next());
-/// assert_eq(Some(KeyPerm::grant()), i.next());
-/// assert_eq(Some(KeyPerm::use_()), i.next());
+/// assert_eq(Some(KeyPerm::ManageBlob), i.next());
+/// assert_eq(Some(KeyPerm::Grant), i.next());
+/// assert_eq(Some(KeyPerm::Use), i.next());
/// assert_eq(None, i.next());
/// ```
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
@@ -373,7 +203,7 @@
let p = self.vec.0 & (1 << self.pos);
self.pos += 1;
if p != 0 {
- return Some(KeyPerm::from(KeyPermission(p)));
+ return Some(KeyPerm::from(p));
}
}
}
@@ -382,7 +212,7 @@
impl From<KeyPerm> for KeyPermSet {
fn from(p: KeyPerm) -> Self {
- Self((p.0).0 as i32)
+ Self(p as i32)
}
}
@@ -417,7 +247,7 @@
macro_rules! key_perm_set {
() => { KeyPermSet(0) };
($head:expr $(, $tail:expr)* $(,)?) => {
- KeyPermSet(($head.0).0 $(| ($tail.0).0)*)
+ KeyPermSet($head as i32 $(| $tail as i32)*)
};
}
@@ -430,14 +260,14 @@
}
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` may access
+/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` may access
/// the given permision `perm` of the `keystore2` security class.
pub fn check_keystore_permission(caller_ctx: &CStr, perm: KeystorePerm) -> anyhow::Result<()> {
let target_context = getcon().context("check_keystore_permission: getcon failed.")?;
- selinux::check_access(caller_ctx, &target_context, "keystore2", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt` has
+/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt` has
/// all the permissions indicated in `access_vec` for the target domain indicated by the key
/// descriptor `key` in the security class `keystore2_key`.
///
@@ -462,27 +292,24 @@
_ => return Err(KsError::sys()).context(format!("Cannot grant {:?}.", key.domain)),
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", "grant")
+ selinux::check_permission(caller_ctx, &target_context, KeyPerm::Grant)
.context("Grant permission is required when granting.")?;
- if access_vec.includes(KeyPerm::grant()) {
+ if access_vec.includes(KeyPerm::Grant) {
return Err(selinux::Error::perm()).context("Grant permission cannot be granted.");
}
for p in access_vec.into_iter() {
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", p.to_selinux())
- .context(format!(
- concat!(
- "check_grant_permission: check_access failed. ",
- "The caller may have tried to grant a permission that they don't possess. {:?}"
- ),
- p
- ))?
+ selinux::check_permission(caller_ctx, &target_context, p).context(format!(
+ "check_grant_permission: check_permission failed. \
+ The caller may have tried to grant a permission that they don't possess. {:?}",
+ p
+ ))?
}
Ok(())
}
-/// Uses `selinux::check_access` to check if the given caller context `caller_cxt`
+/// Uses `selinux::check_permission` to check if the given caller context `caller_cxt`
/// has the permissions indicated by `perm` for the target domain indicated by the key
/// descriptor `key` in the security class `keystore2_key`.
///
@@ -492,7 +319,7 @@
/// backend, and the result is used as target context.
/// * `Domain::BLOB` Same as SELinux but the "manage_blob" permission is always checked additionally
/// to the one supplied in `perm`.
-/// * `Domain::GRANT` Does not use selinux::check_access. Instead the `access_vector`
+/// * `Domain::GRANT` Does not use selinux::check_permission. Instead the `access_vector`
/// parameter is queried for permission, which must be supplied in this case.
///
/// ## Return values.
@@ -536,7 +363,7 @@
match access_vector {
Some(_) => {
return Err(selinux::Error::perm())
- .context(format!("\"{}\" not granted", perm.to_selinux()));
+ .context(format!("\"{}\" not granted", perm.name()));
}
None => {
// If DOMAIN_GRANT was selected an access vector must be supplied.
@@ -557,12 +384,7 @@
.context("Domain::BLOB: Failed to lookup namespace.")?;
// If DOMAIN_KEY_BLOB was specified, we check for the "manage_blob"
// permission in addition to the requested permission.
- selinux::check_access(
- caller_ctx,
- &tctx,
- "keystore2_key",
- KeyPerm::manage_blob().to_selinux(),
- )?;
+ selinux::check_permission(caller_ctx, &tctx, KeyPerm::ManageBlob)?;
tctx
}
@@ -572,7 +394,7 @@
}
};
- selinux::check_access(caller_ctx, &target_context, "keystore2_key", perm.to_selinux())
+ selinux::check_permission(caller_ctx, &target_context, perm)
}
#[cfg(test)]
@@ -583,49 +405,49 @@
use keystore2_selinux::*;
const ALL_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- KeyPerm::req_forced_op(),
- KeyPerm::gen_unique_id(),
- KeyPerm::grant(),
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
- KeyPerm::convert_storage_key_to_ephemeral(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
];
const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- // No KeyPerm::grant()
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- KeyPerm::req_forced_op(),
- KeyPerm::gen_unique_id(),
- // No KeyPerm::grant()
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
- KeyPerm::convert_storage_key_to_ephemeral(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ // No KeyPerm::Grant
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
+ KeyPerm::ConvertStorageKeyToEphemeral,
];
const UNPRIV_PERMS: KeyPermSet = key_perm_set![
- KeyPerm::delete(),
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::Delete,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
/// The su_key namespace as defined in su.te and keystore_key_contexts of the
@@ -672,28 +494,26 @@
#[test]
fn check_keystore_permission_test() -> Result<()> {
let system_server_ctx = Context::new("u:r:system_server:s0")?;
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::add_auth()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_ns()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::get_state()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::lock()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::reset()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::unlock()).is_ok());
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::change_user()).is_ok());
- assert!(
- check_keystore_permission(&system_server_ctx, KeystorePerm::change_password()).is_ok()
- );
- assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_uid()).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::GetState).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok());
let shell_ctx = Context::new("u:r:shell:s0")?;
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::add_auth()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_ns()));
- assert!(check_keystore_permission(&shell_ctx, KeystorePerm::get_state()).is_ok());
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::list()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::lock()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::reset()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::unlock()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_user()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_password()));
- assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_uid()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs));
+ assert!(check_keystore_permission(&shell_ctx, KeystorePerm::GetState).is_ok());
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID));
Ok(())
}
@@ -708,7 +528,7 @@
// attempts to grant the grant permission must always fail even when privileged.
assert_perm_failed!(check_grant_permission(
&system_server_ctx,
- KeyPerm::grant().into(),
+ KeyPerm::Grant.into(),
&key
));
// unprivileged grant attempts always fail. shell does not have the grant permission.
@@ -728,7 +548,7 @@
if is_su {
assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok());
// attempts to grant the grant permission must always fail even when privileged.
- assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::grant().into(), &key));
+ assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key));
} else {
// unprivileged grant attempts always fail. shell does not have the grant permission.
assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key));
@@ -743,7 +563,7 @@
assert_perm_failed!(check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::grant(),
+ KeyPerm::Grant,
&key,
&Some(UNPRIV_PERMS)
));
@@ -751,7 +571,7 @@
check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
&Some(ALL_PERMS),
)
@@ -765,61 +585,31 @@
let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None };
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::use_(), &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::delete(), &key, &None).is_ok());
- assert!(
- check_key_permission(0, &system_server_ctx, KeyPerm::get_info(), &key, &None).is_ok()
- );
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::rebind(), &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::update(), &key, &None).is_ok());
- assert!(check_key_permission(0, &system_server_ctx, KeyPerm::grant(), &key, &None).is_ok());
- assert!(
- check_key_permission(0, &system_server_ctx, KeyPerm::use_dev_id(), &key, &None).is_ok()
- );
- assert!(
- check_key_permission(0, &gmscore_app, KeyPerm::gen_unique_id(), &key, &None).is_ok()
- );
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok());
+ assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::use_(), &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::delete(), &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::get_info(), &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::rebind(), &key, &None).is_ok());
- assert!(check_key_permission(0, &shell_ctx, KeyPerm::update(), &key, &None).is_ok());
- assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::grant(), &key, &None));
- assert_perm_failed!(check_key_permission(
- 0,
- &shell_ctx,
- KeyPerm::req_forced_op(),
- &key,
- &None
- ));
- assert_perm_failed!(check_key_permission(
- 0,
- &shell_ctx,
- KeyPerm::manage_blob(),
- &key,
- &None
- ));
- assert_perm_failed!(check_key_permission(
- 0,
- &shell_ctx,
- KeyPerm::use_dev_id(),
- &key,
- &None
- ));
- assert_perm_failed!(check_key_permission(
- 0,
- &shell_ctx,
- KeyPerm::gen_unique_id(),
- &key,
- &None
- ));
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok());
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None));
// Also make sure that the permission fails if the caller is not the owner.
assert_perm_failed!(check_key_permission(
1, // the owner is 0
&system_server_ctx,
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
&None
));
@@ -827,18 +617,18 @@
assert!(check_key_permission(
1,
&system_server_ctx,
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
- &Some(key_perm_set![KeyPerm::use_()])
+ &Some(key_perm_set![KeyPerm::Use])
)
.is_ok());
// But fail if the grant did not cover the requested permission.
assert_perm_failed!(check_key_permission(
1,
&system_server_ctx,
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
- &Some(key_perm_set![KeyPerm::get_info()])
+ &Some(key_perm_set![KeyPerm::GetInfo])
));
Ok(())
@@ -854,46 +644,24 @@
blob: None,
};
+ assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok());
+
if is_su {
- assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::manage_blob(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::gen_unique_id(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::req_forced_op(), &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok());
+ assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok());
} else {
- assert!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::delete(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::get_info(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::rebind(), &key, &None).is_ok());
- assert!(check_key_permission(0, &sctx, KeyPerm::update(), &key, &None).is_ok());
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::grant(), &key, &None));
- assert_perm_failed!(check_key_permission(
- 0,
- &sctx,
- KeyPerm::req_forced_op(),
- &key,
- &None
- ));
- assert_perm_failed!(check_key_permission(
- 0,
- &sctx,
- KeyPerm::manage_blob(),
- &key,
- &None
- ));
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_dev_id(), &key, &None));
- assert_perm_failed!(check_key_permission(
- 0,
- &sctx,
- KeyPerm::gen_unique_id(),
- &key,
- &None
- ));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None));
}
Ok(())
}
@@ -909,9 +677,9 @@
};
if is_su {
- check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None)
+ check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)
} else {
- assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::use_(), &key, &None));
+ assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None));
Ok(())
}
}
@@ -925,7 +693,7 @@
check_key_permission(
0,
&selinux::Context::new("ignored").unwrap(),
- KeyPerm::use_(),
+ KeyPerm::Use,
&key,
&None
)
@@ -940,45 +708,45 @@
#[test]
fn key_perm_set_all_test() {
let v = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- KeyPerm::req_forced_op(),
- KeyPerm::gen_unique_id(),
- KeyPerm::grant(),
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_() // Test if the macro accepts missing comma at the end of the list.
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use // Test if the macro accepts missing comma at the end of the list.
];
let mut i = v.into_iter();
- assert_eq!(i.next().unwrap().to_selinux(), "delete");
- assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id");
- assert_eq!(i.next().unwrap().to_selinux(), "get_info");
- assert_eq!(i.next().unwrap().to_selinux(), "grant");
- assert_eq!(i.next().unwrap().to_selinux(), "manage_blob");
- assert_eq!(i.next().unwrap().to_selinux(), "rebind");
- assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op");
- assert_eq!(i.next().unwrap().to_selinux(), "update");
- assert_eq!(i.next().unwrap().to_selinux(), "use");
- assert_eq!(i.next().unwrap().to_selinux(), "use_dev_id");
+ assert_eq!(i.next().unwrap().name(), "delete");
+ assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+ assert_eq!(i.next().unwrap().name(), "get_info");
+ assert_eq!(i.next().unwrap().name(), "grant");
+ assert_eq!(i.next().unwrap().name(), "manage_blob");
+ assert_eq!(i.next().unwrap().name(), "rebind");
+ assert_eq!(i.next().unwrap().name(), "req_forced_op");
+ assert_eq!(i.next().unwrap().name(), "update");
+ assert_eq!(i.next().unwrap().name(), "use");
+ assert_eq!(i.next().unwrap().name(), "use_dev_id");
assert_eq!(None, i.next());
}
#[test]
fn key_perm_set_sparse_test() {
let v = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::req_forced_op(),
- KeyPerm::gen_unique_id(),
- KeyPerm::update(),
- KeyPerm::use_(), // Test if macro accepts the comma at the end of the list.
+ KeyPerm::ManageBlob,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Update,
+ KeyPerm::Use, // Test if macro accepts the comma at the end of the list.
];
let mut i = v.into_iter();
- assert_eq!(i.next().unwrap().to_selinux(), "gen_unique_id");
- assert_eq!(i.next().unwrap().to_selinux(), "manage_blob");
- assert_eq!(i.next().unwrap().to_selinux(), "req_forced_op");
- assert_eq!(i.next().unwrap().to_selinux(), "update");
- assert_eq!(i.next().unwrap().to_selinux(), "use");
+ assert_eq!(i.next().unwrap().name(), "gen_unique_id");
+ assert_eq!(i.next().unwrap().name(), "manage_blob");
+ assert_eq!(i.next().unwrap().name(), "req_forced_op");
+ assert_eq!(i.next().unwrap().name(), "update");
+ assert_eq!(i.next().unwrap().name(), "use");
assert_eq!(None, i.next());
}
#[test]
@@ -990,23 +758,23 @@
#[test]
fn key_perm_set_include_subset_test() {
let v1 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::use_dev_id(),
- KeyPerm::req_forced_op(),
- KeyPerm::gen_unique_id(),
- KeyPerm::grant(),
- KeyPerm::get_info(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::UseDevId,
+ KeyPerm::ReqForcedOp,
+ KeyPerm::GenUniqueId,
+ KeyPerm::Grant,
+ KeyPerm::GetInfo,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
assert!(v1.includes(v2));
assert!(!v2.includes(v1));
@@ -1014,18 +782,18 @@
#[test]
fn key_perm_set_include_equal_test() {
let v1 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
assert!(v1.includes(v2));
assert!(v2.includes(v1));
@@ -1033,33 +801,29 @@
#[test]
fn key_perm_set_include_overlap_test() {
let v1 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::grant(), // only in v1
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::Grant, // only in v1
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
let v2 = key_perm_set![
- KeyPerm::manage_blob(),
- KeyPerm::delete(),
- KeyPerm::req_forced_op(), // only in v2
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
+ KeyPerm::ManageBlob,
+ KeyPerm::Delete,
+ KeyPerm::ReqForcedOp, // only in v2
+ KeyPerm::Rebind,
+ KeyPerm::Update,
+ KeyPerm::Use,
];
assert!(!v1.includes(v2));
assert!(!v2.includes(v1));
}
#[test]
fn key_perm_set_include_no_overlap_test() {
- let v1 = key_perm_set![KeyPerm::manage_blob(), KeyPerm::delete(), KeyPerm::grant(),];
- let v2 = key_perm_set![
- KeyPerm::req_forced_op(),
- KeyPerm::rebind(),
- KeyPerm::update(),
- KeyPerm::use_(),
- ];
+ let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,];
+ let v2 =
+ key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,];
assert!(!v1.includes(v2));
assert!(!v2.includes(v1));
}
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index cd54915..991535f 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -62,11 +62,11 @@
/// Get a [`KeyMintDevice`] for the given [`SecurityLevel`]
pub fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> {
- let (asp, hw_info, km_uuid) = get_keymint_device(&security_level)
+ let (km_dev, hw_info, km_uuid) = get_keymint_device(&security_level)
.context("In KeyMintDevice::get: get_keymint_device failed")?;
Ok(KeyMintDevice {
- km_dev: asp.get_interface()?,
+ km_dev,
km_uuid,
version: hw_info.versionNumber,
security_level: hw_info.securityLevel,
@@ -120,7 +120,7 @@
blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
db.store_new_key(
- &key_desc,
+ key_desc,
key_type,
&key_parameters,
&(&creation_result.keyBlob, &blob_metadata),
@@ -148,7 +148,7 @@
key_desc: &KeyDescriptor,
key_type: KeyType,
) -> Result<(KeyIdGuard, KeyEntry)> {
- db.load_key_entry(&key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(()))
+ db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(()))
.context("In lookup_from_desc: load_key_entry failed.")
}
@@ -228,8 +228,8 @@
};
}
- self.create_and_store_key(db, &key_desc, key_type, |km_dev| {
- km_dev.generateKey(¶ms, None)
+ self.create_and_store_key(db, key_desc, key_type, |km_dev| {
+ km_dev.generateKey(params, None)
})
.context("In lookup_or_generate_key: generate_and_store_key failed")?;
Self::lookup_from_desc(db, key_desc, key_type)
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index 212bf39..66e1988 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -38,13 +38,15 @@
};
use anyhow::{Context, Result};
use keystore2_crypto::parse_subject_from_certificate;
+use serde_cbor::Value;
+use std::collections::BTreeMap;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::database::{CertificateChain, KeystoreDB, Uuid};
use crate::error::{self, map_or_log_err, map_rem_prov_error, Error};
use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB};
use crate::metrics_store::log_rkp_error_stats;
-use crate::utils::{watchdog as wd, Asp};
+use crate::utils::watchdog as wd;
use android_security_metrics::aidl::android::security::metrics::RkpError::RkpError as MetricsRkpError;
/// Contains helper functions to check if remote provisioning is enabled on the system and, if so,
@@ -56,6 +58,11 @@
is_hal_present: AtomicBool,
}
+static COSE_KEY_XCOORD: Value = Value::Integer(-2);
+static COSE_KEY_YCOORD: Value = Value::Integer(-3);
+static COSE_MAC0_LEN: usize = 4;
+static COSE_MAC0_PAYLOAD: usize = 2;
+
impl RemProvState {
/// Creates a RemProvState struct.
pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Self {
@@ -182,7 +189,7 @@
// and therefore will not be attested.
Ok(None)
} else {
- match self.get_rem_prov_attest_key(&key, caller_uid, db) {
+ match self.get_rem_prov_attest_key(key, caller_uid, db) {
Err(e) => {
log::error!(
concat!(
@@ -218,7 +225,7 @@
/// Implementation of the IRemoteProvisioning service.
#[derive(Default)]
pub struct RemoteProvisioningService {
- device_by_sec_level: HashMap<SecurityLevel, Asp>,
+ device_by_sec_level: HashMap<SecurityLevel, Strong<dyn IRemotelyProvisionedComponent>>,
curve_by_sec_level: HashMap<SecurityLevel, i32>,
}
@@ -228,7 +235,7 @@
sec_level: &SecurityLevel,
) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
if let Some(dev) = self.device_by_sec_level.get(sec_level) {
- dev.get_interface().context("In get_dev_by_sec_level.")
+ Ok(dev.clone())
} else {
Err(error::Error::sys()).context(concat!(
"In get_dev_by_sec_level: Remote instance for requested security level",
@@ -242,21 +249,17 @@
let mut result: Self = Default::default();
let dev = get_remotely_provisioned_component(&SecurityLevel::TRUSTED_ENVIRONMENT)
.context("In new_native_binder: Failed to get TEE Remote Provisioner instance.")?;
- let rkp_tee_dev: Strong<dyn IRemotelyProvisionedComponent> = dev.get_interface()?;
result.curve_by_sec_level.insert(
SecurityLevel::TRUSTED_ENVIRONMENT,
- rkp_tee_dev
- .getHardwareInfo()
+ dev.getHardwareInfo()
.context("In new_native_binder: Failed to get hardware info for the TEE.")?
.supportedEekCurve,
);
result.device_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, dev);
if let Ok(dev) = get_remotely_provisioned_component(&SecurityLevel::STRONGBOX) {
- let rkp_sb_dev: Strong<dyn IRemotelyProvisionedComponent> = dev.get_interface()?;
result.curve_by_sec_level.insert(
SecurityLevel::STRONGBOX,
- rkp_sb_dev
- .getHardwareInfo()
+ dev.getHardwareInfo()
.context("In new_native_binder: Failed to get hardware info for StrongBox.")?
.supportedEekCurve,
);
@@ -265,6 +268,27 @@
Ok(BnRemoteProvisioning::new_binder(result, BinderFeatures::default()))
}
+ fn extract_payload_from_cose_mac(data: &[u8]) -> Result<Value> {
+ let cose_mac0: Vec<Value> = serde_cbor::from_slice(data).context(
+ "In extract_payload_from_cose_mac: COSE_Mac0 returned from IRPC cannot be parsed",
+ )?;
+ if cose_mac0.len() != COSE_MAC0_LEN {
+ return Err(error::Error::sys()).context(format!(
+ "In extract_payload_from_cose_mac: COSE_Mac0 has improper length. \
+ Expected: {}, Actual: {}",
+ COSE_MAC0_LEN,
+ cose_mac0.len(),
+ ));
+ }
+ match &cose_mac0[COSE_MAC0_PAYLOAD] {
+ Value::Bytes(key) => Ok(serde_cbor::from_slice(key)
+ .context("In extract_payload_from_cose_mac: COSE_Mac0 payload is malformed.")?),
+ _ => Err(error::Error::sys()).context(
+ "In extract_payload_from_cose_mac: COSE_Mac0 payload is the wrong type.",
+ )?,
+ }
+ }
+
/// Generates a CBOR blob which will be assembled by the calling code into a larger
/// CBOR blob intended for delivery to a provisioning serever. This blob will contain
/// `num_csr` certificate signing requests for attestation keys generated in the TEE,
@@ -294,7 +318,7 @@
.map(|key| MacedPublicKey { macedKey: key.to_vec() })
.collect())
})?;
- let mut mac = map_rem_prov_error(dev.generateCertificateRequest(
+ let mac = map_rem_prov_error(dev.generateCertificateRequest(
test_mode,
&keys_to_sign,
eek,
@@ -303,30 +327,16 @@
protected_data,
))
.context("In generate_csr: Failed to generate csr")?;
- // TODO(b/180392379): Replace this manual CBOR generation with the cbor-serde crate as well.
- // This generates an array consisting of the mac and the public key Maps.
- // Just generate the actual MacedPublicKeys structure when the crate is
- // available.
- let mut cose_mac_0: Vec<u8> = vec![
- (0b100_00000 | (keys_to_sign.len() + 1)) as u8,
- 0b010_11000, // mac
- (mac.len() as u8),
- ];
- cose_mac_0.append(&mut mac);
- // If this is a test mode key, there is an extra 6 bytes added as an additional entry in
- // the COSE_Key struct to denote that.
- let test_mode_entry_shift = if test_mode { 0 } else { 6 };
- let byte_dist_mac0_payload = 8;
- let cose_key_size = 83 - test_mode_entry_shift;
+ let mut mac_and_keys: Vec<Value> = vec![Value::from(mac)];
for maced_public_key in keys_to_sign {
- if maced_public_key.macedKey.len() > cose_key_size + byte_dist_mac0_payload {
- cose_mac_0.extend_from_slice(
- &maced_public_key.macedKey
- [byte_dist_mac0_payload..cose_key_size + byte_dist_mac0_payload],
- );
- }
+ mac_and_keys.push(
+ Self::extract_payload_from_cose_mac(&maced_public_key.macedKey)
+ .context("In generate_csr: Failed to get the payload from the COSE_Mac0")?,
+ )
}
- Ok(cose_mac_0)
+ let cbor_array: Value = Value::Array(mac_and_keys);
+ serde_cbor::to_vec(&cbor_array)
+ .context("In generate_csr: Failed to serialize the mac and keys array")
}
/// Provisions a certificate chain for a key whose CSR was included in generate_csr. The
@@ -355,6 +365,66 @@
})
}
+ fn parse_cose_mac0_for_coords(data: &[u8]) -> Result<Vec<u8>> {
+ let cose_mac0: Vec<Value> = serde_cbor::from_slice(data).context(
+ "In parse_cose_mac0_for_coords: COSE_Mac0 returned from IRPC cannot be parsed",
+ )?;
+ if cose_mac0.len() != COSE_MAC0_LEN {
+ return Err(error::Error::sys()).context(format!(
+ "In parse_cose_mac0_for_coords: COSE_Mac0 has improper length. \
+ Expected: {}, Actual: {}",
+ COSE_MAC0_LEN,
+ cose_mac0.len(),
+ ));
+ }
+ let cose_key: BTreeMap<Value, Value> = match &cose_mac0[COSE_MAC0_PAYLOAD] {
+ Value::Bytes(key) => serde_cbor::from_slice(key)
+ .context("In parse_cose_mac0_for_coords: COSE_Key is malformed.")?,
+ _ => Err(error::Error::sys())
+ .context("In parse_cose_mac0_for_coords: COSE_Mac0 payload is the wrong type.")?,
+ };
+ if !cose_key.contains_key(&COSE_KEY_XCOORD) || !cose_key.contains_key(&COSE_KEY_YCOORD) {
+ return Err(error::Error::sys()).context(
+ "In parse_cose_mac0_for_coords: \
+ COSE_Key returned from IRPC is lacking required fields",
+ );
+ }
+ let mut raw_key: Vec<u8> = vec![0; 64];
+ match &cose_key[&COSE_KEY_XCOORD] {
+ Value::Bytes(x_coord) if x_coord.len() == 32 => {
+ raw_key[0..32].clone_from_slice(x_coord)
+ }
+ Value::Bytes(x_coord) => {
+ return Err(error::Error::sys()).context(format!(
+ "In parse_cose_mac0_for_coords: COSE_Key X-coordinate is not the right length. \
+ Expected: 32; Actual: {}",
+ x_coord.len()
+ ))
+ }
+ _ => {
+ return Err(error::Error::sys())
+ .context("In parse_cose_mac0_for_coords: COSE_Key X-coordinate is not a bstr")
+ }
+ }
+ match &cose_key[&COSE_KEY_YCOORD] {
+ Value::Bytes(y_coord) if y_coord.len() == 32 => {
+ raw_key[32..64].clone_from_slice(y_coord)
+ }
+ Value::Bytes(y_coord) => {
+ return Err(error::Error::sys()).context(format!(
+ "In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not the right length. \
+ Expected: 32; Actual: {}",
+ y_coord.len()
+ ))
+ }
+ _ => {
+ return Err(error::Error::sys())
+ .context("In parse_cose_mac0_for_coords: COSE_Key Y-coordinate is not a bstr")
+ }
+ }
+ Ok(raw_key)
+ }
+
/// Submits a request to the Remote Provisioner HAL to generate a signing key pair.
/// `is_test_mode` indicates whether or not the returned public key should be marked as being
/// for testing in order to differentiate them from private keys. If the call is successful,
@@ -366,18 +436,8 @@
let priv_key =
map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key))
.context("In generate_key_pair: Failed to generated ECDSA keypair.")?;
- // TODO(b/180392379): This is a brittle hack that relies on the consistent formatting of
- // the returned CBOR blob in order to extract the public key.
- let data = &maced_key.macedKey;
- if data.len() < 85 {
- return Err(error::Error::sys()).context(concat!(
- "In generate_key_pair: CBOR blob returned from",
- "RemotelyProvisionedComponent is definitely malformatted or empty."
- ));
- }
- let mut raw_key: Vec<u8> = vec![0; 64];
- raw_key[0..32].clone_from_slice(&data[18..18 + 32]);
- raw_key[32..64].clone_from_slice(&data[53..53 + 32]);
+ let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey)
+ .context("In generate_key_pair: Failed to parse raw key")?;
DB.with::<_, Result<()>>(|db| {
let mut db = db.borrow_mut();
db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid)
@@ -493,3 +553,108 @@
map_or_log_err(self.delete_all_keys(), Ok)
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use serde_cbor::Value;
+ use std::collections::BTreeMap;
+
+ #[test]
+ fn test_parse_cose_mac0_for_coords_raw_bytes() -> Result<()> {
+ let cose_mac0: Vec<u8> = vec![
+ 0x84, 0x01, 0x02, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, 0x21, 0x58,
+ 0x20, 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9,
+ 0x21, 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C,
+ 0x43, 0x22, 0xC8, 0xEE, 0x03, 0x22, 0x58, 0x20, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95,
+ 0x90, 0xA7, 0x5C, 0x5A, 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A,
+ 0xA3, 0xB3, 0x1A, 0xB4, 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC, 0x03,
+ ];
+ let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&cose_mac0)?;
+ assert_eq!(
+ raw_key,
+ vec![
+ 0x1A, 0xFB, 0xB2, 0xD9, 0x9D, 0xF6, 0x2D, 0xF0, 0xC3, 0xA8, 0xFC, 0x7E, 0xC9, 0x21,
+ 0x26, 0xED, 0xB5, 0x4A, 0x98, 0x9B, 0xF3, 0x0D, 0x91, 0x3F, 0xC6, 0x42, 0x5C, 0x43,
+ 0x22, 0xC8, 0xEE, 0x03, 0x40, 0xB3, 0x9B, 0xFC, 0x47, 0x95, 0x90, 0xA7, 0x5C, 0x5A,
+ 0x16, 0x31, 0x34, 0xAF, 0x0C, 0x5B, 0xF2, 0xB2, 0xD8, 0x2A, 0xA3, 0xB3, 0x1A, 0xB4,
+ 0x4C, 0xA6, 0x3B, 0xE7, 0x22, 0xEC, 0x41, 0xDC,
+ ]
+ );
+ Ok(())
+ }
+
+ #[test]
+ fn test_parse_cose_mac0_for_coords_constructed_mac() -> Result<()> {
+ let x_coord: Vec<u8> = vec![0; 32];
+ let y_coord: Vec<u8> = vec![1; 32];
+ let mut expected_key: Vec<u8> = Vec::new();
+ expected_key.extend(&x_coord);
+ expected_key.extend(&y_coord);
+ let key_map: BTreeMap<Value, Value> = BTreeMap::from([
+ (Value::Integer(1), Value::Integer(2)),
+ (Value::Integer(3), Value::Integer(-7)),
+ (Value::Integer(-1), Value::Integer(1)),
+ (Value::Integer(-2), Value::Bytes(x_coord)),
+ (Value::Integer(-3), Value::Bytes(y_coord)),
+ ]);
+ let cose_mac0: Vec<Value> = vec![
+ Value::Integer(0),
+ Value::Integer(1),
+ Value::from(serde_cbor::to_vec(&key_map)?),
+ Value::Integer(2),
+ ];
+ let raw_key = RemoteProvisioningService::parse_cose_mac0_for_coords(&serde_cbor::to_vec(
+ &Value::from(cose_mac0),
+ )?)?;
+ assert_eq!(expected_key, raw_key);
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac() -> Result<()> {
+ let key_map = Value::Map(BTreeMap::from([(Value::Integer(1), Value::Integer(2))]));
+ let payload = Value::Bytes(serde_cbor::to_vec(&key_map)?);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_map = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ )?;
+ assert_eq!(key_map, extracted_map);
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_malformed_payload() -> Result<()> {
+ let payload = Value::Bytes(vec![5; 10]);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_type() -> Result<()> {
+ let payload = Value::Integer(1);
+ let cose_mac0 =
+ Value::Array(vec![Value::Integer(0), Value::Integer(1), payload, Value::Integer(3)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+
+ #[test]
+ fn test_extract_payload_from_cose_mac_fails_length() -> Result<()> {
+ let cose_mac0 = Value::Array(vec![Value::Integer(0), Value::Integer(1)]);
+ let extracted_payload = RemoteProvisioningService::extract_payload_from_cose_mac(
+ &serde_cbor::to_vec(&cose_mac0)?,
+ );
+ assert!(extracted_payload.is_err());
+ Ok(())
+ }
+}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 1b2e348..da1ff16 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -28,7 +28,7 @@
use crate::super_key::{KeyBlob, SuperKeyManager};
use crate::utils::{
check_device_attestation_permissions, check_key_permission, is_device_id_attestation_tag,
- key_characteristics_to_internal, uid_to_android_user, watchdog as wd, Asp,
+ key_characteristics_to_internal, uid_to_android_user, watchdog as wd,
};
use crate::{
database::{
@@ -63,7 +63,7 @@
/// Implementation of the IKeystoreSecurityLevel Interface.
pub struct KeystoreSecurityLevel {
security_level: SecurityLevel,
- keymint: Asp,
+ keymint: Strong<dyn IKeyMintDevice>,
hw_info: KeyMintHardwareInfo,
km_uuid: Uuid,
operation_db: OperationDb,
@@ -218,10 +218,10 @@
let scoping_blob: Vec<u8>;
let (km_blob, key_properties, key_id_guard, blob_metadata) = match key.domain {
Domain::BLOB => {
- check_key_permission(KeyPerm::use_(), key, &None)
+ check_key_permission(KeyPerm::Use, key, &None)
.context("In create_operation: checking use permission for Domain::BLOB.")?;
if forced {
- check_key_permission(KeyPerm::req_forced_op(), key, &None).context(
+ check_key_permission(KeyPerm::ReqForcedOp, key, &None).context(
"In create_operation: checking forced permission for Domain::BLOB.",
)?;
}
@@ -243,16 +243,16 @@
_ => {
let (key_id_guard, mut key_entry) = DB
.with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
- LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ LEGACY_MIGRATOR.with_try_migrate(key, caller_uid, || {
db.borrow_mut().load_key_entry(
- &key,
+ key,
KeyType::Client,
KeyEntryLoadBits::KM,
caller_uid,
|k, av| {
- check_key_permission(KeyPerm::use_(), k, &av)?;
+ check_key_permission(KeyPerm::Use, k, &av)?;
if forced {
- check_key_permission(KeyPerm::req_forced_op(), k, &av)?;
+ check_key_permission(KeyPerm::ReqForcedOp, k, &av)?;
}
Ok(())
},
@@ -306,25 +306,25 @@
.unwrap_key_if_required(&blob_metadata, km_blob)
.context("In create_operation. Failed to handle super encryption.")?;
- let km_dev: Strong<dyn IKeyMintDevice> = self
- .keymint
- .get_interface()
- .context("In create_operation: Failed to get KeyMint device")?;
-
let (begin_result, upgraded_blob) = self
.upgrade_keyblob_if_required_with(
- &*km_dev,
+ &*self.keymint,
key_id_guard,
&km_blob,
&blob_metadata,
- &operation_parameters,
+ operation_parameters,
|blob| loop {
match map_km_error({
let _wp = self.watch_millis(
"In KeystoreSecurityLevel::create_operation: calling begin",
500,
);
- km_dev.begin(purpose, blob, &operation_parameters, immediate_hat.as_ref())
+ self.keymint.begin(
+ purpose,
+ blob,
+ operation_parameters,
+ immediate_hat.as_ref(),
+ )
}) {
Err(Error::Km(ErrorCode::TOO_MANY_OPERATIONS)) => {
self.operation_db.prune(caller_uid, forced)?;
@@ -442,7 +442,7 @@
}
if params.iter().any(|kp| kp.tag == Tag::INCLUDE_UNIQUE_ID) {
- check_key_permission(KeyPerm::gen_unique_id(), key, &None).context(concat!(
+ check_key_permission(KeyPerm::GenUniqueId, key, &None).context(concat!(
"In add_required_parameters: ",
"Caller does not have the permission to generate a unique ID"
))?;
@@ -514,7 +514,7 @@
// generate_key requires the rebind permission.
// Must return on error for security reasons.
- check_key_permission(KeyPerm::rebind(), &key, &None).context("In generate_key.")?;
+ check_key_permission(KeyPerm::Rebind, &key, &None).context("In generate_key.")?;
let attestation_key_info = match (key.domain, attest_key_descriptor) {
(Domain::BLOB, _) => None,
@@ -535,8 +535,6 @@
.add_required_parameters(caller_uid, params, &key)
.context("In generate_key: Trying to get aaid.")?;
- let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?;
-
let creation_result = match attestation_key_info {
Some(AttestationKeyInfo::UserGenerated {
key_id_guard,
@@ -545,7 +543,7 @@
issuer_subject,
}) => self
.upgrade_keyblob_if_required_with(
- &*km_dev,
+ &*self.keymint,
Some(key_id_guard),
&KeyBlob::Ref(&blob),
&blob_metadata,
@@ -564,7 +562,7 @@
),
5000, // Generate can take a little longer.
);
- km_dev.generateKey(¶ms, attest_key.as_ref())
+ self.keymint.generateKey(¶ms, attest_key.as_ref())
})
},
)
@@ -579,7 +577,7 @@
),
5000, // Generate can take a little longer.
);
- km_dev.generateKey(¶ms, Some(&attestation_key))
+ self.keymint.generateKey(¶ms, Some(&attestation_key))
})
.context("While generating Key with remote provisioned attestation key.")
.map(|mut creation_result| {
@@ -595,7 +593,7 @@
),
5000, // Generate can take a little longer.
);
- km_dev.generateKey(¶ms, None)
+ self.keymint.generateKey(¶ms, None)
})
.context("While generating Key without explicit attestation key."),
}
@@ -630,7 +628,7 @@
};
// import_key requires the rebind permission.
- check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_key.")?;
+ check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_key.")?;
let params = self
.add_required_parameters(caller_uid, params, &key)
@@ -652,8 +650,7 @@
})
.context("In import_key.")?;
- let km_dev: Strong<dyn IKeyMintDevice> =
- self.keymint.get_interface().context("In import_key: Trying to get the KM device")?;
+ let km_dev = &self.keymint;
let creation_result = map_km_error({
let _wp =
self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500);
@@ -715,17 +712,17 @@
};
// Import_wrapped_key requires the rebind permission for the new key.
- check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?;
+ check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_wrapped_key.")?;
let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
.with(|db| {
LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
db.borrow_mut().load_key_entry(
- &wrapping_key,
+ wrapping_key,
KeyType::Client,
KeyEntryLoadBits::KM,
caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ |k, av| check_key_permission(KeyPerm::Use, k, &av),
)
})
})
@@ -763,10 +760,9 @@
let masking_key = masking_key.unwrap_or(ZERO_BLOB_32);
- let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface()?;
let (creation_result, _) = self
.upgrade_keyblob_if_required_with(
- &*km_dev,
+ &*self.keymint,
Some(wrapping_key_id_guard),
&wrapping_key_blob,
&wrapping_blob_metadata,
@@ -776,11 +772,11 @@
"In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.",
500,
);
- let creation_result = map_km_error(km_dev.importWrappedKey(
+ let creation_result = map_km_error(self.keymint.importWrappedKey(
wrapped_data,
wrapping_blob,
masking_key,
- ¶ms,
+ params,
pw_sid,
fp_sid,
))?;
@@ -800,7 +796,7 @@
upgraded_blob: &[u8],
) -> Result<()> {
let (upgraded_blob_to_be_stored, new_blob_metadata) =
- SuperKeyManager::reencrypt_if_required(key_blob, &upgraded_blob)
+ SuperKeyManager::reencrypt_if_required(key_blob, upgraded_blob)
.context("In store_upgraded_keyblob: Failed to handle super encryption.")?;
let mut new_blob_metadata = new_blob_metadata.unwrap_or_default();
@@ -907,13 +903,10 @@
)?;
// convert_storage_key_to_ephemeral requires the associated permission
- check_key_permission(KeyPerm::convert_storage_key_to_ephemeral(), storage_key, &None)
+ check_key_permission(KeyPerm::ConvertStorageKeyToEphemeral, storage_key, &None)
.context("In convert_storage_key_to_ephemeral: Check permission")?;
- let km_dev: Strong<dyn IKeyMintDevice> = self.keymint.get_interface().context(concat!(
- "In IKeystoreSecurityLevel convert_storage_key_to_ephemeral: ",
- "Getting keymint device interface"
- ))?;
+ let km_dev = &self.keymint;
match {
let _wp = self.watch_millis(
concat!(
@@ -969,17 +962,14 @@
.ok_or(error::Error::Km(ErrorCode::INVALID_ARGUMENT))
.context("In IKeystoreSecurityLevel delete_key: No key blob specified")?;
- check_key_permission(KeyPerm::delete(), key, &None)
+ check_key_permission(KeyPerm::Delete, key, &None)
.context("In IKeystoreSecurityLevel delete_key: Checking delete permissions")?;
- let km_dev: Strong<dyn IKeyMintDevice> = self
- .keymint
- .get_interface()
- .context("In IKeystoreSecurityLevel delete_key: Getting keymint device interface")?;
+ let km_dev = &self.keymint;
{
let _wp =
self.watch_millis("In KeystoreSecuritylevel::delete_key: calling deleteKey", 500);
- map_km_error(km_dev.deleteKey(&key_blob)).context("In keymint device deleteKey")
+ map_km_error(km_dev.deleteKey(key_blob)).context("In keymint device deleteKey")
}
}
}
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index d65743d..70f5c71 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,7 +22,7 @@
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
check_grant_permission, check_key_permission, check_keystore_permission,
- key_parameters_to_authorizations, watchdog as wd, Asp,
+ key_parameters_to_authorizations, watchdog as wd,
};
use crate::{
database::Uuid,
@@ -51,7 +51,7 @@
/// Implementation of the IKeystoreService.
#[derive(Default)]
pub struct KeystoreService {
- i_sec_level_by_uuid: HashMap<Uuid, Asp>,
+ i_sec_level_by_uuid: HashMap<Uuid, Strong<dyn IKeystoreSecurityLevel>>,
uuid_by_sec_level: HashMap<SecurityLevel, Uuid>,
}
@@ -68,15 +68,13 @@
.context(concat!(
"In KeystoreService::new_native_binder: ",
"Trying to construct mandatory security level TEE."
- ))
- .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))?;
+ ))?;
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid);
// Strongbox is optional, so we ignore errors and turn the result into an Option.
if let Ok((dev, uuid)) =
KeystoreSecurityLevel::new_native_binder(SecurityLevel::STRONGBOX, id_rotation_state)
- .map(|(dev, uuid)| (Asp::new(dev.as_binder()), uuid))
{
result.i_sec_level_by_uuid.insert(uuid, dev);
result.uuid_by_sec_level.insert(SecurityLevel::STRONGBOX, uuid);
@@ -107,7 +105,7 @@
fn get_i_sec_level_by_uuid(&self, uuid: &Uuid) -> Result<Strong<dyn IKeystoreSecurityLevel>> {
if let Some(dev) = self.i_sec_level_by_uuid.get(uuid) {
- dev.get_interface().context("In get_i_sec_level_by_uuid.")
+ Ok(dev.clone())
} else {
Err(error::Error::sys())
.context("In get_i_sec_level_by_uuid: KeyMint instance for key not found.")
@@ -123,7 +121,7 @@
.get(&sec_level)
.and_then(|uuid| self.i_sec_level_by_uuid.get(uuid))
{
- dev.get_interface().context("In get_security_level.")
+ Ok(dev.clone())
} else {
Err(error::Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE))
.context("In get_security_level: No such security level.")
@@ -134,13 +132,13 @@
let caller_uid = ThreadState::get_calling_uid();
let (key_id_guard, mut key_entry) = DB
.with(|db| {
- LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ LEGACY_MIGRATOR.with_try_migrate(key, caller_uid, || {
db.borrow_mut().load_key_entry(
- &key,
+ key,
KeyType::Client,
KeyEntryLoadBits::PUBLIC,
caller_uid,
- |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
+ |k, av| check_key_permission(KeyPerm::GetInfo, k, &av),
)
})
})
@@ -185,14 +183,14 @@
) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
DB.with::<_, Result<()>>(|db| {
- let entry = match LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ let entry = match LEGACY_MIGRATOR.with_try_migrate(key, caller_uid, || {
db.borrow_mut().load_key_entry(
- &key,
+ key,
KeyType::Client,
KeyEntryLoadBits::NONE,
caller_uid,
|k, av| {
- check_key_permission(KeyPerm::update(), k, &av)
+ check_key_permission(KeyPerm::Update, k, &av)
.context("In update_subcomponent.")
},
)
@@ -238,7 +236,7 @@
};
// Security critical: This must return on failure. Do not remove the `?`;
- check_key_permission(KeyPerm::rebind(), &key, &None)
+ check_key_permission(KeyPerm::Rebind, &key, &None)
.context("Caller does not have permission to insert this certificate.")?;
db.store_new_certificate(
@@ -271,12 +269,12 @@
// If the first check fails we check if the caller has the list permission allowing to list
// any namespace. In that case we also adjust the queried namespace if a specific uid was
// selected.
- match check_key_permission(KeyPerm::get_info(), &k, &None) {
+ match check_key_permission(KeyPerm::GetInfo, &k, &None) {
Err(e) => {
if let Some(selinux::Error::PermissionDenied) =
e.root_cause().downcast_ref::<selinux::Error>()
{
- check_keystore_permission(KeystorePerm::list())
+ check_keystore_permission(KeystorePerm::List)
.context("In list_entries: While checking keystore permission.")?;
if namespace != -1 {
k.nspace = namespace;
@@ -309,9 +307,9 @@
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
DB.with(|db| {
- LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
- db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| {
- check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.")
+ LEGACY_MIGRATOR.with_try_migrate(key, caller_uid, || {
+ db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid, |k, av| {
+ check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.")
})
})
})
@@ -327,9 +325,9 @@
) -> Result<KeyDescriptor> {
let caller_uid = ThreadState::get_calling_uid();
DB.with(|db| {
- LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ LEGACY_MIGRATOR.with_try_migrate(key, caller_uid, || {
db.borrow_mut().grant(
- &key,
+ key,
caller_uid,
grantee_uid as u32,
access_vector,
@@ -342,8 +340,8 @@
fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> Result<()> {
DB.with(|db| {
- db.borrow_mut().ungrant(&key, ThreadState::get_calling_uid(), grantee_uid as u32, |k| {
- check_key_permission(KeyPerm::grant(), k, &None)
+ db.borrow_mut().ungrant(key, ThreadState::get_calling_uid(), grantee_uid as u32, |k| {
+ check_key_permission(KeyPerm::Grant, k, &None)
})
})
.context("In KeystoreService::ungrant.")
diff --git a/keystore2/src/shared_secret_negotiation.rs b/keystore2/src/shared_secret_negotiation.rs
index 64bc2c3..1862f73 100644
--- a/keystore2/src/shared_secret_negotiation.rs
+++ b/keystore2/src/shared_secret_negotiation.rs
@@ -21,7 +21,7 @@
ISharedSecret::ISharedSecret, SharedSecretParameters::SharedSecretParameters,
};
use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService;
-use anyhow::{Context, Result};
+use anyhow::Result;
use keystore2_vintf::{get_aidl_instances, get_hidl_instances};
use std::fmt::{self, Display, Formatter};
use std::time::Duration;
@@ -118,47 +118,32 @@
.iter()
.map(|(ma, mi)| {
get_hidl_instances(KEYMASTER_PACKAGE_NAME, *ma, *mi, KEYMASTER_INTERFACE_NAME)
- .as_vec()
- .with_context(|| format!("Trying to convert KM{}.{} names to vector.", *ma, *mi))
- .map(|instances| {
- instances
- .into_iter()
- .filter_map(|name| {
- filter_map_legacy_km_instances(name.to_string(), (*ma, *mi)).and_then(
- |sp| {
- if let SharedSecretParticipant::Hidl {
- is_strongbox: true,
- ..
- } = &sp
- {
- if !legacy_strongbox_found {
- legacy_strongbox_found = true;
- return Some(sp);
- }
- } else if !legacy_default_found {
- legacy_default_found = true;
- return Some(sp);
- }
- None
- },
- )
- })
- .collect::<Vec<SharedSecretParticipant>>()
+ .into_iter()
+ .filter_map(|name| {
+ filter_map_legacy_km_instances(name, (*ma, *mi)).and_then(|sp| {
+ if let SharedSecretParticipant::Hidl { is_strongbox: true, .. } = &sp {
+ if !legacy_strongbox_found {
+ legacy_strongbox_found = true;
+ return Some(sp);
+ }
+ } else if !legacy_default_found {
+ legacy_default_found = true;
+ return Some(sp);
+ }
+ None
+ })
})
+ .collect::<Vec<SharedSecretParticipant>>()
})
- .collect::<Result<Vec<_>>>()
- .map(|v| v.into_iter().flatten())
- .and_then(|i| {
- let participants_aidl: Vec<SharedSecretParticipant> =
- get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME)
- .as_vec()
- .context("In list_participants: Trying to convert KM1.0 names to vector.")?
- .into_iter()
- .map(|name| SharedSecretParticipant::Aidl(name.to_string()))
- .collect();
- Ok(i.chain(participants_aidl.into_iter()))
+ .into_iter()
+ .flatten()
+ .chain({
+ get_aidl_instances(SHARED_SECRET_PACKAGE_NAME, 1, SHARED_SECRET_INTERFACE_NAME)
+ .into_iter()
+ .map(SharedSecretParticipant::Aidl)
+ .collect::<Vec<_>>()
+ .into_iter()
})
- .context("In list_participants.")?
.collect())
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index e02d9bc..a1e4c48 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -46,7 +46,7 @@
aes_gcm_decrypt, aes_gcm_encrypt, generate_aes256_key, generate_salt, Password, ZVec,
AES_256_KEY_LENGTH,
};
-use keystore2_system_property::PropertyWatcher;
+use rustutils::system_properties::PropertyWatcher;
use std::{
collections::HashMap,
sync::Arc,
@@ -126,10 +126,8 @@
fn from_metadata(metadata: &BlobMetaData) -> Option<Self> {
if let Some(EncryptedBy::KeyId(key_id)) = metadata.encrypted_by() {
Some(SuperKeyIdentifier::DatabaseId(*key_id))
- } else if let Some(boot_level) = metadata.max_boot_level() {
- Some(SuperKeyIdentifier::BootLevel(*boot_level))
} else {
- None
+ metadata.max_boot_level().map(|boot_level| SuperKeyIdentifier::BootLevel(*boot_level))
}
}
@@ -398,7 +396,7 @@
.get_or_create_key_with(
Domain::APP,
user as u64 as i64,
- &USER_SUPER_KEY.alias,
+ USER_SUPER_KEY.alias,
crate::database::KEYSTORE_UUID,
|| {
// For backward compatibility we need to check if there is a super key present.
@@ -501,7 +499,7 @@
user_id: UserId,
) -> Result<bool> {
let key_in_db = db
- .key_exists(Domain::APP, user_id as u64 as i64, &USER_SUPER_KEY.alias, KeyType::Super)
+ .key_exists(Domain::APP, user_id as u64 as i64, USER_SUPER_KEY.alias, KeyType::Super)
.context("In super_key_exists_in_db_for_user.")?;
if key_in_db {
@@ -737,7 +735,7 @@
match Enforcements::super_encryption_required(domain, key_parameters, flags) {
SuperEncryptionType::None => Ok((key_blob.to_vec(), BlobMetaData::new())),
SuperEncryptionType::LskfBound => self
- .super_encrypt_on_key_init(db, legacy_migrator, user_id, &key_blob)
+ .super_encrypt_on_key_init(db, legacy_migrator, user_id, key_blob)
.context(concat!(
"In handle_super_encryption_on_key_init. ",
"Failed to super encrypt with LskfBound key."
@@ -746,7 +744,7 @@
let mut data = self.data.lock().unwrap();
let entry = data.user_keys.entry(user_id).or_default();
if let Some(super_key) = entry.screen_lock_bound.as_ref() {
- Self::encrypt_with_aes_super_key(key_blob, &super_key).context(concat!(
+ Self::encrypt_with_aes_super_key(key_blob, super_key).context(concat!(
"In handle_super_encryption_on_key_init. ",
"Failed to encrypt with ScreenLockBound key."
))
@@ -1215,8 +1213,8 @@
fn deref(&self) -> &Self::Target {
match self {
- Self::Sensitive { key, .. } => &key,
- Self::NonSensitive(key) => &key,
+ Self::Sensitive { key, .. } => key,
+ Self::NonSensitive(key) => key,
Self::Ref(key) => key,
}
}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index a110c64..f6d92ee 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -29,14 +29,13 @@
use android_system_keystore2::aidl::android::system::keystore2::{
Authorization::Authorization, KeyDescriptor::KeyDescriptor,
};
-use anyhow::{anyhow, Context};
-use binder::{FromIBinder, SpIBinder, ThreadState};
+use anyhow::Context;
+use binder::{Strong, ThreadState};
use keystore2_apc_compat::{
ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
APC_COMPAT_ERROR_SYSTEM_ERROR,
};
-use std::sync::Mutex;
/// This function uses its namesake in the permission module and in
/// combination with with_calling_sid from the binder crate to check
@@ -44,7 +43,7 @@
pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> {
ThreadState::with_calling_sid(|calling_sid| {
permission::check_keystore_permission(
- &calling_sid.ok_or_else(Error::sys).context(
+ calling_sid.ok_or_else(Error::sys).context(
"In check_keystore_permission: Cannot check permission without calling_sid.",
)?,
perm,
@@ -58,7 +57,7 @@
pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> {
ThreadState::with_calling_sid(|calling_sid| {
permission::check_grant_permission(
- &calling_sid.ok_or_else(Error::sys).context(
+ calling_sid.ok_or_else(Error::sys).context(
"In check_grant_permission: Cannot check permission without calling_sid.",
)?,
access_vec,
@@ -78,7 +77,7 @@
ThreadState::with_calling_sid(|calling_sid| {
permission::check_key_permission(
ThreadState::get_calling_uid(),
- &calling_sid
+ calling_sid
.ok_or_else(Error::sys)
.context("In check_key_permission: Cannot check permission without calling_sid.")?,
perm,
@@ -103,7 +102,7 @@
/// identifiers. It throws an error if the permissions cannot be verified, or if the caller doesn't
/// have the right permissions, and returns silently otherwise.
pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
- let permission_controller: binder::Strong<dyn IPermissionController::IPermissionController> =
+ let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
let binder_result = {
@@ -128,39 +127,6 @@
}
}
-/// Thread safe wrapper around SpIBinder. It is safe to have SpIBinder smart pointers to the
-/// same object in multiple threads, but cloning a SpIBinder is not thread safe.
-/// Keystore frequently hands out binder tokens to the security level interface. If this
-/// is to happen from a multi threaded thread pool, the SpIBinder needs to be protected by a
-/// Mutex.
-#[derive(Debug)]
-pub struct Asp(Mutex<SpIBinder>);
-
-impl Asp {
- /// Creates a new instance owning a SpIBinder wrapped in a Mutex.
- pub fn new(i: SpIBinder) -> Self {
- Self(Mutex::new(i))
- }
-
- /// Clones the owned SpIBinder and attempts to convert it into the requested interface.
- pub fn get_interface<T: FromIBinder + ?Sized>(&self) -> anyhow::Result<binder::Strong<T>> {
- // We can use unwrap here because we never panic when locked, so the mutex
- // can never be poisoned.
- let lock = self.0.lock().unwrap();
- (*lock)
- .clone()
- .into_interface()
- .map_err(|e| anyhow!(format!("get_interface failed with error code {:?}", e)))
- }
-}
-
-impl Clone for Asp {
- fn clone(&self) -> Self {
- let lock = self.0.lock().unwrap();
- Self(Mutex::new((*lock).clone()))
- }
-}
-
/// Converts a set of key characteristics as returned from KeyMint into the internal
/// representation of the keystore service.
pub fn key_characteristics_to_internal(
@@ -222,16 +188,15 @@
}
/// AID offset for uid space partitioning.
-pub const AID_USER_OFFSET: u32 = cutils_bindgen::AID_USER_OFFSET;
+pub const AID_USER_OFFSET: u32 = rustutils::users::AID_USER_OFFSET;
/// AID of the keystore process itself, used for keys that
/// keystore generates for its own use.
-pub const AID_KEYSTORE: u32 = cutils_bindgen::AID_KEYSTORE;
+pub const AID_KEYSTORE: u32 = rustutils::users::AID_KEYSTORE;
/// Extracts the android user from the given uid.
pub fn uid_to_android_user(uid: u32) -> u32 {
- // Safety: No memory access
- unsafe { cutils_bindgen::multiuser_get_user_id(uid) }
+ rustutils::users::multiuser_get_user_id(uid)
}
/// This module provides helpers for simplified use of the watchdog module.
diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp
index 3ab0ec5..34719aa 100644
--- a/keystore2/src/vintf/Android.bp
+++ b/keystore2/src/vintf/Android.bp
@@ -26,39 +26,32 @@
crate_name: "keystore2_vintf",
srcs: ["lib.rs"],
rustlibs: [
- "libkeystore2_vintf_bindgen",
+ "libcxx",
],
shared_libs: [
+ "libvintf",
+ ],
+ static_libs: [
"libkeystore2_vintf_cpp",
- "libvintf",
],
}
-cc_library {
+cc_library_static {
name: "libkeystore2_vintf_cpp",
- srcs: [
- "vintf.cpp",
- ],
+ srcs: ["vintf.cpp"],
+ generated_headers: ["cxx-bridge-header"],
+ generated_sources: ["vintf_bridge_code"],
shared_libs: [
"libvintf",
],
}
-rust_bindgen {
- name: "libkeystore2_vintf_bindgen",
- wrapper_src: "vintf.hpp",
- crate_name: "keystore2_vintf_bindgen",
- source_stem: "bindings",
- host_supported: true,
- shared_libs: ["libvintf"],
- bindgen_flags: [
- "--size_t-is-usize",
- "--allowlist-function", "getHalNames",
- "--allowlist-function", "getHalNamesAndVersions",
- "--allowlist-function", "getHidlInstances",
- "--allowlist-function", "getAidlInstances",
- "--allowlist-function", "freeNames",
- ],
+genrule {
+ name: "vintf_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["vintf_cxx_generated.cc"],
}
rust_test {
@@ -68,7 +61,7 @@
test_suites: ["general-tests"],
auto_gen_config: true,
rustlibs: [
- "libkeystore2_vintf_bindgen",
+ "libcxx",
],
static_libs: [
"libkeystore2_vintf_cpp",
diff --git a/keystore2/src/vintf/lib.rs b/keystore2/src/vintf/lib.rs
index 8730a3e..89e18eb 100644
--- a/keystore2/src/vintf/lib.rs
+++ b/keystore2/src/vintf/lib.rs
@@ -14,96 +14,35 @@
//! Bindings for getting the list of HALs.
-use keystore2_vintf_bindgen::{
- freeNames, getAidlInstances, getHalNames, getHalNamesAndVersions, getHidlInstances,
-};
-use std::ffi::{CStr, CString};
-use std::os::raw::c_char;
-use std::str::Utf8Error;
+#[cxx::bridge]
+mod ffi {
+ unsafe extern "C++" {
+ include!("vintf.hpp");
-/// A struct that contains a list of HALs (optionally with version numbers).
-/// To use it, call as_vec to get a Vec view of the data it contains.
-pub struct HalNames {
- data: *mut *mut c_char,
- len: usize,
-}
+ /// Gets all HAL names.
+ /// Note that this is not a zero-cost shim: it will make copies of the strings.
+ fn get_hal_names() -> Vec<String>;
-impl Drop for HalNames {
- fn drop(&mut self) {
- // Safety: The memory is allocated by our C shim so it must free it as well.
- unsafe { freeNames(self.data, self.len) }
+ /// Gets all HAL names and versions.
+ /// Note that this is not a zero-cost shim: it will make copies of the strings.
+ fn get_hal_names_and_versions() -> Vec<String>;
+
+ /// Gets the instances of the given package, version, and interface tuple.
+ /// Note that this is not a zero-cost shim: it will make copies of the strings.
+ fn get_hidl_instances(
+ package: &str,
+ major_version: usize,
+ minor_version: usize,
+ interface_name: &str,
+ ) -> Vec<String>;
+
+ /// Gets the instances of the given package, version, and interface tuple.
+ /// Note that this is not a zero-cost shim: it will make copies of the strings.
+ fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> Vec<String>;
}
}
-impl<'a> HalNames {
- /// Get a Vec view of the list of HALs.
- pub fn as_vec(&'a self) -> Result<Vec<&'a str>, Utf8Error> {
- // Safety: self.data contains self.len C strings.
- // The lifetimes ensure that the HalNames (and hence the strings) live
- // at least as long as the returned vector.
- unsafe { (0..self.len).map(|i| CStr::from_ptr(*self.data.add(i)).to_str()) }.collect()
- }
-}
-
-/// Gets all HAL names.
-/// Note that this is not a zero-cost shim: it will make copies of the strings.
-pub fn get_hal_names() -> HalNames {
- let mut len: usize = 0;
- // Safety: We'll wrap this in HalNames to free the memory it allocates.
- // It stores the size of the array it returns in len.
- let raw_strs = unsafe { getHalNames(&mut len) };
- HalNames { data: raw_strs, len }
-}
-
-/// Gets all HAL names and versions.
-/// Note that this is not a zero-cost shim: it will make copies of the strings.
-pub fn get_hal_names_and_versions() -> HalNames {
- let mut len: usize = 0;
- // Safety: We'll wrap this in HalNames to free the memory it allocates.
- // It stores the size of the array it returns in len.
- let raw_strs = unsafe { getHalNamesAndVersions(&mut len) };
- HalNames { data: raw_strs, len }
-}
-
-/// Gets the instances of the given package, version, and interface tuple.
-/// Note that this is not a zero-cost shim: it will make copies of the strings.
-pub fn get_hidl_instances(
- package: &str,
- major_version: usize,
- minor_version: usize,
- interface_name: &str,
-) -> HalNames {
- let mut len: usize = 0;
- let packages = CString::new(package).expect("Failed to make CString from package.");
- let interface_name =
- CString::new(interface_name).expect("Failed to make CString from interface_name.");
- // Safety: We'll wrap this in HalNames to free the memory it allocates.
- // It stores the size of the array it returns in len.
- let raw_strs = unsafe {
- getHidlInstances(
- &mut len,
- packages.as_ptr(),
- major_version,
- minor_version,
- interface_name.as_ptr(),
- )
- };
- HalNames { data: raw_strs, len }
-}
-
-/// Gets the instances of the given package, version, and interface tuple.
-/// Note that this is not a zero-cost shim: it will make copies of the strings.
-pub fn get_aidl_instances(package: &str, version: usize, interface_name: &str) -> HalNames {
- let mut len: usize = 0;
- let packages = CString::new(package).expect("Failed to make CString from package.");
- let interface_name =
- CString::new(interface_name).expect("Failed to make CString from interface_name.");
- // Safety: We'll wrap this in HalNames to free the memory it allocates.
- // It stores the size of the array it returns in len.
- let raw_strs =
- unsafe { getAidlInstances(&mut len, packages.as_ptr(), version, interface_name.as_ptr()) };
- HalNames { data: raw_strs, len }
-}
+pub use ffi::*;
#[cfg(test)]
mod tests {
@@ -111,17 +50,13 @@
use super::*;
#[test]
- fn test() -> Result<(), Utf8Error> {
- let result = get_hal_names();
- let names = result.as_vec()?;
+ fn test() {
+ let names = get_hal_names();
assert_ne!(names.len(), 0);
- let result = get_hal_names_and_versions();
- let names_and_versions = result.as_vec()?;
+ let names_and_versions = get_hal_names_and_versions();
assert_ne!(names_and_versions.len(), 0);
assert!(names_and_versions.len() >= names.len());
-
- Ok(())
}
}
diff --git a/keystore2/src/vintf/vintf.cpp b/keystore2/src/vintf/vintf.cpp
index e407efa..00625bf 100644
--- a/keystore2/src/vintf/vintf.cpp
+++ b/keystore2/src/vintf/vintf.cpp
@@ -14,55 +14,43 @@
* limitations under the License.
*/
-#include "vintf.hpp"
-
+#include <algorithm>
#include <vintf/HalManifest.h>
#include <vintf/VintfObject.h>
-// Converts a set<string> into a C-style array of C strings.
-static char** convert(const std::set<std::string>& names) {
- char** ret = new char*[names.size()];
- char** ptr = ret;
- for (const auto& name : names) {
- *(ptr++) = strdup(name.c_str());
- }
- return ret;
+#include "rust/cxx.h"
+
+rust::Vec<rust::String> convert(const std::set<std::string>& names) {
+ rust::Vec<rust::String> result;
+ std::copy(names.begin(), names.end(), std::back_inserter(result));
+ return result;
}
-char** getHalNames(size_t* len) {
- auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+rust::Vec<rust::String> get_hal_names() {
+ const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
const auto names = manifest->getHalNames();
- *len = names.size();
return convert(names);
}
-char** getHalNamesAndVersions(size_t* len) {
- auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+rust::Vec<rust::String> get_hal_names_and_versions() {
+ const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
const auto names = manifest->getHalNamesAndVersions();
- *len = names.size();
return convert(names);
}
-char** getHidlInstances(size_t* len, const char* package, size_t major_version,
- size_t minor_version, const char* interfaceName) {
+rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version,
+ size_t minor_version, rust::Str interfaceName) {
android::vintf::Version version(major_version, minor_version);
- auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
- const auto names = manifest->getHidlInstances(package, version, interfaceName);
- *len = names.size();
+ const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getHidlInstances(static_cast<std::string>(package), version,
+ static_cast<std::string>(interfaceName));
return convert(names);
}
-char** getAidlInstances(size_t* len, const char* package, size_t version,
- const char* interfaceName) {
- auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
- const auto names = manifest->getAidlInstances(package, version, interfaceName);
- *len = names.size();
+rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version,
+ rust::Str interfaceName) {
+ const auto manifest = android::vintf::VintfObject::GetDeviceHalManifest();
+ const auto names = manifest->getAidlInstances(static_cast<std::string>(package), version,
+ static_cast<std::string>(interfaceName));
return convert(names);
}
-
-void freeNames(char** names, size_t len) {
- for (int i = 0; i < len; i++) {
- free(names[i]);
- }
- delete[] names;
-}
diff --git a/keystore2/src/vintf/vintf.hpp b/keystore2/src/vintf/vintf.hpp
index 091e8e8..dbc88f0 100644
--- a/keystore2/src/vintf/vintf.hpp
+++ b/keystore2/src/vintf/vintf.hpp
@@ -14,20 +14,13 @@
* limitations under the License.
*/
-#ifndef __VINTF_H__
-#define __VINTF_H__
+#pragma once
-#include <stddef.h>
+#include "rust/cxx.h"
-extern "C" {
-
-char** getHalNames(size_t* len);
-char** getHalNamesAndVersions(size_t* len);
-char** getHidlInstances(size_t* len, const char* package, size_t major_version,
- size_t minor_version, const char* interfaceName);
-char** getAidlInstances(size_t* len, const char* package, size_t version,
- const char* interfaceName);
-void freeNames(char** names, size_t len);
-}
-
-#endif // __VINTF_H__
+rust::Vec<rust::String> get_hal_names();
+rust::Vec<rust::String> get_hal_names_and_versions();
+rust::Vec<rust::String> get_hidl_instances(rust::Str package, size_t major_version,
+ size_t minor_version, rust::Str interfaceName);
+rust::Vec<rust::String> get_aidl_instances(rust::Str package, size_t version,
+ rust::Str interfaceName);
diff --git a/keystore2/system_property/Android.bp b/keystore2/system_property/Android.bp
deleted file mode 100644
index 773804d..0000000
--- a/keystore2/system_property/Android.bp
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2021, 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.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "system_security_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["system_security_license"],
-}
-
-rust_bindgen {
- name: "libkeystore2_system_property_bindgen",
- wrapper_src: "system_property_bindgen.hpp",
- crate_name: "keystore2_system_property_bindgen",
- source_stem: "bindings",
-
- bindgen_flags: [
- "--size_t-is-usize",
- "--allowlist-function=__system_property_find",
- "--allowlist-function=__system_property_read_callback",
- "--allowlist-function=__system_property_set",
- "--allowlist-function=__system_property_wait",
- ],
-}
-
-rust_library {
- name: "libkeystore2_system_property-rust",
- crate_name: "keystore2_system_property",
- srcs: [
- "lib.rs",
- ],
- rustlibs: [
- "libanyhow",
- "libkeystore2_system_property_bindgen",
- "libthiserror",
- ],
- shared_libs: [
- "libbase",
- ],
-}
diff --git a/keystore2/system_property/lib.rs b/keystore2/system_property/lib.rs
deleted file mode 100644
index b993c87..0000000
--- a/keystore2/system_property/lib.rs
+++ /dev/null
@@ -1,217 +0,0 @@
-// Copyright 2021, 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.
-
-//! This crate provides the PropertyWatcher type, which watches for changes
-//! in Android system properties.
-
-use anyhow::{anyhow, Context, Result as AnyhowResult};
-use keystore2_system_property_bindgen::prop_info as PropInfo;
-use std::os::raw::c_char;
-use std::ptr::null;
-use std::{
- ffi::{c_void, CStr, CString},
- str::Utf8Error,
-};
-use thiserror::Error;
-
-/// Errors this crate can generate
-#[derive(Error, Debug)]
-pub enum PropertyWatcherError {
- /// We can't watch for a property whose name contains a NUL character.
- #[error("Cannot convert name to C string")]
- BadNameError(#[from] std::ffi::NulError),
- /// We can only watch for properties that exist when the watcher is created.
- #[error("System property is absent")]
- SystemPropertyAbsent,
- /// __system_property_wait timed out despite being given no timeout.
- #[error("Wait failed")]
- WaitFailed,
- /// read callback was not called
- #[error("__system_property_read_callback did not call callback")]
- ReadCallbackNotCalled,
- /// read callback gave us a NULL pointer
- #[error("__system_property_read_callback gave us a NULL pointer instead of a string")]
- MissingCString,
- /// read callback gave us a bad C string
- #[error("__system_property_read_callback gave us a non-UTF8 C string")]
- BadCString(#[from] Utf8Error),
- /// read callback returned an error
- #[error("Callback failed")]
- CallbackError(#[from] anyhow::Error),
- /// Failure in setting the system property
- #[error("__system_property_set failed.")]
- SetPropertyFailed,
-}
-
-/// Result type specific for this crate.
-pub type Result<T> = std::result::Result<T, PropertyWatcherError>;
-
-/// PropertyWatcher takes the name of an Android system property such
-/// as `keystore.boot_level`; it can report the current value of this
-/// property, or wait for it to change.
-pub struct PropertyWatcher {
- prop_name: CString,
- prop_info: *const PropInfo,
- serial: keystore2_system_property_bindgen::__uint32_t,
-}
-
-impl PropertyWatcher {
- /// Create a PropertyWatcher for the named system property.
- pub fn new(name: &str) -> Result<Self> {
- Ok(Self { prop_name: CString::new(name)?, prop_info: null(), serial: 0 })
- }
-
- // Lazy-initializing accessor for self.prop_info.
- fn get_prop_info(&mut self) -> Option<*const PropInfo> {
- if self.prop_info.is_null() {
- // Unsafe required for FFI call. Input and output are both const.
- // The returned pointer is valid for the lifetime of the program.
- self.prop_info = unsafe {
- keystore2_system_property_bindgen::__system_property_find(self.prop_name.as_ptr())
- };
- }
- if self.prop_info.is_null() {
- None
- } else {
- Some(self.prop_info)
- }
- }
-
- fn read_raw(prop_info: *const PropInfo, mut f: impl FnOnce(Option<&CStr>, Option<&CStr>)) {
- // Unsafe function converts values passed to us by
- // __system_property_read_callback to Rust form
- // and pass them to inner callback.
- unsafe extern "C" fn callback(
- res_p: *mut c_void,
- name: *const c_char,
- value: *const c_char,
- _: keystore2_system_property_bindgen::__uint32_t,
- ) {
- let name = if name.is_null() { None } else { Some(CStr::from_ptr(name)) };
- let value = if value.is_null() { None } else { Some(CStr::from_ptr(value)) };
- let f = &mut *res_p.cast::<&mut dyn FnMut(Option<&CStr>, Option<&CStr>)>();
- f(name, value);
- }
-
- let mut f: &mut dyn FnOnce(Option<&CStr>, Option<&CStr>) = &mut f;
-
- // Unsafe block for FFI call. We convert the FnOnce
- // to a void pointer, and unwrap it in our callback.
- unsafe {
- keystore2_system_property_bindgen::__system_property_read_callback(
- prop_info,
- Some(callback),
- &mut f as *mut _ as *mut c_void,
- )
- }
- }
-
- /// Call the passed function, passing it the name and current value
- /// of this system property. See documentation for
- /// `__system_property_read_callback` for details.
- /// Returns an error if the property is empty or doesn't exist.
- pub fn read<T, F>(&mut self, f: F) -> Result<T>
- where
- F: FnOnce(&str, &str) -> anyhow::Result<T>,
- {
- let prop_info = self.get_prop_info().ok_or(PropertyWatcherError::SystemPropertyAbsent)?;
- let mut result = Err(PropertyWatcherError::ReadCallbackNotCalled);
- Self::read_raw(prop_info, |name, value| {
- // use a wrapping closure as an erzatz try block.
- result = (|| {
- let name = name.ok_or(PropertyWatcherError::MissingCString)?.to_str()?;
- let value = value.ok_or(PropertyWatcherError::MissingCString)?.to_str()?;
- f(name, value).map_err(PropertyWatcherError::CallbackError)
- })()
- });
- result
- }
-
- // Waits for the property that self is watching to be created. Returns immediately if the
- // property already exists.
- fn wait_for_property_creation(&mut self) -> Result<()> {
- let mut global_serial = 0;
- loop {
- match self.get_prop_info() {
- Some(_) => return Ok(()),
- None => {
- // Unsafe call for FFI. The function modifies only global_serial, and has
- // no side-effects.
- if !unsafe {
- // Wait for a global serial number change, then try again. On success,
- // the function will update global_serial with the last version seen.
- keystore2_system_property_bindgen::__system_property_wait(
- null(),
- global_serial,
- &mut global_serial,
- null(),
- )
- } {
- return Err(PropertyWatcherError::WaitFailed);
- }
- }
- }
- }
- }
-
- /// Wait for the system property to change. This
- /// records the serial number of the last change, so
- /// race conditions are avoided.
- pub fn wait(&mut self) -> Result<()> {
- // If the property is null, then wait for it to be created. Subsequent waits will
- // skip this step and wait for our specific property to change.
- if self.prop_info.is_null() {
- return self.wait_for_property_creation();
- }
-
- let mut new_serial = self.serial;
- // Unsafe block to call __system_property_wait.
- // All arguments are private to PropertyWatcher so we
- // can be confident they are valid.
- if !unsafe {
- keystore2_system_property_bindgen::__system_property_wait(
- self.prop_info,
- self.serial,
- &mut new_serial,
- null(),
- )
- } {
- return Err(PropertyWatcherError::WaitFailed);
- }
- self.serial = new_serial;
- Ok(())
- }
-}
-
-/// Writes a system property.
-pub fn write(name: &str, value: &str) -> AnyhowResult<()> {
- if
- // Unsafe required for FFI call. Input and output are both const and valid strings.
- unsafe {
- // If successful, __system_property_set returns 0, otherwise, returns -1.
- keystore2_system_property_bindgen::__system_property_set(
- CString::new(name)
- .context("In keystore2::system_property::write: Construction CString from name.")?
- .as_ptr(),
- CString::new(value)
- .context("In keystore2::system_property::write: Constructing CString from value.")?
- .as_ptr(),
- )
- } == 0
- {
- Ok(())
- } else {
- Err(anyhow!(PropertyWatcherError::SetPropertyFailed))
- }
-}
diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs
index 627af20..a355544 100644
--- a/keystore2/test_utils/lib.rs
+++ b/keystore2/test_utils/lib.rs
@@ -19,6 +19,8 @@
use std::path::{Path, PathBuf};
use std::{env::temp_dir, ops::Deref};
+pub mod run_as;
+
/// Represents the lifecycle of a temporary directory for testing.
#[derive(Debug)]
pub struct TempDir {
diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs
new file mode 100644
index 0000000..d42303d
--- /dev/null
+++ b/keystore2/test_utils/run_as.rs
@@ -0,0 +1,191 @@
+// Copyright 2021, 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.
+
+//! This module is intended for testing access control enforcement of services such as keystore2,
+//! by assuming various identities with varying levels of privilege. Consequently, appropriate
+//! privileges are required, or the attempt will fail causing a panic.
+//! The `run_as` module provides the function `run_as`, which takes a UID, GID, an SELinux
+//! context, and a closure. The return type of the closure, which is also the return type of
+//! `run_as`, must implement `serde::Serialize` and `serde::Deserialize`.
+//! `run_as` forks, transitions to the given identity, and executes the closure in the newly
+//! forked process. If the closure returns, i.e., does not panic, the forked process exits with
+//! a status of `0`, and the return value is serialized and sent through a pipe to the parent where
+//! it gets deserialized and returned. The STDIO is not changed and the parent's panic handler
+//! remains unchanged. So if the closure panics, the panic message is printed on the parent's STDERR
+//! and the exit status is set to a non `0` value. The latter causes the parent to panic as well,
+//! and if run in a test context, the test to fail.
+
+use keystore2_selinux as selinux;
+use nix::sys::wait::{waitpid, WaitStatus};
+use nix::unistd::{
+ close, fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write,
+ ForkResult, Gid, Uid,
+};
+use serde::{de::DeserializeOwned, Serialize};
+use std::os::unix::io::RawFd;
+
+fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) {
+ setgid(gid).expect("Failed to set GID. This test might need more privileges.");
+ setuid(uid).expect("Failed to set UID. This test might need more privileges.");
+
+ selinux::setcon(&se_context)
+ .expect("Failed to set SELinux context. This test might need more privileges.");
+}
+
+/// PipeReader is a simple wrapper around raw pipe file descriptors.
+/// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which
+/// reads from the pipe into an expending vector, until no more data can be read.
+struct PipeReader(RawFd);
+
+impl PipeReader {
+ pub fn read_all(&self) -> Result<Vec<u8>, nix::Error> {
+ let mut buffer = [0u8; 128];
+ let mut result = Vec::<u8>::new();
+ loop {
+ let bytes = nix_read(self.0, &mut buffer)?;
+ if bytes == 0 {
+ return Ok(result);
+ }
+ result.extend_from_slice(&buffer[0..bytes]);
+ }
+ }
+}
+
+impl Drop for PipeReader {
+ fn drop(&mut self) {
+ close(self.0).expect("Failed to close reader pipe fd.");
+ }
+}
+
+/// PipeWriter is a simple wrapper around raw pipe file descriptors.
+/// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which
+/// writes the given buffer into the pipe, returning the number of bytes written.
+struct PipeWriter(RawFd);
+
+impl PipeWriter {
+ pub fn write(&self, data: &[u8]) -> Result<usize, nix::Error> {
+ nix_write(self.0, data)
+ }
+}
+
+impl Drop for PipeWriter {
+ fn drop(&mut self) {
+ close(self.0).expect("Failed to close writer pipe fd.");
+ }
+}
+
+fn pipe() -> Result<(PipeReader, PipeWriter), nix::Error> {
+ let (read_fd, write_fd) = nix_pipe()?;
+ Ok((PipeReader(read_fd), PipeWriter(write_fd)))
+}
+
+/// Run the given closure in a new process running with the new identity given as
+/// `uid`, `gid`, and `se_context`.
+pub fn run_as<F, R>(se_context: &str, uid: Uid, gid: Gid, f: F) -> R
+where
+ R: Serialize + DeserializeOwned,
+ F: 'static + Send + FnOnce() -> R,
+{
+ let se_context =
+ selinux::Context::new(se_context).expect("Unable to construct selinux::Context.");
+ let (reader, writer) = pipe().expect("Failed to create pipe.");
+
+ match unsafe { fork() } {
+ Ok(ForkResult::Parent { child, .. }) => {
+ drop(writer);
+ let status = waitpid(child, None).expect("Failed while waiting for child.");
+ if let WaitStatus::Exited(_, 0) = status {
+ // Child exited successfully.
+ // Read the result from the pipe.
+ let serialized_result =
+ reader.read_all().expect("Failed to read result from child.");
+
+ // Deserialize the result and return it.
+ serde_cbor::from_slice(&serialized_result).expect("Failed to deserialize result.")
+ } else {
+ panic!("Child did not exit as expected {:?}", status);
+ }
+ }
+ Ok(ForkResult::Child) => {
+ // This will panic on error or insufficient privileges.
+ transition(se_context, uid, gid);
+
+ // Run the closure.
+ let result = f();
+
+ // Serialize the result of the closure.
+ let vec = serde_cbor::to_vec(&result).expect("Result serialization failed");
+
+ // Send the result to the parent using the pipe.
+ writer.write(&vec).expect("Failed to send serialized result to parent.");
+
+ // Set exit status to `0`.
+ std::process::exit(0);
+ }
+ Err(errno) => {
+ panic!("Failed to fork: {:?}", errno);
+ }
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use keystore2_selinux as selinux;
+ use nix::unistd::{getgid, getuid};
+ use serde::{Deserialize, Serialize};
+
+ /// This test checks that the closure does not produce an exit status of `0` when run inside a
+ /// test and the closure panics. This would mask test failures as success.
+ #[test]
+ #[should_panic]
+ fn test_run_as_panics_on_closure_panic() {
+ run_as(selinux::getcon().unwrap().to_str().unwrap(), getuid(), getgid(), || {
+ panic!("Closure must panic.")
+ });
+ }
+
+ static TARGET_UID: Uid = Uid::from_raw(10020);
+ static TARGET_GID: Gid = Gid::from_raw(10020);
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+
+ /// Tests that the closure is running as the target identity.
+ #[test]
+ fn test_transition_to_untrusted_app() {
+ run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || {
+ assert_eq!(TARGET_UID, getuid());
+ assert_eq!(TARGET_GID, getgid());
+ assert_eq!(TARGET_CTX, selinux::getcon().unwrap().to_str().unwrap());
+ });
+ }
+
+ #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+ struct SomeResult {
+ a: u32,
+ b: u64,
+ c: String,
+ }
+
+ #[test]
+ fn test_serialized_result() {
+ let test_result = SomeResult {
+ a: 5,
+ b: 0xffffffffffffffff,
+ c: "supercalifragilisticexpialidocious".to_owned(),
+ };
+ let test_result_clone = test_result.clone();
+ let result = run_as(TARGET_CTX, TARGET_UID, TARGET_GID, || test_result_clone);
+ assert_eq!(test_result, result);
+ }
+}
diff --git a/ondevice-signing/Android.bp b/ondevice-signing/Android.bp
index 432e585..bdc94b7 100644
--- a/ondevice-signing/Android.bp
+++ b/ondevice-signing/Android.bp
@@ -11,8 +11,6 @@
// 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.
-// List of clang-tidy checks that are reported as errors.
-// Please keep this list ordered lexicographically.
package {
// See: http://go/android-license-faq
@@ -23,6 +21,8 @@
default_applicable_licenses: ["system_security_license"],
}
+// List of clang-tidy checks that are reported as errors.
+// Please keep this list ordered lexicographically.
tidy_errors = [
"cert-err34-c",
"google-default-arguments",
@@ -74,6 +74,33 @@
],
}
+cc_library {
+ name: "libsigningutils",
+ defaults: [
+ "odsign_flags_defaults",
+ ],
+ cpp_std: "experimental",
+ srcs: [
+ "CertUtils.cpp",
+ "VerityUtils.cpp",
+ ],
+
+ static_libs: [
+ "libc++fs",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ "libcrypto_utils",
+ "libfsverity",
+ "libprotobuf-cpp-lite",
+ "libutils",
+ ],
+ export_include_dirs: ["include"],
+ recovery_available: true,
+}
+
cc_binary {
name: "odsign",
defaults: [
@@ -82,20 +109,18 @@
cpp_std: "experimental",
init_rc: ["odsign.rc"],
srcs: [
- "odsign_main.cpp",
- "CertUtils.cpp",
"KeystoreKey.cpp",
"KeystoreHmacKey.cpp",
- "VerityUtils.cpp",
+ "odsign_main.cpp",
],
header_libs: ["odrefresh_headers"],
static_libs: [
"libc++fs",
+ "libsigningutils",
"lib_odsign_proto",
],
-
shared_libs: [
"android.system.keystore2-V1-cpp",
"android.hardware.security.keymint-V1-cpp",
@@ -105,7 +130,7 @@
"libcrypto_utils",
"libfsverity",
"liblogwrap",
- "libprotobuf-cpp-full",
+ "libprotobuf-cpp-lite",
"libutils",
],
}
diff --git a/ondevice-signing/CertUtils.cpp b/ondevice-signing/CertUtils.cpp
index b0b75a6..8fe0816 100644
--- a/ondevice-signing/CertUtils.cpp
+++ b/ondevice-signing/CertUtils.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include "CertUtils.h"
+
#include <android-base/logging.h>
#include <android-base/result.h>
@@ -21,54 +23,126 @@
#include <openssl/crypto.h>
#include <openssl/pkcs7.h>
#include <openssl/rsa.h>
+#include <openssl/x509.h>
#include <openssl/x509v3.h>
-#include <fcntl.h>
+#include <optional>
#include <vector>
#include "KeyConstants.h"
-const char kBasicConstraints[] = "CA:TRUE";
-const char kKeyUsage[] = "critical,keyCertSign,cRLSign,digitalSignature";
-const char kSubjectKeyIdentifier[] = "hash";
+// Common properties for all of our certificates.
constexpr int kCertLifetimeSeconds = 10 * 365 * 24 * 60 * 60;
+const char* const kIssuerCountry = "US";
+const char* const kIssuerOrg = "Android";
-using android::base::Result;
-// using android::base::ErrnoError;
+using android::base::ErrnoError;
using android::base::Error;
+using android::base::Result;
-static bool add_ext(X509* cert, int nid, const char* value) {
- size_t len = strlen(value) + 1;
- std::vector<char> mutableValue(value, value + len);
- X509V3_CTX context;
+static Result<bssl::UniquePtr<X509>> loadX509(const std::string& path) {
+ X509* rawCert;
+ auto f = fopen(path.c_str(), "re");
+ if (f == nullptr) {
+ return Error() << "Failed to open " << path;
+ }
+ if (!d2i_X509_fp(f, &rawCert)) {
+ fclose(f);
+ return Error() << "Unable to decode x509 cert at " << path;
+ }
+ bssl::UniquePtr<X509> cert(rawCert);
- X509V3_set_ctx_nodb(&context);
+ fclose(f);
+ return cert;
+}
- X509V3_set_ctx(&context, cert, cert, nullptr, nullptr, 0);
- X509_EXTENSION* ex = X509V3_EXT_nconf_nid(nullptr, &context, nid, mutableValue.data());
+static X509V3_CTX makeContext(X509* issuer, X509* subject) {
+ X509V3_CTX context = {};
+ X509V3_set_ctx(&context, issuer, subject, nullptr, nullptr, 0);
+ return context;
+}
+
+static bool add_ext(X509V3_CTX* context, X509* cert, int nid, const char* value) {
+ bssl::UniquePtr<X509_EXTENSION> ex(X509V3_EXT_nconf_nid(nullptr, context, nid, value));
if (!ex) {
return false;
}
- X509_add_ext(cert, ex, -1);
- X509_EXTENSION_free(ex);
+ X509_add_ext(cert, ex.get(), -1);
return true;
}
-Result<bssl::UniquePtr<RSA>> getRsa(const std::vector<uint8_t>& publicKey) {
- bssl::UniquePtr<RSA> rsaPubkey(RSA_new());
- rsaPubkey->n = BN_new();
- rsaPubkey->e = BN_new();
+static void addNameEntry(X509_NAME* name, const char* field, const char* value) {
+ X509_NAME_add_entry_by_txt(name, field, MBSTRING_ASC,
+ reinterpret_cast<const unsigned char*>(value), -1, -1, 0);
+}
- BN_bin2bn(publicKey.data(), publicKey.size(), rsaPubkey->n);
- BN_set_word(rsaPubkey->e, kRsaKeyExponent);
+static Result<bssl::UniquePtr<RSA>> getRsaFromModulus(const std::vector<uint8_t>& publicKey) {
+ bssl::UniquePtr<BIGNUM> n(BN_new());
+ bssl::UniquePtr<BIGNUM> e(BN_new());
+ bssl::UniquePtr<RSA> rsaPubkey(RSA_new());
+ if (!n || !e || !rsaPubkey || !BN_bin2bn(publicKey.data(), publicKey.size(), n.get()) ||
+ !BN_set_word(e.get(), kRsaKeyExponent) ||
+ !RSA_set0_key(rsaPubkey.get(), n.get(), e.get(), /*d=*/nullptr)) {
+ return Error() << "Failed to create RSA key";
+ }
+ // RSA_set0_key takes ownership of |n| and |e| on success.
+ (void)n.release();
+ (void)e.release();
return rsaPubkey;
}
+static Result<bssl::UniquePtr<RSA>>
+getRsaFromRsaPublicKey(const std::vector<uint8_t>& rsaPublicKey) {
+ auto derBytes = rsaPublicKey.data();
+ bssl::UniquePtr<RSA> rsaKey(d2i_RSAPublicKey(nullptr, &derBytes, rsaPublicKey.size()));
+ if (rsaKey.get() == nullptr) {
+ return Error() << "Failed to parse RsaPublicKey";
+ }
+ if (derBytes != rsaPublicKey.data() + rsaPublicKey.size()) {
+ return Error() << "Key has unexpected trailing data";
+ }
+
+ return rsaKey;
+}
+
+static Result<bssl::UniquePtr<EVP_PKEY>> modulusToRsaPkey(const std::vector<uint8_t>& publicKey) {
+ // "publicKey" corresponds to the raw public key bytes - need to create
+ // a new RSA key with the correct exponent.
+ auto rsaPubkey = getRsaFromModulus(publicKey);
+ if (!rsaPubkey.ok()) {
+ return rsaPubkey.error();
+ }
+
+ bssl::UniquePtr<EVP_PKEY> public_key(EVP_PKEY_new());
+ if (!EVP_PKEY_assign_RSA(public_key.get(), rsaPubkey->release())) {
+ return Error() << "Failed to assign key";
+ }
+ return public_key;
+}
+
+static Result<bssl::UniquePtr<EVP_PKEY>>
+rsaPublicKeyToRsaPkey(const std::vector<uint8_t>& rsaPublicKey) {
+ // rsaPublicKey contains both modulus and exponent, DER-encoded.
+ auto rsaKey = getRsaFromRsaPublicKey(rsaPublicKey);
+ if (!rsaKey.ok()) {
+ return rsaKey.error();
+ }
+
+ bssl::UniquePtr<EVP_PKEY> public_key(EVP_PKEY_new());
+ if (!EVP_PKEY_assign_RSA(public_key.get(), rsaKey->release())) {
+ return Error() << "Failed to assign key";
+ }
+ return public_key;
+}
+
Result<void> verifySignature(const std::string& message, const std::string& signature,
const std::vector<uint8_t>& publicKey) {
- auto rsaKey = getRsa(publicKey);
+ auto rsaKey = getRsaFromModulus(publicKey);
+ if (!rsaKey.ok()) {
+ return rsaKey.error();
+ }
uint8_t hashBuf[SHA256_DIGEST_LENGTH];
SHA256(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(message.c_str())),
message.length(), hashBuf);
@@ -77,85 +151,157 @@
(const uint8_t*)signature.c_str(), signature.length(), rsaKey->get());
if (!success) {
- return Error() << "Failed to verify signature.";
+ return Error() << "Failed to verify signature";
}
return {};
}
+Result<void> verifyRsaPublicKeySignature(const std::string& message, const std::string& signature,
+ const std::vector<uint8_t>& rsaPublicKey) {
+ auto rsaKey = getRsaFromRsaPublicKey(rsaPublicKey);
+ if (!rsaKey.ok()) {
+ return rsaKey.error();
+ }
+
+ uint8_t hashBuf[SHA256_DIGEST_LENGTH];
+ SHA256(reinterpret_cast<const uint8_t*>(message.data()), message.size(), hashBuf);
+
+ bool success = RSA_verify(NID_sha256, hashBuf, sizeof(hashBuf),
+ reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
+ rsaKey->get());
+ if (!success) {
+ return Error() << "Failed to verify signature";
+ }
+ return {};
+}
+
+static Result<void> createCertificate(
+ const CertSubject& subject, EVP_PKEY* publicKey,
+ const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
+ const std::optional<std::string>& issuerCertPath, const std::string& path) {
+
+ // If an issuer cert is specified, we are signing someone else's key.
+ // Otherwise we are signing our key - a self-signed certificate.
+ bool selfSigned = !issuerCertPath;
+
+ bssl::UniquePtr<X509> x509(X509_new());
+ if (!x509) {
+ return Error() << "Unable to allocate x509 container";
+ }
+ X509_set_version(x509.get(), 2);
+ X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
+ X509_gmtime_adj(X509_get_notAfter(x509.get()), kCertLifetimeSeconds);
+ ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), subject.serialNumber);
+
+ bssl::UniquePtr<X509_ALGOR> algor(X509_ALGOR_new());
+ if (!algor ||
+ !X509_ALGOR_set0(algor.get(), OBJ_nid2obj(NID_sha256WithRSAEncryption), V_ASN1_NULL,
+ NULL) ||
+ !X509_set1_signature_algo(x509.get(), algor.get())) {
+ return Error() << "Unable to set x509 signature algorithm";
+ }
+
+ if (!X509_set_pubkey(x509.get(), publicKey)) {
+ return Error() << "Unable to set x509 public key";
+ }
+
+ X509_NAME* subjectName = X509_get_subject_name(x509.get());
+ if (!subjectName) {
+ return Error() << "Unable to get x509 subject name";
+ }
+ addNameEntry(subjectName, "C", kIssuerCountry);
+ addNameEntry(subjectName, "O", kIssuerOrg);
+ addNameEntry(subjectName, "CN", subject.commonName);
+
+ if (selfSigned) {
+ if (!X509_set_issuer_name(x509.get(), subjectName)) {
+ return Error() << "Unable to set x509 issuer name";
+ }
+ } else {
+ X509_NAME* issuerName = X509_get_issuer_name(x509.get());
+ if (!issuerName) {
+ return Error() << "Unable to get x509 issuer name";
+ }
+ addNameEntry(issuerName, "C", kIssuerCountry);
+ addNameEntry(issuerName, "O", kIssuerOrg);
+ addNameEntry(issuerName, "CN", kRootSubject.commonName);
+ }
+
+ // Beware: context contains a pointer to issuerCert, so we need to keep it alive.
+ bssl::UniquePtr<X509> issuerCert;
+ X509V3_CTX context;
+
+ if (selfSigned) {
+ context = makeContext(x509.get(), x509.get());
+ } else {
+ auto certStatus = loadX509(*issuerCertPath);
+ if (!certStatus.ok()) {
+ return Error() << "Unable to load issuer cert: " << certStatus.error();
+ }
+ issuerCert = std::move(certStatus.value());
+ context = makeContext(issuerCert.get(), x509.get());
+ }
+
+ // If it's a self-signed cert we use it for signing certs, otherwise only for signing data.
+ const char* basicConstraints = selfSigned ? "CA:TRUE" : "CA:FALSE";
+ const char* keyUsage =
+ selfSigned ? "critical,keyCertSign,cRLSign,digitalSignature" : "critical,digitalSignature";
+
+ add_ext(&context, x509.get(), NID_basic_constraints, basicConstraints);
+ add_ext(&context, x509.get(), NID_key_usage, keyUsage);
+ add_ext(&context, x509.get(), NID_subject_key_identifier, "hash");
+ add_ext(&context, x509.get(), NID_authority_key_identifier, "keyid:always");
+
+ // Get the data to be signed
+ unsigned char* to_be_signed_buf(nullptr);
+ size_t to_be_signed_length = i2d_re_X509_tbs(x509.get(), &to_be_signed_buf);
+
+ auto signed_data = signFunction(
+ std::string(reinterpret_cast<const char*>(to_be_signed_buf), to_be_signed_length));
+ if (!signed_data.ok()) {
+ return signed_data.error();
+ }
+
+ if (!X509_set1_signature_value(x509.get(),
+ reinterpret_cast<const uint8_t*>(signed_data->data()),
+ signed_data->size())) {
+ return Error() << "Unable to set x509 signature";
+ }
+
+ auto f = fopen(path.c_str(), "wbe");
+ if (f == nullptr) {
+ return ErrnoError() << "Failed to open " << path;
+ }
+ i2d_X509_fp(f, x509.get());
+ if (fclose(f) != 0) {
+ return ErrnoError() << "Failed to close " << path;
+ }
+
+ return {};
+}
+
Result<void> createSelfSignedCertificate(
const std::vector<uint8_t>& publicKey,
const std::function<Result<std::string>(const std::string&)>& signFunction,
const std::string& path) {
- bssl::UniquePtr<X509> x509(X509_new());
- if (!x509) {
- return Error() << "Unable to allocate x509 container";
- }
- X509_set_version(x509.get(), 2);
-
- ASN1_INTEGER_set(X509_get_serialNumber(x509.get()), 1);
- X509_gmtime_adj(X509_get_notBefore(x509.get()), 0);
- X509_gmtime_adj(X509_get_notAfter(x509.get()), kCertLifetimeSeconds);
-
- // "publicKey" corresponds to the raw public key bytes - need to create
- // a new RSA key with the correct exponent.
- auto rsaPubkey = getRsa(publicKey);
-
- EVP_PKEY* public_key = EVP_PKEY_new();
- EVP_PKEY_assign_RSA(public_key, rsaPubkey->release());
-
- if (!X509_set_pubkey(x509.get(), public_key)) {
- return Error() << "Unable to set x509 public key";
+ auto rsa_pkey = modulusToRsaPkey(publicKey);
+ if (!rsa_pkey.ok()) {
+ return rsa_pkey.error();
}
- X509_NAME* name = X509_get_subject_name(x509.get());
- if (!name) {
- return Error() << "Unable to get x509 subject name";
- }
- X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("US"), -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("Android"), -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("ODS"), -1, -1, 0);
- if (!X509_set_issuer_name(x509.get(), name)) {
- return Error() << "Unable to set x509 issuer name";
+ return createCertificate(kRootSubject, rsa_pkey.value().get(), signFunction, {}, path);
+}
+
+android::base::Result<void> createLeafCertificate(
+ const CertSubject& subject, const std::vector<uint8_t>& rsaPublicKey,
+ const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
+ const std::string& issuerCertPath, const std::string& path) {
+ auto rsa_pkey = rsaPublicKeyToRsaPkey(rsaPublicKey);
+ if (!rsa_pkey.ok()) {
+ return rsa_pkey.error();
}
- add_ext(x509.get(), NID_basic_constraints, kBasicConstraints);
- add_ext(x509.get(), NID_key_usage, kKeyUsage);
- add_ext(x509.get(), NID_subject_key_identifier, kSubjectKeyIdentifier);
- add_ext(x509.get(), NID_authority_key_identifier, "keyid:always");
-
- X509_ALGOR_set0(x509->cert_info->signature, OBJ_nid2obj(NID_sha256WithRSAEncryption),
- V_ASN1_NULL, NULL);
- X509_ALGOR_set0(x509->sig_alg, OBJ_nid2obj(NID_sha256WithRSAEncryption), V_ASN1_NULL, NULL);
-
- // Get the data to be signed
- char* to_be_signed_buf(nullptr);
- size_t to_be_signed_length = i2d_re_X509_tbs(x509.get(), (unsigned char**)&to_be_signed_buf);
-
- auto signed_data = signFunction(std::string(to_be_signed_buf, to_be_signed_length));
- if (!signed_data.ok()) {
- return signed_data.error();
- }
-
- // This is the only part that doesn't use boringssl default functions - we manually copy in the
- // signature that was provided to us.
- x509->signature->data = (unsigned char*)OPENSSL_malloc(signed_data->size());
- memcpy(x509->signature->data, signed_data->c_str(), signed_data->size());
- x509->signature->length = signed_data->size();
-
- x509->signature->flags &= ~(ASN1_STRING_FLAG_BITS_LEFT | 0x07);
- x509->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT;
- auto f = fopen(path.c_str(), "wbe");
- if (f == nullptr) {
- return Error() << "Failed to open " << path;
- }
- i2d_X509_fp(f, x509.get());
- fclose(f);
-
- EVP_PKEY_free(public_key);
- return {};
+ return createCertificate(subject, rsa_pkey.value().get(), signFunction, issuerCertPath, path);
}
Result<std::vector<uint8_t>> extractPublicKey(EVP_PKEY* pkey) {
@@ -163,15 +309,14 @@
return Error() << "Failed to extract public key from x509 cert";
}
- if (EVP_PKEY_type(pkey->type) != EVP_PKEY_RSA) {
+ if (EVP_PKEY_id(pkey) != EVP_PKEY_RSA) {
return Error() << "The public key is not an RSA key";
}
- RSA* rsa = EVP_PKEY_get1_RSA(pkey);
- auto num_bytes = BN_num_bytes(rsa->n);
+ RSA* rsa = EVP_PKEY_get0_RSA(pkey);
+ auto num_bytes = BN_num_bytes(RSA_get0_n(rsa));
std::vector<uint8_t> pubKey(num_bytes);
- int res = BN_bn2bin(rsa->n, pubKey.data());
- RSA_free(rsa);
+ int res = BN_bn2bin(RSA_get0_n(rsa), pubKey.data());
if (!res) {
return Error() << "Failed to convert public key to bytes";
@@ -183,14 +328,14 @@
Result<std::vector<uint8_t>>
extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& keyData) {
auto keyDataBytes = keyData.data();
- EVP_PKEY* public_key = d2i_PUBKEY(nullptr, &keyDataBytes, keyData.size());
+ bssl::UniquePtr<EVP_PKEY> public_key(d2i_PUBKEY(nullptr, &keyDataBytes, keyData.size()));
- return extractPublicKey(public_key);
+ return extractPublicKey(public_key.get());
}
-Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& keyData) {
- auto keyDataBytes = keyData.data();
- bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &keyDataBytes, keyData.size()));
+Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::vector<uint8_t>& derCert) {
+ auto derCertBytes = derCert.data();
+ bssl::UniquePtr<X509> decoded_cert(d2i_X509(nullptr, &derCertBytes, derCert.size()));
if (decoded_cert.get() == nullptr) {
return Error() << "Failed to decode X509 certificate.";
}
@@ -200,21 +345,75 @@
}
Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path) {
- X509* cert;
- auto f = fopen(path.c_str(), "re");
- if (f == nullptr) {
- return Error() << "Failed to open " << path;
+ auto cert = loadX509(path);
+ if (!cert.ok()) {
+ return cert.error();
}
- if (!d2i_X509_fp(f, &cert)) {
- fclose(f);
- return Error() << "Unable to decode x509 cert at " << path;
- }
-
- fclose(f);
- return extractPublicKey(X509_get_pubkey(cert));
+ return extractPublicKey(X509_get_pubkey(cert.value().get()));
}
-Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest) {
+static Result<std::vector<uint8_t>> extractRsaPublicKey(EVP_PKEY* pkey) {
+ RSA* rsa = EVP_PKEY_get0_RSA(pkey);
+ if (rsa == nullptr) {
+ return Error() << "The public key is not an RSA key";
+ }
+
+ uint8_t* out = nullptr;
+ int size = i2d_RSAPublicKey(rsa, &out);
+ if (size < 0 || !out) {
+ return Error() << "Failed to convert to RSAPublicKey";
+ }
+
+ bssl::UniquePtr<uint8_t> buffer(out);
+ std::vector<uint8_t> result(out, out + size);
+ return result;
+}
+
+Result<CertInfo> verifyAndExtractCertInfoFromX509(const std::string& path,
+ const std::vector<uint8_t>& publicKey) {
+ auto public_key = modulusToRsaPkey(publicKey);
+ if (!public_key.ok()) {
+ return public_key.error();
+ }
+
+ auto cert = loadX509(path);
+ if (!cert.ok()) {
+ return cert.error();
+ }
+ X509* x509 = cert.value().get();
+
+ // Make sure we signed it.
+ if (X509_verify(x509, public_key.value().get()) != 1) {
+ return Error() << "Failed to verify certificate.";
+ }
+
+ bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509));
+ auto subject_key = extractRsaPublicKey(pkey.get());
+ if (!subject_key.ok()) {
+ return subject_key.error();
+ }
+
+ // The pointers here are all owned by x509, and each function handles an
+ // error return from the previous call correctly.
+ X509_NAME* name = X509_get_subject_name(x509);
+ int index = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+ X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, index);
+ ASN1_STRING* asn1cn = X509_NAME_ENTRY_get_data(entry);
+ unsigned char* utf8cn;
+ int length = ASN1_STRING_to_UTF8(&utf8cn, asn1cn);
+ if (length < 0) {
+ return Error() << "Failed to read subject CN";
+ }
+
+ bssl::UniquePtr<unsigned char> utf8owner(utf8cn);
+ std::string cn(reinterpret_cast<char*>(utf8cn), static_cast<size_t>(length));
+
+ CertInfo cert_info{std::move(cn), std::move(subject_key.value())};
+ return cert_info;
+}
+
+Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signed_digest,
+ const CertSubject& signer) {
CBB out, outer_seq, wrapped_seq, seq, digest_algos_set, digest_algo, null;
CBB content_info, issuer_and_serial, signer_infos, signer_info, sign_algo, signature;
uint8_t *pkcs7_data, *name_der;
@@ -222,19 +421,20 @@
BIGNUM* serial = BN_new();
int sig_nid = NID_rsaEncryption;
- X509_NAME* name = X509_NAME_new();
- if (!name) {
- return Error() << "Unable to get x509 subject name";
+ X509_NAME* issuer_name = X509_NAME_new();
+ if (!issuer_name) {
+ return Error() << "Unable to create x509 subject name";
}
- X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("US"), -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("Android"), -1, -1, 0);
- X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
- reinterpret_cast<const unsigned char*>("ODS"), -1, -1, 0);
+ X509_NAME_add_entry_by_txt(issuer_name, "C", MBSTRING_ASC,
+ reinterpret_cast<const unsigned char*>(kIssuerCountry), -1, -1, 0);
+ X509_NAME_add_entry_by_txt(issuer_name, "O", MBSTRING_ASC,
+ reinterpret_cast<const unsigned char*>(kIssuerOrg), -1, -1, 0);
+ X509_NAME_add_entry_by_txt(issuer_name, "CN", MBSTRING_ASC,
+ reinterpret_cast<const unsigned char*>(kRootSubject.commonName), -1,
+ -1, 0);
- BN_set_word(serial, 1);
- name_der_len = i2d_X509_NAME(name, &name_der);
+ BN_set_word(serial, signer.serialNumber);
+ name_der_len = i2d_X509_NAME(issuer_name, &name_der);
CBB_init(&out, 1024);
if (!CBB_add_asn1(&out, &outer_seq, CBS_ASN1_SEQUENCE) ||
diff --git a/ondevice-signing/CertUtils.h b/ondevice-signing/CertUtils.h
deleted file mode 100644
index 66dff04..0000000
--- a/ondevice-signing/CertUtils.h
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#pragma once
-
-#include <android-base/result.h>
-
-android::base::Result<void> createSelfSignedCertificate(
- const std::vector<uint8_t>& publicKey,
- const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
- const std::string& path);
-android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData);
-
-android::base::Result<std::vector<uint8_t>>
-extractPublicKeyFromX509(const std::vector<uint8_t>& x509);
-android::base::Result<std::vector<uint8_t>>
-extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& subjectKeyInfo);
-android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path);
-
-android::base::Result<void> verifySignature(const std::string& message,
- const std::string& signature,
- const std::vector<uint8_t>& publicKey);
diff --git a/ondevice-signing/KeystoreHmacKey.cpp b/ondevice-signing/KeystoreHmacKey.cpp
index a2208ce..09677d7 100644
--- a/ondevice-signing/KeystoreHmacKey.cpp
+++ b/ondevice-signing/KeystoreHmacKey.cpp
@@ -112,7 +112,7 @@
KeyMetadata metadata;
auto status = mSecurityLevel->generateKey(mDescriptor, {}, params, 0, {}, &metadata);
if (!status.isOk()) {
- return Error() << "Failed to create new HMAC key";
+ return Error() << "Failed to create new HMAC key: " << status;
}
return {};
@@ -209,8 +209,7 @@
auto status = mSecurityLevel->createOperation(mDescriptor, params, false, &opResponse);
if (!status.isOk()) {
- return Error() << "Failed to create keystore signing operation: "
- << status.serviceSpecificErrorCode();
+ return Error() << "Failed to create keystore signing operation: " << status;
}
auto operation = opResponse.iOperation;
@@ -240,8 +239,7 @@
auto status = mSecurityLevel->createOperation(mDescriptor, params, false, &opResponse);
if (!status.isOk()) {
- return Error() << "Failed to create keystore verification operation: "
- << status.serviceSpecificErrorCode();
+ return Error() << "Failed to create keystore verification operation: " << status;
}
auto operation = opResponse.iOperation;
@@ -260,3 +258,12 @@
return {};
}
+
+Result<void> KeystoreHmacKey::deleteKey() const {
+ auto status = mService->deleteKey(mDescriptor);
+ if (!status.isOk()) {
+ return Error() << "Failed to delete HMAC key: " << status;
+ }
+
+ return {};
+}
diff --git a/ondevice-signing/KeystoreHmacKey.h b/ondevice-signing/KeystoreHmacKey.h
index fbad0fd..782969a 100644
--- a/ondevice-signing/KeystoreHmacKey.h
+++ b/ondevice-signing/KeystoreHmacKey.h
@@ -37,6 +37,7 @@
android::base::Result<std::string> sign(const std::string& message) const;
android::base::Result<void> verify(const std::string& message,
const std::string& signature) const;
+ android::base::Result<void> deleteKey() const;
private:
android::base::Result<void> createKey();
diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp
index 0951d92..03bb6d5 100644
--- a/ondevice-signing/KeystoreKey.cpp
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -119,7 +119,7 @@
KeyMetadata metadata;
auto status = mSecurityLevel->generateKey(mDescriptor, {}, params, 0, {}, &metadata);
if (!status.isOk()) {
- return Error() << "Failed to create new key";
+ return Error() << "Failed to create new key: " << status;
}
// Extract the public key from the certificate, HMAC it and store the signature
@@ -172,11 +172,13 @@
auto key = getOrCreateKey();
if (!key.ok()) {
+ // Delete the HMAC, just in case signing failed, and we could recover by recreating it.
+ mHmacKey.deleteKey();
LOG(ERROR) << key.error().message();
return false;
}
mPublicKey = *key;
- LOG(ERROR) << "Initialized Keystore key.";
+ LOG(INFO) << "Initialized Keystore key.";
return true;
}
@@ -297,19 +299,13 @@
auto status = mSecurityLevel->createOperation(mDescriptor, opParameters, false, &opResponse);
if (!status.isOk()) {
- return Error() << "Failed to create keystore signing operation: "
- << status.serviceSpecificErrorCode();
+ return Error() << "Failed to create keystore signing operation: " << status;
}
auto operation = opResponse.iOperation;
- std::optional<std::vector<uint8_t>> out;
- status = operation->update({message.begin(), message.end()}, &out);
- if (!status.isOk()) {
- return Error() << "Failed to call keystore update operation.";
- }
-
+ std::optional<std::vector<uint8_t>> input{std::in_place, message.begin(), message.end()};
std::optional<std::vector<uint8_t>> signature;
- status = operation->finish({}, {}, &signature);
+ status = operation->finish(input, {}, &signature);
if (!status.isOk()) {
return Error() << "Failed to call keystore finish operation.";
}
diff --git a/ondevice-signing/KeystoreKey.h b/ondevice-signing/KeystoreKey.h
index 1257cbb..f2fbb70 100644
--- a/ondevice-signing/KeystoreKey.h
+++ b/ondevice-signing/KeystoreKey.h
@@ -20,7 +20,6 @@
#include <android-base/macros.h>
#include <android-base/result.h>
-#include <android-base/unique_fd.h>
#include <utils/StrongPointer.h>
diff --git a/ondevice-signing/TEST_MAPPING b/ondevice-signing/TEST_MAPPING
index 03b9b95..4b2c8c6 100644
--- a/ondevice-signing/TEST_MAPPING
+++ b/ondevice-signing/TEST_MAPPING
@@ -1,7 +1,7 @@
{
"presubmit": [
{
- "name": "odsign_e2e_tests"
+ "name": "libsigningutils_test"
}
]
}
diff --git a/ondevice-signing/VerityUtils.cpp b/ondevice-signing/VerityUtils.cpp
index cd38308..b3b7520 100644
--- a/ondevice-signing/VerityUtils.cpp
+++ b/ondevice-signing/VerityUtils.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <charconv>
#include <filesystem>
#include <map>
#include <span>
@@ -27,6 +28,7 @@
#include <android-base/logging.h>
#include <android-base/unique_fd.h>
+#include <asm/byteorder.h>
#include <libfsverity.h>
#include <linux/fsverity.h>
@@ -42,22 +44,7 @@
static const char* kFsVerityInitPath = "/system/bin/fsverity_init";
-#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
-#define cpu_to_le16(v) ((__force __le16)(uint16_t)(v))
-#define le16_to_cpu(v) ((__force uint16_t)(__le16)(v))
-#else
-#define cpu_to_le16(v) ((__force __le16)__builtin_bswap16(v))
-#define le16_to_cpu(v) (__builtin_bswap16((__force uint16_t)(v)))
-#endif
-
-struct fsverity_signed_digest {
- char magic[8]; /* must be "FSVerity" */
- __le16 digest_algorithm;
- __le16 digest_size;
- __u8 digest[];
-};
-
-static std::string toHex(std::span<uint8_t> data) {
+static std::string toHex(std::span<const uint8_t> data) {
std::stringstream ss;
for (auto it = data.begin(); it != data.end(); ++it) {
ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
@@ -65,22 +52,34 @@
return ss.str();
}
+static std::vector<uint8_t> fromHex(std::string_view hex) {
+ if (hex.size() % 2 != 0) {
+ return {};
+ }
+ std::vector<uint8_t> result;
+ result.reserve(hex.size() / 2);
+ for (size_t i = 0; i < hex.size(); i += 2) {
+ uint8_t byte;
+ auto conversion_result = std::from_chars(&hex[i], &hex[i + 2], byte, 16);
+ if (conversion_result.ptr != &hex[i + 2] || conversion_result.ec != std::errc()) {
+ return {};
+ }
+ result.push_back(byte);
+ }
+ return result;
+}
+
static int read_callback(void* file, void* buf, size_t count) {
int* fd = (int*)file;
if (TEMP_FAILURE_RETRY(read(*fd, buf, count)) < 0) return errno ? -errno : -EIO;
return 0;
}
-Result<std::vector<uint8_t>> createDigest(const std::string& path) {
+static Result<std::vector<uint8_t>> createDigest(int fd) {
struct stat filestat;
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (fd < 0) {
- return ErrnoError() << "Failed to open " << path;
- }
-
- int ret = stat(path.c_str(), &filestat);
+ int ret = fstat(fd, &filestat);
if (ret < 0) {
- return ErrnoError() << "Failed to stat " << path;
+ return ErrnoError() << "Failed to fstat";
}
struct libfsverity_merkle_tree_params params = {
.version = 1,
@@ -92,7 +91,7 @@
struct libfsverity_digest* digest;
ret = libfsverity_compute_digest(&fd, &read_callback, ¶ms, &digest);
if (ret < 0) {
- return ErrnoError() << "Failed to compute fs-verity digest for " << path;
+ return ErrnoError() << "Failed to compute fs-verity digest";
}
int expected_digest_size = libfsverity_get_digest_size(FS_VERITY_HASH_ALG_SHA256);
if (digest->digest_size != expected_digest_size) {
@@ -104,6 +103,14 @@
return digestVector;
}
+Result<std::vector<uint8_t>> createDigest(const std::string& path) {
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Unable to open";
+ }
+ return createDigest(fd.get());
+}
+
namespace {
template <typename T> struct DeleteAsPODArray {
void operator()(T* x) {
@@ -126,11 +133,11 @@
static Result<std::vector<uint8_t>> signDigest(const SigningKey& key,
const std::vector<uint8_t>& digest) {
- auto d = makeUniqueWithTrailingData<fsverity_signed_digest>(digest.size());
+ auto d = makeUniqueWithTrailingData<fsverity_formatted_digest>(digest.size());
memcpy(d->magic, "FSVerity", 8);
- d->digest_algorithm = cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
- d->digest_size = cpu_to_le16(digest.size());
+ d->digest_algorithm = __cpu_to_le16(FS_VERITY_HASH_ALG_SHA256);
+ d->digest_size = __cpu_to_le16(digest.size());
memcpy(d->digest, digest.data(), digest.size());
auto signed_digest = key.sign(std::string((char*)d.get(), sizeof(*d) + digest.size()));
@@ -141,10 +148,27 @@
return std::vector<uint8_t>(signed_digest->begin(), signed_digest->end());
}
-Result<std::string> enableFsVerity(const std::string& path, const SigningKey& key) {
- auto digest = createDigest(path);
+static Result<void> enableFsVerity(int fd, std::span<uint8_t> pkcs7) {
+ struct fsverity_enable_arg arg = {.version = 1};
+
+ arg.sig_ptr = reinterpret_cast<uint64_t>(pkcs7.data());
+ arg.sig_size = pkcs7.size();
+ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+ arg.block_size = 4096;
+
+ int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
+
+ if (ret != 0) {
+ return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY";
+ }
+
+ return {};
+}
+
+static Result<std::string> enableFsVerity(int fd, const SigningKey& key) {
+ auto digest = createDigest(fd);
if (!digest.ok()) {
- return digest.error();
+ return Error() << digest.error();
}
auto signed_digest = signDigest(key, digest.value());
@@ -152,65 +176,64 @@
return signed_digest.error();
}
- auto pkcs7_data = createPkcs7(signed_digest.value());
+ auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject);
+ if (!pkcs7_data.ok()) {
+ return pkcs7_data.error();
+ }
- struct fsverity_enable_arg arg = {.version = 1};
-
- arg.sig_ptr = (uint64_t)pkcs7_data->data();
- arg.sig_size = pkcs7_data->size();
- arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
- arg.block_size = 4096;
-
- unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- int ret = ioctl(fd, FS_IOC_ENABLE_VERITY, &arg);
-
- if (ret != 0) {
- return ErrnoError() << "Failed to call FS_IOC_ENABLE_VERITY on " << path;
+ auto enabled = enableFsVerity(fd, pkcs7_data.value());
+ if (!enabled.ok()) {
+ return Error() << enabled.error();
}
// Return the root hash as a hex string
return toHex(digest.value());
}
-Result<std::string> isFileInVerity(const std::string& path) {
- unsigned int flags;
+static Result<std::string> isFileInVerity(int fd) {
+ auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
+ d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
+ auto ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
+ if (ret < 0) {
+ if (errno == ENODATA) {
+ return Error() << "File is not in fs-verity";
+ } else {
+ return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY";
+ }
+ }
+ return toHex({&d->digest[0], &d->digest[d->digest_size]});
+}
+static Result<std::string> isFileInVerity(const std::string& path) {
unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
- if (fd < 0) {
+ if (!fd.ok()) {
return ErrnoError() << "Failed to open " << path;
}
- int ret = ioctl(fd, FS_IOC_GETFLAGS, &flags);
- if (ret < 0) {
- return ErrnoError() << "Failed to FS_IOC_GETFLAGS for " << path;
- }
- if (!(flags & FS_VERITY_FL)) {
- return Error() << "File is not in fs-verity: " << path;
+ auto digest = isFileInVerity(fd.get());
+ if (!digest.ok()) {
+ return Error() << digest.error() << ": " << path;
}
- auto d = makeUniqueWithTrailingData<fsverity_digest>(FS_VERITY_MAX_DIGEST_SIZE);
- d->digest_size = FS_VERITY_MAX_DIGEST_SIZE;
- ret = ioctl(fd, FS_IOC_MEASURE_VERITY, d.get());
- if (ret < 0) {
- return ErrnoError() << "Failed to FS_IOC_MEASURE_VERITY for " << path;
- }
- return toHex({&d->digest[0], &d->digest[d->digest_size]});
+ return digest;
}
Result<std::map<std::string, std::string>> addFilesToVerityRecursive(const std::string& path,
const SigningKey& key) {
std::map<std::string, std::string> digests;
+
std::error_code ec;
-
auto it = std::filesystem::recursive_directory_iterator(path, ec);
- auto end = std::filesystem::recursive_directory_iterator();
-
- while (!ec && it != end) {
+ for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
if (it->is_regular_file()) {
- auto digest = isFileInVerity(it->path());
+ unique_fd fd(TEMP_FAILURE_RETRY(open(it->path().c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Failed to open " << path;
+ }
+ auto digest = isFileInVerity(fd);
if (!digest.ok()) {
LOG(INFO) << "Adding " << it->path() << " to fs-verity...";
- auto result = enableFsVerity(it->path(), key);
+ auto result = enableFsVerity(fd, key);
if (!result.ok()) {
return result.error();
}
@@ -220,10 +243,9 @@
digests[it->path()] = *digest;
}
}
- ++it;
}
if (ec) {
- return Error() << "Failed to iterate " << path << ": " << ec;
+ return Error() << "Failed to iterate " << path << ": " << ec.message();
}
return digests;
@@ -260,10 +282,88 @@
return digests;
}
-Result<void> addCertToFsVerityKeyring(const std::string& path) {
- const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", "fsv_ods"};
+Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path,
+ std::map<std::string, std::string> digests,
+ const SigningKey& signing_key) {
+ std::error_code ec;
+ auto it = std::filesystem::recursive_directory_iterator(directory_path, ec);
+ size_t verified_count = 0;
+ for (auto end = std::filesystem::recursive_directory_iterator(); it != end; it.increment(ec)) {
+ auto& path = it->path();
+ if (it->is_regular_file()) {
+ auto entry = digests.find(path);
+ if (entry == digests.end()) {
+ return Error() << "Unexpected file found: " << path;
+ }
+ auto& compos_digest = entry->second;
+
+ unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_CLOEXEC)));
+ if (!fd.ok()) {
+ return ErrnoError() << "Can't open " << path;
+ }
+
+ auto verity_digest = isFileInVerity(fd);
+ if (verity_digest.ok()) {
+ // The file is already in fs-verity. We need to make sure it was signed
+ // by CompOS, so we just check that it has the digest we expect.
+ if (verity_digest.value() == compos_digest) {
+ ++verified_count;
+ } else {
+ return Error() << "fs-verity digest does not match CompOS digest: " << path;
+ }
+ } else {
+ // Not in fs-verity yet. We know the digest CompOS provided; If
+ // it's not the correct digest for the file then enabling
+ // fs-verity will fail, so we don't need to check it explicitly
+ // ourselves. Otherwise we should be good.
+ LOG(INFO) << "Adding " << path << " to fs-verity...";
+
+ auto digest_bytes = fromHex(compos_digest);
+ if (digest_bytes.empty()) {
+ return Error() << "Invalid digest " << compos_digest;
+ }
+ auto signed_digest = signDigest(signing_key, digest_bytes);
+ if (!signed_digest.ok()) {
+ return signed_digest.error();
+ }
+
+ auto pkcs7_data = createPkcs7(signed_digest.value(), kRootSubject);
+ if (!pkcs7_data.ok()) {
+ return pkcs7_data.error();
+ }
+
+ auto enabled = enableFsVerity(fd, pkcs7_data.value());
+ if (!enabled.ok()) {
+ return Error() << enabled.error();
+ }
+ ++verified_count;
+ }
+ } else if (it->is_directory()) {
+ // These are fine to ignore
+ } else if (it->is_symlink()) {
+ return Error() << "Rejecting artifacts, symlink at " << path;
+ } else {
+ return Error() << "Rejecting artifacts, unexpected file type for " << path;
+ }
+ }
+ if (ec) {
+ return Error() << "Failed to iterate " << directory_path << ": " << ec.message();
+ }
+ // Make sure all the files we expected have been seen
+ if (verified_count != digests.size()) {
+ return Error() << "Verified " << verified_count << "files, but expected " << digests.size();
+ }
+
+ return {};
+}
+
+Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName) {
+ const char* const argv[] = {kFsVerityInitPath, "--load-extra-key", keyName};
int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fd == -1) {
+ return ErrnoError() << "Failed to open " << path;
+ }
pid_t pid = fork();
if (pid == 0) {
dup2(fd, STDIN_FILENO);
@@ -272,7 +372,7 @@
char* argv_child[argc + 1];
memcpy(argv_child, argv, argc * sizeof(char*));
argv_child[argc] = nullptr;
- execvp(argv_child[0], const_cast<char**>(argv_child));
+ execvp(argv_child[0], argv_child);
PLOG(ERROR) << "exec in ForkExecvp";
_exit(EXIT_FAILURE);
} else {
@@ -288,10 +388,8 @@
if (!WIFEXITED(status)) {
return Error() << kFsVerityInitPath << ": abnormal process exit";
}
- if (WEXITSTATUS(status)) {
- if (status != 0) {
- return Error() << kFsVerityInitPath << " exited with " << status;
- }
+ if (WEXITSTATUS(status) != 0) {
+ return Error() << kFsVerityInitPath << " exited with " << WEXITSTATUS(status);
}
return {};
diff --git a/ondevice-signing/include/CertUtils.h b/ondevice-signing/include/CertUtils.h
new file mode 100644
index 0000000..fe703fa
--- /dev/null
+++ b/ondevice-signing/include/CertUtils.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+#include <functional>
+#include <string>
+#include <vector>
+
+#include <android-base/result.h>
+
+// Information extracted from a certificate.
+struct CertInfo {
+ std::string subjectCn;
+ std::vector<uint8_t> subjectRsaPublicKey;
+};
+
+// Subjects of certificates we issue.
+struct CertSubject {
+ const char* commonName;
+ unsigned serialNumber;
+};
+
+// These are all the certificates we ever sign (the first one being our
+// self-signed cert). We shouldn't really re-use serial numbers for different
+// certificates for the same subject but we do; only one should be in use at a
+// time though.
+inline const CertSubject kRootSubject{"ODS", 1};
+inline const CertSubject kCompOsSubject{"CompOs", 2};
+
+android::base::Result<void> createSelfSignedCertificate(
+ const std::vector<uint8_t>& publicKey,
+ const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
+ const std::string& path);
+
+android::base::Result<void> createLeafCertificate(
+ const CertSubject& subject, const std::vector<uint8_t>& publicKey,
+ const std::function<android::base::Result<std::string>(const std::string&)>& signFunction,
+ const std::string& issuerCertPath, const std::string& outPath);
+
+android::base::Result<std::vector<uint8_t>> createPkcs7(const std::vector<uint8_t>& signedData,
+ const CertSubject& signer);
+
+android::base::Result<std::vector<uint8_t>>
+extractPublicKeyFromX509(const std::vector<uint8_t>& x509);
+android::base::Result<std::vector<uint8_t>>
+extractPublicKeyFromSubjectPublicKeyInfo(const std::vector<uint8_t>& subjectKeyInfo);
+android::base::Result<std::vector<uint8_t>> extractPublicKeyFromX509(const std::string& path);
+
+android::base::Result<CertInfo>
+verifyAndExtractCertInfoFromX509(const std::string& path, const std::vector<uint8_t>& publicKey);
+
+android::base::Result<void> verifySignature(const std::string& message,
+ const std::string& signature,
+ const std::vector<uint8_t>& publicKey);
+
+android::base::Result<void> verifyRsaPublicKeySignature(const std::string& message,
+ const std::string& signature,
+ const std::vector<uint8_t>& rsaPublicKey);
diff --git a/ondevice-signing/SigningKey.h b/ondevice-signing/include/SigningKey.h
similarity index 100%
rename from ondevice-signing/SigningKey.h
rename to ondevice-signing/include/SigningKey.h
diff --git a/ondevice-signing/VerityUtils.h b/ondevice-signing/include/VerityUtils.h
similarity index 76%
rename from ondevice-signing/VerityUtils.h
rename to ondevice-signing/include/VerityUtils.h
index a834e61..7715628 100644
--- a/ondevice-signing/VerityUtils.h
+++ b/ondevice-signing/include/VerityUtils.h
@@ -18,9 +18,13 @@
#include <android-base/result.h>
+#include <map>
+#include <string>
+#include <vector>
+
#include "SigningKey.h"
-android::base::Result<void> addCertToFsVerityKeyring(const std::string& path);
+android::base::Result<void> addCertToFsVerityKeyring(const std::string& path, const char* keyName);
android::base::Result<std::vector<uint8_t>> createDigest(const std::string& path);
android::base::Result<std::map<std::string, std::string>>
verifyAllFilesInVerity(const std::string& path);
@@ -29,3 +33,7 @@
// for those files it will return the existing digest.
android::base::Result<std::map<std::string, std::string>>
addFilesToVerityRecursive(const std::string& path, const SigningKey& key);
+
+android::base::Result<void> verifyAllFilesUsingCompOs(const std::string& directory_path,
+ std::map<std::string, std::string> digests,
+ const SigningKey& signing_key);
diff --git a/ondevice-signing/odsign_main.cpp b/ondevice-signing/odsign_main.cpp
index 425776b..03a9eaa 100644
--- a/ondevice-signing/odsign_main.cpp
+++ b/ondevice-signing/odsign_main.cpp
@@ -39,34 +39,71 @@
using android::base::ErrnoError;
using android::base::Error;
+using android::base::GetProperty;
using android::base::Result;
using android::base::SetProperty;
using OdsignInfo = ::odsign::proto::OdsignInfo;
-const std::string kSigningKeyBlob = "/data/misc/odsign/key.blob";
const std::string kSigningKeyCert = "/data/misc/odsign/key.cert";
const std::string kOdsignInfo = "/data/misc/odsign/odsign.info";
const std::string kOdsignInfoSignature = "/data/misc/odsign/odsign.info.signature";
const std::string kArtArtifactsDir = "/data/misc/apexdata/com.android.art/dalvik-cache";
-static const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh";
+constexpr const char* kOdrefreshPath = "/apex/com.android.art/bin/odrefresh";
+constexpr const char* kCompOsVerifyPath = "/apex/com.android.compos/bin/compos_verify_key";
+constexpr const char* kFsVerityProcPath = "/proc/sys/fs/verity";
+constexpr const char* kKvmDevicePath = "/dev/kvm";
-static const char* kFsVerityProcPath = "/proc/sys/fs/verity";
+constexpr bool kForceCompilation = false;
+constexpr bool kUseCompOs = true;
-static const bool kForceCompilation = false;
+const std::string kCompOsCert = "/data/misc/odsign/compos_key.cert";
-static const char* kOdsignVerificationDoneProp = "odsign.verification.done";
-static const char* kOdsignKeyDoneProp = "odsign.key.done";
+const std::string kCompOsCurrentPublicKey =
+ "/data/misc/apexdata/com.android.compos/current/key.pubkey";
+const std::string kCompOsPendingPublicKey =
+ "/data/misc/apexdata/com.android.compos/pending/key.pubkey";
+const std::string kCompOsPendingArtifactsDir = "/data/misc/apexdata/com.android.art/compos-pending";
+const std::string kCompOsInfo = kArtArtifactsDir + "/compos.info";
+const std::string kCompOsInfoSignature = kCompOsInfo + ".signature";
-static const char* kOdsignVerificationStatusProp = "odsign.verification.success";
-static const char* kOdsignVerificationStatusValid = "1";
-static const char* kOdsignVerificationStatusError = "0";
+constexpr const char* kCompOsPendingInfoPath =
+ "/data/misc/apexdata/com.android.art/compos-pending/compos.info";
+constexpr const char* kCompOsPendingInfoSignaturePath =
+ "/data/misc/apexdata/com.android.art/compos-pending/compos.info.signature";
-static const char* kStopServiceProp = "ctl.stop";
+constexpr const char* kOdsignVerificationDoneProp = "odsign.verification.done";
+constexpr const char* kOdsignKeyDoneProp = "odsign.key.done";
-static int removeDirectory(const std::string& directory) {
+constexpr const char* kOdsignVerificationStatusProp = "odsign.verification.success";
+constexpr const char* kOdsignVerificationStatusValid = "1";
+constexpr const char* kOdsignVerificationStatusError = "0";
+
+constexpr const char* kStopServiceProp = "ctl.stop";
+
+enum class CompOsInstance { kCurrent, kPending };
+
+namespace {
+
+std::vector<uint8_t> readBytesFromFile(const std::string& path) {
+ std::string str;
+ android::base::ReadFileToString(path, &str);
+ return std::vector<uint8_t>(str.begin(), str.end());
+}
+
+bool rename(const std::string& from, const std::string& to) {
+ std::error_code ec;
+ std::filesystem::rename(from, to, ec);
+ if (ec) {
+ LOG(ERROR) << "Can't rename " << from << " to " << to << ": " << ec.message();
+ return false;
+ }
+ return true;
+}
+
+int removeDirectory(const std::string& directory) {
std::error_code ec;
auto num_removed = std::filesystem::remove_all(directory, ec);
if (ec) {
@@ -80,13 +117,50 @@
}
}
-Result<void> verifyExistingCert(const SigningKey& key) {
+bool directoryHasContent(const std::string& directory) {
+ std::error_code ec;
+ return std::filesystem::is_directory(directory, ec) &&
+ !std::filesystem::is_empty(directory, ec);
+}
+
+art::odrefresh::ExitCode compileArtifacts(bool force) {
+ const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
+ const int exit_code =
+ logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+ return static_cast<art::odrefresh::ExitCode>(exit_code);
+}
+
+art::odrefresh::ExitCode checkArtifacts() {
+ const char* const argv[] = {kOdrefreshPath, "--check"};
+ const int exit_code =
+ logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+ return static_cast<art::odrefresh::ExitCode>(exit_code);
+}
+
+std::string toHex(const std::vector<uint8_t>& digest) {
+ std::stringstream ss;
+ for (auto it = digest.begin(); it != digest.end(); ++it) {
+ ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
+ }
+ return ss.str();
+}
+
+bool compOsPresent() {
+ return access(kCompOsVerifyPath, X_OK) == 0 && access(kKvmDevicePath, F_OK) == 0;
+}
+
+bool isDebugBuild() {
+ std::string build_type = GetProperty("ro.build.type", "");
+ return build_type == "userdebug" || build_type == "eng";
+}
+
+Result<void> verifyExistingRootCert(const SigningKey& key) {
if (access(kSigningKeyCert.c_str(), F_OK) < 0) {
return ErrnoError() << "Key certificate not found: " << kSigningKeyCert;
}
auto trustedPublicKey = key.getPublicKey();
if (!trustedPublicKey.ok()) {
- return Error() << "Failed to retrieve signing public key.";
+ return Error() << "Failed to retrieve signing public key: " << trustedPublicKey.error();
}
auto publicKeyFromExistingCert = extractPublicKeyFromX509(kSigningKeyCert);
@@ -98,11 +172,12 @@
<< " does not match signing public key.";
}
- // At this point, we know the cert matches
+ // At this point, we know the cert is for our key; it's unimportant whether it's
+ // actually self-signed.
return {};
}
-Result<void> createX509Cert(const SigningKey& key, const std::string& outPath) {
+Result<void> createX509RootCert(const SigningKey& key, const std::string& outPath) {
auto publicKey = key.getPublicKey();
if (!publicKey.ok()) {
@@ -110,30 +185,109 @@
}
auto keySignFunction = [&](const std::string& to_be_signed) { return key.sign(to_be_signed); };
- createSelfSignedCertificate(*publicKey, keySignFunction, outPath);
- return {};
+ return createSelfSignedCertificate(*publicKey, keySignFunction, outPath);
}
-art::odrefresh::ExitCode checkArtifacts() {
- const char* const argv[] = {kOdrefreshPath, "--check"};
- const int exit_code =
- logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
- return static_cast<art::odrefresh::ExitCode>(exit_code);
-}
-
-art::odrefresh::ExitCode compileArtifacts(bool force) {
- const char* const argv[] = {kOdrefreshPath, force ? "--force-compile" : "--compile"};
- const int exit_code =
- logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
- return static_cast<art::odrefresh::ExitCode>(exit_code);
-}
-
-static std::string toHex(const std::vector<uint8_t>& digest) {
- std::stringstream ss;
- for (auto it = digest.begin(); it != digest.end(); ++it) {
- ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned>(*it);
+Result<std::vector<uint8_t>> extractRsaPublicKeyFromLeafCert(const SigningKey& key,
+ const std::string& certPath,
+ const std::string& expectedCn) {
+ if (access(certPath.c_str(), F_OK) < 0) {
+ return ErrnoError() << "Certificate not found: " << certPath;
}
- return ss.str();
+ auto trustedPublicKey = key.getPublicKey();
+ if (!trustedPublicKey.ok()) {
+ return Error() << "Failed to retrieve signing public key: " << trustedPublicKey.error();
+ }
+
+ auto existingCertInfo = verifyAndExtractCertInfoFromX509(certPath, trustedPublicKey.value());
+ if (!existingCertInfo.ok()) {
+ return Error() << "Failed to verify certificate at " << certPath << ": "
+ << existingCertInfo.error();
+ }
+
+ auto& actualCn = existingCertInfo.value().subjectCn;
+ if (actualCn != expectedCn) {
+ return Error() << "CN of existing certificate at " << certPath << " is " << actualCn
+ << ", should be " << expectedCn;
+ }
+
+ return existingCertInfo.value().subjectRsaPublicKey;
+}
+
+// Attempt to start a CompOS VM for the specified instance to get it to
+// verify ita public key & key blob.
+bool startCompOsAndVerifyKey(CompOsInstance instance) {
+ bool isCurrent = instance == CompOsInstance::kCurrent;
+ const std::string& keyPath = isCurrent ? kCompOsCurrentPublicKey : kCompOsPendingPublicKey;
+ if (access(keyPath.c_str(), R_OK) != 0) {
+ return false;
+ }
+
+ const char* const argv[] = {kCompOsVerifyPath, "--instance", isCurrent ? "current" : "pending"};
+ int result =
+ logwrap_fork_execvp(arraysize(argv), argv, nullptr, false, LOG_ALOG, false, nullptr);
+ if (result == 0) {
+ return true;
+ }
+
+ LOG(ERROR) << kCompOsVerifyPath << " returned " << result;
+ return false;
+}
+
+Result<std::vector<uint8_t>> verifyCompOsKey(const SigningKey& signingKey) {
+ bool verified = false;
+
+ // If a pending key has been generated we don't know if it is the correct
+ // one for the pending CompOS VM, so we need to start it and ask it.
+ if (startCompOsAndVerifyKey(CompOsInstance::kPending)) {
+ verified = true;
+ }
+
+ if (!verified) {
+ // Alternatively if we signed a cert for the key on a previous boot, then we
+ // can use that straight away.
+ auto existing_key =
+ extractRsaPublicKeyFromLeafCert(signingKey, kCompOsCert, kCompOsSubject.commonName);
+ if (existing_key.ok()) {
+ LOG(INFO) << "Found and verified existing CompOS public key certificate: "
+ << kCompOsCert;
+ return existing_key.value();
+ }
+ }
+
+ // Otherwise, if there is an existing key that we haven't signed yet, then we can sign
+ // it now if CompOS confirms it's OK.
+ if (!verified && startCompOsAndVerifyKey(CompOsInstance::kCurrent)) {
+ verified = true;
+ }
+
+ if (!verified) {
+ return Error() << "No valid CompOS key present.";
+ }
+
+ // If the pending key was verified it will have been promoted to current, so
+ // at this stage if there is a key it will be the current one.
+ auto publicKey = readBytesFromFile(kCompOsCurrentPublicKey);
+ if (publicKey.empty()) {
+ // This shouldn`t really happen.
+ return Error() << "Failed to read CompOS key.";
+ }
+
+ // One way or another we now have a valid public key. Persist a certificate so
+ // we can simplify the checks on subsequent boots.
+
+ auto signFunction = [&](const std::string& to_be_signed) {
+ return signingKey.sign(to_be_signed);
+ };
+ auto certStatus = createLeafCertificate(kCompOsSubject, publicKey, signFunction,
+ kSigningKeyCert, kCompOsCert);
+ if (!certStatus.ok()) {
+ return Error() << "Failed to create CompOS cert: " << certStatus.error();
+ }
+
+ LOG(INFO) << "Verified key, wrote new CompOS cert";
+
+ return publicKey;
}
Result<std::map<std::string, std::string>> computeDigests(const std::string& path) {
@@ -147,7 +301,8 @@
if (it->is_regular_file()) {
auto digest = createDigest(it->path());
if (!digest.ok()) {
- return Error() << "Failed to compute digest for " << it->path();
+ return Error() << "Failed to compute digest for " << it->path() << ": "
+ << digest.error();
}
digests[it->path()] = toHex(*digest);
}
@@ -281,22 +436,8 @@
return {};
}
-static int removeArtifacts() {
- std::error_code ec;
- auto num_removed = std::filesystem::remove_all(kArtArtifactsDir, ec);
- if (ec) {
- LOG(ERROR) << "Can't remove " << kArtArtifactsDir << ": " << ec.message();
- return 0;
- } else {
- if (num_removed > 0) {
- LOG(INFO) << "Removed " << num_removed << " entries from " << kArtArtifactsDir;
- }
- return num_removed;
- }
-}
-
-static Result<void> verifyArtifacts(const std::map<std::string, std::string>& trusted_digests,
- bool supportsFsVerity) {
+Result<void> verifyArtifactsIntegrity(const std::map<std::string, std::string>& trusted_digests,
+ bool supportsFsVerity) {
Result<void> integrityStatus;
if (supportsFsVerity) {
@@ -305,16 +446,145 @@
integrityStatus = verifyIntegrityNoFsVerity(trusted_digests);
}
if (!integrityStatus.ok()) {
- return Error() << integrityStatus.error().message();
+ return integrityStatus.error();
}
return {};
}
-int main(int /* argc */, char** /* argv */) {
+Result<std::vector<uint8_t>> addCompOsCertToFsVerityKeyring(const SigningKey& signingKey) {
+ auto publicKey = verifyCompOsKey(signingKey);
+ if (!publicKey.ok()) {
+ return publicKey.error();
+ }
+
+ auto cert_add_result = addCertToFsVerityKeyring(kCompOsCert, "fsv_compos");
+ if (!cert_add_result.ok()) {
+ // Best efforts only - nothing we can do if deletion fails.
+ unlink(kCompOsCert.c_str());
+ return Error() << "Failed to add CompOS certificate to fs-verity keyring: "
+ << cert_add_result.error();
+ }
+
+ return publicKey;
+}
+
+Result<OdsignInfo> getComposInfo(const std::vector<uint8_t>& compos_key) {
+ std::string compos_signature;
+ if (!android::base::ReadFileToString(kCompOsInfoSignature, &compos_signature)) {
+ return ErrnoError() << "Failed to read " << kCompOsInfoSignature;
+ }
+
+ std::string compos_info_str;
+ if (!android::base::ReadFileToString(kCompOsInfo, &compos_info_str)) {
+ return ErrnoError() << "Failed to read " << kCompOsInfo;
+ }
+
+ // Delete the files - if they're valid we don't need them any more, and
+ // they'd confuse artifact verification; if they're not we never need to
+ // look at them again.
+ if (unlink(kCompOsInfo.c_str()) != 0 || unlink(kCompOsInfoSignature.c_str()) != 0) {
+ return ErrnoError() << "Unable to delete CompOS info/signature file";
+ }
+
+ // Verify the signature
+ auto verified = verifyRsaPublicKeySignature(compos_info_str, compos_signature, compos_key);
+ if (!verified.ok()) {
+ return Error() << kCompOsInfoSignature << " does not match.";
+ } else {
+ LOG(INFO) << kCompOsInfoSignature << " matches.";
+ }
+
+ OdsignInfo compos_info;
+ if (!compos_info.ParseFromString(compos_info_str)) {
+ return Error() << "Failed to parse " << kCompOsInfo;
+ }
+
+ LOG(INFO) << "Loaded " << kCompOsInfo;
+ return compos_info;
+}
+
+art::odrefresh::ExitCode checkCompOsPendingArtifacts(const std::vector<uint8_t>& compos_key,
+ const SigningKey& signing_key,
+ bool* digests_verified) {
+ if (!directoryHasContent(kCompOsPendingArtifactsDir)) {
+ return art::odrefresh::ExitCode::kCompilationRequired;
+ }
+
+ // CompOS has generated some artifacts that may, or may not, match the
+ // current state. But if there are already valid artifacts present the
+ // CompOS ones are redundant.
+ art::odrefresh::ExitCode odrefresh_status = checkArtifacts();
+ if (odrefresh_status != art::odrefresh::ExitCode::kCompilationRequired) {
+ if (odrefresh_status == art::odrefresh::ExitCode::kOkay) {
+ LOG(INFO) << "Current artifacts are OK, deleting pending artifacts";
+ removeDirectory(kCompOsPendingArtifactsDir);
+ }
+ return odrefresh_status;
+ }
+
+ // No useful current artifacts, lets see if the CompOS ones are ok
+ if (access(kCompOsPendingInfoPath, R_OK) != 0 ||
+ access(kCompOsPendingInfoSignaturePath, R_OK) != 0) {
+ LOG(INFO) << "Missing CompOS info/signature, deleting pending artifacts";
+ removeDirectory(kCompOsPendingArtifactsDir);
+ return art::odrefresh::ExitCode::kCompilationRequired;
+ }
+
+ LOG(INFO) << "Current artifacts are out of date, switching to pending artifacts";
+ removeDirectory(kArtArtifactsDir);
+ if (!rename(kCompOsPendingArtifactsDir, kArtArtifactsDir)) {
+ removeDirectory(kCompOsPendingArtifactsDir);
+ return art::odrefresh::ExitCode::kCompilationRequired;
+ }
+
+ odrefresh_status = checkArtifacts();
+ if (odrefresh_status != art::odrefresh::ExitCode::kOkay) {
+ LOG(WARNING) << "Pending artifacts are not OK";
+ return odrefresh_status;
+ }
+
+ // The artifacts appear to be up to date - but we haven't
+ // verified that they are genuine yet.
+
+ auto compos_info = getComposInfo(compos_key);
+ if (!compos_info.ok()) {
+ LOG(WARNING) << compos_info.error();
+ } else {
+ std::map<std::string, std::string> compos_digests(compos_info->file_hashes().begin(),
+ compos_info->file_hashes().end());
+
+ auto status = verifyAllFilesUsingCompOs(kArtArtifactsDir, compos_digests, signing_key);
+ if (!status.ok()) {
+ LOG(WARNING) << status.error();
+ } else {
+ auto persisted = persistDigests(compos_digests, signing_key);
+
+ if (!persisted.ok()) {
+ LOG(WARNING) << persisted.error();
+ } else {
+ LOG(INFO) << "Pending artifacts successfully verified.";
+ *digests_verified = true;
+ return art::odrefresh::ExitCode::kOkay;
+ }
+ }
+ }
+
+ // We can't use the existing artifacts, so we will need to generate new
+ // ones.
+ removeDirectory(kArtArtifactsDir);
+ return art::odrefresh::ExitCode::kCompilationRequired;
+}
+} // namespace
+
+int main(int /* argc */, char** argv) {
+ android::base::InitLogging(argv, android::base::LogdLogger(android::base::SYSTEM));
+
auto errorScopeGuard = []() {
- // In case we hit any error, remove the artifacts and tell Zygote not to use anything
- removeArtifacts();
+ // In case we hit any error, remove the artifacts and tell Zygote not to use
+ // anything
+ removeDirectory(kArtArtifactsDir);
+ removeDirectory(kCompOsPendingArtifactsDir);
// Tell init we don't need to use our key anymore
SetProperty(kOdsignKeyDoneProp, "1");
// Tell init we're done with verification, and that it was an error
@@ -332,7 +602,7 @@
auto keystoreResult = KeystoreKey::getInstance();
if (!keystoreResult.ok()) {
- LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error().message();
+ LOG(ERROR) << "Could not create keystore key: " << keystoreResult.error();
return -1;
}
SigningKey* key = keystoreResult.value();
@@ -342,30 +612,46 @@
LOG(INFO) << "Device doesn't support fsverity. Falling back to full verification.";
}
+ bool useCompOs = kUseCompOs && supportsFsVerity && compOsPresent() && isDebugBuild();
+
if (supportsFsVerity) {
- auto existing_cert = verifyExistingCert(*key);
+ auto existing_cert = verifyExistingRootCert(*key);
if (!existing_cert.ok()) {
- LOG(WARNING) << existing_cert.error().message();
+ LOG(WARNING) << existing_cert.error();
// Try to create a new cert
- auto new_cert = createX509Cert(*key, kSigningKeyCert);
+ auto new_cert = createX509RootCert(*key, kSigningKeyCert);
if (!new_cert.ok()) {
- LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error().message();
+ LOG(ERROR) << "Failed to create X509 certificate: " << new_cert.error();
// TODO apparently the key become invalid - delete the blob / cert
return -1;
}
} else {
LOG(INFO) << "Found and verified existing public key certificate: " << kSigningKeyCert;
}
- auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert);
+ auto cert_add_result = addCertToFsVerityKeyring(kSigningKeyCert, "fsv_ods");
if (!cert_add_result.ok()) {
LOG(ERROR) << "Failed to add certificate to fs-verity keyring: "
- << cert_add_result.error().message();
+ << cert_add_result.error();
return -1;
}
}
- art::odrefresh::ExitCode odrefresh_status = checkArtifacts();
+ art::odrefresh::ExitCode odrefresh_status = art::odrefresh::ExitCode::kCompilationRequired;
+ bool digests_verified = false;
+
+ if (useCompOs) {
+ auto compos_key = addCompOsCertToFsVerityKeyring(*key);
+ if (!compos_key.ok()) {
+ odrefresh_status = art::odrefresh::ExitCode::kCompilationRequired;
+ LOG(WARNING) << compos_key.error();
+ } else {
+ odrefresh_status =
+ checkCompOsPendingArtifacts(compos_key.value(), *key, &digests_verified);
+ }
+ } else {
+ odrefresh_status = checkArtifacts();
+ }
// The artifacts dir doesn't necessarily need to exist; if the existing
// artifacts on the system partition are valid, those can be used.
@@ -373,8 +659,9 @@
// If we receive any error other than ENOENT, be suspicious
bool artifactsPresent = (err == 0) || (err < 0 && errno != ENOENT);
- if (artifactsPresent && (odrefresh_status == art::odrefresh::ExitCode::kOkay ||
- odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired)) {
+ if (artifactsPresent && !digests_verified &&
+ (odrefresh_status == art::odrefresh::ExitCode::kOkay ||
+ odrefresh_status == art::odrefresh::ExitCode::kCompilationRequired)) {
// If we haven't verified the digests yet, we need to validate them. We
// need to do this both in case the existing artifacts are okay, but
// also if odrefresh said that a recompile is required. In the latter
@@ -395,7 +682,7 @@
SetProperty(kOdsignKeyDoneProp, "1");
}
- auto verificationResult = verifyArtifacts(trusted_digests, supportsFsVerity);
+ auto verificationResult = verifyArtifactsIntegrity(trusted_digests, supportsFsVerity);
if (!verificationResult.ok()) {
int num_removed = removeDirectory(kArtArtifactsDir);
if (num_removed == 0) {
@@ -430,12 +717,12 @@
digests = computeDigests(kArtArtifactsDir);
}
if (!digests.ok()) {
- LOG(ERROR) << digests.error().message();
+ LOG(ERROR) << digests.error();
return -1;
}
auto persistStatus = persistDigests(*digests, *key);
if (!persistStatus.ok()) {
- LOG(ERROR) << persistStatus.error().message();
+ LOG(ERROR) << persistStatus.error();
return -1;
}
} else if (odrefresh_status == art::odrefresh::ExitCode::kCleanupFailed) {
diff --git a/ondevice-signing/proto/Android.bp b/ondevice-signing/proto/Android.bp
index fd48f31..356e661 100644
--- a/ondevice-signing/proto/Android.bp
+++ b/ondevice-signing/proto/Android.bp
@@ -23,7 +23,22 @@
host_supported: true,
proto: {
export_proto_headers: true,
- type: "full",
+ type: "lite",
},
srcs: ["odsign_info.proto"],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.compos",
+ ],
+}
+
+rust_protobuf {
+ name: "libodsign_proto_rust",
+ crate_name: "odsign_proto",
+ protos: ["odsign_info.proto"],
+ source_stem: "odsign_proto",
+ host_supported: true,
+ apex_available: [
+ "com.android.compos",
+ ],
}
diff --git a/ondevice-signing/tests/Android.bp b/ondevice-signing/tests/Android.bp
new file mode 100644
index 0000000..4027220
--- /dev/null
+++ b/ondevice-signing/tests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2021 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "system_security_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["system_security_license"],
+}
+
+cc_test {
+ name: "libsigningutils_test",
+ srcs: ["SigningUtilsTest.cpp"],
+ test_suites: ["device-tests"],
+ compile_multilib: "both",
+ defaults: [
+ "odsign_flags_defaults",
+ ],
+ static_libs: [
+ "libsigningutils",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ ],
+ data: [
+ "test_file",
+ "test_file.sig",
+ "SigningUtils.cert.der",
+ ],
+}
diff --git a/ondevice-signing/tests/SigningUtils.cert.der b/ondevice-signing/tests/SigningUtils.cert.der
new file mode 100644
index 0000000..0703d59
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtils.cert.der
Binary files differ
diff --git a/ondevice-signing/tests/SigningUtils.pem b/ondevice-signing/tests/SigningUtils.pem
new file mode 100644
index 0000000..01dfa5e
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtils.pem
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAwyOUvcTpPuPxw2I9E28uhXT9pbJAgNE0dc1emOWRfapZ5iow
+6PC7am47DfHyyZ/dwtw0vvi3QWCLIpsyFQbspO5XfmJH7mHBTvG8SkV40rFjptf8
+iwMl5i1ZUc5LChxuRh6DIS1fT2tFiXQ8iX3FUNNqjj/tOOtp45JEsgFK7WDW8YU3
+UseUn2BpkccUHMbe78EkEb4F6/AJWISk2INMOMPzRPUwdE3eUPVrufkp6jVJs69I
+c6skRvAAqpiOx4rYpVzs3V5PuzhmCr7jt8hTQ0DA6q501UT06og6gIKPdqLjugKK
+jt88XNXhhofckSbIH7edwa20LhvE4vtM/x7E0u+sCJozr0uyNgRXWs6imsVrC7Hb
+cclecBUFqETVzP6RG49cCXVkuOkSNBm3HLt4291+O821gn3yygTE9bpf9uuDHSb1
+l13MOMbVeT3ipaRGMwD1bp7kBW3cEZ2zrQ4WSwwtADeWI6Z0k5SpcHfLxp+6dhPN
+JtqBmgUTHuycmvoAPZK6XMnV0wCo2gUYS2q3Vc9yqsJRG0q2CKhOtKnuEmSqcjcC
+52OnP06M5pLfvms+qoOLVUHMhidMtYs3yRPZ1ZPzScQhjg64QDsRiG+PC0lL/NFz
+XPFcgzSsoIJ8UNZNjw7l5hh9NehJam2pqMizIcECl3gwrqGtq7Qo/AIXc4sCAwEA
+AQKCAgBqkCqw+zBYvMgQ57vsugGQtdOyQcaB0j0wu6cWHf+2vWl8jLvK6XOfanTr
+Z54rRxcmS3SueUox9JPmoRPXccGXS+URyn/3iQC0qMQnVwrlHCQMP9TU4TI4Ibmu
+N9a4vc/mkNERNCLhTvZZWtWYS8uOGPYOmpBkTgK0WPMUtioBuamHmTUeCol6A3+D
+MVElaeDi0vlsivXW421nHoCbEBB2y2M03CTKzp9CXNOoao3eLZ2C94y8RdB4wKXM
+g6UtCQDIRRfAx7kIx4LKCXZ3rXjyuBDh18VLle2diilQdnv70HZF5Q9feD8Rf2c6
+PUVRKvmMgIww8Tf9GgMJ5SwmAdp/VblgcKsdjr8tnT7V9ljkYCL4K3H4FT5LKXjI
+FiGyqBie8jvLYCLx6DlVTIi+Q/kvxaFT9twazVlz9jfgufQ4ICBKlNp4A6kpwrzb
+9QnMrHI+gTOrCCEeklg90M+xk5UERueLPnXYbvAG8cv1FeVpi9ldSQds5VHN1ZJ4
+hWWeWfGgIDiNeZuKL141NLS/sX9rCGEsQyVLTKkSDIgh5ncepVcXhB40GZFfggml
+A3HfNRN5lHLwH5+JKWlVx7PfUGPOTgx62i7HF+qcV3bQJfqnAMLPA7hh1bo8xIPp
+hiAbaZkwCGlNCwadzOq6U99Dx7eorwfAgGDKWnAr/rMaK9n+OQKCAQEA8UItzBLq
+yyJ+Xj7n/M3x/+v2E6dcDYH64oeMpVEEdH6cSSNcdUm0vgX4p25W0uY5kgj1RsXV
+gOk+9W6cM84p+2+DIGI8fydnuIv8q6TFiouDCY+T3FVdj4MasNZuAeHR7c6Coc5N
+Eckv3F1sfoGlH4z2AzppY0T3VX5TkKkh719X7A/cpIirZBRLfddDA3Hr6pm/vgSo
+mX1X5BhjrujasRoZhU2RsXnAflXp18aP67zvlRlzGrpMGeueRjp2NXF3pItiMAjN
+EOSoY3elEi6Um+WomFGLsY5SAuId+TJy8SqsGKWNHN+UPx4tGYQmEPA1e76Nu5Ex
+xDxpHIWyZ4Hz/wKCAQEAzw/9bPFgihzSnrBbJiFZVGPhFxw39aeuB5/3ZMPjknfK
+fyWonbhrJF14/86JSd16Xime6J4a3OsXKzTo36sLBDsYfYXof7bwfKk/GUryMFOG
+0aZqiZbiRQ8uCfzd+MtnNxO0WxiyZvj8i7hMjK50yBhRs/5YAHSoLxFOfVVSLNAi
+nhIDqtzeLA2W05PGusgz/0w8FHI6J64jZY4EQLgr43K6DXoFLQjtsl8JZfMO9fEc
+0j5vRytXtYlTSlQEtKeQ8cvpcqbY0gmrEasZ4v1jEzemXzvFkx+ck+Ayl/vHx60A
+AZCom66BudFArAnuVdrMAWUH+78Xf2l5en7kz40QdQKCAQBWxkran+M7dQimtVGT
+qC9msWQs5YFCioHGgKKhw2Yq0G8+Dy3uMbiEsHkjH5iy+oOydu5hqj6Ew2AVvtcH
++xs2iIFNYIgJ5A52XkNfKUCz+EIFalLwaPPh7nHnMPkYTDTJqAFsWVt3DjnctO2V
+AuR1WKoTtyq4vdGIOour+GlwQ4bILVxbAZ1Dvdj5RjegQZVtKCfDHMHXkzHNpMgV
+3ULreEu9mozQnM4ToqsdJRoW3DoAEstHzcIZgJnJALYLuughktCaHlBDxzqZrCr/
+QynIeO4O+yWXk20EBHhrbS3SeFq18rWysOgNW7k0+EcIyJ00CPHJiQuxXVkhHSVx
+/VfZAoIBAGA1isg642No/wgS41c1OZ93hRfK2cl/ruIGFtowFqZwmJs5cT5PeSD9
+eYJKggnbKcdkyVxGUi8B4NMHk4iRnd3KY5e3R49H/je+H/5tj1ibBtKU432oqNvz
+sK2dW7oFMKEru6p0MDieSiHVcWQQj1yFyDi83kDf82FjRjgAE92Um/EcZ63VUDnh
+2onWaQlSiq59ypCpfpH/XJ0MPrefm2zkWsR2RL9nHaK6e9Bt/i6SaJTbw7Kq1ecY
+tqWbolAaZ8OhvoeyNJ5rNZxRBwcsOwOr4NbxG90/W+5txrRNnccOgCk6AM3NaKNh
+Mg590sr7jby8J9h2MsHVzUb4fPJfFh0CggEBANS+aqEzWflHMOR4knhM7QHHaTfS
+4wwR3zL3/tSaV/cxNGehtjyEg85/aKklt/ude3/sm/Z0m6jQEMWgAt5iwuBovPA+
+1/rGkWTHL+dQr0i81C3b/Ymx7izL7BobaYV0o00EoKotC0NP5qR0fBKSkTfFqAYG
+SxnHtw/vduxu2H6TyIrdtvNNqc1PbHdzDI/FwzcWZFNyHBzSWwZxB5w+21uRUayv
+Iz3zcytrZZbAuOjCnhxNL/6XgcttqWSVFB4Ul1xiXrXDx2Xq+FfM40UF7oKGd+Kt
+B0wMqoZJj+0CdFfRZxHA6/n8v1Al+8lYo8smp+R9fR6qZKcugEFgdVkIl7E=
+-----END RSA PRIVATE KEY-----
diff --git a/ondevice-signing/tests/SigningUtilsTest.cpp b/ondevice-signing/tests/SigningUtilsTest.cpp
new file mode 100644
index 0000000..10f7629
--- /dev/null
+++ b/ondevice-signing/tests/SigningUtilsTest.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2021 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 <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include "CertUtils.h"
+#include "VerityUtils.h"
+
+// These files were created using the following commands:
+// openssl genrsa -out SigningUtils.pem 4096
+// openssl req -new -x509 -key SigningUtils.pem -out SigningUtils.cert.pem
+// openssl x509 -in SigningUtils.cert.pem -out SigningUtils.cert.der -outform DER
+// head -c 4096 </dev/urandom >test_file
+// openssl dgst -sign SigningUtils.pem -keyform PEM -sha256 -out test_file.sig -binary test_file
+const std::string kTestCert = "SigningUtils.cert.der";
+const std::string kTestFile = "test_file";
+const std::string kTestFileSignature = "test_file.sig";
+
+TEST(SigningUtilsTest, CheckVerifySignature) {
+ std::string signature;
+ std::string sigFile = android::base::GetExecutableDirectory() + "/" + kTestFileSignature;
+ ASSERT_TRUE(android::base::ReadFileToString(sigFile, &signature));
+
+ std::string data;
+ std::string testFile = android::base::GetExecutableDirectory() + "/" + kTestFile;
+ ASSERT_TRUE(android::base::ReadFileToString(testFile, &data));
+
+ std::string testCert = android::base::GetExecutableDirectory() + "/" + kTestCert;
+ auto trustedKey = extractPublicKeyFromX509(testCert.c_str());
+ ASSERT_TRUE(trustedKey.ok());
+
+ auto result = verifySignature(data, signature, *trustedKey);
+ ASSERT_TRUE(result.ok());
+}
diff --git a/ondevice-signing/tests/test_file b/ondevice-signing/tests/test_file
new file mode 100644
index 0000000..8a121be
--- /dev/null
+++ b/ondevice-signing/tests/test_file
Binary files differ
diff --git a/ondevice-signing/tests/test_file.sig b/ondevice-signing/tests/test_file.sig
new file mode 100644
index 0000000..ffd95dc
--- /dev/null
+++ b/ondevice-signing/tests/test_file.sig
Binary files differ
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index afbc405..aac4878 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -48,7 +48,7 @@
vendor: true,
srcs: ["rkp_factory_extraction_tool.cpp"],
shared_libs: [
- "android.hardware.security.keymint-V1-ndk_platform",
+ "android.hardware.security.keymint-V1-ndk",
"libbinder",
"libbinder_ndk",
"libcrypto",
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index c439b99..9786c3d 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -22,6 +22,7 @@
#include <cppbor.h>
#include <gflags/gflags.h>
#include <keymaster/cppcose/cppcose.h>
+#include <openssl/base64.h>
#include <remote_prov/remote_prov_utils.h>
#include <sys/random.h>
@@ -49,6 +50,26 @@
constexpr size_t kChallengeSize = 16;
+std::string toBase64(const std::vector<uint8_t>& buffer) {
+ size_t base64Length;
+ int rc = EVP_EncodedLength(&base64Length, buffer.size());
+ if (!rc) {
+ std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
+ exit(-1);
+ }
+
+ std::string base64(base64Length, ' ');
+ rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
+ ++rc; // Account for NUL, which BoringSSL does not for some reason.
+ if (rc != base64Length) {
+ std::cerr << "Error writing base64. Expected " << base64Length
+ << " bytes to be written, but " << rc << " bytes were actually written."
+ << std::endl;
+ exit(-1);
+ }
+ return base64;
+}
+
std::vector<uint8_t> generateChallenge() {
std::vector<uint8_t> challenge(kChallengeSize);
@@ -56,9 +77,13 @@
uint8_t* writePtr = challenge.data();
while (bytesRemaining > 0) {
int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0);
- if (bytesRead < 0 && errno != EINTR) {
- std::cerr << errno << ": " << strerror(errno) << std::endl;
- exit(-1);
+ if (bytesRead < 0) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ std::cerr << errno << ": " << strerror(errno) << std::endl;
+ exit(-1);
+ }
}
bytesRemaining -= bytesRead;
writePtr += bytesRead;
@@ -96,7 +121,10 @@
std::cerr << "Failed to generate test EEK somehow: " << eekOrErr.message() << std::endl;
exit(-1);
}
- auto [eek, ignored_pubkey, ignored_privkey] = eekOrErr.moveValue();
+ auto [eek, pubkey, privkey] = eekOrErr.moveValue();
+ std::cout << "EEK raw keypair:" << std::endl;
+ std::cout << " pub: " << toBase64(pubkey) << std::endl;
+ std::cout << " priv: " << toBase64(privkey) << std::endl;
return eek;
}
@@ -134,7 +162,7 @@
auto rkp_service = IRemotelyProvisionedComponent::fromBinder(rkp_binder);
if (!rkp_service) {
std::cerr << "Unable to get binder object for '" << fullName << "', skipping.";
- return;
+ exit(-1);
}
std::vector<uint8_t> keysToSignMac;