Merge "Backport LocCharacterization." into main
diff --git a/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp b/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
index 8727232..5e18f1b 100644
--- a/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
+++ b/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
@@ -76,7 +76,7 @@
.proxy = std::nullopt},
.flags = {.type = Flags::Type::PRE_PROC,
.insert = Flags::Insert::FIRST,
- .volume = Flags::Volume::CTRL},
+ .volume = Flags::Volume::NONE},
.name = AcousticEchoCancelerSw::kEffectName,
.implementor = "The Android Open Source Project"},
.capability = AcousticEchoCancelerSw::kCapability};
diff --git a/audio/aidl/default/noiseSuppression/NoiseSuppressionSw.cpp b/audio/aidl/default/noiseSuppression/NoiseSuppressionSw.cpp
index 99f2caf..a3208df 100644
--- a/audio/aidl/default/noiseSuppression/NoiseSuppressionSw.cpp
+++ b/audio/aidl/default/noiseSuppression/NoiseSuppressionSw.cpp
@@ -67,7 +67,7 @@
.proxy = std::nullopt},
.flags = {.type = Flags::Type::PRE_PROC,
.insert = Flags::Insert::FIRST,
- .volume = Flags::Volume::CTRL},
+ .volume = Flags::Volume::NONE},
.name = NoiseSuppressionSw::kEffectName,
.implementor = "The Android Open Source Project"}};
diff --git a/audio/aidl/default/visualizer/VisualizerSw.cpp b/audio/aidl/default/visualizer/VisualizerSw.cpp
index 0909f25..285c102 100644
--- a/audio/aidl/default/visualizer/VisualizerSw.cpp
+++ b/audio/aidl/default/visualizer/VisualizerSw.cpp
@@ -73,7 +73,7 @@
.proxy = std::nullopt},
.flags = {.type = Flags::Type::INSERT,
.insert = Flags::Insert::FIRST,
- .volume = Flags::Volume::CTRL},
+ .volume = Flags::Volume::NONE},
.name = VisualizerSw::kEffectName,
.implementor = "The Android Open Source Project"},
.capability = VisualizerSw::kCapability};
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
index ca1cea9..aaf9ad4 100644
--- a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
@@ -596,8 +596,14 @@
Parameter::Id id = Parameter::Id::make<Parameter::Id::commonTag>(Parameter::volumeStereo);
Parameter::VolumeStereo volume = {.left = 10.0, .right = 10.0};
- ASSERT_NO_FATAL_FAILURE(
- setAndGetParameter(id, Parameter::make<Parameter::volumeStereo>(volume)));
+ if (mDescriptor.common.flags.volume == Flags::Volume::CTRL) {
+ Parameter get;
+ EXPECT_IS_OK(mEffect->setParameter(volume));
+ EXPECT_IS_OK(mEffect->getParameter(id, &get));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(
+ setAndGetParameter(id, Parameter::make<Parameter::volumeStereo>(volume)));
+ }
ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::STOP));
ASSERT_NO_FATAL_FAILURE(expectState(mEffect, State::IDLE));
diff --git a/compatibility_matrices/compatibility_matrix.9.xml b/compatibility_matrices/compatibility_matrix.9.xml
index 2a8b772..2f33a28 100644
--- a/compatibility_matrices/compatibility_matrix.9.xml
+++ b/compatibility_matrices/compatibility_matrix.9.xml
@@ -304,6 +304,14 @@
<instance>default</instance>
</interface>
</hal>
+ <hal format="aidl" optional="true">
+ <name>android.hardware.security.secretkeeper</name>
+ <version>1</version>
+ <interface>
+ <name>ISecretkeeper</name>
+ <instance>nonsecure</instance>
+ </interface>
+ </hal>
<hal format="aidl" optional="true" updatable-via-apex="true">
<name>android.hardware.security.keymint</name>
<version>1-3</version>
@@ -672,7 +680,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.wifi.hostapd</name>
- <version>1</version>
+ <version>1-2</version>
<interface>
<name>IHostapd</name>
<instance>default</instance>
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index aa7bf28..be29f59 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -974,8 +974,8 @@
* time in milliseconds. This value is used when generating attestation or self signed
* certificates. ErrorCode::MISSING_NOT_BEFORE must be returned if this tag is not provided if
* this tag is not provided to generateKey or importKey. For importWrappedKey, there is no way
- * to specify the value of this tag for the wrapped key, so a value of 0 must be used for
- * certificate generation.
+ * to specify the value of this tag for a wrapped asymmetric key, so a value of 0 is suggested
+ * for certificate generation.
*/
CERTIFICATE_NOT_BEFORE = TagType.DATE | 1008,
@@ -983,8 +983,9 @@
* Tag::CERTIFICATE_NOT_AFTER the end of the validity of the certificate in UNIX epoch time in
* milliseconds. This value is used when generating attestation or self signed certificates.
* ErrorCode::MISSING_NOT_AFTER must be returned if this tag is not provided to generateKey or
- * importKey. For importWrappedKey, there is no way to specify the value of this tag for the
- * wrapped key, so a value of 253402300799000 is used for certificate generation.
+ * importKey. For importWrappedKey, there is no way to specify the value of this tag for a
+ * wrapped asymmetric key, so a value of 253402300799000 is suggested for certificate
+ * generation.
*/
CERTIFICATE_NOT_AFTER = TagType.DATE | 1009,
diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
index a8f17dd..d4adab5 100644
--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp
+++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp
@@ -4123,13 +4123,13 @@
* when the EC_CURVE is not explicitly specified.
*/
TEST_P(ImportKeyTest, EcdsaSuccessCurveNotSpecified) {
- if (AidlVersion() < 4) {
+ if (get_vsr_api_level() < __ANDROID_API_V__) {
/*
- * The KeyMint spec before V4 was not clear as to whether EC_CURVE was optional on import of
- * EC keys. However, this was not checked at the time so we can only be strict about
- * checking this for implementations of KeyMint version 4 and above.
+ * The KeyMint spec was previously not clear as to whether EC_CURVE was optional on import
+ * of EC keys. However, this was not checked at the time so we can only be strict about
+ * checking this for implementations at VSR-V or later.
*/
- GTEST_SKIP() << "Skipping EC_CURVE on import only strict since KeyMint v4";
+ GTEST_SKIP() << "Skipping EC_CURVE on import only strict >= VSR-V";
}
ASSERT_EQ(ErrorCode::OK, ImportKey(AuthorizationSetBuilder()
diff --git a/security/secretkeeper/aidl/Android.bp b/security/secretkeeper/aidl/Android.bp
new file mode 100644
index 0000000..c77d299
--- /dev/null
+++ b/security/secretkeeper/aidl/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 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"],
+}
+
+aidl_interface {
+ name: "android.hardware.security.secretkeeper",
+ vendor_available: true,
+ srcs: ["android/hardware/security/secretkeeper/*.aidl"],
+ stability: "vintf",
+ backend: {
+ ndk: {
+ enabled: true,
+ },
+ rust: {
+ enabled: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+ },
+ },
+}
diff --git a/security/secretkeeper/aidl/aidl_api/android.hardware.security.secretkeeper/current/android/hardware/security/secretkeeper/ISecretkeeper.aidl b/security/secretkeeper/aidl/aidl_api/android.hardware.security.secretkeeper/current/android/hardware/security/secretkeeper/ISecretkeeper.aidl
new file mode 100644
index 0000000..2eb33c5
--- /dev/null
+++ b/security/secretkeeper/aidl/aidl_api/android.hardware.security.secretkeeper/current/android/hardware/security/secretkeeper/ISecretkeeper.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.security.secretkeeper;
+@VintfStability
+interface ISecretkeeper {
+ byte[] processSecretManagementRequest(in byte[] request);
+}
diff --git a/security/secretkeeper/aidl/android/hardware/security/secretkeeper/ISecretkeeper.aidl b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/ISecretkeeper.aidl
new file mode 100644
index 0000000..af715a9
--- /dev/null
+++ b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/ISecretkeeper.aidl
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.security.secretkeeper;
+
+@VintfStability
+/**
+ * Secretkeeper service definition.
+ *
+ * An ISecretkeeper instance provides secure storage of secrets on behalf of other components in
+ * Android, in particular for protected virtual machine instances. From the perspective of security
+ * privilege, Secretkeeper must be implemented in an environment with privilege higher than any of
+ * its clients. Since AVF based protected Virtual Machines are one set of its clients, the
+ * implementation of ISecretkeeper should live in a secure environment, such as:
+ * - A trusted execution environment such as ARM TrustZone.
+ * - A completely separate, purpose-built and certified secure CPU.
+ *
+ * TODO(b/291224769): Extend the HAL interface to include:
+ * 1. Session setup api: This is used to perform cryptographic operations that allow shared keys to
+ * be exchanged between session participants, typically (but not necessarily) a pVM instance and
+ * Secretkeeper. This session setup is based on public key cryptography.
+ * 2. Dice policy operation - These allow sealing of the secrets with a class of Dice chains.
+ * Typical operations are (securely) updating the dice policy sealing the Secrets above. These
+ * operations are core to AntiRollback protected secrets - ie, ensuring secrets of a pVM are only
+ * accessible to same or higher versions of the images.
+ * 3. Maintenance api: This is required for removing the Secretkeeper entries for obsolete pvMs.
+ */
+interface ISecretkeeper {
+ /**
+ * processSecretManagementRequest method is used for interacting with the Secret Management API
+ *
+ * Secret Management API: The clients can use this API to store (& get) 32 bytes of data.
+ * The API is a CBOR based protocol, which follows request/response model.
+ * See SecretManagement.cddl for the API spec.
+ *
+ * Further, the requests (from client) & responses (from service) must be encrypted into
+ * ProtectedRequestPacket & ProtectedResponsePacket using symmetric keys agreed between
+ * the client & service. This cryptographic protection is required because the messages are
+ * ferried via Android, which is allowed to be outside the TCB of clients (for example protected
+ * Virtual Machines). For this, service (& client) must implement a key exchange protocol, which
+ * is critical for establishing the secure channel.
+ *
+ * Secretkeeper database should guarantee the following properties:
+ * 1. Confidentiality: No entity (of security privilege lower than Secretkeeper) should
+ * be able to get a client's data in clear.
+ *
+ * 2. Integrity: The data is protected against malicious Android OS tampering with database.
+ * ie, if Android (userspace & kernel) tampers with the client's secret, the Secretkeeper
+ * service must be able to detect it & return error when clients requests for their secrets.
+ * Note: the integrity requirements also include Antirollback protection ie, reverting the
+ * database into an old state should be detected.
+ *
+ * 3. The data is persistent across device boot.
+ * Note: Denial of service is not in scope. A malicious Android may be able to delete data,
+ * but for ideal Android, the data should be persistent.
+ *
+ * @param CBOR-encoded ProtectedRequestPacket. See SecretManagement.cddl for its definition.
+ * @return CBOR-encoded ProtectedResponsePacket. See SecretManagement.cddl for its definition
+ */
+ byte[] processSecretManagementRequest(in byte[] request);
+}
diff --git a/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
new file mode 100644
index 0000000..5631937
--- /dev/null
+++ b/security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
@@ -0,0 +1,116 @@
+; CDDL for the Secret Management API.
+; Also see processSecretManagementRequest method in ISecretkeeper.aidl
+
+; ProtectedRequestPacket is used by client for accessing Secret Management API
+; in Secretkeeper service. The service returns ProtectedResponsePacket of the corresponding type.
+
+; ProtectedRequestPacket & ProtectedResponsePacket are encrypted wrappers
+; on RequestPacket & ResponsePacket using symmetric keys agreed between Secretkeeper & clients
+; (these are referred to as KeySourceToSink & KeySinkToSource)
+;
+; The API operation required is encoded using 'Opcode', the arguments using 'Params'
+; and returned values as 'Result'.
+
+ProtectedRequestPacket =
+ ProtectedGetVersionRequest / ProtectedStoreSecretRequest / ProtectedGetSecretRequest
+ProtectedResponsePacket =
+ ProtectedGetVersionResponse / ProtectedStoreSecretResponse / ProtectedGetSecretResponse
+
+ProtectedGetVersionRequest = ProtectedRequestPacket<GetVersionRequestPacket>
+ProtectedGetVersionResponse = ProtectedResponsePacket<GetVersionResponsePacket>
+ProtectedStoreSecretRequest = ProtectedRequestPacket<StoreSecretRequestPacket>
+ProtectedStoreSecretResponse = ProtectedResponsePacket<StoreSecretResponsePacket>
+ProtectedGetSecretRequest = ProtectedRequestPacket<GetSecretRequestPacket>
+ProtectedGetSecretResponse = ProtectedResponsePacket<GetSecretResponsePacket>
+
+GetVersionRequestPacket = RequestPacket<GetVersionOpcode, GetVersionParams>
+GetVersionResponsePacket = ResponsePacket<GetVersionResult>
+StoreSecretRequestPacket = RequestPacket<StoreSecretOpcode, StoreSecretParams>
+StoreSecretResponsePacket = ResponsePacket<StoreSecretResult>
+GetSecretRequestPacket = RequestPacket<GetOpcode, GetSecretParams>
+GetSecretResponsePacket = ResponsePacket<GetSecretResult>
+
+RequestPacket<Opcode, Params> = [
+ Opcode,
+ Params
+]
+ResponsePacket<Result> = ResponsePacketError / ResponsePacketSuccess<Result>
+
+ResponsePacketSuccess = [
+ 0, ; Indicates successful Response
+ result : Result
+]
+ResponsePacketError = [
+ error_code: ErrorCode, ; Indicate the error
+ error_message: tstr ; Additional human-readable context
+]
+
+Opcode = &(
+ GetVersionOpcode: 1, ; Get version of the SecretManagement API
+ StoreSecretOpcode: 2, ; Store a secret
+ GetSecretOpcode: 3, ; Get the secret
+)
+
+GetVersionParams = ()
+GetVersionResult = (version : uint)
+
+StoreSecretParams = (
+ id : bstr .size 64 ; Unique identifier of the secret
+ secret : bstr .size 32,
+ sealing_policy : bstr .cbor DicePolicy, ; See DicePolicy.cddl for definition of DicePolicy
+)
+StoreSecretResult = ()
+
+GetSecretParams = (
+ id : bstr .size 64 ; Unique identifier of the secret
+ ; Use this to update the sealing policy associated with a secret during GetSecret operation.
+ updated_sealing_policy : bstr .cbor DicePolicy / nil,
+)
+GetSecretResult = (secret : bstr .size 32)
+
+
+ProtectedRequestPacket<Payload, Key> = CryptoPayload<Payload, KeySourceToSink>
+ProtectedResponsePacket<Payload, Key> = ProtectedResponseError
+ / ProtectedResponseSuccess<Payload>
+
+ProtectedResponseSuccess<Payload> = [
+ 0, ; Indicates successful crypto operations. Note: Payload
+ ; may contain Error from functional layer.
+ message: CryptoPayload<Payload, KeySinkToSource> ; message is the encrypted payload
+]
+
+ProtectedResponseError = [
+ error_code: CryptoErrorCode, ; Indicates the error. This is in cleartext & will be
+ ; visible to Android. These are errors from crypto
+ ; layer & indicates the request could not even be read
+ message: tstr ; Additional human-readable context
+]
+
+CryptoPayload<Payload, Key> = [ ; COSE_Encrypt0 (untagged), [RFC 9052 s5.2]
+ protected: bstr .cbor {
+ 1 : 3, ; Algorithm: AES-GCM mode w/ 256-bit key, 128-bit tag
+ 4 : bstr ; key identifier, uniquely identifies the session
+ ; TODO(b/291228560): Refer to the Key Exchange spec.
+ },
+ unprotected: {
+ 5 : bstr .size 12 ; IV
+ },
+ ciphertext : bstr ; AES-GCM-256(Key, bstr .cbor Payload)
+ ; AAD for the encryption is CBOR-serialized
+ ; Enc_structure (RFC 9052 s5.3) with empty external_aad.
+]
+
+; TODO(b/291224769): Create a more exhaustive set of CryptoErrorCode
+CryptoErrorCode = &(
+ CryptoErrorCode_SessionExpired: 1,
+)
+
+; TODO(b/291224769): Create a more exhaustive set of ErrorCodes
+ErrorCode = &(
+ ; Use this as if no other error code can be used.
+ ErrorCode_UnexpectedServerError: 1,
+ ; Indicate the Request was malformed & hence couldnt be served.
+ ErrorCode_RequestMalformed: 2,
+)
+
+; INCLUDE DicePolicy.cddl for: DicePolicy
\ No newline at end of file
diff --git a/security/secretkeeper/aidl/vts/Android.bp b/security/secretkeeper/aidl/vts/Android.bp
new file mode 100644
index 0000000..6818298
--- /dev/null
+++ b/security/secretkeeper/aidl/vts/Android.bp
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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_test {
+ name: "VtsSecretkeeperTargetTest",
+ srcs: ["secretkeeper_test_client.rs"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ rustlibs: [
+ "libsecretkeeper_comm_nostd",
+ "android.hardware.security.secretkeeper-V1-rust",
+ "libbinder_rs",
+ "liblog_rust",
+ ],
+ require_root: true,
+}
diff --git a/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs b/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs
new file mode 100644
index 0000000..28923f7
--- /dev/null
+++ b/security/secretkeeper/aidl/vts/secretkeeper_test_client.rs
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+#[cfg(test)]
+use binder::StatusCode;
+use log::warn;
+use secretkeeper_comm::data_types::error::SecretkeeperError;
+use secretkeeper_comm::data_types::request::Request;
+use secretkeeper_comm::data_types::request_response_impl::{
+ GetVersionRequest, GetVersionResponse,
+};
+use secretkeeper_comm::data_types::response::Response;
+use secretkeeper_comm::data_types::packet::{ResponsePacket, ResponseType};
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
+
+const SECRETKEEPER_IDENTIFIER: &str =
+ "android.hardware.security.secretkeeper.ISecretkeeper/nonsecure";
+const CURRENT_VERSION: u64 = 1;
+
+fn get_connection() -> Option<binder::Strong<dyn ISecretkeeper>> {
+ match binder::get_interface(SECRETKEEPER_IDENTIFIER) {
+ Ok(sk) => Some(sk),
+ Err(StatusCode::NAME_NOT_FOUND) => None,
+ Err(e) => {
+ panic!(
+ "unexpected error while fetching connection to Secretkeeper {:?}",
+ e
+ );
+ }
+ }
+}
+
+// TODO(b/2797757): Add tests that match different HAL defined objects (like request/response)
+// with expected bytes.
+
+#[test]
+fn secret_management_get_version() {
+ let secretkeeper = match get_connection() {
+ Some(sk) => sk,
+ None => {
+ warn!("Secretkeeper HAL is unavailable, skipping test");
+ return;
+ }
+ };
+ let request = GetVersionRequest {};
+ let request_packet = request.serialize_to_packet();
+ let request_bytes = request_packet.into_bytes().unwrap();
+
+ // TODO(b/291224769) The request will need to be encrypted & response need to be decrypted
+ // with key & related artifacts pre-shared via Authgraph Key Exchange HAL.
+
+ let response_bytes = secretkeeper
+ .processSecretManagementRequest(&request_bytes)
+ .unwrap();
+
+ let response_packet = ResponsePacket::from_bytes(&response_bytes).unwrap();
+ assert_eq!(
+ response_packet.response_type().unwrap(),
+ ResponseType::Success
+ );
+ let get_version_response =
+ *GetVersionResponse::deserialize_from_packet(response_packet).unwrap();
+ assert_eq!(get_version_response.version(), CURRENT_VERSION);
+}
+
+#[test]
+fn secret_management_malformed_request() {
+ let secretkeeper = match get_connection() {
+ Some(sk) => sk,
+ None => {
+ warn!("Secretkeeper HAL is unavailable, skipping test");
+ return;
+ }
+ };
+ let request = GetVersionRequest {};
+ let request_packet = request.serialize_to_packet();
+ let mut request_bytes = request_packet.into_bytes().unwrap();
+
+ // Deform the request
+ request_bytes[0] = !request_bytes[0];
+
+ // TODO(b/291224769) The request will need to be encrypted & response need to be decrypted
+ // with key & related artifacts pre-shared via Authgraph Key Exchange HAL.
+
+ let response_bytes = secretkeeper
+ .processSecretManagementRequest(&request_bytes)
+ .unwrap();
+
+ let response_packet = ResponsePacket::from_bytes(&response_bytes).unwrap();
+ assert_eq!(
+ response_packet.response_type().unwrap(),
+ ResponseType::Error
+ );
+ let err = *SecretkeeperError::deserialize_from_packet(response_packet).unwrap();
+ assert_eq!(err, SecretkeeperError::RequestMalformed);
+}
diff --git a/security/secretkeeper/default/Android.bp b/security/secretkeeper/default/Android.bp
new file mode 100644
index 0000000..1c39fa6
--- /dev/null
+++ b/security/secretkeeper/default/Android.bp
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 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_binary {
+ name: "android.hardware.security.secretkeeper-service.nonsecure",
+ relative_install_path: "hw",
+ vendor: true,
+ init_rc: ["secretkeeper.rc"],
+ vintf_fragments: ["secretkeeper.xml"],
+ rustlibs: [
+ "android.hardware.security.secretkeeper-V1-rust",
+ "libandroid_logger",
+ "libbinder_rs",
+ "liblog_rust",
+ "libsecretkeeper_comm_nostd",
+ ],
+ srcs: [
+ "src/main.rs",
+ ],
+}
diff --git a/security/secretkeeper/default/secretkeeper.rc b/security/secretkeeper/default/secretkeeper.rc
new file mode 100644
index 0000000..f39f9b7
--- /dev/null
+++ b/security/secretkeeper/default/secretkeeper.rc
@@ -0,0 +1,5 @@
+service vendor.secretkeeper /vendor/bin/hw/android.hardware.security.secretkeeper-service.nonsecure
+ interface aidl android.hardware.security.secretkeeper.ISecretkeeper/nonsecure
+ class hal
+ user nobody
+ group nobody
diff --git a/security/secretkeeper/default/secretkeeper.xml b/security/secretkeeper/default/secretkeeper.xml
new file mode 100644
index 0000000..40aebe0
--- /dev/null
+++ b/security/secretkeeper/default/secretkeeper.xml
@@ -0,0 +1,28 @@
+<manifest version="1.0" type="device">
+<!--
+/*
+** Copyright 2022, 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.
+*/
+-->
+
+ <hal format="aidl">
+ <name>android.hardware.security.secretkeeper</name>
+ <version>1</version>
+ <interface>
+ <name>ISecretkeeper</name>
+ <instance>nonsecure</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/security/secretkeeper/default/src/main.rs b/security/secretkeeper/default/src/main.rs
new file mode 100644
index 0000000..2d367c5
--- /dev/null
+++ b/security/secretkeeper/default/src/main.rs
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2023 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 binder::{BinderFeatures, Interface};
+use log::{error, info, Level};
+use secretkeeper_comm::data_types::error::SecretkeeperError;
+use secretkeeper_comm::data_types::packet::{RequestPacket, ResponsePacket};
+use secretkeeper_comm::data_types::request::Request;
+use secretkeeper_comm::data_types::request_response_impl::{
+ GetVersionRequest, GetVersionResponse, Opcode,
+};
+use secretkeeper_comm::data_types::response::Response;
+
+use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::{
+ BnSecretkeeper, BpSecretkeeper, ISecretkeeper,
+};
+
+const CURRENT_VERSION: u64 = 1;
+
+#[derive(Debug, Default)]
+pub struct NonSecureSecretkeeper;
+
+impl Interface for NonSecureSecretkeeper {}
+
+impl ISecretkeeper for NonSecureSecretkeeper {
+ fn processSecretManagementRequest(&self, request: &[u8]) -> binder::Result<Vec<u8>> {
+ Ok(self.process_opaque_request(request))
+ }
+}
+
+impl NonSecureSecretkeeper {
+ // A set of requests to Secretkeeper are 'opaque' - encrypted bytes with inner structure
+ // described by CDDL. They need to be decrypted, deserialized and processed accordingly.
+ fn process_opaque_request(&self, request: &[u8]) -> Vec<u8> {
+ // TODO(b/291224769) The request will need to be decrypted & response need to be encrypted
+ // with key & related artifacts pre-shared via Authgraph Key Exchange HAL.
+ self.process_opaque_request_unhandled_error(request)
+ .unwrap_or_else(
+ // SecretkeeperError is also a valid 'Response', serialize to a response packet.
+ |sk_err| {
+ Response::serialize_to_packet(&sk_err)
+ .into_bytes()
+ .expect("Panicking due to serialization failing")
+ },
+ )
+ }
+
+ fn process_opaque_request_unhandled_error(
+ &self,
+ request: &[u8],
+ ) -> Result<Vec<u8>, SecretkeeperError> {
+ let request_packet = RequestPacket::from_bytes(request).map_err(|e| {
+ error!("Failed to get Request packet from bytes: {:?}", e);
+ SecretkeeperError::RequestMalformed
+ })?;
+ let response_packet = match request_packet
+ .opcode()
+ .map_err(|_| SecretkeeperError::RequestMalformed)?
+ {
+ Opcode::GetVersion => Self::process_get_version_request(request_packet)?,
+ _ => todo!("TODO(b/291224769): Unimplemented operations"),
+ };
+
+ response_packet
+ .into_bytes()
+ .map_err(|_| SecretkeeperError::UnexpectedServerError)
+ }
+
+ fn process_get_version_request(
+ request: RequestPacket,
+ ) -> Result<ResponsePacket, SecretkeeperError> {
+ // Deserialization really just verifies the structural integrity of the request such
+ // as args being empty.
+ let _request = GetVersionRequest::deserialize_from_packet(request)
+ .map_err(|_| SecretkeeperError::RequestMalformed)?;
+ let response = GetVersionResponse::new(CURRENT_VERSION);
+ Ok(response.serialize_to_packet())
+ }
+}
+
+fn main() {
+ // Initialize Android logging.
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("NonSecureSecretkeeper")
+ .with_min_level(Level::Info)
+ .with_log_id(android_logger::LogId::System),
+ );
+ // Redirect panic messages to logcat.
+ std::panic::set_hook(Box::new(|panic_info| {
+ error!("{}", panic_info);
+ }));
+
+ let service = NonSecureSecretkeeper::default();
+ let service_binder = BnSecretkeeper::new_binder(service, BinderFeatures::default());
+ let service_name = format!(
+ "{}/nonsecure",
+ <BpSecretkeeper as ISecretkeeper>::get_descriptor()
+ );
+ binder::add_service(&service_name, service_binder.as_binder()).unwrap_or_else(|e| {
+ panic!(
+ "Failed to register service {} because of {:?}.",
+ service_name, e
+ );
+ });
+ info!("Registered Binder service, joining threadpool.");
+ binder::ProcessState::join_thread_pool();
+}
diff --git a/wifi/hostapd/aidl/Android.bp b/wifi/hostapd/aidl/Android.bp
index 54895c1..cdc94bb 100644
--- a/wifi/hostapd/aidl/Android.bp
+++ b/wifi/hostapd/aidl/Android.bp
@@ -27,6 +27,9 @@
srcs: [
"android/hardware/wifi/hostapd/*.aidl",
],
+ imports: [
+ "android.hardware.wifi.common-V1",
+ ],
stability: "vintf",
backend: {
java: {
@@ -40,6 +43,9 @@
ndk: {
gen_trace: true,
},
+ cpp: {
+ enabled: false,
+ },
},
versions_with_info: [
{
@@ -47,5 +53,4 @@
imports: [],
},
],
-
}
diff --git a/wifi/hostapd/aidl/aidl_api/android.hardware.wifi.hostapd/current/android/hardware/wifi/hostapd/IfaceParams.aidl b/wifi/hostapd/aidl/aidl_api/android.hardware.wifi.hostapd/current/android/hardware/wifi/hostapd/IfaceParams.aidl
index 0c88a39..64367bb 100644
--- a/wifi/hostapd/aidl/aidl_api/android.hardware.wifi.hostapd/current/android/hardware/wifi/hostapd/IfaceParams.aidl
+++ b/wifi/hostapd/aidl/aidl_api/android.hardware.wifi.hostapd/current/android/hardware/wifi/hostapd/IfaceParams.aidl
@@ -37,4 +37,5 @@
String name;
android.hardware.wifi.hostapd.HwModeParams hwModeParams;
android.hardware.wifi.hostapd.ChannelParams[] channelParams;
+ @nullable android.hardware.wifi.common.OuiKeyedData[] vendorData;
}
diff --git a/wifi/hostapd/aidl/android/hardware/wifi/hostapd/IfaceParams.aidl b/wifi/hostapd/aidl/android/hardware/wifi/hostapd/IfaceParams.aidl
index a8abec3..3f8ee39 100644
--- a/wifi/hostapd/aidl/android/hardware/wifi/hostapd/IfaceParams.aidl
+++ b/wifi/hostapd/aidl/android/hardware/wifi/hostapd/IfaceParams.aidl
@@ -16,6 +16,7 @@
package android.hardware.wifi.hostapd;
+import android.hardware.wifi.common.OuiKeyedData;
import android.hardware.wifi.hostapd.ChannelParams;
import android.hardware.wifi.hostapd.HwModeParams;
@@ -36,4 +37,8 @@
* The list of the channel params for the dual interfaces.
*/
ChannelParams[] channelParams;
+ /**
+ * Optional vendor-specific configuration parameters.
+ */
+ @nullable OuiKeyedData[] vendorData;
}
diff --git a/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl b/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
index 80d8546..8aa593f 100644
--- a/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
+++ b/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
@@ -45,6 +45,9 @@
oneway void onGroupStarted(in String groupIfname, in boolean isGroupOwner, in byte[] ssid, in int frequency, in byte[] psk, in String passphrase, in byte[] goDeviceAddress, in boolean isPersistent);
oneway void onInvitationReceived(in byte[] srcAddress, in byte[] goDeviceAddress, in byte[] bssid, in int persistentNetworkId, in int operatingFrequency);
oneway void onInvitationResult(in byte[] bssid, in android.hardware.wifi.supplicant.P2pStatusCode status);
+ /**
+ * @deprecated This callback is deprecated from AIDL v3, newer HAL should call onProvisionDiscoveryCompletedEvent.
+ */
oneway void onProvisionDiscoveryCompleted(in byte[] p2pDeviceAddress, in boolean isRequest, in android.hardware.wifi.supplicant.P2pProvDiscStatusCode status, in android.hardware.wifi.supplicant.WpsConfigMethods configMethods, in String generatedPin);
oneway void onR2DeviceFound(in byte[] srcAddress, in byte[] p2pDeviceAddress, in byte[] primaryDeviceType, in String deviceName, in android.hardware.wifi.supplicant.WpsConfigMethods configMethods, in byte deviceCapabilities, in android.hardware.wifi.supplicant.P2pGroupCapabilityMask groupCapabilities, in byte[] wfdDeviceInfo, in byte[] wfdR2DeviceInfo);
oneway void onServiceDiscoveryResponse(in byte[] srcAddress, in char updateIndicator, in byte[] tlvs);
@@ -61,4 +64,5 @@
oneway void onGroupStartedWithParams(in android.hardware.wifi.supplicant.P2pGroupStartedEventParams groupStartedEventParams);
oneway void onPeerClientJoined(in android.hardware.wifi.supplicant.P2pPeerClientJoinedEventParams clientJoinedEventParams);
oneway void onPeerClientDisconnected(in android.hardware.wifi.supplicant.P2pPeerClientDisconnectedEventParams clientDisconnectedEventParams);
+ oneway void onProvisionDiscoveryCompletedEvent(in android.hardware.wifi.supplicant.P2pProvisionDiscoveryCompletedEventParams provisionDiscoveryCompletedEventParams);
}
diff --git a/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl b/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl
new file mode 100644
index 0000000..587c7c6
--- /dev/null
+++ b/wifi/supplicant/aidl/aidl_api/android.hardware.wifi.supplicant/current/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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 FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+// the interface (from the latest frozen version), the build system will
+// prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.wifi.supplicant;
+@VintfStability
+parcelable P2pProvisionDiscoveryCompletedEventParams {
+ byte[6] p2pDeviceAddress;
+ boolean isRequest;
+ android.hardware.wifi.supplicant.P2pProvDiscStatusCode status;
+ android.hardware.wifi.supplicant.WpsConfigMethods configMethods;
+ String generatedPin;
+ String groupInterfaceName;
+}
diff --git a/wifi/supplicant/aidl/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl b/wifi/supplicant/aidl/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
index 810fe48..8befc0d 100644
--- a/wifi/supplicant/aidl/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
+++ b/wifi/supplicant/aidl/android/hardware/wifi/supplicant/ISupplicantP2pIfaceCallback.aidl
@@ -21,6 +21,7 @@
import android.hardware.wifi.supplicant.P2pPeerClientDisconnectedEventParams;
import android.hardware.wifi.supplicant.P2pPeerClientJoinedEventParams;
import android.hardware.wifi.supplicant.P2pProvDiscStatusCode;
+import android.hardware.wifi.supplicant.P2pProvisionDiscoveryCompletedEventParams;
import android.hardware.wifi.supplicant.P2pStatusCode;
import android.hardware.wifi.supplicant.WpsConfigMethods;
import android.hardware.wifi.supplicant.WpsDevPasswordId;
@@ -144,6 +145,9 @@
/**
* Used to indicate the completion of a P2P provision discovery request.
+ * <p>
+ * @deprecated This callback is deprecated from AIDL v3, newer HAL should call
+ * onProvisionDiscoveryCompletedEvent.
*
* @param p2pDeviceAddress P2P device address.
* @param isRequest Whether we received or sent the provision discovery.
@@ -275,4 +279,13 @@
*/
void onPeerClientDisconnected(
in P2pPeerClientDisconnectedEventParams clientDisconnectedEventParams);
+
+ /**
+ * Used to indicate the completion of a P2P provision discovery request.
+ *
+ * @param provisionDiscoveryCompletedEventParams Parameters associated with
+ * P2P provision discovery frame notification.
+ */
+ void onProvisionDiscoveryCompletedEvent(
+ in P2pProvisionDiscoveryCompletedEventParams provisionDiscoveryCompletedEventParams);
}
diff --git a/wifi/supplicant/aidl/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl b/wifi/supplicant/aidl/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl
new file mode 100644
index 0000000..7fa7f22
--- /dev/null
+++ b/wifi/supplicant/aidl/android/hardware/wifi/supplicant/P2pProvisionDiscoveryCompletedEventParams.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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 android.hardware.wifi.supplicant;
+
+import android.hardware.wifi.supplicant.P2pProvDiscStatusCode;
+import android.hardware.wifi.supplicant.WpsConfigMethods;
+
+/**
+ * Parameters passed as a part of P2P provision discovery frame notification.
+ */
+@VintfStability
+parcelable P2pProvisionDiscoveryCompletedEventParams {
+ /**
+ * P2P device interface MAC address of the device who sent the request or responded to our
+ * request.
+ */
+ byte[6] p2pDeviceAddress;
+ /** True if this is a request, false if this is a response. */
+ boolean isRequest;
+ /** Status of the provision discovery */
+ P2pProvDiscStatusCode status;
+ /** Mask of WPS configuration methods supported */
+ WpsConfigMethods configMethods;
+ /** 8-digit pin generated */
+ String generatedPin;
+ /**
+ * Interface name of this device group owner. (For ex: p2p-p2p0-1)
+ * This field is filled only when the provision discovery request is received
+ * with P2P Group ID attribute. i.e., when the peer device is joining this
+ * device operating P2P group.
+ * Refer to WFA Wi-Fi_Direct_Specification_v1.9 section 3.2.1 for more details.
+ */
+ String groupInterfaceName;
+}
diff --git a/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
index d3dd2e0..0db1653 100644
--- a/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
+++ b/wifi/supplicant/aidl/vts/functional/supplicant_p2p_iface_aidl_test.cpp
@@ -41,6 +41,7 @@
using aidl::android::hardware::wifi::supplicant::P2pPeerClientDisconnectedEventParams;
using aidl::android::hardware::wifi::supplicant::P2pPeerClientJoinedEventParams;
using aidl::android::hardware::wifi::supplicant::P2pProvDiscStatusCode;
+using aidl::android::hardware::wifi::supplicant::P2pProvisionDiscoveryCompletedEventParams;
using aidl::android::hardware::wifi::supplicant::P2pStatusCode;
using aidl::android::hardware::wifi::supplicant::SupplicantStatusCode;
using aidl::android::hardware::wifi::supplicant::WpsConfigMethods;
@@ -193,6 +194,11 @@
override {
return ndk::ScopedAStatus::ok();
}
+ ::ndk::ScopedAStatus onProvisionDiscoveryCompletedEvent(
+ const P2pProvisionDiscoveryCompletedEventParams&
+ /* provisionDiscoveryCompletedEventParams */) override {
+ return ndk::ScopedAStatus::ok();
+ }
};
class SupplicantP2pIfaceAidlTest : public testing::TestWithParam<std::string> {