Merge changes from topic "revert-1540633-DNRXHDZSUB"
* changes:
Revert "matrix: IVibrator AIDL has version 2."
Revert "Update AIDL IVibrator manifest version to 2."
diff --git a/audio/7.0/IDevice.hal b/audio/7.0/IDevice.hal
index e30e545..d9e0ad2 100644
--- a/audio/7.0/IDevice.hal
+++ b/audio/7.0/IDevice.hal
@@ -103,6 +103,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -111,7 +116,8 @@
May be used by implementations to configure hardware effects.
* @return retval operation completion status.
* @return outStream created output stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openOutputStream(
AudioIoHandle ioHandle,
@@ -128,6 +134,11 @@
* If the stream can not be opened with the proposed audio config,
* HAL must provide suggested values for the audio config.
*
+ * Note: INVALID_ARGUMENTS is returned both in the case when the
+ * HAL can not use the provided config and in the case when
+ * the value of any argument is invalid. In the latter case the
+ * HAL must provide a default initialized suggested config.
+ *
* @param ioHandle handle assigned by AudioFlinger.
* @param device device type and (if needed) address.
* @param config stream configuration.
@@ -136,7 +147,8 @@
* May be used by implementations to configure processing effects.
* @return retval operation completion status.
* @return inStream in case of success, created input stream.
- * @return suggestedConfig in case of invalid parameters, suggested config.
+ * @return suggestedConfig in the case of rejection of the proposed config,
+ * a config suggested by the HAL.
*/
openInputStream(
AudioIoHandle ioHandle,
diff --git a/audio/7.0/IStreamIn.hal b/audio/7.0/IStreamIn.hal
index 0a3f24b..bf9ae52 100644
--- a/audio/7.0/IStreamIn.hal
+++ b/audio/7.0/IStreamIn.hal
@@ -41,6 +41,18 @@
setGain(float gain) generates (Result retval);
/**
+ * Called when the metadata of the stream's sink has been changed.
+ * Optional method
+ *
+ * @param sinkMetadata Description of the audio that is suggested by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSinkMetadata(SinkMetadata sinkMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver reader thread.
*/
enum ReadCommand : int32_t {
@@ -82,12 +94,6 @@
};
/**
- * Called when the metadata of the stream's sink has been changed.
- * @param sinkMetadata Description of the audio that is suggested by the clients.
- */
- updateSinkMetadata(SinkMetadata sinkMetadata);
-
- /**
* Set up required transports for receiving audio buffers from the driver.
*
* The transport consists of three message queues:
diff --git a/audio/7.0/IStreamOut.hal b/audio/7.0/IStreamOut.hal
index 38d750f..4daab26 100644
--- a/audio/7.0/IStreamOut.hal
+++ b/audio/7.0/IStreamOut.hal
@@ -45,6 +45,18 @@
setVolume(float left, float right) generates (Result retval);
/**
+ * Called when the metadata of the stream's source has been changed.
+ * Optional method
+ *
+ * @param sourceMetadata Description of the audio that is played by the clients.
+ * @return retval operation completion status.
+ * If any of the metadata fields contains an invalid value,
+ * returns INVALID_ARGUMENTS.
+ * If method isn't supported by the HAL returns NOT_SUPPORTED.
+ */
+ updateSourceMetadata(SourceMetadata sourceMetadata) generates (Result retval);
+
+ /**
* Commands that can be executed on the driver writer thread.
*/
enum WriteCommand : int32_t {
@@ -77,12 +89,6 @@
};
/**
- * Called when the metadata of the stream's source has been changed.
- * @param sourceMetadata Description of the audio that is played by the clients.
- */
- updateSourceMetadata(SourceMetadata sourceMetadata);
-
- /**
* Set up required transports for passing audio buffers to the driver.
*
* The transport consists of three message queues:
diff --git a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
index b7c1cc9..c0042db 100644
--- a/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
+++ b/audio/common/7.0/enums/include/android_audio_policy_configuration_V7_0-enums.h
@@ -212,12 +212,11 @@
return isOutputDevice(stringToAudioDevice(device));
}
-static inline bool isVendorExtension(const std::string& device) {
+static inline bool isVendorExtension(const std::string& s) {
// Must match the "vendorExtension" rule from the XSD file.
static const std::string vendorPrefix = "VX_";
- return device.size() > vendorPrefix.size() &&
- device.substr(0, vendorPrefix.size()) == vendorPrefix &&
- std::all_of(device.begin() + vendorPrefix.size(), device.end(),
+ return s.size() > vendorPrefix.size() && s.substr(0, vendorPrefix.size()) == vendorPrefix &&
+ std::all_of(s.begin() + vendorPrefix.size(), s.end(),
[](unsigned char c) { return c == '_' || std::isalnum(c); });
}
diff --git a/audio/common/7.0/types.hal b/audio/common/7.0/types.hal
index ed56c73..ed6d94f 100644
--- a/audio/common/7.0/types.hal
+++ b/audio/common/7.0/types.hal
@@ -270,14 +270,29 @@
*/
struct AudioConfig {
AudioConfigBase base;
- AudioOffloadInfo offloadInfo;
+ safe_union OffloadInfo {
+ Monostate unspecified;
+ AudioOffloadInfo info;
+ } offloadInfo;
uint64_t frameCount;
};
+/**
+ * AudioTag is an additional use case qualifier complementing
+ * AudioUsage and AudioContentType. Tags are set by vendor specific applications
+ * and must be prefixed by "VX_". Vendor must namespace their tag
+ * names to avoid conflicts. See 'vendorExtension' in audio_policy_configuration.xsd
+ * for a formal definition.
+ */
+typedef string AudioTag;
+
/** Metadata of a playback track for a StreamOut. */
struct PlaybackTrackMetadata {
AudioUsage usage;
AudioContentType contentType;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
+ AudioChannelMask channelMask;
/**
* Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation,
* 2 means double amplification...
@@ -294,6 +309,9 @@
/** Metadata of a record track for a StreamIn. */
struct RecordTrackMetadata {
AudioSource source;
+ /** Tags from AudioTrack audio atttributes */
+ vec<AudioTag> tags;
+ AudioChannelMask channelMask;
/**
* Positive linear gain applied to the track samples. 0 being muted and 1 is no attenuation,
* 2 means double amplification...
diff --git a/audio/common/all-versions/default/7.0/HidlUtils.cpp b/audio/common/all-versions/default/7.0/HidlUtils.cpp
index c985a70..de19faf 100644
--- a/audio/common/all-versions/default/7.0/HidlUtils.cpp
+++ b/audio/common/all-versions/default/7.0/HidlUtils.cpp
@@ -21,6 +21,7 @@
#include <log/log.h>
#include <android_audio_policy_configuration_V7_0-enums.h>
+#include <common/all-versions/HidlSupport.h>
#include <common/all-versions/VersionUtils.h>
#include "HidlUtils.h"
@@ -306,7 +307,12 @@
audio_config_base_t halConfigBase = {halConfig.sample_rate, halConfig.channel_mask,
halConfig.format};
CONVERT_CHECKED(audioConfigBaseFromHal(halConfigBase, isInput, &config->base), result);
- CONVERT_CHECKED(audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo), result);
+ if (halConfig.offload_info.sample_rate != 0) {
+ config->offloadInfo.info({});
+ CONVERT_CHECKED(
+ audioOffloadInfoFromHal(halConfig.offload_info, &config->offloadInfo.info()),
+ result);
+ }
config->frameCount = halConfig.frame_count;
return result;
}
@@ -319,7 +325,11 @@
halConfig->sample_rate = halConfigBase.sample_rate;
halConfig->channel_mask = halConfigBase.channel_mask;
halConfig->format = halConfigBase.format;
- CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo, &halConfig->offload_info), result);
+ if (config.offloadInfo.getDiscriminator() ==
+ AudioConfig::OffloadInfo::hidl_discriminator::info) {
+ CONVERT_CHECKED(audioOffloadInfoToHal(config.offloadInfo.info(), &halConfig->offload_info),
+ result);
+ }
halConfig->frame_count = config.frameCount;
return result;
}
@@ -800,6 +810,47 @@
return result;
}
+status_t HidlUtils::audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags) {
+ std::vector<std::string> strTags = utils::splitString(halTags, sAudioTagSeparator);
+ status_t result = NO_ERROR;
+ tags->resize(strTags.size());
+ size_t to = 0;
+ for (size_t from = 0; from < strTags.size(); ++from) {
+ if (xsd::isVendorExtension(strTags[from])) {
+ (*tags)[to++] = strTags[from];
+ } else {
+ result = BAD_VALUE;
+ }
+ }
+ if (to != strTags.size()) {
+ tags->resize(to);
+ }
+ return result;
+}
+
+status_t HidlUtils::audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags) {
+ memset(halTags, 0, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+ status_t result = NO_ERROR;
+ std::ostringstream halTagsBuffer;
+ bool hasValue = false;
+ for (const auto& tag : tags) {
+ if (hasValue) {
+ halTagsBuffer << sAudioTagSeparator;
+ }
+ if (xsd::isVendorExtension(tag) && strchr(tag.c_str(), sAudioTagSeparator) == nullptr) {
+ halTagsBuffer << tag;
+ hasValue = true;
+ } else {
+ result = BAD_VALUE;
+ }
+ }
+ std::string fullHalTags{std::move(halTagsBuffer.str())};
+ strncpy(halTags, fullHalTags.c_str(), AUDIO_ATTRIBUTES_TAGS_MAX_SIZE);
+ CONVERT_CHECKED(fullHalTags.length() <= AUDIO_ATTRIBUTES_TAGS_MAX_SIZE ? NO_ERROR : BAD_VALUE,
+ result);
+ return result;
+}
+
status_t HidlUtils::deviceAddressFromHal(audio_devices_t halDeviceType,
const char* halDeviceAddress, DeviceAddress* device) {
status_t result = NO_ERROR;
diff --git a/audio/common/all-versions/default/HidlUtils.h b/audio/common/all-versions/default/HidlUtils.h
index a0bd1bc..8e9275c 100644
--- a/audio/common/all-versions/default/HidlUtils.h
+++ b/audio/common/all-versions/default/HidlUtils.h
@@ -77,6 +77,8 @@
#endif
#if MAJOR_VERSION >= 7
+ static constexpr char sAudioTagSeparator = ';';
+
static status_t audioChannelMaskFromHal(audio_channel_mask_t halChannelMask, bool isInput,
AudioChannelMask* channelMask);
static status_t audioChannelMasksFromHal(const std::vector<std::string>& halChannelMasks,
@@ -107,6 +109,8 @@
AudioStreamType* streamType);
static status_t audioStreamTypeToHal(const AudioStreamType& streamType,
audio_stream_type_t* halStreamType);
+ static status_t audioTagsFromHal(const char* halTags, hidl_vec<AudioTag>* tags);
+ static status_t audioTagsToHal(const hidl_vec<AudioTag>& tags, char* halTags);
private:
static status_t audioIndexChannelMaskFromHal(audio_channel_mask_t halChannelMask,
@@ -126,6 +130,7 @@
struct audio_port_config_device_ext* device,
struct audio_port_config_mix_ext* mix,
struct audio_port_config_session_ext* session);
+
#endif // MAJOR_VERSION >= 7
// V4 and below have DeviceAddress defined in the 'core' interface.
diff --git a/audio/common/all-versions/default/tests/hidlutils_tests.cpp b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
index 22571c0..fef88b4 100644
--- a/audio/common/all-versions/default/tests/hidlutils_tests.cpp
+++ b/audio/common/all-versions/default/tests/hidlutils_tests.cpp
@@ -589,16 +589,29 @@
config.base.sampleRateHz = 44100;
config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
- config.offloadInfo.base = config.base;
- config.offloadInfo.streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 1024;
- config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
- config.offloadInfo.encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM;
- config.offloadInfo.contentId = 42;
- config.offloadInfo.syncId = 13;
+ audio_config_t halConfig;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig));
+ AudioConfig configBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigFromHal(halConfig, false /*isInput*/, &configBack));
+ EXPECT_EQ(config, configBack);
+}
+
+TEST(HidlUtils, ConvertConfigWithOffloadInfo) {
+ AudioConfig config = {};
+ config.base.sampleRateHz = 44100;
+ config.base.channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO);
+ config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
+ config.offloadInfo.info(
+ AudioOffloadInfo{.base = config.base,
+ .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+ .bitRatePerSecond = 320,
+ .durationMicroseconds = -1,
+ .bitWidth = 16,
+ .bufferSize = 1024,
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .encapsulationMode = AudioEncapsulationMode::ELEMENTARY_STREAM,
+ .contentId = 42,
+ .syncId = 13});
audio_config_t halConfig;
EXPECT_EQ(NO_ERROR, HidlUtils::audioConfigToHal(config, &halConfig));
AudioConfig configBack;
@@ -707,3 +720,43 @@
EXPECT_EQ(NO_ERROR, HidlUtils::audioPortToHal(portBack, &halPortBack));
EXPECT_TRUE(audio_ports_v7_are_equal(&halPort, &halPortBack));
}
+
+TEST(HidlUtils, ConvertInvalidAudioTags) {
+ char halTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+
+ hidl_vec<AudioTag> emptyTag = {{""}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(emptyTag, halTag));
+
+ hidl_vec<AudioTag> longTag = {{std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE + 1, 'A')}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(longTag, halTag));
+
+ hidl_vec<AudioTag> tagSeparator = {
+ {std::string(AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1, HidlUtils::sAudioTagSeparator)}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(tagSeparator, halTag));
+
+ hidl_vec<AudioTag> notExtensions = {{"random string", "VX_", "VX_GOOGLE_$$"}};
+ EXPECT_EQ(BAD_VALUE, HidlUtils::audioTagsToHal(notExtensions, halTag));
+}
+
+TEST(HidlUtils, ConvertAudioTags) {
+ hidl_vec<AudioTag> emptyTags;
+ char halEmptyTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(emptyTags, halEmptyTags));
+ hidl_vec<AudioTag> emptyTagsBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halEmptyTags, &emptyTagsBack));
+ EXPECT_EQ(emptyTags, emptyTagsBack);
+
+ hidl_vec<AudioTag> oneTag = {{"VX_GOOGLE_VR"}};
+ char halOneTag[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(oneTag, halOneTag));
+ hidl_vec<AudioTag> oneTagBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halOneTag, &oneTagBack));
+ EXPECT_EQ(oneTag, oneTagBack);
+
+ hidl_vec<AudioTag> twoTags = {{"VX_GOOGLE_VR_42", "VX_GOOGLE_1E100"}};
+ char halTwoTags[AUDIO_ATTRIBUTES_TAGS_MAX_SIZE] = {};
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsToHal(twoTags, halTwoTags));
+ hidl_vec<AudioTag> twoTagsBack;
+ EXPECT_EQ(NO_ERROR, HidlUtils::audioTagsFromHal(halTwoTags, &twoTagsBack));
+ EXPECT_EQ(twoTags, twoTagsBack);
+}
diff --git a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
index b514a43..d7802cf 100644
--- a/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
+++ b/audio/common/all-versions/util/include/common/all-versions/HidlSupport.h
@@ -20,6 +20,9 @@
#include <hidl/HidlSupport.h>
#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
namespace android::hardware::audio::common::utils {
@@ -29,6 +32,16 @@
return std::find(values.begin(), values.end(), e) != values.end();
}
+static inline std::vector<std::string> splitString(const std::string& s, char separator) {
+ std::istringstream iss(s);
+ std::string t;
+ std::vector<std::string> result;
+ while (std::getline(iss, t, separator)) {
+ result.push_back(std::move(t));
+ }
+ return result;
+}
+
} // namespace android::hardware::audio::common::utils
#endif // android_hardware_audio_common_HidlSupport_H_
diff --git a/audio/core/all-versions/default/Conversions.cpp b/audio/core/all-versions/default/Conversions.cpp
index 8e0a140..f1752cc 100644
--- a/audio/core/all-versions/default/Conversions.cpp
+++ b/audio/core/all-versions/default/Conversions.cpp
@@ -32,14 +32,10 @@
using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
-#if MAJOR_VERSION <= 6
-std::string deviceAddressToHal(const DeviceAddress& address) {
- audio_devices_t halDevice;
- char halAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
- (void)deviceAddressToHal(address, &halDevice, halAddress);
- return halAddress;
-}
-#endif
+#define CONVERT_CHECKED(expr, result) \
+ if (status_t status = (expr); status != NO_ERROR) { \
+ result = status; \
+ }
status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
char* halDeviceAddress) {
@@ -97,6 +93,52 @@
}
return status;
}
+
+status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata>* halTracks) {
+ status_t result = NO_ERROR;
+ if (halTracks != nullptr) {
+ halTracks->reserve(sinkMetadata.tracks.size());
+ }
+ for (auto& metadata : sinkMetadata.tracks) {
+ record_track_metadata halTrackMetadata{.gain = metadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source),
+ result);
+#if MAJOR_VERSION >= 5
+ if (metadata.destination.getDiscriminator() ==
+ RecordTrackMetadata::Destination::hidl_discriminator::device) {
+ CONVERT_CHECKED(
+ deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device,
+ halTrackMetadata.dest_device_address),
+ result);
+ }
+#endif
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_t>* halTracks) {
+ status_t result = NO_ERROR;
+ if (halTracks != nullptr) {
+ halTracks->reserve(sourceMetadata.tracks.size());
+ }
+ for (auto& metadata : sourceMetadata.tracks) {
+ playback_track_metadata_t halTrackMetadata{.gain = metadata.gain};
+ CONVERT_CHECKED(HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioContentTypeToHal(metadata.contentType,
+ &halTrackMetadata.content_type),
+ result);
+ if (halTracks != nullptr) {
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 7
@@ -135,6 +177,50 @@
}
return success;
}
+
+status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata_v7_t>* halTracks) {
+ std::vector<record_track_metadata> bases;
+ status_t result = sinkMetadataToHal(sinkMetadata, halTracks != nullptr ? &bases : nullptr);
+ if (halTracks != nullptr) {
+ halTracks->reserve(bases.size());
+ }
+ auto baseIter = std::make_move_iterator(bases.begin());
+ for (auto& metadata : sinkMetadata.tracks) {
+ record_track_metadata_v7_t halTrackMetadata;
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result);
+ if (halTracks != nullptr) {
+ halTrackMetadata.base = std::move(*baseIter++);
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
+
+status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_v7_t>* halTracks) {
+ std::vector<playback_track_metadata_t> bases;
+ status_t result = sourceMetadataToHal(sourceMetadata, halTracks != nullptr ? &bases : nullptr);
+ if (halTracks != nullptr) {
+ halTracks->reserve(bases.size());
+ }
+ auto baseIter = std::make_move_iterator(bases.begin());
+ for (auto& metadata : sourceMetadata.tracks) {
+ playback_track_metadata_v7_t halTrackMetadata;
+ CONVERT_CHECKED(HidlUtils::audioChannelMaskToHal(metadata.channelMask,
+ &halTrackMetadata.channel_mask),
+ result);
+ CONVERT_CHECKED(HidlUtils::audioTagsToHal(metadata.tags, halTrackMetadata.tags), result);
+ if (halTracks != nullptr) {
+ halTrackMetadata.base = std::move(*baseIter++);
+ halTracks->push_back(std::move(halTrackMetadata));
+ }
+ }
+ return result;
+}
#endif
} // namespace implementation
diff --git a/audio/core/all-versions/default/Device.cpp b/audio/core/all-versions/default/Device.cpp
index bb69f0b..05c1066 100644
--- a/audio/core/all-versions/default/Device.cpp
+++ b/audio/core/all-versions/default/Device.cpp
@@ -135,13 +135,14 @@
Return<void> Device::getInputBufferSize(const AudioConfig& config, getInputBufferSize_cb _hidl_cb) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
- size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
Result retval(Result::INVALID_ARGUMENTS);
uint64_t bufferSize = 0;
- if (halBufferSize != 0) {
- retval = Result::OK;
- bufferSize = halBufferSize;
+ if (HidlUtils::audioConfigToHal(config, &halConfig) == NO_ERROR) {
+ size_t halBufferSize = mDevice->get_input_buffer_size(mDevice, &halConfig);
+ if (halBufferSize != 0) {
+ retval = Result::OK;
+ bufferSize = halBufferSize;
+ }
}
_hidl_cb(retval, bufferSize);
return Void();
@@ -153,7 +154,9 @@
const AudioOutputFlags& flags,
AudioConfig* suggestedConfig) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
+ if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
audio_stream_out_t* halStream;
audio_devices_t halDevice;
char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
@@ -186,7 +189,9 @@
int32_t ioHandle, const DeviceAddress& device, const AudioConfig& config,
const AudioInputFlags& flags, AudioSource source, AudioConfig* suggestedConfig) {
audio_config_t halConfig;
- HidlUtils::audioConfigToHal(config, &halConfig);
+ if (HidlUtils::audioConfigToHal(config, &halConfig) != NO_ERROR) {
+ return {Result::INVALID_ARGUMENTS, nullptr};
+ }
audio_stream_in_t* halStream;
audio_devices_t halDevice;
char halDeviceAddress[AUDIO_DEVICE_MAX_ADDRESS_LEN];
@@ -248,6 +253,14 @@
#endif
const SourceMetadata& sourceMetadata,
openOutputStream_cb _hidl_cb) {
+#if MAJOR_VERSION <= 6
+ if (status_t status = sourceMetadataToHal(sourceMetadata, nullptr); status != NO_ERROR) {
+#else
+ if (status_t status = sourceMetadataToHalV7(sourceMetadata, nullptr); status != NO_ERROR) {
+#endif
+ _hidl_cb(analyzeStatus("sourceMetadataToHal", status), nullptr, AudioConfig{});
+ return Void();
+ }
AudioConfig suggestedConfig;
auto [result, streamOut] =
openOutputStreamImpl(ioHandle, device, config, flags, &suggestedConfig);
@@ -271,7 +284,15 @@
// This should never happen, the framework must not create as stream
// if there is no client
ALOGE("openInputStream called without tracks connected");
- _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig());
+ _hidl_cb(Result::INVALID_ARGUMENTS, nullptr, AudioConfig{});
+ return Void();
+ }
+#if MAJOR_VERSION <= 6
+ if (status_t status = sinkMetadataToHal(sinkMetadata, nullptr); status != NO_ERROR) {
+#else
+ if (status_t status = sinkMetadataToHalV7(sinkMetadata, nullptr); status != NO_ERROR) {
+#endif
+ _hidl_cb(analyzeStatus("sinkMetadataToHal", status), nullptr, AudioConfig{});
return Void();
}
// Pick the first one as the main.
diff --git a/audio/core/all-versions/default/Stream.cpp b/audio/core/all-versions/default/Stream.cpp
index c74079d..f964cbb 100644
--- a/audio/core/all-versions/default/Stream.cpp
+++ b/audio/core/all-versions/default/Stream.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "StreamHAL"
#include "core/default/Stream.h"
+#include "common/all-versions/HidlSupport.h"
#include "common/all-versions/default/EffectMap.h"
#include "core/default/Conversions.h"
#include "core/default/Util.h"
@@ -37,6 +38,7 @@
namespace implementation {
using ::android::hardware::audio::common::CPP_VERSION::implementation::HidlUtils;
+using ::android::hardware::audio::common::utils::splitString;
Stream::Stream(bool isInput, audio_stream_t* stream) : mIsInput(isInput), mStream(stream) {
(void)mIsInput; // prevent 'unused field' warnings in pre-V7 versions.
@@ -220,7 +222,7 @@
// Ensure that the separator is one character, despite that it's defined as a C string.
static_assert(sizeof(AUDIO_PARAMETER_VALUE_LIST_SEPARATOR) == 2);
std::vector<std::string> halFormats =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<AudioFormat> formats;
(void)HidlUtils::audioFormatsFromHal(halFormats, &formats);
std::vector<AudioProfile> tempProfiles;
@@ -235,7 +237,7 @@
result = getParam(AudioParameter::keyStreamSupportedSamplingRates, &halListValue, context);
if (result != Result::OK) break;
std::vector<std::string> halSampleRates =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<uint32_t> sampleRates;
sampleRates.resize(halSampleRates.size());
for (size_t i = 0; i < sampleRates.size(); ++i) {
@@ -245,7 +247,7 @@
result = getParam(AudioParameter::keyStreamSupportedChannels, &halListValue, context);
if (result != Result::OK) break;
std::vector<std::string> halChannelMasks =
- util::splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
+ splitString(halListValue.string(), AUDIO_PARAMETER_VALUE_LIST_SEPARATOR[0]);
hidl_vec<AudioChannelMask> channelMasks;
(void)HidlUtils::audioChannelMasksFromHal(halChannelMasks, &channelMasks);
// Create a profile.
diff --git a/audio/core/all-versions/default/StreamIn.cpp b/audio/core/all-versions/default/StreamIn.cpp
index ead7204..2c5e9f1 100644
--- a/audio/core/all-versions/default/StreamIn.cpp
+++ b/audio/core/all-versions/default/StreamIn.cpp
@@ -478,31 +478,70 @@
}
#if MAJOR_VERSION >= 4
-Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
- if (mStream->update_sink_metadata == nullptr) {
- return Void(); // not supported by the HAL
- }
+Result StreamIn::doUpdateSinkMetadata(const SinkMetadata& sinkMetadata) {
std::vector<record_track_metadata> halTracks;
- halTracks.reserve(sinkMetadata.tracks.size());
- for (auto& metadata : sinkMetadata.tracks) {
- record_track_metadata halTrackMetadata = {.gain = metadata.gain};
- (void)HidlUtils::audioSourceToHal(metadata.source, &halTrackMetadata.source);
-#if MAJOR_VERSION >= 5
- if (metadata.destination.getDiscriminator() ==
- RecordTrackMetadata::Destination::hidl_discriminator::device) {
- (void)deviceAddressToHal(metadata.destination.device(), &halTrackMetadata.dest_device,
- halTrackMetadata.dest_device_address);
+#if MAJOR_VERSION <= 6
+ (void)sinkMetadataToHal(sinkMetadata, &halTracks);
+#else
+ // Validate whether a conversion to V7 is possible. This is needed
+ // to have a consistent behavior of the HAL regardless of the API
+ // version of the legacy HAL (and also to be consistent with openInputStream).
+ std::vector<record_track_metadata_v7> halTracksV7;
+ if (status_t status = sinkMetadataToHalV7(sinkMetadata, &halTracksV7); status == NO_ERROR) {
+ halTracks.reserve(halTracksV7.size());
+ for (auto metadata_v7 : halTracksV7) {
+ halTracks.push_back(std::move(metadata_v7.base));
}
-#endif
- halTracks.push_back(halTrackMetadata);
+ } else {
+ return Stream::analyzeStatus("sinkMetadataToHal", status);
}
+#endif // MAJOR_VERSION <= 6
const sink_metadata_t halMetadata = {
.track_count = halTracks.size(),
.tracks = halTracks.data(),
};
mStream->update_sink_metadata(mStream, &halMetadata);
+ return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamIn::doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata) {
+ std::vector<record_track_metadata_v7> halTracks;
+ if (status_t status = sinkMetadataToHalV7(sinkMetadata, &halTracks); status != NO_ERROR) {
+ return Stream::analyzeStatus("sinkMetadataToHal", status);
+ }
+ const sink_metadata_v7_t halMetadata = {
+ .track_count = halTracks.size(),
+ .tracks = halTracks.data(),
+ };
+ mStream->update_sink_metadata_v7(mStream, &halMetadata);
+ return Result::OK;
+}
+#endif // MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+ if (mStream->update_sink_metadata == nullptr) {
+ return Void(); // not supported by the HAL
+ }
+ (void)doUpdateSinkMetadata(sinkMetadata);
return Void();
}
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamIn::updateSinkMetadata(const SinkMetadata& sinkMetadata) {
+ if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+ if (mStream->update_sink_metadata == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSinkMetadata(sinkMetadata);
+ } else {
+ if (mStream->update_sink_metadata_v7 == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSinkMetadataV7(sinkMetadata);
+ }
+}
+#endif
Return<void> StreamIn::getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) {
Result retval = Result::NOT_SUPPORTED;
diff --git a/audio/core/all-versions/default/StreamOut.cpp b/audio/core/all-versions/default/StreamOut.cpp
index 5633cbb..ffd3b6b 100644
--- a/audio/core/all-versions/default/StreamOut.cpp
+++ b/audio/core/all-versions/default/StreamOut.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "StreamOutHAL"
#include "core/default/StreamOut.h"
+#include "core/default/Conversions.h"
#include "core/default/Util.h"
//#define LOG_NDEBUG 0
@@ -585,26 +586,71 @@
}
#if MAJOR_VERSION >= 4
-Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
- if (mStream->update_source_metadata == nullptr) {
- return Void(); // not supported by the HAL
- }
+Result StreamOut::doUpdateSourceMetadata(const SourceMetadata& sourceMetadata) {
std::vector<playback_track_metadata_t> halTracks;
- halTracks.reserve(sourceMetadata.tracks.size());
- for (auto& metadata : sourceMetadata.tracks) {
- playback_track_metadata_t halTrackMetadata = {.gain = metadata.gain};
- (void)HidlUtils::audioUsageToHal(metadata.usage, &halTrackMetadata.usage);
- (void)HidlUtils::audioContentTypeToHal(metadata.contentType,
- &halTrackMetadata.content_type);
- halTracks.push_back(std::move(halTrackMetadata));
+#if MAJOR_VERSION <= 6
+ (void)sourceMetadataToHal(sourceMetadata, &halTracks);
+#else
+ // Validate whether a conversion to V7 is possible. This is needed
+ // to have a consistent behavior of the HAL regardless of the API
+ // version of the legacy HAL (and also to be consistent with openOutputStream).
+ std::vector<playback_track_metadata_v7> halTracksV7;
+ if (status_t status = sourceMetadataToHalV7(sourceMetadata, &halTracksV7); status == NO_ERROR) {
+ halTracks.reserve(halTracksV7.size());
+ for (auto metadata_v7 : halTracksV7) {
+ halTracks.push_back(std::move(metadata_v7.base));
+ }
+ } else {
+ return Stream::analyzeStatus("sourceMetadataToHal", status);
}
+#endif // MAJOR_VERSION <= 6
const source_metadata_t halMetadata = {
.track_count = halTracks.size(),
.tracks = halTracks.data(),
};
mStream->update_source_metadata(mStream, &halMetadata);
+ return Result::OK;
+}
+
+#if MAJOR_VERSION >= 7
+Result StreamOut::doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata) {
+ std::vector<playback_track_metadata_v7> halTracks;
+ if (status_t status = sourceMetadataToHalV7(sourceMetadata, &halTracks); status != NO_ERROR) {
+ return Stream::analyzeStatus("sourceMetadataToHal", status);
+ }
+ const source_metadata_v7_t halMetadata = {
+ .track_count = halTracks.size(),
+ .tracks = halTracks.data(),
+ };
+ mStream->update_source_metadata_v7(mStream, &halMetadata);
+ return Result::OK;
+}
+#endif // MAJOR_VERSION >= 7
+
+#if MAJOR_VERSION <= 6
+Return<void> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+ if (mStream->update_source_metadata == nullptr) {
+ return Void(); // not supported by the HAL
+ }
+ (void)doUpdateSourceMetadata(sourceMetadata);
return Void();
}
+#elif MAJOR_VERSION >= 7
+Return<Result> StreamOut::updateSourceMetadata(const SourceMetadata& sourceMetadata) {
+ if (mDevice->version() < AUDIO_DEVICE_API_VERSION_3_2) {
+ if (mStream->update_source_metadata == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSourceMetadata(sourceMetadata);
+ } else {
+ if (mStream->update_source_metadata_v7 == nullptr) {
+ return Result::NOT_SUPPORTED;
+ }
+ return doUpdateSourceMetadataV7(sourceMetadata);
+ }
+}
+#endif
+
Return<Result> StreamOut::selectPresentation(int32_t /*presentationId*/, int32_t /*programId*/) {
return Result::NOT_SUPPORTED; // TODO: propagate to legacy
}
diff --git a/audio/core/all-versions/default/include/core/default/Conversions.h b/audio/core/all-versions/default/include/core/default/Conversions.h
index 2372771..61720d5 100644
--- a/audio/core/all-versions/default/include/core/default/Conversions.h
+++ b/audio/core/all-versions/default/include/core/default/Conversions.h
@@ -35,12 +35,6 @@
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
-#if MAJOR_VERSION <= 6
-// Temporary version for compatibility with forks of the default implementation.
-// Will be removed, do not use!
-std::string deviceAddressToHal(const DeviceAddress& address);
-#endif
-
status_t deviceAddressToHal(const DeviceAddress& device, audio_devices_t* halDeviceType,
char* halDeviceAddress);
status_t deviceAddressFromHal(audio_devices_t halDeviceType, const char* halDeviceAddress,
@@ -49,6 +43,10 @@
#if MAJOR_VERSION >= 4
bool halToMicrophoneCharacteristics(MicrophoneInfo* pDst,
const struct audio_microphone_characteristic_t& src);
+status_t sinkMetadataToHal(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata>* halTracks);
+status_t sourceMetadataToHal(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_t>* halTracks);
#endif
#if MAJOR_VERSION <= 6
@@ -69,6 +67,11 @@
#else
bool audioInputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_input_flags_t* halFlags);
bool audioOutputFlagsToHal(const hidl_vec<AudioInOutFlag>& flags, audio_output_flags_t* halFlags);
+// Overloading isn't convenient when passing a nullptr.
+status_t sinkMetadataToHalV7(const SinkMetadata& sinkMetadata,
+ std::vector<record_track_metadata_v7_t>* halTracks);
+status_t sourceMetadataToHalV7(const SourceMetadata& sourceMetadata,
+ std::vector<playback_track_metadata_v7_t>* halTracks);
#endif
} // namespace implementation
diff --git a/audio/core/all-versions/default/include/core/default/Device.h b/audio/core/all-versions/default/include/core/default/Device.h
index 461c253..2a4d226 100644
--- a/audio/core/all-versions/default/include/core/default/Device.h
+++ b/audio/core/all-versions/default/include/core/default/Device.h
@@ -146,6 +146,8 @@
void closeOutputStream(audio_stream_out_t* stream);
audio_hw_device_t* device() const { return mDevice; }
+ uint32_t version() const { return mDevice->common.version; }
+
private:
bool mIsClosed;
audio_hw_device_t* mDevice;
@@ -161,8 +163,6 @@
// Methods from ParametersUtil.
char* halGetParameters(const char* keys) override;
int halSetParameters(const char* keysAndValues) override;
-
- uint32_t version() const { return mDevice->common.version; }
};
} // namespace implementation
diff --git a/audio/core/all-versions/default/include/core/default/StreamIn.h b/audio/core/all-versions/default/include/core/default/StreamIn.h
index b861c6c..651b3a6 100644
--- a/audio/core/all-versions/default/include/core/default/StreamIn.h
+++ b/audio/core/all-versions/default/include/core/default/StreamIn.h
@@ -114,9 +114,13 @@
Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
#if MAJOR_VERSION >= 4
+#if MAJOR_VERSION <= 6
Return<void> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
- Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#else
+ Return<Result> updateSinkMetadata(const SinkMetadata& sinkMetadata) override;
#endif
+ Return<void> getActiveMicrophones(getActiveMicrophones_cb _hidl_cb) override;
+#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 5
Return<Result> setMicrophoneDirection(MicrophoneDirection direction) override;
Return<Result> setMicrophoneFieldDimension(float zoom) override;
@@ -124,7 +128,14 @@
static Result getCapturePositionImpl(audio_stream_in_t* stream, uint64_t* frames,
uint64_t* time);
- private:
+ private:
+#if MAJOR_VERSION >= 4
+ Result doUpdateSinkMetadata(const SinkMetadata& sinkMetadata);
+#if MAJOR_VERSION >= 7
+ Result doUpdateSinkMetadataV7(const SinkMetadata& sinkMetadata);
+#endif
+#endif // MAJOR_VERSION >= 4
+
const sp<Device> mDevice;
audio_stream_in_t* mStream;
const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/StreamOut.h b/audio/core/all-versions/default/include/core/default/StreamOut.h
index 9f64e3e..b8e8515 100644
--- a/audio/core/all-versions/default/include/core/default/StreamOut.h
+++ b/audio/core/all-versions/default/include/core/default/StreamOut.h
@@ -123,9 +123,13 @@
Return<void> createMmapBuffer(int32_t minSizeFrames, createMmapBuffer_cb _hidl_cb) override;
Return<void> getMmapPosition(getMmapPosition_cb _hidl_cb) override;
#if MAJOR_VERSION >= 4
- Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
Return<Result> selectPresentation(int32_t presentationId, int32_t programId) override;
+#if MAJOR_VERSION <= 6
+ Return<void> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
+#else
+ Return<Result> updateSourceMetadata(const SourceMetadata& sourceMetadata) override;
#endif
+#endif // MAJOR_VERSION >= 4
#if MAJOR_VERSION >= 6
Return<void> getDualMonoMode(getDualMonoMode_cb _hidl_cb) override;
Return<Result> setDualMonoMode(DualMonoMode mode) override;
@@ -143,6 +147,13 @@
#endif
private:
+#if MAJOR_VERSION >= 4
+ Result doUpdateSourceMetadata(const SourceMetadata& sourceMetadata);
+#if MAJOR_VERSION >= 7
+ Result doUpdateSourceMetadataV7(const SourceMetadata& sourceMetadata);
+#endif
+#endif // MAJOR_VERSION >= 4
+
const sp<Device> mDevice;
audio_stream_out_t* mStream;
const sp<Stream> mStreamCommon;
diff --git a/audio/core/all-versions/default/include/core/default/Util.h b/audio/core/all-versions/default/include/core/default/Util.h
index 3d629cd..78ae03e 100644
--- a/audio/core/all-versions/default/include/core/default/Util.h
+++ b/audio/core/all-versions/default/include/core/default/Util.h
@@ -20,8 +20,6 @@
#include PATH(android/hardware/audio/FILE_VERSION/types.h)
#include <algorithm>
-#include <sstream>
-#include <string>
#include <vector>
#include <system/audio.h>
@@ -72,16 +70,6 @@
return analyzeStatus(status);
}
-static inline std::vector<std::string> splitString(const std::string& s, char separator) {
- std::istringstream iss(s);
- std::string t;
- std::vector<std::string> result;
- while (std::getline(iss, t, separator)) {
- result.push_back(std::move(t));
- }
- return result;
-}
-
} // namespace util
} // namespace implementation
} // namespace CPP_VERSION
diff --git a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
index 9a4a8b2..5076dcf 100644
--- a/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/4.0/AudioPrimaryHidlHalTest.cpp
@@ -72,7 +72,10 @@
config.base.format = toString(xsd::AudioFormat::AUDIO_FORMAT_PCM_16_BIT);
hidl_vec<hidl_string> flags;
const SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
+ {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC),
+ .gain = 1,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
EventFlag* efGroup;
for (auto microphone : microphones) {
@@ -234,22 +237,14 @@
TEST_P(InputStreamTest, updateSinkMetadata) {
doc::test("The HAL should not crash on metadata change");
-
#if MAJOR_VERSION <= 6
hidl_enum_range<AudioSource> range;
-#elif MAJOR_VERSION >= 7
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
-#endif
// Test all possible track configuration
for (auto source : range) {
for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
const SinkMetadata metadata = {{{.source = source, .gain = volume}}};
-#elif MAJOR_VERSION >= 7
- const SinkMetadata metadata = {{{.source = toString(source), .gain = volume}}};
-#endif
ASSERT_OK(stream->updateSinkMetadata(metadata))
- << "source=" << toString(source) << ", volume=" << volume;
+ << "source=" << toString(source) << ", volume=" << volume;
}
}
@@ -257,9 +252,30 @@
// Set no metadata as if all stream track had stopped
ASSERT_OK(stream->updateSinkMetadata({}));
-
// Restore initial
ASSERT_OK(stream->updateSinkMetadata(initMetadata));
+
+#elif MAJOR_VERSION >= 7
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioSource> range;
+ // Test all possible track configuration
+ for (auto source : range) {
+ for (float volume : {0.0, 0.5, 1.0}) {
+ const SinkMetadata metadata = {
+ {{.source = toString(source),
+ .gain = volume,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(metadata))
+ << "source=" << toString(source) << ", volume=" << volume;
+ }
+ }
+ // Do not test concurrent capture as this is not officially supported
+
+ // Set no metadata as if all stream track had stopped
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata({}));
+ // Restore initial
+ ASSERT_RESULT(okOrNotSupported, stream->updateSinkMetadata(initMetadata));
+#endif
}
TEST_P(OutputStreamTest, SelectPresentation) {
@@ -269,56 +285,82 @@
TEST_P(OutputStreamTest, updateSourceMetadata) {
doc::test("The HAL should not crash on metadata change");
-
#if MAJOR_VERSION <= 6
hidl_enum_range<AudioUsage> usageRange;
hidl_enum_range<AudioContentType> contentRange;
-#elif MAJOR_VERSION >= 7
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
- xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
-#endif
// Test all possible track configuration
for (auto usage : usageRange) {
for (auto content : contentRange) {
for (float volume : {0.0, 0.5, 1.0}) {
-#if MAJOR_VERSION <= 6
const SourceMetadata metadata = {{{usage, content, volume}}};
-#elif MAJOR_VERSION >= 7
- const SourceMetadata metadata = {{{toString(usage), toString(content), volume}}};
-#endif
ASSERT_OK(stream->updateSourceMetadata(metadata))
<< "usage=" << toString(usage) << ", content=" << toString(content)
<< ", volume=" << volume;
}
}
}
-
- // clang-format off
// Set many track of different configuration
+ // clang-format off
ASSERT_OK(stream->updateSourceMetadata(
-#if MAJOR_VERSION <= 6
{{{AudioUsage::MEDIA, AudioContentType::MUSIC, 0.1},
{AudioUsage::VOICE_COMMUNICATION, AudioContentType::SPEECH, 1.0},
{AudioUsage::ALARM, AudioContentType::SONIFICATION, 0.0},
{AudioUsage::ASSISTANT, AudioContentType::UNKNOWN, 0.3}}}
-#elif MAJOR_VERSION >= 7
- {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 0.1},
- {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH), 1.0},
- {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION), 0.0},
- {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN), 0.3}}}
-#endif
));
// clang-format on
-
// Set no metadata as if all stream track had stopped
ASSERT_OK(stream->updateSourceMetadata({}));
-
// Restore initial
ASSERT_OK(stream->updateSourceMetadata(initMetadata));
+#elif MAJOR_VERSION >= 7
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioUsage> usageRange;
+ xsdc_enum_range<android::audio::policy::configuration::V7_0::AudioContentType> contentRange;
+ // Test all possible track configuration
+ for (auto usage : usageRange) {
+ for (auto content : contentRange) {
+ for (float volume : {0.0, 0.5, 1.0}) {
+ const SourceMetadata metadata = {
+ {{toString(usage),
+ toString(content),
+ {} /* tags */,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ volume}}};
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(metadata))
+ << "usage=" << toString(usage) << ", content=" << toString(content)
+ << ", volume=" << volume;
+ }
+ }
+ }
+ // Set many track of different configuration
+ // clang-format off
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(
+ {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ 0.1},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_VOICE_COMMUNICATION),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SPEECH),
+ {}, // tags
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ 1.0},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ALARM),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_SONIFICATION),
+ {}, // tags
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ 0.0},
+ {toString(xsd::AudioUsage::AUDIO_USAGE_ASSISTANT),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_UNKNOWN),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_MONO),
+ 0.3}}}
+ ));
+ // clang-format on
+ // Set no metadata as if all stream track had stopped
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata({}));
+ // Restore initial
+ ASSERT_RESULT(okOrNotSupported, stream->updateSourceMetadata(initMetadata));
+#endif
}
TEST_P(AudioPrimaryHidlTest, setMode) {
diff --git a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
index bd8de2d..94fa92a 100644
--- a/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/6.0/AudioPrimaryHidlHalTest.cpp
@@ -18,92 +18,120 @@
#include "5.0/AudioPrimaryHidlHalTest.cpp"
#if MAJOR_VERSION <= 6
-const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getOutputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- auto flags = ioProfile->getFlags();
- for (auto& config : configs) {
- // Some combinations of flags declared in the config file require special
- // treatment.
- if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
- config.offloadInfo.sampleRateHz = config.sampleRateHz;
- config.offloadInfo.channelMask = config.channelMask;
- config.offloadInfo.format = config.format;
- config.offloadInfo.streamType = AudioStreamType::MUSIC;
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 256; // arbitrary value
- config.offloadInfo.usage = AudioUsage::MEDIA;
- result.emplace_back(device, config,
- AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
- AudioOutputFlag::DIRECT));
- } else {
- if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
- flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
- }
- result.emplace_back(device, config, AudioOutputFlag(flags));
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getOutputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ auto flags = ioProfile->getFlags();
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+ config.offloadInfo.sampleRateHz = config.sampleRateHz;
+ config.offloadInfo.channelMask = config.channelMask;
+ config.offloadInfo.format = config.format;
+ config.offloadInfo.streamType = AudioStreamType::MUSIC;
+ config.offloadInfo.bitRatePerSecond = 320;
+ config.offloadInfo.durationMicroseconds = -1;
+ config.offloadInfo.bitWidth = 16;
+ config.offloadInfo.bufferSize = 256; // arbitrary value
+ config.offloadInfo.usage = AudioUsage::MEDIA;
+ result.emplace_back(device, config,
+ AudioOutputFlag(AudioOutputFlag::COMPRESS_OFFLOAD |
+ AudioOutputFlag::DIRECT));
+ } else {
+ if (flags & AUDIO_OUTPUT_FLAG_PRIMARY) { // ignore the flag
+ flags &= ~AUDIO_OUTPUT_FLAG_PRIMARY;
}
+ result.emplace_back(device, config, AudioOutputFlag(flags));
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(false);
return parameters;
}
-const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
- std::vector<DeviceConfigParameter> result;
- for (const auto& device : getDeviceParameters()) {
- auto module =
- getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
- for (const auto& ioProfile : module->getInputProfiles()) {
- for (const auto& profile : ioProfile->getAudioProfiles()) {
- const auto& channels = profile->getChannels();
- const auto& sampleRates = profile->getSampleRates();
- auto configs = ConfigHelper::combineAudioConfig(
- std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
- std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
- profile->getFormat());
- for (const auto& config : configs) {
- result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
- }
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(true);
+ return parameters;
+}
+
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ for (const auto& ioProfile : module->getInputProfiles()) {
+ for (const auto& profile : ioProfile->getAudioProfiles()) {
+ const auto& channels = profile->getChannels();
+ const auto& sampleRates = profile->getSampleRates();
+ auto configs = ConfigHelper::combineAudioConfig(
+ std::vector<audio_channel_mask_t>(channels.begin(), channels.end()),
+ std::vector<uint32_t>(sampleRates.begin(), sampleRates.end()),
+ profile->getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, AudioInputFlag(ioProfile->getFlags()));
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
+ if (oneProfilePerDevice) break;
}
- return result;
- }();
+ }
+ return result;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
return parameters;
}
#endif // MAJOR_VERSION <= 6
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedOutputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
+class SingleOutputConfigTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(SingleOutputConfigTest, CloseDeviceWithOpenedOutputStreams) {
+ doc::test("Verify that a device can't be closed if there are output streams opened");
#if MAJOR_VERSION <= 6
DeviceAddress address{.device = AudioDevice::OUT_DEFAULT};
SourceMetadata initMetadata = {{{AudioUsage::MEDIA, AudioContentType::MUSIC, 1 /* gain */}}};
- auto flags = hidl_bitfield<AudioOutputFlag>(AudioOutputFlag::NONE);
#elif MAJOR_VERSION >= 7
DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
- SourceMetadata initMetadata = {
- {{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
- toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC), 1 /* gain */}}};
- hidl_vec<AudioInOutFlag> flags;
+ SourceMetadata initMetadata = {{{toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ {} /* tags */,
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
+ 1 /* gain */}}};
#endif
- AudioConfig config{};
+ const AudioConfig& config = getConfig();
+ auto flags = getOutputFlags();
sp<IStreamOut> stream;
StreamHelper<IStreamOut> helper(stream);
AudioConfig suggestedConfig{};
@@ -118,23 +146,27 @@
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleOutputConfig, SingleOutputConfigTest,
+ ::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleOutputConfig);
-TEST_P(AudioHidlDeviceTest, CloseDeviceWithOpenedInputStreams) {
- doc::test("Verify that a device can't be closed if there are streams opened");
- if (!getCachedPolicyConfig().haveInputProfilesInModule(getDeviceName())) {
- GTEST_SKIP() << "Device doesn't have input profiles";
- }
+class SingleInputConfigTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(SingleInputConfigTest, CloseDeviceWithOpenedInputStreams) {
+ doc::test("Verify that a device can't be closed if there are input streams opened");
#if MAJOR_VERSION <= 6
DeviceAddress address{.device = AudioDevice::IN_DEFAULT};
SinkMetadata initMetadata = {{{.source = AudioSource::MIC, .gain = 1}}};
- auto flags = hidl_bitfield<AudioInputFlag>(AudioInputFlag::NONE);
#elif MAJOR_VERSION >= 7
DeviceAddress address{.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC), .gain = 1}}};
- hidl_vec<AudioInOutFlag> flags;
+ {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_MIC),
+ .gain = 1,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
- AudioConfig config{};
+ const AudioConfig& config = getConfig();
+ auto flags = getInputFlags();
sp<IStreamIn> stream;
StreamHelper<IStreamIn> helper(stream);
AudioConfig suggestedConfig{};
@@ -149,6 +181,10 @@
ASSERT_OK(getDevice()->close());
ASSERT_TRUE(resetDevice());
}
+INSTANTIATE_TEST_CASE_P(SingleInputConfig, SingleInputConfigTest,
+ ::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(SingleInputConfig);
TEST_P(AudioPatchHidlTest, UpdatePatchInvalidHandle) {
doc::test("Verify that passing an invalid handle to updateAudioPatch is checked");
diff --git a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
index 6bb7995..3b6d5f2 100644
--- a/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
+++ b/audio/core/all-versions/vts/functional/7.0/AudioPrimaryHidlHalTest.cpp
@@ -25,7 +25,6 @@
for (auto channelMask : channelMasks) {
for (auto sampleRate : sampleRates) {
AudioConfig config{};
- // leave offloadInfo to 0
config.base.channelMask = toString(channelMask);
config.base.sampleRateHz = sampleRate;
config.base.format = format;
@@ -35,56 +34,158 @@
return configs;
}
+static std::tuple<std::vector<AudioInOutFlag>, bool> generateOutFlags(
+ const xsd::MixPorts::MixPort& mixPort) {
+ static const std::vector<AudioInOutFlag> offloadFlags = {
+ toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
+ toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
+ std::vector<AudioInOutFlag> flags;
+ bool isOffload = false;
+ if (mixPort.hasFlags()) {
+ auto xsdFlags = mixPort.getFlags();
+ isOffload = std::find(xsdFlags.begin(), xsdFlags.end(),
+ xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
+ xsdFlags.end();
+ if (!isOffload) {
+ for (auto flag : xsdFlags) {
+ if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
+ flags.push_back(toString(flag));
+ }
+ }
+ } else {
+ flags = offloadFlags;
+ }
+ }
+ return {flags, isOffload};
+}
+
+static AudioOffloadInfo generateOffloadInfo(const AudioConfigBase& base) {
+ return AudioOffloadInfo{
+ .base = base,
+ .streamType = toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC),
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .bitRatePerSecond = 320,
+ .durationMicroseconds = -1,
+ .bitWidth = 16,
+ .bufferSize = 256 // arbitrary value
+ };
+}
+
+static std::vector<DeviceConfigParameter> generateOutputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile
+ auto [flags, isOffload] = generateOutFlags(mixPort);
+ for (const auto& profile : mixPort.getProfile()) {
+ auto configs = combineAudioConfig(profile.getChannelMasks(),
+ profile.getSamplingRates(), profile.getFormat());
+ for (auto& config : configs) {
+ // Some combinations of flags declared in the config file require special
+ // treatment.
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(config.base));
+ }
+ result.emplace_back(device, config, flags);
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ }
+ return result;
+}
+
const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateOutputDeviceConfigParameters(true);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getOutputDeviceInvalidConfigParameters(
+ bool generateInvalidFlags = true) {
+ static std::vector<DeviceConfigParameter> parameters = [&] {
std::vector<DeviceConfigParameter> result;
- const std::vector<AudioInOutFlag> offloadFlags = {
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD),
- toString(xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_DIRECT)};
for (const auto& device : getDeviceParameters()) {
auto module =
getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
if (!module || !module->getFirstMixPorts()) break;
+ bool hasRegularConfig = false, hasOffloadConfig = false;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::source) continue; // not an output profile
- std::vector<AudioInOutFlag> flags;
- bool isOffload = false;
- if (mixPort.hasFlags()) {
- auto xsdFlags = mixPort.getFlags();
- isOffload =
- std::find(xsdFlags.begin(), xsdFlags.end(),
- xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) !=
- xsdFlags.end();
- if (!isOffload) {
- for (auto flag : xsdFlags) {
- if (flag != xsd::AudioInOutFlag::AUDIO_OUTPUT_FLAG_PRIMARY) {
- flags.push_back(toString(flag));
- }
- }
- } else {
- flags = offloadFlags;
- }
- }
+ auto [validFlags, isOffload] = generateOutFlags(mixPort);
+ if ((!isOffload && hasRegularConfig) || (isOffload && hasOffloadConfig)) continue;
for (const auto& profile : mixPort.getProfile()) {
- auto configs =
- combineAudioConfig(profile.getChannelMasks(),
- profile.getSamplingRates(), profile.getFormat());
- for (auto& config : configs) {
- // Some combinations of flags declared in the config file require special
- // treatment.
+ if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+ !profile.hasChannelMasks())
+ continue;
+ AudioConfigBase validBase = {
+ profile.getFormat(),
+ static_cast<uint32_t>(profile.getSamplingRates()[0]),
+ toString(profile.getChannelMasks()[0])};
+ {
+ AudioConfig config{.base = validBase};
+ config.base.channelMask = "random_string";
if (isOffload) {
- config.offloadInfo.base = config.base;
- config.offloadInfo.streamType =
- toString(xsd::AudioStreamType::AUDIO_STREAM_MUSIC);
- config.offloadInfo.usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA);
- config.offloadInfo.bitRatePerSecond = 320;
- config.offloadInfo.durationMicroseconds = -1;
- config.offloadInfo.bitWidth = 16;
- config.offloadInfo.bufferSize = 256; // arbitrary value
+ config.offloadInfo.info(generateOffloadInfo(validBase));
}
+ result.emplace_back(device, config, validFlags);
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.base.format = "random_string";
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ }
+ result.emplace_back(device, config, validFlags);
+ }
+ if (generateInvalidFlags) {
+ AudioConfig config{.base = validBase};
+ if (isOffload) {
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ }
+ std::vector<AudioInOutFlag> flags = {"random_string", ""};
result.emplace_back(device, config, flags);
}
+ if (isOffload) {
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().base.channelMask = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().base.format = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().streamType = "random_string";
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.offloadInfo.info(generateOffloadInfo(validBase));
+ config.offloadInfo.info().usage = "random_string";
+ }
+ hasOffloadConfig = true;
+ } else {
+ hasRegularConfig = true;
+ }
+ break;
}
+ if (hasOffloadConfig && hasRegularConfig) break;
}
}
return result;
@@ -92,32 +193,392 @@
return parameters;
}
+static std::vector<DeviceConfigParameter> generateInputDeviceConfigParameters(
+ bool oneProfilePerDevice) {
+ std::vector<DeviceConfigParameter> result;
+ for (const auto& device : getDeviceParameters()) {
+ auto module =
+ getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
+ if (!module || !module->getFirstMixPorts()) break;
+ for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
+ if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
+ std::vector<AudioInOutFlag> flags;
+ if (mixPort.hasFlags()) {
+ std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
+ std::back_inserter(flags), [](auto flag) { return toString(flag); });
+ }
+ for (const auto& profile : mixPort.getProfile()) {
+ auto configs = combineAudioConfig(profile.getChannelMasks(),
+ profile.getSamplingRates(), profile.getFormat());
+ for (const auto& config : configs) {
+ result.emplace_back(device, config, flags);
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ if (oneProfilePerDevice) break;
+ }
+ }
+ return result;
+}
+
const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters() {
- static std::vector<DeviceConfigParameter> parameters = [] {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(false);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters() {
+ static std::vector<DeviceConfigParameter> parameters =
+ generateInputDeviceConfigParameters(true);
+ return parameters;
+}
+
+const std::vector<DeviceConfigParameter>& getInputDeviceInvalidConfigParameters(
+ bool generateInvalidFlags = true) {
+ static std::vector<DeviceConfigParameter> parameters = [&] {
std::vector<DeviceConfigParameter> result;
for (const auto& device : getDeviceParameters()) {
auto module =
getCachedPolicyConfig().getModuleFromName(std::get<PARAM_DEVICE_NAME>(device));
if (!module || !module->getFirstMixPorts()) break;
+ bool hasConfig = false;
for (const auto& mixPort : module->getFirstMixPorts()->getMixPort()) {
if (mixPort.getRole() != xsd::Role::sink) continue; // not an input profile
- std::vector<AudioInOutFlag> flags;
+ std::vector<AudioInOutFlag> validFlags;
if (mixPort.hasFlags()) {
std::transform(mixPort.getFlags().begin(), mixPort.getFlags().end(),
- std::back_inserter(flags),
+ std::back_inserter(validFlags),
[](auto flag) { return toString(flag); });
}
for (const auto& profile : mixPort.getProfile()) {
- auto configs =
- combineAudioConfig(profile.getChannelMasks(),
- profile.getSamplingRates(), profile.getFormat());
- for (const auto& config : configs) {
+ if (!profile.hasFormat() || !profile.hasSamplingRates() ||
+ !profile.hasChannelMasks())
+ continue;
+ AudioConfigBase validBase = {
+ profile.getFormat(),
+ static_cast<uint32_t>(profile.getSamplingRates()[0]),
+ toString(profile.getChannelMasks()[0])};
+ {
+ AudioConfig config{.base = validBase};
+ config.base.channelMask = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ {
+ AudioConfig config{.base = validBase};
+ config.base.format = "random_string";
+ result.emplace_back(device, config, validFlags);
+ }
+ if (generateInvalidFlags) {
+ AudioConfig config{.base = validBase};
+ std::vector<AudioInOutFlag> flags = {"random_string", ""};
result.emplace_back(device, config, flags);
}
+ hasConfig = true;
+ break;
}
+ if (hasConfig) break;
}
}
return result;
}();
return parameters;
}
+
+class InvalidInputConfigNoFlagsTest : public AudioHidlTestWithDeviceConfigParameter {};
+TEST_P(InvalidInputConfigNoFlagsTest, InputBufferSizeTest) {
+ doc::test("Verify that invalid config is rejected by IDevice::getInputBufferSize method.");
+ uint64_t bufferSize;
+ ASSERT_OK(getDevice()->getInputBufferSize(getConfig(), returnIn(res, bufferSize)));
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputBufferSizeInvalidConfig, InvalidInputConfigNoFlagsTest,
+ ::testing::ValuesIn(getInputDeviceInvalidConfigParameters(false /*generateInvalidFlags*/)),
+ &DeviceConfigParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputBufferSizeInvalidConfig);
+
+enum { PARAM_DEVICE_CONFIG, PARAM_ADDRESS, PARAM_METADATA };
+enum { INDEX_SINK, INDEX_SOURCE };
+using SinkOrSourceMetadata = std::variant<SinkMetadata, SourceMetadata>;
+using StreamOpenParameter = std::tuple<DeviceConfigParameter, DeviceAddress, SinkOrSourceMetadata>;
+
+static std::string StreamOpenParameterToString(
+ const ::testing::TestParamInfo<StreamOpenParameter>& info) {
+ return DeviceConfigParameterToString(::testing::TestParamInfo<DeviceConfigParameter>{
+ std::get<PARAM_DEVICE_CONFIG>(info.param), info.index}) +
+ "__" +
+ SanitizeStringForGTestName(
+ ::testing::PrintToString(std::get<PARAM_ADDRESS>(info.param))) +
+ "__" +
+ SanitizeStringForGTestName(std::visit(
+ [](auto&& arg) -> std::string { return ::testing::PrintToString(arg); },
+ std::get<PARAM_METADATA>(info.param)));
+}
+
+class StreamOpenTest : public HidlTest, public ::testing::WithParamInterface<StreamOpenParameter> {
+ protected:
+ void SetUp() override {
+ ASSERT_NO_FATAL_FAILURE(HidlTest::SetUp()); // setup base
+ ASSERT_TRUE(getDevicesFactory() != nullptr);
+ ASSERT_TRUE(getDevice() != nullptr);
+ }
+ const std::string& getFactoryName() const override {
+ return std::get<PARAM_FACTORY_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const std::string& getDeviceName() const override {
+ return std::get<PARAM_DEVICE_NAME>(
+ std::get<PARAM_DEVICE>(std::get<PARAM_DEVICE_CONFIG>(GetParam())));
+ }
+ const AudioConfig& getConfig() const {
+ return std::get<PARAM_CONFIG>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const hidl_vec<AudioInOutFlag> getFlags() const {
+ return std::get<PARAM_FLAGS>(std::get<PARAM_DEVICE_CONFIG>(GetParam()));
+ }
+ const DeviceAddress& getDeviceAddress() const { return std::get<PARAM_ADDRESS>(GetParam()); }
+ bool isParamForInputStream() const {
+ return std::get<PARAM_METADATA>(GetParam()).index() == INDEX_SINK;
+ }
+ const SinkMetadata& getSinkMetadata() const {
+ return std::get<INDEX_SINK>(std::get<PARAM_METADATA>(GetParam()));
+ }
+ const SourceMetadata& getSourceMetadata() const {
+ return std::get<INDEX_SOURCE>(std::get<PARAM_METADATA>(GetParam()));
+ }
+};
+
+static const DeviceAddress& getValidInputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getValidOutputDeviceAddress() {
+ static const DeviceAddress valid = {
+ .deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_OUT_DEFAULT)};
+ return valid;
+}
+
+static const DeviceAddress& getInvalidDeviceAddress() {
+ static const DeviceAddress valid = {.deviceType = "random_string"};
+ return valid;
+}
+
+static const RecordTrackMetadata& getValidRecordTrackMetadata() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1};
+ return valid;
+}
+
+static const RecordTrackMetadata& getValidRecordTrackMetadataWithDest() {
+ static const RecordTrackMetadata valid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getValidOutputDeviceAddress());
+ return dest;
+ }()};
+ return valid;
+}
+
+static const RecordTrackMetadata& getInvalidSourceRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {.source = "random_string", .gain = 1};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getRecordTrackMetadataWithInvalidDest() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .destination = [] {
+ RecordTrackMetadata::Destination dest;
+ dest.device(getInvalidDeviceAddress());
+ return dest;
+ }()};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidChannelMaskRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const RecordTrackMetadata& getInvalidTagsRecordTrackMetadata() {
+ static const RecordTrackMetadata invalid = {
+ .source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getValidPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata valid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return valid;
+}
+
+static const PlaybackTrackMetadata& getInvalidUsagePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = "random_string",
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidContentTypePlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = "random_string",
+ .gain = 1};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidChannelMaskPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .channelMask = "random_string"};
+ return invalid;
+}
+
+static const PlaybackTrackMetadata& getInvalidTagsPlaybackTrackMetadata() {
+ static const PlaybackTrackMetadata invalid = {
+ .usage = toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
+ .contentType = toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ .gain = 1,
+ .tags = {{"random_string"}}};
+ return invalid;
+}
+
+static const std::vector<SourceMetadata>& getInvalidSourceMetadatas() {
+ static const std::vector<SourceMetadata> invalids = {
+ SourceMetadata{.tracks = {{getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getInvalidTagsPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidUsagePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidContentTypePlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidChannelMaskPlaybackTrackMetadata()}}},
+ SourceMetadata{.tracks = {{getValidPlaybackTrackMetadata(),
+ getInvalidTagsPlaybackTrackMetadata()}}}};
+ return invalids;
+}
+static const std::vector<SinkMetadata>& getInvalidSinkMetadatas() {
+ static const std::vector<SinkMetadata> invalids = {
+ SinkMetadata{.tracks = {{getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getInvalidTagsRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidSourceRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getRecordTrackMetadataWithInvalidDest()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidChannelMaskRecordTrackMetadata()}}},
+ SinkMetadata{.tracks = {{getValidRecordTrackMetadata(),
+ getInvalidTagsRecordTrackMetadata()}}}};
+ return invalids;
+}
+template <typename T>
+static inline std::vector<SinkOrSourceMetadata> wrapMetadata(const std::vector<T>& metadata) {
+ return std::vector<SinkOrSourceMetadata>{metadata.begin(), metadata.end()};
+}
+
+TEST_P(StreamOpenTest, OpenInputOrOutputStreamTest) {
+ doc::test(
+ "Verify that invalid arguments are rejected by "
+ "IDevice::open{Input|Output}Stream method.");
+ AudioConfig suggestedConfig{};
+ if (isParamForInputStream()) {
+ sp<IStreamIn> stream;
+ ASSERT_OK(getDevice()->openInputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSinkMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ } else {
+ sp<IStreamOut> stream;
+ ASSERT_OK(getDevice()->openOutputStream(AudioIoHandle{}, getDeviceAddress(), getConfig(),
+ getFlags(), getSourceMetadata(),
+ returnIn(res, stream, suggestedConfig)));
+ ASSERT_TRUE(stream == nullptr);
+ }
+ EXPECT_EQ(Result::INVALID_ARGUMENTS, res);
+ EXPECT_EQ(AudioConfig{}, suggestedConfig);
+}
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SinkMetadata{
+ .tracks = {{getValidRecordTrackMetadata(),
+ getValidRecordTrackMetadataWithDest()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ InputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getInputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidInputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSinkMetadatas()))),
+ &StreamOpenParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputStreamInvalidConfig);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputStreamInvalidAddress);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(InputStreamInvalidMetadata);
+
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidConfig, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceInvalidConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidAddress, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getInvalidDeviceAddress()),
+ ::testing::Values(SourceMetadata{
+ .tracks = {{getValidPlaybackTrackMetadata()}}})),
+ &StreamOpenParameterToString);
+INSTANTIATE_TEST_CASE_P(
+ OutputStreamInvalidMetadata, StreamOpenTest,
+ ::testing::Combine(::testing::ValuesIn(getOutputDeviceSingleConfigParameters()),
+ ::testing::Values(getValidOutputDeviceAddress()),
+ ::testing::ValuesIn(wrapMetadata(getInvalidSourceMetadatas()))),
+ &StreamOpenParameterToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidConfig);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidAddress);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(OutputStreamInvalidMetadata);
+
+TEST_P(OutputStreamTest, UpdateInvalidSourceMetadata) {
+ doc::test("Verify that invalid metadata is rejected by IStreamOut::updateSourceMetadata");
+ for (const auto& metadata : getInvalidSourceMetadatas()) {
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSourceMetadata(metadata))
+ << ::testing::PrintToString(metadata);
+ }
+}
+
+TEST_P(InputStreamTest, UpdateInvalidSinkMetadata) {
+ doc::test("Verify that invalid metadata is rejected by IStreamIn::updateSinkMetadata");
+ for (const auto& metadata : getInvalidSinkMetadatas()) {
+ ASSERT_RESULT(invalidArgsOrNotSupported, stream->updateSinkMetadata(metadata))
+ << ::testing::PrintToString(metadata);
+ }
+}
diff --git a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
index 43c44cb..f145b60 100644
--- a/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
+++ b/audio/core/all-versions/vts/functional/AudioPrimaryHidlHalTest.h
@@ -522,7 +522,9 @@
#if MAJOR_VERSION >= 6
const std::vector<DeviceConfigParameter>& getInputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getInputDeviceSingleConfigParameters();
const std::vector<DeviceConfigParameter>& getOutputDeviceConfigParameters();
+const std::vector<DeviceConfigParameter>& getOutputDeviceSingleConfigParameters();
#endif
#if MAJOR_VERSION >= 4
@@ -883,7 +885,7 @@
AudioHidlTestWithDeviceConfigParameter::TearDown();
}
- protected:
+ protected:
AudioConfig audioConfig;
DeviceAddress address = {};
sp<Stream> stream;
@@ -926,6 +928,8 @@
const SourceMetadata initMetadata = {
{ { toString(xsd::AudioUsage::AUDIO_USAGE_MEDIA),
toString(xsd::AudioContentType::AUDIO_CONTENT_TYPE_MUSIC),
+ {},
+ toString(xsd::AudioChannelMask::AUDIO_CHANNEL_OUT_STEREO),
1 /* gain */ } }};
#endif
};
@@ -971,7 +975,7 @@
ASSERT_NO_FATAL_FAILURE(OpenStreamTest::SetUp()); // setup base
#if MAJOR_VERSION <= 6
address.device = AudioDevice::IN_DEFAULT;
-#elif MAJOR_VERSION <= 7
+#elif MAJOR_VERSION >= 7
address.deviceType = toString(xsd::AudioDevice::AUDIO_DEVICE_IN_DEFAULT);
#endif
const AudioConfig& config = getConfig();
@@ -986,12 +990,15 @@
protected:
#if MAJOR_VERSION == 2
- const AudioSource initMetadata = AudioSource::DEFAULT;
+ const AudioSource initMetadata = AudioSource::DEFAULT;
#elif MAJOR_VERSION >= 4 && MAJOR_VERSION <= 6
const SinkMetadata initMetadata = {{ {.source = AudioSource::DEFAULT, .gain = 1 } }};
#elif MAJOR_VERSION >= 7
const SinkMetadata initMetadata = {
- {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT), .gain = 1}}};
+ {{.source = toString(xsd::AudioSource::AUDIO_SOURCE_DEFAULT),
+ .gain = 1,
+ .tags = {},
+ .channelMask = toString(xsd::AudioChannelMask::AUDIO_CHANNEL_IN_MONO)}}};
#endif
};
diff --git a/identity/aidl/default/Android.bp b/identity/aidl/default/Android.bp
index 2eb0faa..7f342d0 100644
--- a/identity/aidl/default/Android.bp
+++ b/identity/aidl/default/Android.bp
@@ -1,3 +1,67 @@
+cc_library_static {
+ name: "android.hardware.identity-libeic-hal-common",
+ vendor_available: true,
+ srcs: [
+ "common/IdentityCredential.cpp",
+ "common/IdentityCredentialStore.cpp",
+ "common/WritableIdentityCredential.cpp",
+ ],
+ export_include_dirs: [
+ "common",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ shared_libs: [
+ "liblog",
+ "libcrypto",
+ "libbinder_ndk",
+ "libkeymaster_messages",
+ ],
+ static_libs: [
+ "libbase",
+ "libcppbor",
+ "libutils",
+ "libsoft_attestation_cert",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
+ "android.hardware.identity-support-lib",
+ "android.hardware.identity-ndk_platform",
+ "android.hardware.keymaster-ndk_platform",
+ ],
+}
+
+cc_library_static {
+ name: "android.hardware.identity-libeic-library",
+ vendor_available: true,
+ srcs: [
+ "libeic/EicCbor.c",
+ "libeic/EicPresentation.c",
+ "libeic/EicProvisioning.c",
+ "EicOpsImpl.cc",
+ ],
+ export_include_dirs: [
+ "libeic",
+ ],
+ cflags: [
+ "-DEIC_COMPILATION",
+ "-Wall",
+ "-Wextra",
+ "-DEIC_DEBUG",
+ // Allow using C2x extensions such as omitting parameter names
+ "-Wno-c2x-extensions",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcrypto",
+ ],
+ static_libs: [
+ "android.hardware.identity-support-lib",
+ ],
+}
+
cc_binary {
name: "android.hardware.identity-service.example",
relative_install_path: "hw",
@@ -7,23 +71,30 @@
cflags: [
"-Wall",
"-Wextra",
+ "-g",
],
shared_libs: [
- "libbase",
- "libbinder_ndk",
- "libcppbor",
- "libcrypto",
"liblog",
+ "libcrypto",
+ "libbinder_ndk",
+ "libkeymaster_messages",
+ ],
+ static_libs: [
+ "libbase",
+ "libcppbor",
"libutils",
+ "libsoft_attestation_cert",
+ "libkeymaster_portable",
+ "libsoft_attestation_cert",
+ "libpuresoftkeymasterdevice",
"android.hardware.identity-support-lib",
"android.hardware.identity-ndk_platform",
"android.hardware.keymaster-ndk_platform",
+ "android.hardware.identity-libeic-hal-common",
+ "android.hardware.identity-libeic-library",
],
srcs: [
- "IdentityCredential.cpp",
- "IdentityCredentialStore.cpp",
- "WritableIdentityCredential.cpp",
- "Util.cpp",
"service.cpp",
+ "FakeSecureHardwareProxy.cpp",
],
}
diff --git a/identity/aidl/default/EicOpsImpl.cc b/identity/aidl/default/EicOpsImpl.cc
new file mode 100644
index 0000000..3f2ec8b
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.cc
@@ -0,0 +1,506 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "EicOpsImpl"
+
+#include <optional>
+#include <tuple>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include "EicOps.h"
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+void* eicMemSet(void* s, int c, size_t n) {
+ return memset(s, c, n);
+}
+
+void* eicMemCpy(void* dest, const void* src, size_t n) {
+ return memcpy(dest, src, n);
+}
+
+size_t eicStrLen(const char* s) {
+ return strlen(s);
+}
+
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n) {
+ return CRYPTO_memcmp(s1, s2, n);
+}
+
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ HMAC_CTX_init(realCtx);
+ if (HMAC_Init_ex(realCtx, key, keySize, EVP_sha256(), nullptr /* impl */) != 1) {
+ LOG(ERROR) << "Error initializing HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ if (HMAC_Update(realCtx, data, len) != 1) {
+ LOG(ERROR) << "Error updating HMAC_CTX";
+ }
+}
+
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ HMAC_CTX* realCtx = (HMAC_CTX*)ctx;
+ unsigned int size = 0;
+ if (HMAC_Final(realCtx, digest, &size) != 1) {
+ LOG(ERROR) << "Error finalizing HMAC_CTX";
+ }
+ if (size != EIC_SHA256_DIGEST_SIZE) {
+ LOG(ERROR) << "Expected 32 bytes from HMAC_Final, got " << size;
+ }
+}
+
+void eicOpsSha256Init(EicSha256Ctx* ctx) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Init(realCtx);
+}
+
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Update(realCtx, data, len);
+}
+
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ SHA256_CTX* realCtx = (SHA256_CTX*)ctx;
+ SHA256_Final(digest, realCtx);
+}
+
+bool eicOpsRandom(uint8_t* buf, size_t numBytes) {
+ optional<vector<uint8_t>> bytes = ::android::hardware::identity::support::getRandom(numBytes);
+ if (!bytes.has_value()) {
+ return false;
+ }
+ memcpy(buf, bytes.value().data(), numBytes);
+ return true;
+}
+
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData) {
+ vector<uint8_t> cppKey;
+ cppKey.resize(16);
+ memcpy(cppKey.data(), key, 16);
+
+ vector<uint8_t> cppData;
+ cppData.resize(dataSize);
+ if (dataSize > 0) {
+ memcpy(cppData.data(), data, dataSize);
+ }
+
+ vector<uint8_t> cppAAD;
+ cppAAD.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(cppAAD.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+ }
+
+ vector<uint8_t> cppNonce;
+ cppNonce.resize(12);
+ memcpy(cppNonce.data(), nonce, 12);
+
+ optional<vector<uint8_t>> cppEncryptedData =
+ android::hardware::identity::support::encryptAes128Gcm(cppKey, cppNonce, cppData,
+ cppAAD);
+ if (!cppEncryptedData.has_value()) {
+ return false;
+ }
+
+ memcpy(encryptedData, cppEncryptedData.value().data(), cppEncryptedData.value().size());
+ return true;
+}
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData, size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize, uint8_t* data) {
+ vector<uint8_t> keyVec;
+ keyVec.resize(16);
+ memcpy(keyVec.data(), key, 16);
+
+ vector<uint8_t> encryptedDataVec;
+ encryptedDataVec.resize(encryptedDataSize);
+ if (encryptedDataSize > 0) {
+ memcpy(encryptedDataVec.data(), encryptedData, encryptedDataSize);
+ }
+
+ vector<uint8_t> aadVec;
+ aadVec.resize(additionalAuthenticationDataSize);
+ if (additionalAuthenticationDataSize > 0) {
+ memcpy(aadVec.data(), additionalAuthenticationData, additionalAuthenticationDataSize);
+ }
+
+ optional<vector<uint8_t>> decryptedDataVec =
+ android::hardware::identity::support::decryptAes128Gcm(keyVec, encryptedDataVec,
+ aadVec);
+ if (!decryptedDataVec.has_value()) {
+ eicDebug("Error decrypting data");
+ return false;
+ }
+ if (decryptedDataVec.value().size() != encryptedDataSize - 28) {
+ eicDebug("Decrypted data is size %zd, expected %zd", decryptedDataVec.value().size(),
+ encryptedDataSize - 28);
+ return false;
+ }
+
+ if (decryptedDataVec.value().size() > 0) {
+ memcpy(data, decryptedDataVec.value().data(), decryptedDataVec.value().size());
+ }
+ return true;
+}
+
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]) {
+ optional<vector<uint8_t>> keyPair = android::hardware::identity::support::createEcKeyPair();
+ if (!keyPair) {
+ eicDebug("Error creating EC keypair");
+ return false;
+ }
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(keyPair.value());
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ optional<vector<uint8_t>> pubKey =
+ android::hardware::identity::support::ecKeyPairGetPublicKey(keyPair.value());
+ if (!pubKey) {
+ eicDebug("Error extracting public key");
+ return false;
+ }
+ // ecKeyPairGetPublicKey() returns 0x04 | x | y, we don't want the leading 0x04.
+ if (pubKey.value().size() != EIC_P256_PUB_KEY_SIZE + 1) {
+ eicDebug("Private key is %zd bytes long, expected %zd", pubKey.value().size(),
+ (size_t)EIC_P256_PRIV_KEY_SIZE + 1);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+ memcpy(publicKey, pubKey.value().data() + 1, EIC_P256_PUB_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential, uint8_t* cert,
+ size_t* certSize) {
+ vector<uint8_t> challengeVec(challengeSize);
+ memcpy(challengeVec.data(), challenge, challengeSize);
+
+ vector<uint8_t> applicationIdVec(applicationIdSize);
+ memcpy(applicationIdVec.data(), applicationId, applicationIdSize);
+
+ optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> ret =
+ android::hardware::identity::support::createEcKeyPairAndAttestation(
+ challengeVec, applicationIdVec, testCredential);
+ if (!ret) {
+ eicDebug("Error generating CredentialKey and attestation");
+ return false;
+ }
+
+ // Extract certificate chain.
+ vector<uint8_t> flatChain =
+ android::hardware::identity::support::certificateChainJoin(ret.value().second);
+ if (*certSize < flatChain.size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+ flatChain.size());
+ return false;
+ }
+ memcpy(cert, flatChain.data(), flatChain.size());
+ *certSize = flatChain.size();
+
+ // Extract private key.
+ optional<vector<uint8_t>> privKey =
+ android::hardware::identity::support::ecKeyPairGetPrivateKey(ret.value().first);
+ if (!privKey) {
+ eicDebug("Error extracting private key");
+ return false;
+ }
+ if (privKey.value().size() != EIC_P256_PRIV_KEY_SIZE) {
+ eicDebug("Private key is not %zd bytes long as expected", (size_t)EIC_P256_PRIV_KEY_SIZE);
+ return false;
+ }
+
+ memcpy(privateKey, privKey.value().data(), EIC_P256_PRIV_KEY_SIZE);
+
+ return true;
+}
+
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+ const char* issuerName, const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, uint8_t* cert,
+ size_t* certSize) { // inout
+ vector<uint8_t> signingKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(signingKeyVec.data(), signingKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ std::string serialDecimal = android::base::StringPrintf("%d", serial);
+
+ optional<vector<uint8_t>> certVec =
+ android::hardware::identity::support::ecPublicKeyGenerateCertificate(
+ pubKeyVec, signingKeyVec, serialDecimal, issuerName, subjectName,
+ validityNotBefore, validityNotAfter);
+ if (!certVec) {
+ eicDebug("Error generating certificate");
+ return false;
+ }
+
+ if (*certSize < certVec.value().size()) {
+ eicDebug("Buffer for certificate is only %zd bytes long, need %zd bytes", *certSize,
+ certVec.value().size());
+ return false;
+ }
+ memcpy(cert, certVec.value().data(), certVec.value().size());
+ *certSize = certVec.value().size();
+
+ return true;
+}
+
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ vector<uint8_t> digestVec(EIC_SHA256_DIGEST_SIZE);
+ memcpy(digestVec.data(), digestOfData, EIC_SHA256_DIGEST_SIZE);
+
+ optional<vector<uint8_t>> derSignature =
+ android::hardware::identity::support::signEcDsaDigest(privKeyVec, digestVec);
+ if (!derSignature) {
+ eicDebug("Error signing data");
+ return false;
+ }
+
+ ECDSA_SIG* sig;
+ const unsigned char* p = derSignature.value().data();
+ sig = d2i_ECDSA_SIG(nullptr, &p, derSignature.value().size());
+ if (sig == nullptr) {
+ eicDebug("Error decoding DER signature");
+ return false;
+ }
+
+ if (BN_bn2binpad(sig->r, signature, 32) != 32) {
+ eicDebug("Error encoding r");
+ return false;
+ }
+ if (BN_bn2binpad(sig->s, signature + 32, 32) != 32) {
+ eicDebug("Error encoding s");
+ return false;
+ }
+
+ return true;
+}
+
+static const uint8_t hbkTest[16] = {0};
+static const uint8_t hbkReal[16] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
+
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential) {
+ if (testCredential) {
+ return hbkTest;
+ }
+ return hbkReal;
+}
+
+bool eicOpsValidateAuthToken(uint64_t /* challenge */, uint64_t /* secureUserId */,
+ uint64_t /* authenticatorId */, int /* hardwareAuthenticatorType */,
+ uint64_t /* timeStamp */, const uint8_t* /* mac */,
+ size_t /* macSize */, uint64_t /* verificationTokenChallenge */,
+ uint64_t /* verificationTokenTimeStamp */,
+ int /* verificationTokenSecurityLevel */,
+ const uint8_t* /* verificationTokenMac */,
+ size_t /* verificationTokenMacSize */) {
+ // Here's where we would validate the passed-in |authToken| to assure ourselves
+ // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
+ //
+ // However this involves calculating the MAC which requires access to the to
+ // a pre-shared key which we don't have...
+ //
+ return true;
+}
+
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+ size_t* publicKeySize) {
+ vector<uint8_t> chain;
+ chain.resize(x509CertSize);
+ memcpy(chain.data(), x509Cert, x509CertSize);
+ optional<vector<uint8_t>> res =
+ android::hardware::identity::support::certificateChainGetTopMostKey(chain);
+ if (!res) {
+ return false;
+ }
+ if (res.value().size() > *publicKeySize) {
+ eicDebug("Public key size is %zd but buffer only has room for %zd bytes",
+ res.value().size(), *publicKeySize);
+ return false;
+ }
+ *publicKeySize = res.value().size();
+ memcpy(publicKey, res.value().data(), *publicKeySize);
+ eicDebug("Extracted %zd bytes public key from %zd bytes X.509 cert", *publicKeySize,
+ x509CertSize);
+ return true;
+}
+
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ const uint8_t* publicKey, size_t publicKeySize) {
+ vector<uint8_t> certVec(x509Cert, x509Cert + x509CertSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+ return android::hardware::identity::support::certificateSignedByPublicKey(certVec,
+ publicKeyVec);
+}
+
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature, size_t signatureSize,
+ const uint8_t* publicKey, size_t publicKeySize) {
+ vector<uint8_t> digestVec(digest, digest + digestSize);
+ vector<uint8_t> signatureVec(signature, signature + signatureSize);
+ vector<uint8_t> publicKeyVec(publicKey, publicKey + publicKeySize);
+
+ vector<uint8_t> derSignature;
+ if (!android::hardware::identity::support::ecdsaSignatureCoseToDer(signatureVec,
+ derSignature)) {
+ LOG(ERROR) << "Error convering signature to DER format";
+ return false;
+ }
+
+ if (!android::hardware::identity::support::checkEcDsaSignature(digestVec, derSignature,
+ publicKeyVec)) {
+ LOG(ERROR) << "Signature check failed";
+ return false;
+ }
+ return true;
+}
+
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PUB_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]) {
+ vector<uint8_t> pubKeyVec(EIC_P256_PUB_KEY_SIZE + 1);
+ pubKeyVec[0] = 0x04;
+ memcpy(pubKeyVec.data() + 1, publicKey, EIC_P256_PUB_KEY_SIZE);
+
+ vector<uint8_t> privKeyVec(EIC_P256_PRIV_KEY_SIZE);
+ memcpy(privKeyVec.data(), privateKey, EIC_P256_PRIV_KEY_SIZE);
+
+ optional<vector<uint8_t>> shared =
+ android::hardware::identity::support::ecdh(pubKeyVec, privKeyVec);
+ if (!shared) {
+ LOG(ERROR) << "Error performing ECDH";
+ return false;
+ }
+ if (shared.value().size() != EIC_P256_COORDINATE_SIZE) {
+ LOG(ERROR) << "Unexpected size of shared secret " << shared.value().size() << " expected "
+ << EIC_P256_COORDINATE_SIZE << " bytes";
+ return false;
+ }
+ memcpy(sharedSecret, shared.value().data(), EIC_P256_COORDINATE_SIZE);
+ return true;
+}
+
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+ size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+ size_t outputSize) {
+ vector<uint8_t> sharedSecretVec(sharedSecretSize);
+ memcpy(sharedSecretVec.data(), sharedSecret, sharedSecretSize);
+ vector<uint8_t> saltVec(saltSize);
+ memcpy(saltVec.data(), salt, saltSize);
+ vector<uint8_t> infoVec(infoSize);
+ memcpy(infoVec.data(), info, infoSize);
+
+ optional<vector<uint8_t>> result = android::hardware::identity::support::hkdf(
+ sharedSecretVec, saltVec, infoVec, outputSize);
+ if (!result) {
+ LOG(ERROR) << "Error performing HKDF";
+ return false;
+ }
+ if (result.value().size() != outputSize) {
+ LOG(ERROR) << "Unexpected size of HKDF " << result.value().size() << " expected "
+ << outputSize;
+ return false;
+ }
+ memcpy(output, result.value().data(), outputSize);
+ return true;
+}
+
+#ifdef EIC_DEBUG
+
+void eicPrint(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize) {
+ vector<uint8_t> dataVec(dataSize);
+ memcpy(dataVec.data(), data, dataSize);
+ android::hardware::identity::support::hexdump(message, dataVec);
+}
+
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize) {
+ vector<uint8_t> cborDataVec(cborDataSize);
+ memcpy(cborDataVec.data(), cborData, cborDataSize);
+ string str =
+ android::hardware::identity::support::cborPrettyPrint(cborDataVec, maxBStrSize, {});
+ fprintf(stderr, "%s\n", str.c_str());
+}
+
+#endif // EIC_DEBUG
diff --git a/identity/aidl/default/EicOpsImpl.h b/identity/aidl/default/EicOpsImpl.h
new file mode 100644
index 0000000..333cdce
--- /dev/null
+++ b/identity/aidl/default/EicOpsImpl.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_IMPL_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Add whatever includes are needed for definitions below.
+//
+
+#include <openssl/hmac.h>
+#include <openssl/sha.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Set the following defines to match the implementation of the supplied
+// eicOps*() operations. See EicOps.h for details.
+//
+
+#define EIC_SHA256_CONTEXT_SIZE sizeof(SHA256_CTX)
+
+#define EIC_HMAC_SHA256_CONTEXT_SIZE sizeof(HMAC_CTX)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EMBEDDED_IC_H
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.cpp b/identity/aidl/default/FakeSecureHardwareProxy.cpp
new file mode 100644
index 0000000..de6762f
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.cpp
@@ -0,0 +1,324 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "FakeSecureHardwareProxy"
+
+#include "FakeSecureHardwareProxy.h"
+
+#include <android/hardware/identity/support/IdentityCredentialSupport.h>
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <string.h>
+
+#include <openssl/sha.h>
+
+#include <openssl/aes.h>
+#include <openssl/bn.h>
+#include <openssl/crypto.h>
+#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hkdf.h>
+#include <openssl/hmac.h>
+#include <openssl/objects.h>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <openssl/rand.h>
+#include <openssl/x509.h>
+#include <openssl/x509_vfy.h>
+
+#include <libeic.h>
+
+using ::std::optional;
+using ::std::string;
+using ::std::tuple;
+using ::std::vector;
+
+namespace android::hardware::identity {
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwareProvisioningProxy::FakeSecureHardwareProvisioningProxy() {}
+
+FakeSecureHardwareProvisioningProxy::~FakeSecureHardwareProvisioningProxy() {}
+
+bool FakeSecureHardwareProvisioningProxy::shutdown() {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool FakeSecureHardwareProvisioningProxy::initialize(bool testCredential) {
+ LOG(INFO) << "FakeSecureHardwareProvisioningProxy created, sizeof(EicProvisioning): "
+ << sizeof(EicProvisioning);
+ return eicProvisioningInit(&ctx_, testCredential);
+}
+
+// Returns public key certificate.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::createCredentialKey(
+ const vector<uint8_t>& challenge, const vector<uint8_t>& applicationId) {
+ uint8_t publicKeyCert[4096];
+ size_t publicKeyCertSize = sizeof publicKeyCert;
+ if (!eicProvisioningCreateCredentialKey(&ctx_, challenge.data(), challenge.size(),
+ applicationId.data(), applicationId.size(),
+ publicKeyCert, &publicKeyCertSize)) {
+ return {};
+ }
+ vector<uint8_t> pubKeyCert(publicKeyCertSize);
+ memcpy(pubKeyCert.data(), publicKeyCert, publicKeyCertSize);
+ return pubKeyCert;
+}
+
+bool FakeSecureHardwareProvisioningProxy::startPersonalization(
+ int accessControlProfileCount, vector<int> entryCounts, const string& docType,
+ size_t expectedProofOfProvisioningSize) {
+ if (!eicProvisioningStartPersonalization(&ctx_, accessControlProfileCount, entryCounts.data(),
+ entryCounts.size(), docType.c_str(),
+ expectedProofOfProvisioningSize)) {
+ return false;
+ }
+ return true;
+}
+
+// Returns MAC (28 bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) {
+ vector<uint8_t> mac(28);
+ if (!eicProvisioningAddAccessControlProfile(
+ &ctx_, id, readerCertificate.data(), readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis, secureUserId, mac.data())) {
+ return {};
+ }
+ return mac;
+}
+
+bool FakeSecureHardwareProvisioningProxy::beginAddEntry(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ uint64_t entrySize) {
+ uint8_t scratchSpace[512];
+ return eicProvisioningBeginAddEntry(&ctx_, accessControlProfileIds.data(),
+ accessControlProfileIds.size(), nameSpace.c_str(),
+ name.c_str(), entrySize, scratchSpace, sizeof scratchSpace);
+}
+
+// Returns encryptedContent.
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::addEntryValue(
+ const vector<int>& accessControlProfileIds, const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) {
+ vector<uint8_t> eicEncryptedContent;
+ uint8_t scratchSpace[512];
+ eicEncryptedContent.resize(content.size() + 28);
+ if (!eicProvisioningAddEntryValue(
+ &ctx_, accessControlProfileIds.data(), accessControlProfileIds.size(),
+ nameSpace.c_str(), name.c_str(), content.data(), content.size(),
+ eicEncryptedContent.data(), scratchSpace, sizeof scratchSpace)) {
+ return {};
+ }
+ return eicEncryptedContent;
+}
+
+// Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishAddingEntries() {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicProvisioningFinishAddingEntries(&ctx_, signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+// Returns encryptedCredentialKeys (80 bytes).
+optional<vector<uint8_t>> FakeSecureHardwareProvisioningProxy::finishGetCredentialData(
+ const string& docType) {
+ vector<uint8_t> encryptedCredentialKeys(80);
+ if (!eicProvisioningFinishGetCredentialData(&ctx_, docType.c_str(),
+ encryptedCredentialKeys.data())) {
+ return {};
+ }
+ return encryptedCredentialKeys;
+}
+
+// ----------------------------------------------------------------------
+
+FakeSecureHardwarePresentationProxy::FakeSecureHardwarePresentationProxy() {}
+
+FakeSecureHardwarePresentationProxy::~FakeSecureHardwarePresentationProxy() {}
+
+bool FakeSecureHardwarePresentationProxy::initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy created, sizeof(EicPresentation): "
+ << sizeof(EicPresentation);
+ return eicPresentationInit(&ctx_, testCredential, docType.c_str(),
+ encryptedCredentialKeys.data());
+}
+
+// Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+optional<pair<vector<uint8_t>, vector<uint8_t>>>
+FakeSecureHardwarePresentationProxy::generateSigningKeyPair(string docType, time_t now) {
+ uint8_t publicKeyCert[512];
+ size_t publicKeyCertSize = sizeof(publicKeyCert);
+ vector<uint8_t> signingKeyBlob(60);
+
+ if (!eicPresentationGenerateSigningKeyPair(&ctx_, docType.c_str(), now, publicKeyCert,
+ &publicKeyCertSize, signingKeyBlob.data())) {
+ return {};
+ }
+
+ vector<uint8_t> cert;
+ cert.resize(publicKeyCertSize);
+ memcpy(cert.data(), publicKeyCert, publicKeyCertSize);
+
+ return std::make_pair(cert, signingKeyBlob);
+}
+
+// Returns private key
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::createEphemeralKeyPair() {
+ vector<uint8_t> priv(EIC_P256_PRIV_KEY_SIZE);
+ if (!eicPresentationCreateEphemeralKeyPair(&ctx_, priv.data())) {
+ return {};
+ }
+ return priv;
+}
+
+optional<uint64_t> FakeSecureHardwarePresentationProxy::createAuthChallenge() {
+ uint64_t challenge;
+ if (!eicPresentationCreateAuthChallenge(&ctx_, &challenge)) {
+ return {};
+ }
+ return challenge;
+}
+
+bool FakeSecureHardwarePresentationProxy::shutdown() {
+ LOG(INFO) << "FakeSecureHardwarePresentationProxy shutdown";
+ return true;
+}
+
+bool FakeSecureHardwarePresentationProxy::pushReaderCert(const vector<uint8_t>& certX509) {
+ return eicPresentationPushReaderCert(&ctx_, certX509.data(), certX509.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::validateRequestMessage(
+ const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& requestMessage,
+ int coseSignAlg, const vector<uint8_t>& readerSignatureOfToBeSigned) {
+ return eicPresentationValidateRequestMessage(
+ &ctx_, sessionTranscript.data(), sessionTranscript.size(), requestMessage.data(),
+ requestMessage.size(), coseSignAlg, readerSignatureOfToBeSigned.data(),
+ readerSignatureOfToBeSigned.size());
+}
+
+bool FakeSecureHardwarePresentationProxy::setAuthToken(
+ uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel, const vector<uint8_t>& verificationTokenMac) {
+ return eicPresentationSetAuthToken(&ctx_, challenge, secureUserId, authenticatorId,
+ hardwareAuthenticatorType, timeStamp, mac.data(), mac.size(),
+ verificationTokenChallenge, verificationTokenTimestamp,
+ verificationTokenSecurityLevel, verificationTokenMac.data(),
+ verificationTokenMac.size());
+}
+
+optional<bool> FakeSecureHardwarePresentationProxy::validateAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId, const vector<uint8_t>& mac) {
+ bool accessGranted = false;
+ if (!eicPresentationValidateAccessControlProfile(&ctx_, id, readerCertificate.data(),
+ readerCertificate.size(),
+ userAuthenticationRequired, timeoutMillis,
+ secureUserId, mac.data(), &accessGranted)) {
+ return {};
+ }
+ return accessGranted;
+}
+
+bool FakeSecureHardwarePresentationProxy::startRetrieveEntries() {
+ return eicPresentationStartRetrieveEntries(&ctx_);
+}
+
+bool FakeSecureHardwarePresentationProxy::calcMacKey(
+ const vector<uint8_t>& sessionTranscript, const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues, size_t expectedProofOfProvisioningSize) {
+ if (signingKeyBlob.size() != 60) {
+ eicDebug("Unexpected size %zd of signingKeyBlob, expected 60", signingKeyBlob.size());
+ return false;
+ }
+ return eicPresentationCalcMacKey(&ctx_, sessionTranscript.data(), sessionTranscript.size(),
+ readerEphemeralPublicKey.data(), signingKeyBlob.data(),
+ docType.c_str(), numNamespacesWithValues,
+ expectedProofOfProvisioningSize);
+}
+
+AccessCheckResult FakeSecureHardwarePresentationProxy::startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ EicAccessCheckResult result = eicPresentationStartRetrieveEntryValue(
+ &ctx_, nameSpace.c_str(), name.c_str(), newNamespaceNumEntries, entrySize,
+ accessControlProfileIds.data(), accessControlProfileIds.size(), scratchSpace,
+ sizeof scratchSpace);
+ switch (result) {
+ case EIC_ACCESS_CHECK_RESULT_OK:
+ return AccessCheckResult::kOk;
+ case EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES:
+ return AccessCheckResult::kNoAccessControlProfiles;
+ case EIC_ACCESS_CHECK_RESULT_FAILED:
+ return AccessCheckResult::kFailed;
+ case EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kUserAuthenticationFailed;
+ case EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED:
+ return AccessCheckResult::kReaderAuthenticationFailed;
+ }
+ eicDebug("Unknown result with code %d, returning kFailed", (int)result);
+ return AccessCheckResult::kFailed;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) {
+ uint8_t scratchSpace[512];
+ vector<uint8_t> content;
+ content.resize(encryptedContent.size() - 28);
+ if (!eicPresentationRetrieveEntryValue(
+ &ctx_, encryptedContent.data(), encryptedContent.size(), content.data(),
+ nameSpace.c_str(), name.c_str(), accessControlProfileIds.data(),
+ accessControlProfileIds.size(), scratchSpace, sizeof scratchSpace)) {
+ return {};
+ }
+ return content;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::finishRetrieval() {
+ vector<uint8_t> mac(32);
+ size_t macSize = 32;
+ if (!eicPresentationFinishRetrieval(&ctx_, mac.data(), &macSize)) {
+ return {};
+ }
+ mac.resize(macSize);
+ return mac;
+}
+
+optional<vector<uint8_t>> FakeSecureHardwarePresentationProxy::deleteCredential(
+ const string& docType, size_t proofOfDeletionCborSize) {
+ vector<uint8_t> signatureOfToBeSigned(EIC_ECDSA_P256_SIGNATURE_SIZE);
+ if (!eicPresentationDeleteCredential(&ctx_, docType.c_str(), proofOfDeletionCborSize,
+ signatureOfToBeSigned.data())) {
+ return {};
+ }
+ return signatureOfToBeSigned;
+}
+
+} // namespace android::hardware::identity
diff --git a/identity/aidl/default/FakeSecureHardwareProxy.h b/identity/aidl/default/FakeSecureHardwareProxy.h
new file mode 100644
index 0000000..b858dd4
--- /dev/null
+++ b/identity/aidl/default/FakeSecureHardwareProxy.h
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
+
+#include <libeic/libeic.h>
+
+#include "SecureHardwareProxy.h"
+
+namespace android::hardware::identity {
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwareProvisioningProxy : public SecureHardwareProvisioningProxy {
+ public:
+ FakeSecureHardwareProvisioningProxy();
+ virtual ~FakeSecureHardwareProvisioningProxy();
+
+ bool initialize(bool testCredential) override;
+
+ bool shutdown() override;
+
+ // Returns public key certificate.
+ optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) override;
+
+ bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+ const string& docType,
+ size_t expectedProofOfProvisioningSize) override;
+
+ // Returns MAC (28 bytes).
+ optional<vector<uint8_t>> addAccessControlProfile(int id,
+ const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired,
+ uint64_t timeoutMillis,
+ uint64_t secureUserId) override;
+
+ bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, uint64_t entrySize) override;
+
+ // Returns encryptedContent.
+ optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) override;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ optional<vector<uint8_t>> finishAddingEntries() override;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ optional<vector<uint8_t>> finishGetCredentialData(const string& docType) override;
+
+ protected:
+ EicProvisioning ctx_;
+};
+
+// This implementation uses libEmbeddedIC in-process.
+//
+class FakeSecureHardwarePresentationProxy : public SecureHardwarePresentationProxy {
+ public:
+ FakeSecureHardwarePresentationProxy();
+ virtual ~FakeSecureHardwarePresentationProxy();
+
+ bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) override;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+ time_t now) override;
+
+ // Returns private key
+ optional<vector<uint8_t>> createEphemeralKeyPair() override;
+
+ optional<uint64_t> createAuthChallenge() override;
+
+ bool startRetrieveEntries() override;
+
+ bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const vector<uint8_t>& mac,
+ uint64_t verificationTokenChallenge, uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) override;
+
+ bool pushReaderCert(const vector<uint8_t>& certX509) override;
+
+ optional<bool> validateAccessControlProfile(int id, const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId,
+ const vector<uint8_t>& mac) override;
+
+ bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) override;
+
+ bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) override;
+
+ AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) override;
+
+ optional<vector<uint8_t>> finishRetrieval() override;
+
+ optional<vector<uint8_t>> deleteCredential(const string& docType,
+ size_t proofOfDeletionCborSize) override;
+
+ bool shutdown() override;
+
+ protected:
+ EicPresentation ctx_;
+};
+
+// Factory implementation.
+//
+class FakeSecureHardwareProxyFactory : public SecureHardwareProxyFactory {
+ public:
+ FakeSecureHardwareProxyFactory() {}
+ virtual ~FakeSecureHardwareProxyFactory() {}
+
+ sp<SecureHardwareProvisioningProxy> createProvisioningProxy() override {
+ return new FakeSecureHardwareProvisioningProxy();
+ }
+
+ sp<SecureHardwarePresentationProxy> createPresentationProxy() override {
+ return new FakeSecureHardwarePresentationProxy();
+ }
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_FAKESECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/Util.cpp b/identity/aidl/default/Util.cpp
deleted file mode 100644
index 66b9f13..0000000
--- a/identity/aidl/default/Util.cpp
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.
- */
-
-#define LOG_TAG "Util"
-
-#include "Util.h"
-
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <string.h>
-
-#include <android-base/logging.h>
-
-#include <cppbor.h>
-#include <cppbor_parse.h>
-
-namespace aidl::android::hardware::identity {
-
-using namespace ::android::hardware::identity;
-
-// This is not a very random HBK but that's OK because this is the SW
-// implementation where it can't be kept secret.
-vector<uint8_t> hardwareBoundKey = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
-
-const vector<uint8_t>& getHardwareBoundKey() {
- return hardwareBoundKey;
-}
-
-vector<uint8_t> secureAccessControlProfileEncodeCbor(const SecureAccessControlProfile& profile) {
- cppbor::Map map;
- map.add("id", profile.id);
-
- if (profile.readerCertificate.encodedCertificate.size() > 0) {
- map.add("readerCertificate", cppbor::Bstr(profile.readerCertificate.encodedCertificate));
- }
-
- if (profile.userAuthenticationRequired) {
- map.add("userAuthenticationRequired", profile.userAuthenticationRequired);
- map.add("timeoutMillis", profile.timeoutMillis);
- map.add("secureUserId", profile.secureUserId);
- }
-
- return map.encode();
-}
-
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
- const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey) {
- vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return {};
- }
- optional<vector<uint8_t>> macO =
- support::encryptAes128Gcm(storageKey, nonce.value(), {}, cborData);
- if (!macO) {
- return {};
- }
- return macO.value();
-}
-
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& storageKey) {
- vector<uint8_t> cborData = secureAccessControlProfileEncodeCbor(profile);
-
- if (profile.mac.size() < support::kAesGcmIvSize) {
- return false;
- }
- vector<uint8_t> nonce =
- vector<uint8_t>(profile.mac.begin(), profile.mac.begin() + support::kAesGcmIvSize);
- optional<vector<uint8_t>> mac = support::encryptAes128Gcm(storageKey, nonce, {}, cborData);
- if (!mac) {
- return false;
- }
- if (mac.value() != profile.mac) {
- return false;
- }
- return true;
-}
-
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
- const vector<int32_t> accessControlProfileIds) {
- cppbor::Map map;
- map.add("Namespace", nameSpace);
- map.add("Name", name);
-
- cppbor::Array acpIds;
- for (auto id : accessControlProfileIds) {
- acpIds.add(id);
- }
- map.add("AccessControlProfileIds", std::move(acpIds));
- return map.encode();
-}
-
-} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/Util.h b/identity/aidl/default/Util.h
deleted file mode 100644
index 9fccba2..0000000
--- a/identity/aidl/default/Util.h
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.
- */
-
-#ifndef ANDROID_HARDWARE_IDENTITY_UTIL_H
-#define ANDROID_HARDWARE_IDENTITY_UTIL_H
-
-#include <aidl/android/hardware/identity/BnIdentityCredential.h>
-#include <android/hardware/identity/support/IdentityCredentialSupport.h>
-
-#include <map>
-#include <optional>
-#include <set>
-#include <string>
-#include <vector>
-
-#include <cppbor/cppbor.h>
-
-namespace aidl::android::hardware::identity {
-
-using ::std::optional;
-using ::std::string;
-using ::std::vector;
-
-// Returns the hardware-bound AES-128 key.
-const vector<uint8_t>& getHardwareBoundKey();
-
-// Calculates the MAC for |profile| using |storageKey|.
-optional<vector<uint8_t>> secureAccessControlProfileCalcMac(
- const SecureAccessControlProfile& profile, const vector<uint8_t>& storageKey);
-
-// Checks authenticity of the MAC in |profile| using |storageKey|.
-bool secureAccessControlProfileCheckMac(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& storageKey);
-
-// Creates the AdditionalData CBOR used in the addEntryValue() HIDL method.
-vector<uint8_t> entryCreateAdditionalData(const string& nameSpace, const string& name,
- const vector<int32_t> accessControlProfileIds);
-
-} // namespace aidl::android::hardware::identity
-
-#endif // ANDROID_HARDWARE_IDENTITY_UTIL_H
diff --git a/identity/aidl/default/IdentityCredential.cpp b/identity/aidl/default/common/IdentityCredential.cpp
similarity index 65%
rename from identity/aidl/default/IdentityCredential.cpp
rename to identity/aidl/default/common/IdentityCredential.cpp
index dfcd4f5..270fcfa 100644
--- a/identity/aidl/default/IdentityCredential.cpp
+++ b/identity/aidl/default/common/IdentityCredential.cpp
@@ -18,7 +18,6 @@
#include "IdentityCredential.h"
#include "IdentityCredentialStore.h"
-#include "Util.h"
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
@@ -30,6 +29,8 @@
#include <cppbor.h>
#include <cppbor_parse.h>
+#include "FakeSecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::Timestamp;
@@ -69,40 +70,17 @@
docType_ = docTypeItem->value();
testCredential_ = testCredentialItem->value();
- vector<uint8_t> hardwareBoundKey;
- if (testCredential_) {
- hardwareBoundKey = support::getTestHardwareBoundKey();
- } else {
- hardwareBoundKey = getHardwareBoundKey();
- }
-
const vector<uint8_t>& encryptedCredentialKeys = encryptedCredentialKeysItem->value();
- const vector<uint8_t> docTypeVec(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> decryptedCredentialKeys =
- support::decryptAes128Gcm(hardwareBoundKey, encryptedCredentialKeys, docTypeVec);
- if (!decryptedCredentialKeys) {
- LOG(ERROR) << "Error decrypting CredentialKeys";
+
+ if (encryptedCredentialKeys.size() != 80) {
+ LOG(ERROR) << "Unexpected size for encrypted CredentialKeys";
return IIdentityCredentialStore::STATUS_INVALID_DATA;
}
- auto [dckItem, dckPos, dckMessage] = cppbor::parse(decryptedCredentialKeys.value());
- if (dckItem == nullptr) {
- LOG(ERROR) << "Decrypted CredentialKeys is not valid CBOR: " << dckMessage;
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
+ if (!hwProxy_->initialize(testCredential_, docType_, encryptedCredentialKeys)) {
+ LOG(ERROR) << "hwProxy->initialize failed";
+ return false;
}
- const cppbor::Array* dckArrayItem = dckItem->asArray();
- if (dckArrayItem == nullptr || dckArrayItem->size() != 2) {
- LOG(ERROR) << "Decrypted CredentialKeys is not an array with two elements";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- const cppbor::Bstr* storageKeyItem = (*dckArrayItem)[0]->asBstr();
- const cppbor::Bstr* credentialPrivKeyItem = (*dckArrayItem)[1]->asBstr();
- if (storageKeyItem == nullptr || credentialPrivKeyItem == nullptr) {
- LOG(ERROR) << "CredentialKeys unexpected item types";
- return IIdentityCredentialStore::STATUS_INVALID_DATA;
- }
- storageKey_ = storageKeyItem->value();
- credentialPrivKey_ = credentialPrivKeyItem->value();
return IIdentityCredentialStore::STATUS_OK;
}
@@ -110,12 +88,20 @@
ndk::ScopedAStatus IdentityCredential::deleteCredential(
vector<uint8_t>* outProofOfDeletionSignature) {
cppbor::Array array = {"ProofOfDeletion", docType_, testCredential_};
- vector<uint8_t> proofOfDeletion = array.encode();
+ vector<uint8_t> proofOfDeletionCbor = array.encode();
+ vector<uint8_t> podDigest = support::sha256(proofOfDeletionCbor);
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- proofOfDeletion, // payload
- {}, // additionalData
- {}); // certificateChain
+ optional<vector<uint8_t>> signatureOfToBeSigned =
+ hwProxy_->deleteCredential(docType_, proofOfDeletionCbor.size());
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error signing ProofOfDeletion"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ proofOfDeletionCbor, // data
+ {}); // certificateChain
if (!signature) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
@@ -126,22 +112,28 @@
}
ndk::ScopedAStatus IdentityCredential::createEphemeralKeyPair(vector<uint8_t>* outKeyPair) {
- optional<vector<uint8_t>> kp = support::createEcKeyPair();
- if (!kp) {
+ optional<vector<uint8_t>> ephemeralPriv = hwProxy_->createEphemeralKeyPair();
+ if (!ephemeralPriv) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key pair"));
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key"));
+ }
+ optional<vector<uint8_t>> keyPair = support::ecPrivateKeyToKeyPair(ephemeralPriv.value());
+ if (!keyPair) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error creating ephemeral key-pair"));
}
// Stash public key of this key-pair for later check in startRetrieval().
- optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(kp.value());
+ optional<vector<uint8_t>> publicKey = support::ecKeyPairGetPublicKey(keyPair.value());
if (!publicKey) {
+ LOG(ERROR) << "Error getting public part of ephemeral key pair";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
"Error getting public part of ephemeral key pair"));
}
ephemeralPublicKey_ = publicKey.value();
- *outKeyPair = kp.value();
+ *outKeyPair = keyPair.value();
return ndk::ScopedAStatus::ok();
}
@@ -152,109 +144,15 @@
}
ndk::ScopedAStatus IdentityCredential::createAuthChallenge(int64_t* outChallenge) {
- uint64_t challenge = 0;
- while (challenge == 0) {
- optional<vector<uint8_t>> bytes = support::getRandom(8);
- if (!bytes) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting random data for challenge"));
- }
-
- challenge = 0;
- for (size_t n = 0; n < bytes.value().size(); n++) {
- challenge |= ((bytes.value())[n] << (n * 8));
- }
+ optional<uint64_t> challenge = hwProxy_->createAuthChallenge();
+ if (!challenge) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error generating challenge"));
}
-
- *outChallenge = challenge;
- authChallenge_ = challenge;
+ *outChallenge = challenge.value();
return ndk::ScopedAStatus::ok();
}
-// TODO: this could be a lot faster if we did all the splitting and pubkey extraction
-// ahead of time.
-bool checkReaderAuthentication(const SecureAccessControlProfile& profile,
- const vector<uint8_t>& readerCertificateChain) {
- optional<vector<uint8_t>> acpPubKey =
- support::certificateChainGetTopMostKey(profile.readerCertificate.encodedCertificate);
- if (!acpPubKey) {
- LOG(ERROR) << "Error extracting public key from readerCertificate in profile";
- return false;
- }
-
- optional<vector<vector<uint8_t>>> certificatesInChain =
- support::certificateChainSplit(readerCertificateChain);
- if (!certificatesInChain) {
- LOG(ERROR) << "Error splitting readerCertificateChain";
- return false;
- }
- for (const vector<uint8_t>& certInChain : certificatesInChain.value()) {
- optional<vector<uint8_t>> certPubKey = support::certificateChainGetTopMostKey(certInChain);
- if (!certPubKey) {
- LOG(ERROR)
- << "Error extracting public key from certificate in chain presented by reader";
- return false;
- }
- if (acpPubKey.value() == certPubKey.value()) {
- return true;
- }
- }
- return false;
-}
-
-bool checkUserAuthentication(const SecureAccessControlProfile& profile,
- const VerificationToken& verificationToken,
- const HardwareAuthToken& authToken, uint64_t authChallenge) {
- if (profile.secureUserId != authToken.userId) {
- LOG(ERROR) << "secureUserId in profile (" << profile.secureUserId
- << ") differs from userId in authToken (" << authToken.userId << ")";
- return false;
- }
-
- if (verificationToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "VerificationToken is not set";
- return false;
- }
- if (authToken.timestamp.milliSeconds == 0) {
- LOG(ERROR) << "AuthToken is not set";
- return false;
- }
-
- if (profile.timeoutMillis == 0) {
- if (authToken.challenge == 0) {
- LOG(ERROR) << "No challenge in authToken";
- return false;
- }
-
- if (authToken.challenge != int64_t(authChallenge)) {
- LOG(ERROR) << "Challenge in authToken (" << uint64_t(authToken.challenge) << ") "
- << "doesn't match the challenge we created (" << authChallenge << ")";
- return false;
- }
- return true;
- }
-
- // Timeout-based user auth follows. The verification token conveys what the
- // time is right now in the environment which generated the auth token. This
- // is what makes it possible to do timeout-based checks.
- //
- const Timestamp now = verificationToken.timestamp;
- if (authToken.timestamp.milliSeconds > now.milliSeconds) {
- LOG(ERROR) << "Timestamp in authToken (" << authToken.timestamp.milliSeconds
- << ") is in the future (now: " << now.milliSeconds << ")";
- return false;
- }
- if (now.milliSeconds > authToken.timestamp.milliSeconds + profile.timeoutMillis) {
- LOG(ERROR) << "Deadline for authToken (" << authToken.timestamp.milliSeconds << " + "
- << profile.timeoutMillis << " = "
- << (authToken.timestamp.milliSeconds + profile.timeoutMillis)
- << ") is in the past (now: " << now.milliSeconds << ")";
- return false;
- }
- return true;
-}
-
ndk::ScopedAStatus IdentityCredential::setRequestedNamespaces(
const vector<RequestNamespace>& requestNamespaces) {
requestNamespaces_ = requestNamespaces;
@@ -284,6 +182,7 @@
}
if (numStartRetrievalCalls_ > 0) {
if (sessionTranscript_ != sessionTranscript) {
+ LOG(ERROR) << "Session Transcript changed";
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_SESSION_TRANSCRIPT_MISMATCH,
"Passed-in SessionTranscript doesn't match previously used SessionTranscript"));
@@ -291,6 +190,40 @@
}
sessionTranscript_ = sessionTranscript;
+ // This resets various state in the TA...
+ if (!hwProxy_->startRetrieveEntries()) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+ }
+
+ optional<vector<uint8_t>> signatureOfToBeSigned;
+ if (readerSignature.size() > 0) {
+ signatureOfToBeSigned = support::coseSignGetSignature(readerSignature);
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signatureOfToBeSigned from COSE_Sign1"));
+ }
+ }
+
+ // Feed the auth token to secure hardware.
+ if (!hwProxy_->setAuthToken(authToken.challenge, authToken.userId, authToken.authenticatorId,
+ int(authToken.authenticatorType), authToken.timestamp.milliSeconds,
+ authToken.mac, verificationToken_.challenge,
+ verificationToken_.timestamp.milliSeconds,
+ int(verificationToken_.securityLevel), verificationToken_.mac)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA, "Invalid Auth Token"));
+ }
+
+ // We'll be feeding ACPs interleaved with certificates from the reader
+ // certificate chain...
+ vector<SecureAccessControlProfile> remainingAcps = accessControlProfiles;
+
+ // ... and we'll use those ACPs to build up a 32-bit mask indicating which
+ // of the possible 32 ACPs grants access.
+ uint32_t accessControlProfileMask = 0;
+
// If there is a signature, validate that it was made with the top-most key in the
// certificate chain embedded in the COSE_Sign1 structure.
optional<vector<uint8_t>> readerCertificateChain;
@@ -302,45 +235,113 @@
"Unable to get reader certificate chain from COSE_Sign1"));
}
- if (!support::certificateChainValidate(readerCertificateChain.value())) {
+ // First, feed all the reader certificates to the secure hardware. We start
+ // at the end..
+ optional<vector<vector<uint8_t>>> splitCerts =
+ support::certificateChainSplit(readerCertificateChain.value());
+ if (!splitCerts || splitCerts.value().size() == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Error validating reader certificate chain"));
+ "Error splitting certificate chain from COSE_Sign1"));
+ }
+ for (ssize_t n = splitCerts.value().size() - 1; n >= 0; --n) {
+ const vector<uint8_t>& x509Cert = splitCerts.value()[n];
+ if (!hwProxy_->pushReaderCert(x509Cert)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ StringPrintf("Error validating reader certificate %zd", n).c_str()));
+ }
+
+ // If we have ACPs for that particular certificate, send them to the
+ // TA right now...
+ //
+ // Remember in this case certificate equality is done by comparing public keys,
+ // not bitwise comparison of the certificates.
+ //
+ optional<vector<uint8_t>> x509CertPubKey =
+ support::certificateChainGetTopMostKey(x509Cert);
+ if (!x509CertPubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ StringPrintf("Error getting public key from reader certificate %zd", n)
+ .c_str()));
+ }
+ vector<SecureAccessControlProfile>::iterator it = remainingAcps.begin();
+ while (it != remainingAcps.end()) {
+ const SecureAccessControlProfile& profile = *it;
+ if (profile.readerCertificate.encodedCertificate.size() == 0) {
+ ++it;
+ continue;
+ }
+ optional<vector<uint8_t>> profilePubKey = support::certificateChainGetTopMostKey(
+ profile.readerCertificate.encodedCertificate);
+ if (!profilePubKey) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error getting public key from profile"));
+ }
+ if (profilePubKey.value() == x509CertPubKey.value()) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis,
+ profile.secureUserId, profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ it = remainingAcps.erase(it);
+ } else {
+ ++it;
+ }
+ }
}
- optional<vector<uint8_t>> readerPublicKey =
- support::certificateChainGetTopMostKey(readerCertificateChain.value());
- if (!readerPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "Unable to get public key from reader certificate chain"));
- }
-
- const vector<uint8_t>& itemsRequestBytes = itemsRequest;
- vector<uint8_t> encodedReaderAuthentication =
- cppbor::Array()
- .add("ReaderAuthentication")
- .add(std::move(sessionTranscriptItem))
- .add(cppbor::Semantic(24, itemsRequestBytes))
- .encode();
- vector<uint8_t> encodedReaderAuthenticationBytes =
- cppbor::Semantic(24, encodedReaderAuthentication).encode();
- if (!support::coseCheckEcDsaSignature(readerSignature,
- encodedReaderAuthenticationBytes, // detached content
- readerPublicKey.value())) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
- "readerSignature check failed"));
+ // ... then pass the request message and have the TA check it's signed by the
+ // key in last certificate we pushed.
+ if (sessionTranscript.size() > 0 && itemsRequest.size() > 0 && readerSignature.size() > 0) {
+ optional<vector<uint8_t>> tbsSignature = support::coseSignGetSignature(readerSignature);
+ if (!tbsSignature) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting toBeSigned from COSE_Sign1"));
+ }
+ optional<int> coseSignAlg = support::coseSignGetAlg(readerSignature);
+ if (!coseSignAlg) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "Error extracting signature algorithm from COSE_Sign1"));
+ }
+ if (!hwProxy_->validateRequestMessage(sessionTranscript, itemsRequest,
+ coseSignAlg.value(), tbsSignature.value())) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_SIGNATURE_CHECK_FAILED,
+ "readerMessage is not signed by top-level certificate"));
+ }
}
}
- // Here's where we would validate the passed-in |authToken| to assure ourselves
- // that it comes from the e.g. biometric hardware and wasn't made up by an attacker.
- //
- // However this involves calculating the MAC. However this requires access
- // to the key needed to a pre-shared key which we don't have...
- //
+ // Feed remaining access control profiles...
+ for (const SecureAccessControlProfile& profile : remainingAcps) {
+ optional<bool> res = hwProxy_->validateAccessControlProfile(
+ profile.id, profile.readerCertificate.encodedCertificate,
+ profile.userAuthenticationRequired, profile.timeoutMillis, profile.secureUserId,
+ profile.mac);
+ if (!res) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_INVALID_DATA,
+ "Error validating access control profile"));
+ }
+ if (res.value()) {
+ accessControlProfileMask |= (1 << profile.id);
+ }
+ }
+ // TODO: move this check to the TA
+#if 1
// To prevent replay-attacks, we check that the public part of the ephemeral
// key we previously created, is present in the DeviceEngagement part of
// SessionTranscript as a COSE_Key, in uncompressed form.
@@ -364,6 +365,7 @@
"SessionTranscript (make sure leading zeroes are not used)"));
}
}
+#endif
// itemsRequest: If non-empty, contains request data that may be signed by the
// reader. The content can be defined in the way appropriate for the
@@ -463,30 +465,6 @@
}
}
- // Validate all the access control profiles in the requestData.
- bool haveAuthToken = (authToken.timestamp.milliSeconds != int64_t(0));
- for (const auto& profile : accessControlProfiles) {
- if (!secureAccessControlProfileCheckMac(profile, storageKey_)) {
- LOG(ERROR) << "Error checking MAC for profile";
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error checking MAC for profile"));
- }
- int accessControlCheck = IIdentityCredentialStore::STATUS_OK;
- if (profile.userAuthenticationRequired) {
- if (!haveAuthToken ||
- !checkUserAuthentication(profile, verificationToken_, authToken, authChallenge_)) {
- accessControlCheck = IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED;
- }
- } else if (profile.readerCertificate.encodedCertificate.size() > 0) {
- if (!readerCertificateChain ||
- !checkReaderAuthentication(profile, readerCertificateChain.value())) {
- accessControlCheck = IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED;
- }
- }
- profileIdToAccessCheckResult_[profile.id] = accessControlCheck;
- }
-
deviceNameSpacesMap_ = cppbor::Map();
currentNameSpaceDeviceNameSpacesMap_ = cppbor::Map();
@@ -496,8 +474,36 @@
itemsRequest_ = itemsRequest;
signingKeyBlob_ = signingKeyBlob;
- // Finally, calculate the size of DeviceNameSpaces. We need to know it ahead of time.
- expectedDeviceNameSpacesSize_ = calcDeviceNameSpacesSize();
+ // calculate the size of DeviceNameSpaces. We need to know it ahead of time.
+ calcDeviceNameSpacesSize(accessControlProfileMask);
+
+ // Count the number of non-empty namespaces
+ size_t numNamespacesWithValues = 0;
+ for (size_t n = 0; n < expectedNumEntriesPerNamespace_.size(); n++) {
+ if (expectedNumEntriesPerNamespace_[n] > 0) {
+ numNamespacesWithValues += 1;
+ }
+ }
+
+ // Finally, pass info so the HMAC key can be derived and the TA can start
+ // creating the DeviceNameSpaces CBOR...
+ if (sessionTranscript_.size() > 0 && readerPublicKey_.size() > 0 && signingKeyBlob.size() > 0) {
+ // We expect the reader ephemeral public key to be same size and curve
+ // as the ephemeral key we generated (e.g. P-256 key), otherwise ECDH
+ // won't work. So its length should be 65 bytes and it should be
+ // starting with 0x04.
+ if (readerPublicKey_.size() != 65 || readerPublicKey_[0] != 0x04) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Reader public key is not in expected format"));
+ }
+ vector<uint8_t> pubKeyP256(readerPublicKey_.begin() + 1, readerPublicKey_.end());
+ if (!hwProxy_->calcMacKey(sessionTranscript_, pubKeyP256, signingKeyBlob, docType_,
+ numNamespacesWithValues, expectedDeviceNameSpacesSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "Error starting retrieving entries"));
+ }
+ }
numStartRetrievalCalls_ += 1;
return ndk::ScopedAStatus::ok();
@@ -520,7 +526,7 @@
return 1 + cborNumBytesForLength(value.size()) + value.size();
}
-size_t IdentityCredential::calcDeviceNameSpacesSize() {
+void IdentityCredential::calcDeviceNameSpacesSize(uint32_t accessControlProfileMask) {
/*
* This is how DeviceNameSpaces is defined:
*
@@ -539,7 +545,7 @@
* encoded.
*/
size_t ret = 0;
- size_t numNamespacesWithValues = 0;
+ vector<unsigned int> numEntriesPerNamespace;
for (const RequestNamespace& rns : requestNamespaces_) {
vector<RequestDataItem> itemsToInclude;
@@ -562,13 +568,9 @@
//
bool authorized = false;
for (auto id : rdi.accessControlProfileIds) {
- auto it = profileIdToAccessCheckResult_.find(id);
- if (it != profileIdToAccessCheckResult_.end()) {
- int accessControlForProfile = it->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- authorized = true;
- break;
- }
+ if (accessControlProfileMask & (1 << id)) {
+ authorized = true;
+ break;
}
}
if (!authorized) {
@@ -578,7 +580,10 @@
itemsToInclude.push_back(rdi);
}
- // If no entries are to be in the namespace, we don't include it...
+ numEntriesPerNamespace.push_back(itemsToInclude.size());
+
+ // If no entries are to be in the namespace, we don't include it in
+ // the CBOR...
if (itemsToInclude.size() == 0) {
continue;
}
@@ -597,15 +602,14 @@
// that.
ret += item.size;
}
-
- numNamespacesWithValues++;
}
- // Now that we now the nunber of namespaces with values, we know how many
+ // Now that we know the number of namespaces with values, we know how many
// bytes the DeviceNamespaces map in the beginning is going to take up.
- ret += 1 + cborNumBytesForLength(numNamespacesWithValues);
+ ret += 1 + cborNumBytesForLength(numEntriesPerNamespace.size());
- return ret;
+ expectedDeviceNameSpacesSize_ = ret;
+ expectedNumEntriesPerNamespace_ = numEntriesPerNamespace;
}
ndk::ScopedAStatus IdentityCredential::startRetrieveEntryValue(
@@ -626,9 +630,11 @@
"No more name spaces left to go through"));
}
+ bool newNamespace;
if (currentNameSpace_ == "") {
// First call.
currentNameSpace_ = nameSpace;
+ newNamespace = true;
}
if (nameSpace == currentNameSpace_) {
@@ -655,6 +661,7 @@
requestCountsRemaining_.erase(requestCountsRemaining_.begin());
currentNameSpace_ = nameSpace;
+ newNamespace = true;
}
// It's permissible to have an empty itemsRequest... but if non-empty you can
@@ -674,35 +681,52 @@
}
}
- // Enforce access control.
- //
- // Access is granted if at least one of the profiles grants access.
- //
- // If an item is configured without any profiles, access is denied.
- //
- int accessControl = IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES;
- for (auto id : accessControlProfileIds) {
- auto search = profileIdToAccessCheckResult_.find(id);
- if (search == profileIdToAccessCheckResult_.end()) {
+ unsigned int newNamespaceNumEntries = 0;
+ if (newNamespace) {
+ if (expectedNumEntriesPerNamespace_.size() == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Requested entry with unvalidated profile id"));
+ "No more populated name spaces left to go through"));
}
- int accessControlForProfile = search->second;
- if (accessControlForProfile == IIdentityCredentialStore::STATUS_OK) {
- accessControl = IIdentityCredentialStore::STATUS_OK;
- break;
- }
- accessControl = accessControlForProfile;
- }
- if (accessControl != IIdentityCredentialStore::STATUS_OK) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- int(accessControl), "Access control check failed"));
+ newNamespaceNumEntries = expectedNumEntriesPerNamespace_[0];
+ expectedNumEntriesPerNamespace_.erase(expectedNumEntriesPerNamespace_.begin());
}
- entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
+ // Access control is enforced in the secure hardware.
+ //
+ // ... except for STATUS_NOT_IN_REQUEST_MESSAGE, that's handled above (TODO:
+ // consolidate).
+ //
+ AccessCheckResult res = hwProxy_->startRetrieveEntryValue(
+ nameSpace, name, newNamespaceNumEntries, entrySize, accessControlProfileIds);
+ switch (res) {
+ case AccessCheckResult::kOk:
+ /* Do nothing. */
+ break;
+ case AccessCheckResult::kFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Access control check failed (failed)"));
+ break;
+ case AccessCheckResult::kNoAccessControlProfiles:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_NO_ACCESS_CONTROL_PROFILES,
+ "Access control check failed (no access control profiles)"));
+ break;
+ case AccessCheckResult::kUserAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_USER_AUTHENTICATION_FAILED,
+ "Access control check failed (user auth)"));
+ break;
+ case AccessCheckResult::kReaderAuthenticationFailed:
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_READER_AUTHENTICATION_FAILED,
+ "Access control check failed (reader auth)"));
+ break;
+ }
currentName_ = name;
+ currentAccessControlProfileIds_ = accessControlProfileIds;
entryRemainingBytes_ = entrySize;
entryValue_.resize(0);
@@ -711,8 +735,8 @@
ndk::ScopedAStatus IdentityCredential::retrieveEntryValue(const vector<uint8_t>& encryptedContent,
vector<uint8_t>* outContent) {
- optional<vector<uint8_t>> content =
- support::decryptAes128Gcm(storageKey_, encryptedContent, entryAdditionalData_);
+ optional<vector<uint8_t>> content = hwProxy_->retrieveEntryValue(
+ encryptedContent, currentNameSpace_, currentName_, currentAccessControlProfileIds_);
if (!content) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Error decrypting data"));
@@ -777,28 +801,14 @@
optional<vector<uint8_t>> mac;
if (signingKeyBlob_.size() > 0 && sessionTranscript_.size() > 0 &&
readerPublicKey_.size() > 0) {
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> signingKey =
- support::decryptAes128Gcm(storageKey_, signingKeyBlob_, docTypeAsBlob);
- if (!signingKey) {
+ optional<vector<uint8_t>> digestToBeMaced = hwProxy_->finishRetrieval();
+ if (!digestToBeMaced || digestToBeMaced.value().size() != 32) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
- "Error decrypting signingKeyBlob"));
+ "Error generating digestToBeMaced"));
}
-
- vector<uint8_t> sessionTranscriptBytes = cppbor::Semantic(24, sessionTranscript_).encode();
- optional<vector<uint8_t>> eMacKey =
- support::calcEMacKey(signingKey.value(), readerPublicKey_, sessionTranscriptBytes);
- if (!eMacKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error calculating EMacKey"));
- }
- mac = support::calcMac(sessionTranscript_, docType_, encodedDeviceNameSpaces,
- eMacKey.value());
- if (!mac) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error MACing data"));
- }
+ // Now construct COSE_Mac0 from the returned MAC...
+ mac = support::coseMacWithDigest(digestToBeMaced.value(), {} /* data */);
}
*outMac = mac.value_or(vector<uint8_t>({}));
@@ -808,56 +818,18 @@
ndk::ScopedAStatus IdentityCredential::generateSigningKeyPair(
vector<uint8_t>* outSigningKeyBlob, Certificate* outSigningKeyCertificate) {
- string serialDecimal = "1";
- string issuer = "Android Identity Credential Key";
- string subject = "Android Identity Credential Authentication Key";
- time_t validityNotBefore = time(nullptr);
- time_t validityNotAfter = validityNotBefore + 365 * 24 * 3600;
-
- optional<vector<uint8_t>> signingKeyPKCS8 = support::createEcKeyPair();
- if (!signingKeyPKCS8) {
+ time_t now = time(NULL);
+ optional<pair<vector<uint8_t>, vector<uint8_t>>> pair =
+ hwProxy_->generateSigningKeyPair(docType_, now);
+ if (!pair) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
}
- optional<vector<uint8_t>> signingPublicKey =
- support::ecKeyPairGetPublicKey(signingKeyPKCS8.value());
- if (!signingPublicKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of signingKey"));
- }
-
- optional<vector<uint8_t>> signingKey = support::ecKeyPairGetPrivateKey(signingKeyPKCS8.value());
- if (!signingKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of signingKey"));
- }
-
- optional<vector<uint8_t>> certificate = support::ecPublicKeyGenerateCertificate(
- signingPublicKey.value(), credentialPrivKey_, serialDecimal, issuer, subject,
- validityNotBefore, validityNotAfter);
- if (!certificate) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error creating signingKey"));
- }
-
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error getting random"));
- }
- vector<uint8_t> docTypeAsBlob(docType_.begin(), docType_.end());
- optional<vector<uint8_t>> encryptedSigningKey = support::encryptAes128Gcm(
- storageKey_, nonce.value(), signingKey.value(), docTypeAsBlob);
- if (!encryptedSigningKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error encrypting signingKey"));
- }
- *outSigningKeyBlob = encryptedSigningKey.value();
*outSigningKeyCertificate = Certificate();
- outSigningKeyCertificate->encodedCertificate = certificate.value();
+ outSigningKeyCertificate->encodedCertificate = pair->first;
+
+ *outSigningKeyBlob = pair->second;
return ndk::ScopedAStatus::ok();
}
diff --git a/identity/aidl/default/IdentityCredential.h b/identity/aidl/default/common/IdentityCredential.h
similarity index 88%
rename from identity/aidl/default/IdentityCredential.h
rename to identity/aidl/default/common/IdentityCredential.h
index a8a6409..2281821 100644
--- a/identity/aidl/default/IdentityCredential.h
+++ b/identity/aidl/default/common/IdentityCredential.h
@@ -29,10 +29,15 @@
#include <cppbor/cppbor.h>
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
using ::aidl::android::hardware::keymaster::HardwareAuthToken;
using ::aidl::android::hardware::keymaster::VerificationToken;
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwarePresentationProxy;
using ::std::map;
using ::std::set;
using ::std::string;
@@ -40,10 +45,11 @@
class IdentityCredential : public BnIdentityCredential {
public:
- IdentityCredential(const vector<uint8_t>& credentialData)
- : credentialData_(credentialData),
+ IdentityCredential(sp<SecureHardwarePresentationProxy> hwProxy,
+ const vector<uint8_t>& credentialData)
+ : hwProxy_(hwProxy),
+ credentialData_(credentialData),
numStartRetrievalCalls_(0),
- authChallenge_(0),
expectedDeviceNameSpacesSize_(0) {}
// Parses and decrypts credentialData_, return a status code from
@@ -75,14 +81,13 @@
private:
// Set by constructor
+ sp<SecureHardwarePresentationProxy> hwProxy_;
vector<uint8_t> credentialData_;
int numStartRetrievalCalls_;
// Set by initialize()
string docType_;
bool testCredential_;
- vector<uint8_t> storageKey_;
- vector<uint8_t> credentialPrivKey_;
// Set by createEphemeralKeyPair()
vector<uint8_t> ephemeralPublicKey_;
@@ -90,9 +95,6 @@
// Set by setReaderEphemeralPublicKey()
vector<uint8_t> readerPublicKey_;
- // Set by createAuthChallenge()
- uint64_t authChallenge_;
-
// Set by setRequestedNamespaces()
vector<RequestNamespace> requestNamespaces_;
@@ -100,7 +102,6 @@
VerificationToken verificationToken_;
// Set at startRetrieval() time.
- map<int32_t, int> profileIdToAccessCheckResult_;
vector<uint8_t> signingKeyBlob_;
vector<uint8_t> sessionTranscript_;
vector<uint8_t> itemsRequest_;
@@ -111,15 +112,16 @@
// Calculated at startRetrieval() time.
size_t expectedDeviceNameSpacesSize_;
+ vector<unsigned int> expectedNumEntriesPerNamespace_;
// Set at startRetrieveEntryValue() time.
string currentNameSpace_;
string currentName_;
+ vector<int32_t> currentAccessControlProfileIds_;
size_t entryRemainingBytes_;
vector<uint8_t> entryValue_;
- vector<uint8_t> entryAdditionalData_;
- size_t calcDeviceNameSpacesSize();
+ void calcDeviceNameSpacesSize(uint32_t accessControlProfileMask);
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/IdentityCredentialStore.cpp b/identity/aidl/default/common/IdentityCredentialStore.cpp
similarity index 90%
rename from identity/aidl/default/IdentityCredentialStore.cpp
rename to identity/aidl/default/common/IdentityCredentialStore.cpp
index 30dc6f3..13f91aa 100644
--- a/identity/aidl/default/IdentityCredentialStore.cpp
+++ b/identity/aidl/default/common/IdentityCredentialStore.cpp
@@ -39,8 +39,9 @@
ndk::ScopedAStatus IdentityCredentialStore::createCredential(
const string& docType, bool testCredential,
shared_ptr<IWritableIdentityCredential>* outWritableCredential) {
+ sp<SecureHardwareProvisioningProxy> hwProxy = hwProxyFactory_->createProvisioningProxy();
shared_ptr<WritableIdentityCredential> wc =
- ndk::SharedRefBase::make<WritableIdentityCredential>(docType, testCredential);
+ ndk::SharedRefBase::make<WritableIdentityCredential>(hwProxy, docType, testCredential);
if (!wc->initialize()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
@@ -60,8 +61,9 @@
"Unsupported cipher suite"));
}
+ sp<SecureHardwarePresentationProxy> hwProxy = hwProxyFactory_->createPresentationProxy();
shared_ptr<IdentityCredential> credential =
- ndk::SharedRefBase::make<IdentityCredential>(credentialData);
+ ndk::SharedRefBase::make<IdentityCredential>(hwProxy, credentialData);
auto ret = credential->initialize();
if (ret != IIdentityCredentialStore::STATUS_OK) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
diff --git a/identity/aidl/default/IdentityCredentialStore.h b/identity/aidl/default/common/IdentityCredentialStore.h
similarity index 85%
rename from identity/aidl/default/IdentityCredentialStore.h
rename to identity/aidl/default/common/IdentityCredentialStore.h
index 4f3a421..d35e632 100644
--- a/identity/aidl/default/IdentityCredentialStore.h
+++ b/identity/aidl/default/common/IdentityCredentialStore.h
@@ -19,15 +19,20 @@
#include <aidl/android/hardware/identity/BnIdentityCredentialStore.h>
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
using ::std::shared_ptr;
using ::std::string;
using ::std::vector;
class IdentityCredentialStore : public BnIdentityCredentialStore {
public:
- IdentityCredentialStore() {}
+ IdentityCredentialStore(sp<SecureHardwareProxyFactory> hwProxyFactory)
+ : hwProxyFactory_(hwProxyFactory) {}
// The GCM chunk size used by this implementation is 64 KiB.
static constexpr size_t kGcmChunkSize = 64 * 1024;
@@ -41,6 +46,9 @@
ndk::ScopedAStatus getCredential(CipherSuite cipherSuite, const vector<uint8_t>& credentialData,
shared_ptr<IIdentityCredential>* outCredential) override;
+
+ private:
+ sp<SecureHardwareProxyFactory> hwProxyFactory_;
};
} // namespace aidl::android::hardware::identity
diff --git a/identity/aidl/default/common/SecureHardwareProxy.h b/identity/aidl/default/common/SecureHardwareProxy.h
new file mode 100644
index 0000000..b89ad87
--- /dev/null
+++ b/identity/aidl/default/common/SecureHardwareProxy.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+#define ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
+
+#include <utils/RefBase.h>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+namespace android::hardware::identity {
+
+using ::android::RefBase;
+using ::std::optional;
+using ::std::pair;
+using ::std::string;
+using ::std::vector;
+
+// These classes are used to communicate with Secure Hardware. They mimic the
+// API in libEmbeddedIC 1:1 (except for using C++ types) as each call is intended
+// to be forwarded to the Secure Hardware.
+//
+// Instances are instantiated when a provisioning or presentation session
+// starts. When the session is complete, the shutdown() method is called.
+//
+
+// Forward declare.
+//
+class SecureHardwareProvisioningProxy;
+class SecureHardwarePresentationProxy;
+
+// This is a class used to create proxies.
+//
+class SecureHardwareProxyFactory : public RefBase {
+ public:
+ SecureHardwareProxyFactory() {}
+ virtual ~SecureHardwareProxyFactory() {}
+
+ virtual sp<SecureHardwareProvisioningProxy> createProvisioningProxy() = 0;
+ virtual sp<SecureHardwarePresentationProxy> createPresentationProxy() = 0;
+};
+
+// The proxy used for provisioning.
+//
+class SecureHardwareProvisioningProxy : public RefBase {
+ public:
+ SecureHardwareProvisioningProxy() {}
+ virtual ~SecureHardwareProvisioningProxy() {}
+
+ virtual bool initialize(bool testCredential) = 0;
+
+ // Returns public key certificate chain with attestation.
+ //
+ // This must return an entire certificate chain and its implementation must
+ // be coordinated with the implementation of eicOpsCreateCredentialKey() on
+ // the TA side (which may return just a single certificate or the entire
+ // chain).
+ virtual optional<vector<uint8_t>> createCredentialKey(const vector<uint8_t>& challenge,
+ const vector<uint8_t>& applicationId) = 0;
+
+ virtual bool startPersonalization(int accessControlProfileCount, vector<int> entryCounts,
+ const string& docType,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ // Returns MAC (28 bytes).
+ virtual optional<vector<uint8_t>> addAccessControlProfile(
+ int id, const vector<uint8_t>& readerCertificate, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) = 0;
+
+ virtual bool beginAddEntry(const vector<int>& accessControlProfileIds, const string& nameSpace,
+ const string& name, uint64_t entrySize) = 0;
+
+ // Returns encryptedContent.
+ virtual optional<vector<uint8_t>> addEntryValue(const vector<int>& accessControlProfileIds,
+ const string& nameSpace, const string& name,
+ const vector<uint8_t>& content) = 0;
+
+ // Returns signatureOfToBeSigned (EIC_ECDSA_P256_SIGNATURE_SIZE bytes).
+ virtual optional<vector<uint8_t>> finishAddingEntries() = 0;
+
+ // Returns encryptedCredentialKeys (80 bytes).
+ virtual optional<vector<uint8_t>> finishGetCredentialData(const string& docType) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+enum AccessCheckResult {
+ kOk,
+ kFailed,
+ kNoAccessControlProfiles,
+ kUserAuthenticationFailed,
+ kReaderAuthenticationFailed,
+};
+
+// The proxy used for presentation.
+//
+class SecureHardwarePresentationProxy : public RefBase {
+ public:
+ SecureHardwarePresentationProxy() {}
+ virtual ~SecureHardwarePresentationProxy() {}
+
+ virtual bool initialize(bool testCredential, string docType,
+ vector<uint8_t> encryptedCredentialKeys) = 0;
+
+ // Returns publicKeyCert (1st component) and signingKeyBlob (2nd component)
+ virtual optional<pair<vector<uint8_t>, vector<uint8_t>>> generateSigningKeyPair(string docType,
+ time_t now) = 0;
+
+ // Returns private key
+ virtual optional<vector<uint8_t>> createEphemeralKeyPair() = 0;
+
+ virtual optional<uint64_t> createAuthChallenge() = 0;
+
+ virtual bool startRetrieveEntries() = 0;
+
+ virtual bool setAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp,
+ const vector<uint8_t>& mac, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const vector<uint8_t>& verificationTokenMac) = 0;
+
+ virtual bool pushReaderCert(const vector<uint8_t>& certX509) = 0;
+
+ virtual optional<bool> validateAccessControlProfile(int id,
+ const vector<uint8_t>& readerCertificate,
+ bool userAuthenticationRequired,
+ int timeoutMillis, uint64_t secureUserId,
+ const vector<uint8_t>& mac) = 0;
+
+ virtual bool validateRequestMessage(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& requestMessage, int coseSignAlg,
+ const vector<uint8_t>& readerSignatureOfToBeSigned) = 0;
+
+ virtual bool calcMacKey(const vector<uint8_t>& sessionTranscript,
+ const vector<uint8_t>& readerEphemeralPublicKey,
+ const vector<uint8_t>& signingKeyBlob, const string& docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedProofOfProvisioningSize) = 0;
+
+ virtual AccessCheckResult startRetrieveEntryValue(
+ const string& nameSpace, const string& name, unsigned int newNamespaceNumEntries,
+ int32_t entrySize, const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> retrieveEntryValue(
+ const vector<uint8_t>& encryptedContent, const string& nameSpace, const string& name,
+ const vector<int32_t>& accessControlProfileIds) = 0;
+
+ virtual optional<vector<uint8_t>> finishRetrieval();
+
+ virtual optional<vector<uint8_t>> deleteCredential(const string& docType,
+ size_t proofOfDeletionCborSize) = 0;
+
+ virtual bool shutdown() = 0;
+};
+
+} // namespace android::hardware::identity
+
+#endif // ANDROID_HARDWARE_IDENTITY_SECUREHARDWAREPROXY_H
diff --git a/identity/aidl/default/WritableIdentityCredential.cpp b/identity/aidl/default/common/WritableIdentityCredential.cpp
similarity index 70%
rename from identity/aidl/default/WritableIdentityCredential.cpp
rename to identity/aidl/default/common/WritableIdentityCredential.cpp
index 141b4de..1328f36 100644
--- a/identity/aidl/default/WritableIdentityCredential.cpp
+++ b/identity/aidl/default/common/WritableIdentityCredential.cpp
@@ -17,7 +17,6 @@
#define LOG_TAG "WritableIdentityCredential"
#include "WritableIdentityCredential.h"
-#include "IdentityCredentialStore.h"
#include <android/hardware/identity/support/IdentityCredentialSupport.h>
@@ -30,8 +29,8 @@
#include <utility>
#include "IdentityCredentialStore.h"
-#include "Util.h"
-#include "WritableIdentityCredential.h"
+
+#include "FakeSecureHardwareProxy.h"
namespace aidl::android::hardware::identity {
@@ -40,74 +39,55 @@
using namespace ::android::hardware::identity;
bool WritableIdentityCredential::initialize() {
- optional<vector<uint8_t>> random = support::getRandom(16);
- if (!random) {
- LOG(ERROR) << "Error creating storageKey";
+ if (!hwProxy_->initialize(testCredential_)) {
+ LOG(ERROR) << "hwProxy->initialize failed";
return false;
}
- storageKey_ = random.value();
startPersonalizationCalled_ = false;
firstEntry_ = true;
return true;
}
-// This function generates the attestation certificate using the passed in
-// |attestationApplicationId| and |attestationChallenge|. It will generate an
-// attestation certificate with current time and expires one year from now. The
-// certificate shall contain all values as specified in hal.
+WritableIdentityCredential::~WritableIdentityCredential() {}
+
ndk::ScopedAStatus WritableIdentityCredential::getAttestationCertificate(
- const vector<uint8_t>& attestationApplicationId, //
- const vector<uint8_t>& attestationChallenge, //
- vector<Certificate>* outCertificateChain) {
- if (!credentialPrivKey_.empty() || !credentialPubKey_.empty() || !certificateChain_.empty()) {
+ const vector<uint8_t>& attestationApplicationId,
+ const vector<uint8_t>& attestationChallenge, vector<Certificate>* outCertificateChain) {
+ if (getAttestationCertificateAlreadyCalled_) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
"Error attestation certificate previously generated"));
}
+ getAttestationCertificateAlreadyCalled_ = true;
+
if (attestationChallenge.empty()) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA, "Challenge can not be empty"));
}
- vector<uint8_t> challenge(attestationChallenge.begin(), attestationChallenge.end());
- vector<uint8_t> appId(attestationApplicationId.begin(), attestationApplicationId.end());
-
- optional<std::pair<vector<uint8_t>, vector<vector<uint8_t>>>> keyAttestationPair =
- support::createEcKeyPairAndAttestation(challenge, appId, testCredential_);
- if (!keyAttestationPair) {
- LOG(ERROR) << "Error creating credentialKey and attestation";
+ optional<vector<uint8_t>> certChain =
+ hwProxy_->createCredentialKey(attestationChallenge, attestationApplicationId);
+ if (!certChain) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error creating credentialKey and attestation"));
+ "Error generating attestation certificate chain"));
}
- vector<uint8_t> keyPair = keyAttestationPair.value().first;
- certificateChain_ = keyAttestationPair.value().second;
-
- optional<vector<uint8_t>> pubKey = support::ecKeyPairGetPublicKey(keyPair);
- if (!pubKey) {
+ optional<vector<vector<uint8_t>>> certs = support::certificateChainSplit(certChain.value());
+ if (!certs) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED,
- "Error getting public part of credentialKey"));
+ "Error splitting chain into separate certificates"));
}
- credentialPubKey_ = pubKey.value();
- optional<vector<uint8_t>> privKey = support::ecKeyPairGetPrivateKey(keyPair);
- if (!privKey) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED,
- "Error getting private part of credentialKey"));
- }
- credentialPrivKey_ = privKey.value();
-
- // convert from vector<vector<uint8_t>>> to vector<Certificate>*
*outCertificateChain = vector<Certificate>();
- for (const vector<uint8_t>& cert : certificateChain_) {
+ for (const vector<uint8_t>& cert : certs.value()) {
Certificate c = Certificate();
c.encodedCertificate = cert;
outCertificateChain->push_back(std::move(c));
}
+
return ndk::ScopedAStatus::ok();
}
@@ -123,8 +103,8 @@
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "startPersonalization called already"));
}
-
startPersonalizationCalled_ = true;
+
numAccessControlProfileRemaining_ = accessControlProfileCount;
remainingEntryCounts_ = entryCounts;
entryNameSpace_ = "";
@@ -133,6 +113,12 @@
signedDataNamespaces_ = cppbor::Map();
signedDataCurrentNamespace_ = cppbor::Array();
+ if (!hwProxy_->startPersonalization(accessControlProfileCount, entryCounts, docType_,
+ expectedProofOfProvisioningSize_)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicStartPersonalization"));
+ }
+
return ndk::ScopedAStatus::ok();
}
@@ -140,8 +126,6 @@
int32_t id, const Certificate& readerCertificate, bool userAuthenticationRequired,
int64_t timeoutMillis, int64_t secureUserId,
SecureAccessControlProfile* outSecureAccessControlProfile) {
- SecureAccessControlProfile profile;
-
if (numAccessControlProfileRemaining_ == 0) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_INVALID_DATA,
@@ -169,25 +153,21 @@
"userAuthenticationRequired is false but timeout is non-zero"));
}
- // If |userAuthenticationRequired| is true, then |secureUserId| must be non-zero.
- if (userAuthenticationRequired && secureUserId == 0) {
+ optional<vector<uint8_t>> mac = hwProxy_->addAccessControlProfile(
+ id, readerCertificate.encodedCertificate, userAuthenticationRequired, timeoutMillis,
+ secureUserId);
+ if (!mac) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_INVALID_DATA,
- "userAuthenticationRequired is true but secureUserId is zero"));
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddAccessControlProfile"));
}
+ SecureAccessControlProfile profile;
profile.id = id;
profile.readerCertificate = readerCertificate;
profile.userAuthenticationRequired = userAuthenticationRequired;
profile.timeoutMillis = timeoutMillis;
profile.secureUserId = secureUserId;
- optional<vector<uint8_t>> mac = secureAccessControlProfileCalcMac(profile, storageKey_);
- if (!mac) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error calculating MAC for profile"));
- }
profile.mac = mac.value();
-
cppbor::Map profileMap;
profileMap.add("id", profile.id);
if (profile.readerCertificate.encodedCertificate.size() > 0) {
@@ -261,14 +241,18 @@
remainingEntryCounts_[0] -= 1;
}
- entryAdditionalData_ = entryCreateAdditionalData(nameSpace, name, accessControlProfileIds);
-
entryRemainingBytes_ = entrySize;
entryNameSpace_ = nameSpace;
entryName_ = name;
entryAccessControlProfileIds_ = accessControlProfileIds;
entryBytes_.resize(0);
// LOG(INFO) << "name=" << name << " entrySize=" << entrySize;
+
+ if (!hwProxy_->beginAddEntry(accessControlProfileIds, nameSpace, name, entrySize)) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicBeginAddEntry"));
+ }
+
return ndk::ScopedAStatus::ok();
}
@@ -297,16 +281,11 @@
}
}
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error getting nonce"));
- }
- optional<vector<uint8_t>> encryptedContent =
- support::encryptAes128Gcm(storageKey_, nonce.value(), content, entryAdditionalData_);
+ optional<vector<uint8_t>> encryptedContent = hwProxy_->addEntryValue(
+ entryAccessControlProfileIds_, entryNameSpace_, entryName_, content);
if (!encryptedContent) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error encrypting content"));
+ IIdentityCredentialStore::STATUS_FAILED, "eicAddEntryValue"));
}
if (entryRemainingBytes_ == 0) {
@@ -332,50 +311,6 @@
return ndk::ScopedAStatus::ok();
}
-// Writes CBOR-encoded structure to |credentialKeys| containing |storageKey| and
-// |credentialPrivKey|.
-static bool generateCredentialKeys(const vector<uint8_t>& storageKey,
- const vector<uint8_t>& credentialPrivKey,
- vector<uint8_t>& credentialKeys) {
- if (storageKey.size() != 16) {
- LOG(ERROR) << "Size of storageKey is not 16";
- return false;
- }
-
- cppbor::Array array;
- array.add(cppbor::Bstr(storageKey));
- array.add(cppbor::Bstr(credentialPrivKey));
- credentialKeys = array.encode();
- return true;
-}
-
-// Writes CBOR-encoded structure to |credentialData| containing |docType|,
-// |testCredential| and |credentialKeys|. The latter element will be stored in
-// encrypted form, using |hardwareBoundKey| as the encryption key.
-bool generateCredentialData(const vector<uint8_t>& hardwareBoundKey, const string& docType,
- bool testCredential, const vector<uint8_t>& credentialKeys,
- vector<uint8_t>& credentialData) {
- optional<vector<uint8_t>> nonce = support::getRandom(12);
- if (!nonce) {
- LOG(ERROR) << "Error getting random";
- return false;
- }
- vector<uint8_t> docTypeAsVec(docType.begin(), docType.end());
- optional<vector<uint8_t>> credentialBlob = support::encryptAes128Gcm(
- hardwareBoundKey, nonce.value(), credentialKeys, docTypeAsVec);
- if (!credentialBlob) {
- LOG(ERROR) << "Error encrypting CredentialKeys blob";
- return false;
- }
-
- cppbor::Array array;
- array.add(docType);
- array.add(testCredential);
- array.add(cppbor::Bstr(credentialBlob.value()));
- credentialData = array.encode();
- return true;
-}
-
ndk::ScopedAStatus WritableIdentityCredential::finishAddingEntries(
vector<uint8_t>* outCredentialData, vector<uint8_t>* outProofOfProvisioningSignature) {
if (numAccessControlProfileRemaining_ != 0) {
@@ -411,31 +346,37 @@
.c_str()));
}
- optional<vector<uint8_t>> signature = support::coseSignEcDsa(credentialPrivKey_,
- encodedCbor, // payload
- {}, // additionalData
- {}); // certificateChain
+ optional<vector<uint8_t>> signatureOfToBeSigned = hwProxy_->finishAddingEntries();
+ if (!signatureOfToBeSigned) {
+ return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
+ IIdentityCredentialStore::STATUS_FAILED, "eicFinishAddingEntries"));
+ }
+
+ optional<vector<uint8_t>> signature =
+ support::coseSignEcDsaWithSignature(signatureOfToBeSigned.value(),
+ encodedCbor, // data
+ {}); // certificateChain
if (!signature) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
IIdentityCredentialStore::STATUS_FAILED, "Error signing data"));
}
- vector<uint8_t> credentialKeys;
- if (!generateCredentialKeys(storageKey_, credentialPrivKey_, credentialKeys)) {
+ optional<vector<uint8_t>> encryptedCredentialKeys = hwProxy_->finishGetCredentialData(docType_);
+ if (!encryptedCredentialKeys) {
return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialKeys"));
+ IIdentityCredentialStore::STATUS_FAILED,
+ "Error generating encrypted CredentialKeys"));
}
-
- vector<uint8_t> credentialData;
- if (!generateCredentialData(
- testCredential_ ? support::getTestHardwareBoundKey() : getHardwareBoundKey(),
- docType_, testCredential_, credentialKeys, credentialData)) {
- return ndk::ScopedAStatus(AStatus_fromServiceSpecificErrorWithMessage(
- IIdentityCredentialStore::STATUS_FAILED, "Error generating CredentialData"));
- }
+ cppbor::Array array;
+ array.add(docType_);
+ array.add(testCredential_);
+ array.add(encryptedCredentialKeys.value());
+ vector<uint8_t> credentialData = array.encode();
*outCredentialData = credentialData;
*outProofOfProvisioningSignature = signature.value();
+ hwProxy_->shutdown();
+
return ndk::ScopedAStatus::ok();
}
diff --git a/identity/aidl/default/WritableIdentityCredential.h b/identity/aidl/default/common/WritableIdentityCredential.h
similarity index 85%
rename from identity/aidl/default/WritableIdentityCredential.h
rename to identity/aidl/default/common/WritableIdentityCredential.h
index 5645852..c6f0628 100644
--- a/identity/aidl/default/WritableIdentityCredential.h
+++ b/identity/aidl/default/common/WritableIdentityCredential.h
@@ -23,16 +23,24 @@
#include <cppbor.h>
#include <set>
+#include "IdentityCredentialStore.h"
+#include "SecureHardwareProxy.h"
+
namespace aidl::android::hardware::identity {
+using ::android::sp;
+using ::android::hardware::identity::SecureHardwareProvisioningProxy;
using ::std::set;
using ::std::string;
using ::std::vector;
class WritableIdentityCredential : public BnWritableIdentityCredential {
public:
- WritableIdentityCredential(const string& docType, bool testCredential)
- : docType_(docType), testCredential_(testCredential) {}
+ WritableIdentityCredential(sp<SecureHardwareProvisioningProxy> hwProxy, const string& docType,
+ bool testCredential)
+ : hwProxy_(hwProxy), docType_(docType), testCredential_(testCredential) {}
+
+ ~WritableIdentityCredential();
// Creates the Credential Key. Returns false on failure. Must be called
// right after construction.
@@ -57,7 +65,6 @@
ndk::ScopedAStatus beginAddEntry(const vector<int32_t>& accessControlProfileIds,
const string& nameSpace, const string& name,
int32_t entrySize) override;
-
ndk::ScopedAStatus addEntryValue(const vector<uint8_t>& content,
vector<uint8_t>* outEncryptedContent) override;
@@ -66,18 +73,17 @@
vector<uint8_t>* outProofOfProvisioningSignature) override;
private:
+ // Set by constructor.
+ sp<SecureHardwareProvisioningProxy> hwProxy_;
string docType_;
bool testCredential_;
// This is set in initialize().
- vector<uint8_t> storageKey_;
bool startPersonalizationCalled_;
bool firstEntry_;
- // These are set in getAttestationCertificate().
- vector<uint8_t> credentialPrivKey_;
- vector<uint8_t> credentialPubKey_;
- vector<vector<uint8_t>> certificateChain_;
+ // This is set in getAttestationCertificate().
+ bool getAttestationCertificateAlreadyCalled_ = false;
// These fields are initialized during startPersonalization()
size_t numAccessControlProfileRemaining_;
@@ -92,7 +98,6 @@
// These fields are initialized during beginAddEntry()
size_t entryRemainingBytes_;
- vector<uint8_t> entryAdditionalData_;
string entryNameSpace_;
string entryName_;
vector<int32_t> entryAccessControlProfileIds_;
diff --git a/identity/aidl/default/libeic/EicCbor.c b/identity/aidl/default/libeic/EicCbor.c
new file mode 100644
index 0000000..ec049b1
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicCbor.h"
+
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize) {
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_SHA256;
+ eicOpsSha256Init(&cbor->digester.sha256);
+}
+
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize) {
+ cbor->size = 0;
+ cbor->bufferSize = bufferSize;
+ cbor->buffer = buffer;
+ cbor->digestType = EIC_CBOR_DIGEST_TYPE_HMAC_SHA256;
+ eicOpsHmacSha256Init(&cbor->digester.hmacSha256, hmacKey, hmacKeySize);
+}
+
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Final(&cbor->digester.sha256, digest);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Final(&cbor->digester.hmacSha256, digest);
+ break;
+ }
+}
+
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size) {
+ switch (cbor->digestType) {
+ case EIC_CBOR_DIGEST_TYPE_SHA256:
+ eicOpsSha256Update(&cbor->digester.sha256, data, size);
+ break;
+ case EIC_CBOR_DIGEST_TYPE_HMAC_SHA256:
+ eicOpsHmacSha256Update(&cbor->digester.hmacSha256, data, size);
+ break;
+ }
+
+ if (cbor->size >= cbor->bufferSize) {
+ cbor->size += size;
+ return;
+ }
+
+ size_t numBytesLeft = cbor->bufferSize - cbor->size;
+ size_t numBytesToCopy = size;
+ if (numBytesToCopy > numBytesLeft) {
+ numBytesToCopy = numBytesLeft;
+ }
+ eicMemCpy(cbor->buffer + cbor->size, data, numBytesToCopy);
+
+ cbor->size += size;
+}
+
+size_t eicCborAdditionalLengthBytesFor(size_t size) {
+ if (size < 24) {
+ return 0;
+ } else if (size <= 0xff) {
+ return 1;
+ } else if (size <= 0xffff) {
+ return 2;
+ } else if (size <= 0xffffffff) {
+ return 4;
+ }
+ return 8;
+}
+
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size) {
+ uint8_t data[9];
+
+ if (size < 24) {
+ data[0] = (majorType << 5) | size;
+ eicCborAppend(cbor, data, 1);
+ } else if (size <= 0xff) {
+ data[0] = (majorType << 5) | 24;
+ data[1] = size;
+ eicCborAppend(cbor, data, 2);
+ } else if (size <= 0xffff) {
+ data[0] = (majorType << 5) | 25;
+ data[1] = size >> 8;
+ data[2] = size & 0xff;
+ eicCborAppend(cbor, data, 3);
+ } else if (size <= 0xffffffff) {
+ data[0] = (majorType << 5) | 26;
+ data[1] = (size >> 24) & 0xff;
+ data[2] = (size >> 16) & 0xff;
+ data[3] = (size >> 8) & 0xff;
+ data[4] = size & 0xff;
+ eicCborAppend(cbor, data, 5);
+ } else {
+ data[0] = (majorType << 5) | 24;
+ data[1] = (((uint64_t)size) >> 56) & 0xff;
+ data[2] = (((uint64_t)size) >> 48) & 0xff;
+ data[3] = (((uint64_t)size) >> 40) & 0xff;
+ data[4] = (((uint64_t)size) >> 32) & 0xff;
+ data[5] = (((uint64_t)size) >> 24) & 0xff;
+ data[6] = (((uint64_t)size) >> 16) & 0xff;
+ data[7] = (((uint64_t)size) >> 8) & 0xff;
+ data[8] = ((uint64_t)size) & 0xff;
+ eicCborAppend(cbor, data, 9);
+ }
+}
+
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dataSize);
+ eicCborAppend(cbor, data, dataSize);
+}
+
+void eicCborAppendString(EicCbor* cbor, const char* str) {
+ size_t length = eicStrLen(str);
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_STRING, length);
+ eicCborAppend(cbor, (const uint8_t*)str, length);
+}
+
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SIMPLE, simpleValue);
+}
+
+void eicCborAppendBool(EicCbor* cbor, bool value) {
+ uint8_t simpleValue = value ? EIC_CBOR_SIMPLE_VALUE_TRUE : EIC_CBOR_SIMPLE_VALUE_FALSE;
+ eicCborAppendSimple(cbor, simpleValue);
+}
+
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value) {
+ size_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_SEMANTIC, encoded);
+}
+
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value) {
+ size_t encoded = value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_UNSIGNED, encoded);
+}
+
+void eicCborAppendNumber(EicCbor* cbor, int64_t value) {
+ if (value < 0) {
+ size_t encoded = -1 - value;
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_NEGATIVE, encoded);
+ } else {
+ eicCborAppendUnsigned(cbor, value);
+ }
+}
+
+void eicCborAppendArray(EicCbor* cbor, size_t numElements) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, numElements);
+}
+
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs) {
+ eicCborBegin(cbor, EIC_CBOR_MAJOR_TYPE_MAP, numPairs);
+}
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId) {
+ size_t numPairs = 1;
+ if (readerCertificateSize > 0) {
+ numPairs += 1;
+ }
+ if (userAuthenticationRequired) {
+ numPairs += 2;
+ if (secureUserId > 0) {
+ numPairs += 1;
+ }
+ }
+ eicCborAppendMap(cborBuilder, numPairs);
+ eicCborAppendString(cborBuilder, "id");
+ eicCborAppendUnsigned(cborBuilder, id);
+ if (readerCertificateSize > 0) {
+ eicCborAppendString(cborBuilder, "readerCertificate");
+ eicCborAppendByteString(cborBuilder, readerCertificate, readerCertificateSize);
+ }
+ if (userAuthenticationRequired) {
+ eicCborAppendString(cborBuilder, "userAuthenticationRequired");
+ eicCborAppendBool(cborBuilder, userAuthenticationRequired);
+ eicCborAppendString(cborBuilder, "timeoutMillis");
+ eicCborAppendUnsigned(cborBuilder, timeoutMillis);
+ if (secureUserId > 0) {
+ eicCborAppendString(cborBuilder, "secureUserId");
+ eicCborAppendUnsigned(cborBuilder, secureUserId);
+ }
+ }
+
+ if (cborBuilder->size > cborBuilder->bufferSize) {
+ eicDebug("Buffer for ACP CBOR is too small (%zd) - need %zd bytes", cborBuilder->bufferSize,
+ cborBuilder->size);
+ return false;
+ }
+
+ return true;
+}
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]) {
+ EicCbor cborBuilder;
+
+ eicCborInit(&cborBuilder, cborBuffer, cborBufferSize);
+ eicCborAppendMap(&cborBuilder, 3);
+ eicCborAppendString(&cborBuilder, "Namespace");
+ eicCborAppendString(&cborBuilder, nameSpace);
+ eicCborAppendString(&cborBuilder, "Name");
+ eicCborAppendString(&cborBuilder, name);
+ eicCborAppendString(&cborBuilder, "AccessControlProfileIds");
+ eicCborAppendArray(&cborBuilder, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&cborBuilder, accessControlProfileIds[n]);
+ }
+ if (cborBuilder.size > cborBufferSize) {
+ eicDebug("Not enough space for additionalData - buffer is only %zd bytes, content is %zd",
+ cborBufferSize, cborBuilder.size);
+ return false;
+ }
+ if (outAdditionalDataCborSize != NULL) {
+ *outAdditionalDataCborSize = cborBuilder.size;
+ }
+ eicCborFinal(&cborBuilder, additionalDataSha256);
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicCbor.h b/identity/aidl/default/libeic/EicCbor.h
new file mode 100644
index 0000000..4686b38
--- /dev/null
+++ b/identity/aidl/default/libeic/EicCbor.h
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicOps.h"
+
+typedef enum {
+ EIC_CBOR_DIGEST_TYPE_SHA256,
+ EIC_CBOR_DIGEST_TYPE_HMAC_SHA256,
+} EicCborDigestType;
+
+/* EicCbor is a utility class to build CBOR data structures and calculate
+ * digests on the fly.
+ */
+typedef struct {
+ // Contains the size of the built CBOR, even if it exceeds bufferSize (will
+ // never write to buffer beyond bufferSize though)
+ size_t size;
+
+ // The size of the buffer. Is zero if no data is recorded in which case
+ // only digesting is performed.
+ size_t bufferSize;
+
+ // Whether we're producing a SHA-256 or HMAC-SHA256 digest.
+ EicCborDigestType digestType;
+
+ // The SHA-256 digester object.
+ union {
+ EicSha256Ctx sha256;
+ EicHmacSha256Ctx hmacSha256;
+ } digester;
+
+ // The buffer used for building up CBOR or NULL if bufferSize is 0.
+ uint8_t* buffer;
+} EicCbor;
+
+/* Initializes an EicCbor.
+ *
+ * The given buffer will be used, up to bufferSize.
+ *
+ * If bufferSize is 0, buffer may be NULL.
+ */
+void eicCborInit(EicCbor* cbor, uint8_t* buffer, size_t bufferSize);
+
+/* Like eicCborInit() but uses HMAC-SHA256 instead of SHA-256.
+ */
+void eicCborInitHmacSha256(EicCbor* cbor, uint8_t* buffer, size_t bufferSize,
+ const uint8_t* hmacKey, size_t hmacKeySize);
+
+/* Finishes building CBOR and returns the digest. */
+void eicCborFinal(EicCbor* cbor, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+/* Appends CBOR data to the EicCbor. */
+void eicCborAppend(EicCbor* cbor, const uint8_t* data, size_t size);
+
+#define EIC_CBOR_MAJOR_TYPE_UNSIGNED 0
+#define EIC_CBOR_MAJOR_TYPE_NEGATIVE 1
+#define EIC_CBOR_MAJOR_TYPE_BYTE_STRING 2
+#define EIC_CBOR_MAJOR_TYPE_STRING 3
+#define EIC_CBOR_MAJOR_TYPE_ARRAY 4
+#define EIC_CBOR_MAJOR_TYPE_MAP 5
+#define EIC_CBOR_MAJOR_TYPE_SEMANTIC 6
+#define EIC_CBOR_MAJOR_TYPE_SIMPLE 7
+
+#define EIC_CBOR_SIMPLE_VALUE_FALSE 20
+#define EIC_CBOR_SIMPLE_VALUE_TRUE 21
+
+#define EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR 24
+
+/* Begins a new CBOR value. */
+void eicCborBegin(EicCbor* cbor, int majorType, size_t size);
+
+/* Appends a bytestring. */
+void eicCborAppendByteString(EicCbor* cbor, const uint8_t* data, size_t dataSize);
+
+/* Appends a NUL-terminated UTF-8 string. */
+void eicCborAppendString(EicCbor* cbor, const char* str);
+
+/* Appends a simple value. */
+void eicCborAppendSimple(EicCbor* cbor, uint8_t simpleValue);
+
+/* Appends a boolean. */
+void eicCborAppendBool(EicCbor* cbor, bool value);
+
+/* Appends a semantic */
+void eicCborAppendSemantic(EicCbor* cbor, uint64_t value);
+
+/* Appends an unsigned number. */
+void eicCborAppendUnsigned(EicCbor* cbor, uint64_t value);
+
+/* Appends a number. */
+void eicCborAppendNumber(EicCbor* cbor, int64_t value);
+
+/* Starts appending an array.
+ *
+ * After this numElements CBOR elements must follow.
+ */
+void eicCborAppendArray(EicCbor* cbor, size_t numElements);
+
+/* Starts appending a map.
+ *
+ * After this numPairs pairs of CBOR elements must follow.
+ */
+void eicCborAppendMap(EicCbor* cbor, size_t numPairs);
+
+/* Calculates how many bytes are needed to store a size. */
+size_t eicCborAdditionalLengthBytesFor(size_t size);
+
+bool eicCborCalcAccessControl(EicCbor* cborBuilder, int id, const uint8_t* readerCertificate,
+ size_t readerCertificateSize, bool userAuthenticationRequired,
+ uint64_t timeoutMillis, uint64_t secureUserId);
+
+bool eicCborCalcEntryAdditionalData(const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint8_t* cborBuffer, size_t cborBufferSize,
+ size_t* outAdditionalDataCborSize,
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE]);
+
+// The maximum size of an encoded Secure Access Control Profile that we
+// support. Since the SACP may contain a reader certificate chain these can get
+// pretty big.
+//
+// Currently we allocate space on the stack for this structure which is why we
+// have a maximum size. We can get rid of the maximum size by incrementally
+// building/verifying the SACP. TODO: actually do this.
+//
+#define EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE 512
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_CBOR_H
diff --git a/identity/aidl/default/libeic/EicOps.h b/identity/aidl/default/libeic/EicOps.h
new file mode 100644
index 0000000..da4dabf
--- /dev/null
+++ b/identity/aidl/default/libeic/EicOps.h
@@ -0,0 +1,299 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+// Uncomment or define if debug messages are needed.
+//
+//#define EIC_DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// The following defines must be set to something appropriate
+//
+// EIC_SHA256_CONTEXT_SIZE - the size of EicSha256Ctx
+// EIC_HMAC_SHA256_CONTEXT_SIZE - the size of EicHmacSha256Ctx
+//
+// For example, if EicSha256Ctx is implemented using BoringSSL this would be defined
+// as sizeof(SHA256_CTX).
+//
+// We expect the implementation to provide a header file with the name
+// EicOpsImpl.h to do all this.
+//
+#include "EicOpsImpl.h"
+
+#define EIC_SHA256_DIGEST_SIZE 32
+
+// The size of a P-256 private key.
+//
+#define EIC_P256_PRIV_KEY_SIZE 32
+
+// The size of a P-256 public key in uncompressed form.
+//
+// The public key is stored in uncompressed form, first the X coordinate, then
+// the Y coordinate.
+//
+#define EIC_P256_PUB_KEY_SIZE 64
+
+// Size of one of the coordinates in a curve-point.
+//
+#define EIC_P256_COORDINATE_SIZE 32
+
+// The size of an ECSDA signature using P-256.
+//
+// The R and S values are stored here, first R then S.
+//
+#define EIC_ECDSA_P256_SIGNATURE_SIZE 64
+
+#define EIC_AES_128_KEY_SIZE 16
+
+// The following are definitions of implementation functions the
+// underlying platform must provide.
+//
+
+struct EicSha256Ctx {
+ uint8_t reserved[EIC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicSha256Ctx EicSha256Ctx;
+
+struct EicHmacSha256Ctx {
+ uint8_t reserved[EIC_HMAC_SHA256_CONTEXT_SIZE];
+};
+typedef struct EicHmacSha256Ctx EicHmacSha256Ctx;
+
+#ifdef EIC_DEBUG
+// Debug macro. Don't include a new-line in message.
+//
+#define eicDebug(...) \
+ do { \
+ eicPrint("%s:%d: ", __FILE__, __LINE__); \
+ eicPrint(__VA_ARGS__); \
+ eicPrint("\n"); \
+ } while (0)
+#else
+#define eicDebug(...) \
+ do { \
+ } while (0)
+#endif
+
+// Prints message which should include new-line character. Can be no-op.
+//
+// Don't use this from code, use eicDebug() instead.
+//
+#ifdef EIC_DEBUG
+void eicPrint(const char* format, ...);
+#else
+inline void eicPrint(const char*, ...) {}
+#endif
+
+// Dumps data as pretty-printed hex. Can be no-op.
+//
+#ifdef EIC_DEBUG
+void eicHexdump(const char* message, const uint8_t* data, size_t dataSize);
+#else
+inline void eicHexdump(const char*, const uint8_t*, size_t) {}
+#endif
+
+// Pretty-prints encoded CBOR. Can be no-op.
+//
+// If a byte-string is larger than |maxBStrSize| its contents will not be
+// printed, instead the value of the form "<bstr size=1099016
+// sha1=ef549cca331f73dfae2090e6a37c04c23f84b07b>" will be printed. Pass zero
+// for |maxBStrSize| to disable this.
+//
+#ifdef EIC_DEBUG
+void eicCborPrettyPrint(const uint8_t* cborData, size_t cborDataSize, size_t maxBStrSize);
+#else
+inline void eicCborPrettyPrint(const uint8_t*, size_t, size_t) {}
+#endif
+
+// Memory setting, see memset(3).
+void* eicMemSet(void* s, int c, size_t n);
+
+// Memory copying, see memcpy(3).
+void* eicMemCpy(void* dest, const void* src, size_t n);
+
+// String length, see strlen(3).
+size_t eicStrLen(const char* s);
+
+// Memory compare, see CRYPTO_memcmp(3SSL)
+//
+// It takes an amount of time dependent on len, but independent of the contents of the
+// memory regions pointed to by s1 and s2.
+//
+int eicCryptoMemCmp(const void* s1, const void* s2, size_t n);
+
+// Random number generation.
+bool eicOpsRandom(uint8_t* buf, size_t numBytes);
+
+// If |testCredential| is true, returns the 128-bit AES Hardware-Bound Key (16 bytes).
+//
+// Otherwise returns all zeroes (16 bytes).
+//
+const uint8_t* eicOpsGetHardwareBoundKey(bool testCredential);
+
+// Encrypts |data| with |key| and |additionalAuthenticatedData| using |nonce|,
+// returns the resulting (nonce || ciphertext || tag) in |encryptedData| which
+// must be of size |dataSize| + 28.
+bool eicOpsEncryptAes128Gcm(
+ const uint8_t* key, // Must be 16 bytes
+ const uint8_t* nonce, // Must be 12 bytes
+ const uint8_t* data, // May be NULL if size is 0
+ size_t dataSize,
+ const uint8_t* additionalAuthenticationData, // May be NULL if size is 0
+ size_t additionalAuthenticationDataSize, uint8_t* encryptedData);
+
+// Decrypts |encryptedData| using |key| and |additionalAuthenticatedData|,
+// returns resulting plaintext in |data| must be of size |encryptedDataSize| - 28.
+//
+// The format of |encryptedData| must be as specified in the
+// encryptAes128Gcm() function.
+bool eicOpsDecryptAes128Gcm(const uint8_t* key, // Must be 16 bytes
+ const uint8_t* encryptedData, size_t encryptedDataSize,
+ const uint8_t* additionalAuthenticationData,
+ size_t additionalAuthenticationDataSize, uint8_t* data);
+
+// Creates an EC key using the P-256 curve. The private key is written to
+// |privateKey|. The public key is written to |publicKey|.
+//
+bool eicOpsCreateEcKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t publicKey[EIC_P256_PUB_KEY_SIZE]);
+
+// Generates CredentialKey plus an attestation certificate.
+//
+// The attestation certificate will be signed by the attestation keys the secure
+// area has been provisioned with. The given |challenge| and |applicationId|
+// will be used as will |testCredential|.
+//
+// The generated certificate will be in X.509 format and returned in |cert|
+// and |certSize| must be set to the size of this array and this function will
+// set it to the size of the certification chain on successfully return.
+//
+// This may return either a single certificate or an entire certificate
+// chain. If it returns only a single certificate, the implementation of
+// SecureHardwareProvisioningProxy::createCredentialKey() should amend the
+// remainder of the certificate chain on the HAL side.
+//
+bool eicOpsCreateCredentialKey(uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE], const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, bool testCredential, uint8_t* cert,
+ size_t* certSize); // inout
+
+// Generate an X.509 certificate for the key identified by |publicKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+// The certificate will be signed by the key identified by |signingKey| which
+// must be of the form returned by eicOpsCreateEcKey().
+//
+bool eicOpsSignEcKey(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKey[EIC_P256_PRIV_KEY_SIZE], unsigned int serial,
+ const char* issuerName, const char* subjectName, time_t validityNotBefore,
+ time_t validityNotAfter, uint8_t* cert,
+ size_t* certSize); // inout
+
+// Uses |privateKey| to create an ECDSA signature of some data (the SHA-256 must
+// be given by |digestOfData|). Returns the signature in |signature|.
+//
+bool eicOpsEcDsa(const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ const uint8_t digestOfData[EIC_SHA256_DIGEST_SIZE],
+ uint8_t signature[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+// Performs Elliptic Curve Diffie-Helman.
+//
+bool eicOpsEcdh(const uint8_t publicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t privateKey[EIC_P256_PRIV_KEY_SIZE],
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE]);
+
+// Performs HKDF.
+//
+bool eicOpsHkdf(const uint8_t* sharedSecret, size_t sharedSecretSize, const uint8_t* salt,
+ size_t saltSize, const uint8_t* info, size_t infoSize, uint8_t* output,
+ size_t outputSize);
+
+// SHA-256 functions.
+void eicOpsSha256Init(EicSha256Ctx* ctx);
+void eicOpsSha256Update(EicSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsSha256Final(EicSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// HMAC SHA-256 functions.
+void eicOpsHmacSha256Init(EicHmacSha256Ctx* ctx, const uint8_t* key, size_t keySize);
+void eicOpsHmacSha256Update(EicHmacSha256Ctx* ctx, const uint8_t* data, size_t len);
+void eicOpsHmacSha256Final(EicHmacSha256Ctx* ctx, uint8_t digest[EIC_SHA256_DIGEST_SIZE]);
+
+// Extracts the public key in the given X.509 certificate.
+//
+// If the key is not an EC key, this function fails.
+//
+// Otherwise the public key is stored in uncompressed form in |publicKey| which
+// size should be set in |publicKeySize|. On successful return |publicKeySize|
+// is set to the length of the key. If there is not enough space, the function
+// fails.
+//
+// (The public key returned is not necessarily a P-256 key, even if it is note
+// that its size is not EIC_P256_PUBLIC_KEY_SIZE because of the leading 0x04.)
+//
+bool eicOpsX509GetPublicKey(const uint8_t* x509Cert, size_t x509CertSize, uint8_t* publicKey,
+ size_t* publicKeySize);
+
+// Checks that the X.509 certificate given by |x509Cert| is signed by the public
+// key given by |publicKey| which must be an EC key in uncompressed form (e.g.
+// same formatt as returned by eicOpsX509GetPublicKey()).
+//
+bool eicOpsX509CertSignedByPublicKey(const uint8_t* x509Cert, size_t x509CertSize,
+ const uint8_t* publicKey, size_t publicKeySize);
+
+// Checks that |signature| is a signature of some data (given by |digest|),
+// signed by the public key given by |publicKey|.
+//
+// The key must be an EC key in uncompressed form (e.g. same format as returned
+// by eicOpsX509GetPublicKey()).
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// The size of digest must match the size of the key.
+//
+bool eicOpsEcDsaVerifyWithPublicKey(const uint8_t* digest, size_t digestSize,
+ const uint8_t* signature, size_t signatureSize,
+ const uint8_t* publicKey, size_t publicKeySize);
+
+// Validates that the passed in data constitutes a valid auth- and verification tokens.
+//
+bool eicOpsValidateAuthToken(uint64_t challenge, uint64_t secureUserId, uint64_t authenticatorId,
+ int hardwareAuthenticatorType, uint64_t timeStamp, const uint8_t* mac,
+ size_t macSize, uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac, size_t verificationTokenMacSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_OPS_H
diff --git a/identity/aidl/default/libeic/EicPresentation.c b/identity/aidl/default/libeic/EicPresentation.c
new file mode 100644
index 0000000..d3f5556
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.c
@@ -0,0 +1,728 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicPresentation.h"
+
+#include <inttypes.h>
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+ const uint8_t encryptedCredentialKeys[80]) {
+ uint8_t credentialKeys[52];
+
+ eicMemSet(ctx, '\0', sizeof(EicPresentation));
+
+ if (!eicOpsDecryptAes128Gcm(eicOpsGetHardwareBoundKey(testCredential), encryptedCredentialKeys,
+ 80,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), credentialKeys)) {
+ eicDebug("Error decrypting CredentialKeys");
+ return false;
+ }
+
+ // It's supposed to look like this;
+ //
+ // CredentialKeys = [
+ // bstr, ; storageKey, a 128-bit AES key
+ // bstr ; credentialPrivKey, the private key for credentialKey
+ // ]
+ //
+ // where storageKey is 16 bytes and credentialPrivateKey is 32 bytes.
+ //
+ // So the first two bytes will be 0x82 0x50 indicating resp. an array of two elements
+ // and a bstr of 16 elements. Sixteen bytes later (offset 18 and 19) there will be
+ // a bstr of 32 bytes. It's encoded as two bytes 0x58 and 0x20.
+ //
+ if (credentialKeys[0] != 0x82 || credentialKeys[1] != 0x50 || credentialKeys[18] != 0x58 ||
+ credentialKeys[19] != 0x20) {
+ eicDebug("Invalid CBOR for CredentialKeys");
+ return false;
+ }
+ eicMemCpy(ctx->storageKey, credentialKeys + 2, EIC_AES_128_KEY_SIZE);
+ eicMemCpy(ctx->credentialPrivateKey, credentialKeys + 20, EIC_P256_PRIV_KEY_SIZE);
+ ctx->testCredential = testCredential;
+ return true;
+}
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ uint8_t signingKeyPub[EIC_P256_PUB_KEY_SIZE];
+
+ if (!eicOpsCreateEcKey(signingKeyPriv, signingKeyPub)) {
+ eicDebug("Error creating signing key");
+ return false;
+ }
+
+ const int secondsInOneYear = 365 * 24 * 60 * 60;
+ time_t validityNotBefore = now;
+ time_t validityNotAfter = now + secondsInOneYear; // One year from now.
+ if (!eicOpsSignEcKey(signingKeyPub, ctx->credentialPrivateKey, 1,
+ "Android Identity Credential Key", // issuer CN
+ "Android Identity Credential Authentication Key", // subject CN
+ validityNotBefore, validityNotAfter, publicKeyCert, publicKeyCertSize)) {
+ eicDebug("Error creating certificate for signing key");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, signingKeyPriv, sizeof(signingKeyPriv),
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), signingKeyBlob)) {
+ eicDebug("Error encrypting signing key");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]) {
+ uint8_t ephemeralPublicKey[EIC_P256_PUB_KEY_SIZE];
+ if (!eicOpsCreateEcKey(ctx->ephemeralPrivateKey, ephemeralPublicKey)) {
+ eicDebug("Error creating ephemeral key");
+ return false;
+ }
+ eicMemCpy(ephemeralPrivateKey, ctx->ephemeralPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+ return true;
+}
+
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge) {
+ do {
+ if (!eicOpsRandom((uint8_t*)&(ctx->authChallenge), sizeof(uint64_t))) {
+ eicDebug("Failed generating random challenge");
+ return false;
+ }
+ } while (ctx->authChallenge == 0);
+ eicDebug("Created auth challenge %" PRIu64, ctx->authChallenge);
+ *authChallenge = ctx->authChallenge;
+ return true;
+}
+
+// From "COSE Algorithms" registry
+//
+#define COSE_ALG_ECDSA_256 -7
+
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t* requestMessage, size_t requestMessageSize,
+ int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize) {
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("No public key for reader");
+ return false;
+ }
+
+ // Right now we only support ECDSA with SHA-256 (e.g. ES256).
+ //
+ if (coseSignAlg != COSE_ALG_ECDSA_256) {
+ eicDebug(
+ "COSE Signature algorithm for reader signature is %d, "
+ "only ECDSA with SHA-256 is supported right now",
+ coseSignAlg);
+ return false;
+ }
+
+ // What we're going to verify is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ // So we're going to build that CBOR...
+ //
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> coseSignAlg (e.g. -7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // External_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // ReaderAuthentication = [
+ // "ReaderAuthentication",
+ // SessionTranscript,
+ // ItemsRequestBytes
+ // ]
+ //
+ // ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)
+ //
+ // ReaderAuthenticationBytes = #6.24(bstr .cbor ReaderAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 3
+ calculatedSize += 1; // "ReaderAuthentication" less than 24 bytes
+ calculatedSize += sizeof("ReaderAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(requestMessageSize);
+ calculatedSize += requestMessageSize;
+
+ // However note that we're authenticating ReaderAuthenticationBytes which
+ // is a tagged bstr of the bytes of ReaderAuthentication. So need to get
+ // that in front.
+ size_t rabCalculatedSize = 0;
+ rabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ rabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ rabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for ReaderAuthenticationBytes;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, rabCalculatedSize);
+
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for ReaderAuthentication;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ // And now that we know the size, let's fill it in...
+ //
+ size_t payloadOffset = cbor.size;
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_ARRAY, 3);
+ eicCborAppendString(&cbor, "ReaderAuthentication");
+ eicCborAppend(&cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, requestMessageSize);
+ eicCborAppend(&cbor, requestMessage, requestMessageSize);
+
+ if (cbor.size != payloadOffset + calculatedSize) {
+ eicDebug("CBOR size is %zd but we expected %zd", cbor.size, payloadOffset + calculatedSize);
+ return false;
+ }
+ uint8_t toBeSignedDigest[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, toBeSignedDigest);
+
+ if (!eicOpsEcDsaVerifyWithPublicKey(
+ toBeSignedDigest, EIC_SHA256_DIGEST_SIZE, readerSignatureOfToBeSigned,
+ readerSignatureOfToBeSignedSize, ctx->readerPublicKey, ctx->readerPublicKeySize)) {
+ eicDebug("Request message is not signed by public key");
+ return false;
+ }
+ ctx->requestMessageValidated = true;
+ return true;
+}
+
+// Validates the next certificate in the reader certificate chain.
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+ size_t certX509Size) {
+ // If we had a previous certificate, use its public key to validate this certificate.
+ if (ctx->readerPublicKeySize > 0) {
+ if (!eicOpsX509CertSignedByPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+ ctx->readerPublicKeySize)) {
+ eicDebug("Certificate is not signed by public key in the previous certificate");
+ return false;
+ }
+ }
+
+ // Store the key of this certificate, this is used to validate the next certificate
+ // and also ACPs with certificates that use the same public key...
+ ctx->readerPublicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(certX509, certX509Size, ctx->readerPublicKey,
+ &ctx->readerPublicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (ctx->readerPublicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType,
+ uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimestamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac,
+ size_t verificationTokenMacSize) {
+ if (!eicOpsValidateAuthToken(
+ challenge, secureUserId, authenticatorId, hardwareAuthenticatorType, timeStamp, mac,
+ macSize, verificationTokenChallenge, verificationTokenTimestamp,
+ verificationTokenSecurityLevel, verificationTokenMac, verificationTokenMacSize)) {
+ return false;
+ }
+ ctx->authTokenChallenge = challenge;
+ ctx->authTokenSecureUserId = secureUserId;
+ ctx->authTokenTimestamp = timeStamp;
+ ctx->verificationTokenTimestamp = verificationTokenTimestamp;
+ return true;
+}
+
+static bool checkUserAuth(EicPresentation* ctx, bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId) {
+ if (!userAuthenticationRequired) {
+ return true;
+ }
+
+ if (secureUserId != ctx->authTokenSecureUserId) {
+ eicDebug("secureUserId in profile differs from userId in authToken");
+ return false;
+ }
+
+ if (timeoutMillis == 0) {
+ if (ctx->authTokenChallenge == 0) {
+ eicDebug("No challenge in authToken");
+ return false;
+ }
+
+ // If we didn't create a challenge, too bad but user auth with
+ // timeoutMillis set to 0 needs it.
+ if (ctx->authChallenge == 0) {
+ eicDebug("No challenge was created for this session");
+ return false;
+ }
+ if (ctx->authTokenChallenge != ctx->authChallenge) {
+ eicDebug("Challenge in authToken (%" PRIu64
+ ") doesn't match the challenge "
+ "that was created (%" PRIu64 ") for this session",
+ ctx->authTokenChallenge, ctx->authChallenge);
+ return false;
+ }
+ }
+
+ uint64_t now = ctx->verificationTokenTimestamp;
+ if (ctx->authTokenTimestamp > now) {
+ eicDebug("Timestamp in authToken is in the future");
+ return false;
+ }
+
+ if (timeoutMillis > 0) {
+ if (now > ctx->authTokenTimestamp + timeoutMillis) {
+ eicDebug("Deadline for authToken is in the past");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool checkReaderAuth(EicPresentation* ctx, const uint8_t* readerCertificate,
+ size_t readerCertificateSize) {
+ uint8_t publicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t publicKeySize;
+
+ if (readerCertificateSize == 0) {
+ return true;
+ }
+
+ // Remember in this case certificate equality is done by comparing public
+ // keys, not bitwise comparison of the certificates.
+ //
+ publicKeySize = EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE;
+ if (!eicOpsX509GetPublicKey(readerCertificate, readerCertificateSize, publicKey,
+ &publicKeySize)) {
+ eicDebug("Error extracting public key from certificate");
+ return false;
+ }
+ if (publicKeySize == 0) {
+ eicDebug("Zero-length public key in certificate");
+ return false;
+ }
+
+ if ((ctx->readerPublicKeySize != publicKeySize) ||
+ (eicCryptoMemCmp(ctx->readerPublicKey, publicKey, ctx->readerPublicKeySize) != 0)) {
+ return false;
+ }
+ return true;
+}
+
+// Note: This function returns false _only_ if an error occurred check for access, _not_
+// whether access is granted. Whether access is granted is returned in |accessGranted|.
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted) {
+ *accessGranted = false;
+
+ if (id < 0 || id >= 32) {
+ eicDebug("id value of %d is out of allowed range [0, 32[", id);
+ return false;
+ }
+
+ // Validate the MAC
+ uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+ EicCbor cborBuilder;
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, mac, 28, cborBuilder.buffer, cborBuilder.size,
+ NULL)) {
+ eicDebug("MAC for AccessControlProfile doesn't match");
+ return false;
+ }
+
+ bool passedUserAuth =
+ checkUserAuth(ctx, userAuthenticationRequired, timeoutMillis, secureUserId);
+ bool passedReaderAuth = checkReaderAuth(ctx, readerCertificate, readerCertificateSize);
+
+ ctx->accessControlProfileMaskValidated |= (1 << id);
+ if (readerCertificateSize > 0) {
+ ctx->accessControlProfileMaskUsesReaderAuth |= (1 << id);
+ }
+ if (!passedReaderAuth) {
+ ctx->accessControlProfileMaskFailedReaderAuth |= (1 << id);
+ }
+ if (!passedUserAuth) {
+ ctx->accessControlProfileMaskFailedUserAuth |= (1 << id);
+ }
+
+ if (passedUserAuth && passedReaderAuth) {
+ *accessGranted = true;
+ eicDebug("Access granted for id %d", id);
+ }
+ return true;
+}
+
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedDeviceNamespacesSize) {
+ uint8_t signingKeyPriv[EIC_P256_PRIV_KEY_SIZE];
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, signingKeyBlob, 60, (const uint8_t*)docType,
+ eicStrLen(docType), signingKeyPriv)) {
+ eicDebug("Error decrypting signingKeyBlob");
+ return false;
+ }
+
+ uint8_t sharedSecret[EIC_P256_COORDINATE_SIZE];
+ if (!eicOpsEcdh(readerEphemeralPublicKey, signingKeyPriv, sharedSecret)) {
+ eicDebug("ECDH failed");
+ return false;
+ }
+
+ EicCbor cbor;
+ eicCborInit(&cbor, NULL, 0);
+ eicCborAppendSemantic(&cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborAppendByteString(&cbor, sessionTranscript, sessionTranscriptSize);
+ uint8_t salt[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, salt);
+
+ const uint8_t info[7] = {'E', 'M', 'a', 'c', 'K', 'e', 'y'};
+ uint8_t derivedKey[32];
+ if (!eicOpsHkdf(sharedSecret, EIC_P256_COORDINATE_SIZE, salt, sizeof(salt), info, sizeof(info),
+ derivedKey, sizeof(derivedKey))) {
+ eicDebug("HKDF failed");
+ return false;
+ }
+
+ eicCborInitHmacSha256(&ctx->cbor, NULL, 0, derivedKey, sizeof(derivedKey));
+ ctx->buildCbor = true;
+
+ // What we're going to calculate the HMAC-SHA256 is the COSE ToBeMaced
+ // structure which looks like the following:
+ //
+ // MAC_structure = [
+ // context : "MAC" / "MAC0",
+ // protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "MAC0");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_HMAC_256_256 (5). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x05};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time... the CBOR to be written is
+ //
+ // DeviceAuthentication = [
+ // "DeviceAuthentication",
+ // SessionTranscript,
+ // DocType, ; DocType as used in Documents structure in OfflineResponse
+ // DeviceNameSpacesBytes
+ // ]
+ //
+ // DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
+ //
+ // DeviceAuthenticationBytes = #6.24(bstr .cbor DeviceAuthentication)
+ //
+ // which is easily calculated below
+ //
+ size_t calculatedSize = 0;
+ calculatedSize += 1; // Array of size 4
+ calculatedSize += 1; // "DeviceAuthentication" less than 24 bytes
+ calculatedSize += sizeof("DeviceAuthentication") - 1; // Don't include trailing NUL
+ calculatedSize += sessionTranscriptSize; // Already CBOR encoded
+ size_t docTypeLen = eicStrLen(docType);
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(docTypeLen) + docTypeLen;
+ calculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ calculatedSize += 1 + eicCborAdditionalLengthBytesFor(expectedDeviceNamespacesSize);
+ calculatedSize += expectedDeviceNamespacesSize;
+
+ // However note that we're authenticating DeviceAuthenticationBytes which
+ // is a tagged bstr of the bytes of DeviceAuthentication. So need to get
+ // that in front.
+ size_t dabCalculatedSize = 0;
+ dabCalculatedSize += 2; // Semantic tag EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR (24)
+ dabCalculatedSize += 1 + eicCborAdditionalLengthBytesFor(calculatedSize);
+ dabCalculatedSize += calculatedSize;
+
+ // Begin the bytestring for DeviceAuthenticationBytes;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, dabCalculatedSize);
+
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+
+ // Begins the bytestring for DeviceAuthentication;
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, calculatedSize);
+
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "DeviceAuthentication");
+ eicCborAppend(&ctx->cbor, sessionTranscript, sessionTranscriptSize);
+ eicCborAppendString(&ctx->cbor, docType);
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborAppendSemantic(&ctx->cbor, EIC_CBOR_SEMANTIC_TAG_ENCODED_CBOR);
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedDeviceNamespacesSize);
+ ctx->expectedCborSizeAtEnd = expectedDeviceNamespacesSize + ctx->cbor.size;
+
+ eicCborAppendMap(&ctx->cbor, numNamespacesWithValues);
+ return true;
+}
+
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx) {
+ // HAL may use this object multiple times to retrieve data so need to reset various
+ // state objects here.
+ ctx->requestMessageValidated = false;
+ ctx->buildCbor = false;
+ ctx->accessControlProfileMaskValidated = 0;
+ ctx->accessControlProfileMaskUsesReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedReaderAuth = 0;
+ ctx->accessControlProfileMaskFailedUserAuth = 0;
+ ctx->readerPublicKeySize = 0;
+ return true;
+}
+
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, const char* name,
+ unsigned int newNamespaceNumEntries, int32_t /* entrySize */,
+ const int* accessControlProfileIds, size_t numAccessControlProfileIds,
+ uint8_t* scratchSpace, size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ if (newNamespaceNumEntries > 0) {
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ eicCborAppendMap(&ctx->cbor, newNamespaceNumEntries);
+ }
+
+ // We'll need to calc and store a digest of additionalData to check that it's the same
+ // additionalData being passed in for every eicPresentationRetrieveEntryValue() call...
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ if (numAccessControlProfileIds == 0) {
+ return EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES;
+ }
+
+ // Access is granted if at least one of the profiles grants access.
+ //
+ // If an item is configured without any profiles, access is denied.
+ //
+ EicAccessCheckResult result = EIC_ACCESS_CHECK_RESULT_FAILED;
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ int id = accessControlProfileIds[n];
+ uint32_t idBitMask = (1 << id);
+
+ // If the access control profile wasn't validated, this is an error and we
+ // fail immediately.
+ bool validated = ((ctx->accessControlProfileMaskValidated & idBitMask) != 0);
+ if (!validated) {
+ eicDebug("No ACP for profile id %d", id);
+ return EIC_ACCESS_CHECK_RESULT_FAILED;
+ }
+
+ // Otherwise, we _did_ validate the profile. If none of the checks
+ // failed, we're done
+ bool failedUserAuth = ((ctx->accessControlProfileMaskFailedUserAuth & idBitMask) != 0);
+ bool failedReaderAuth = ((ctx->accessControlProfileMaskFailedReaderAuth & idBitMask) != 0);
+ if (!failedUserAuth && !failedReaderAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_OK;
+ break;
+ }
+ // One of the checks failed, convey which one
+ if (failedUserAuth) {
+ result = EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED;
+ } else {
+ result = EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED;
+ }
+ }
+ eicDebug("Result %d for name %s", result, name);
+
+ if (result == EIC_ACCESS_CHECK_RESULT_OK) {
+ eicCborAppendString(&ctx->cbor, name);
+ }
+ return result;
+}
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content,
+ const char* nameSpace, const char* name,
+ const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+
+ if (!eicOpsDecryptAes128Gcm(ctx->storageKey, encryptedContent, encryptedContentSize,
+ additionalDataCbor, additionalDataCborSize, content)) {
+ eicDebug("Error decrypting content");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, encryptedContentSize - 28);
+
+ return true;
+}
+
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize) {
+ if (!ctx->buildCbor) {
+ *digestToBeMacedSize = 0;
+ return true;
+ }
+ if (*digestToBeMacedSize != 32) {
+ return false;
+ }
+
+ // This verifies that the correct expectedDeviceNamespacesSize value was
+ // passed in at eicPresentationCalcMacKey() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+ eicCborFinal(&ctx->cbor, digestToBeMaced);
+ return true;
+}
+
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+ size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ EicCbor cbor;
+
+ eicCborInit(&cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&cbor, 4);
+ eicCborAppendString(&cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, proofOfDeletionCborSize);
+
+ // Finally, the CBOR that we're actually signing.
+ eicCborAppendArray(&cbor, 3);
+ eicCborAppendString(&cbor, "ProofOfDeletion");
+ eicCborAppendString(&cbor, docType);
+ eicCborAppendBool(&cbor, ctx->testCredential);
+
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+ eicCborFinal(&cbor, cborSha256);
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfDeletion");
+ return false;
+ }
+
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicPresentation.h b/identity/aidl/default/libeic/EicPresentation.h
new file mode 100644
index 0000000..d798962
--- /dev/null
+++ b/identity/aidl/default/libeic/EicPresentation.h
@@ -0,0 +1,229 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+// The maximum size we support for public keys in reader certificates.
+#define EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE 65
+
+typedef struct {
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ // The challenge generated with eicPresentationCreateAuthChallenge()
+ uint64_t authChallenge;
+
+ // Set by eicPresentationSetAuthToken() and contains the fields
+ // from the passed in authToken and verificationToken.
+ //
+ uint64_t authTokenChallenge;
+ uint64_t authTokenSecureUserId;
+ uint64_t authTokenTimestamp;
+ uint64_t verificationTokenTimestamp;
+
+ // The public key for the reader.
+ //
+ // (During the process of pushing reader certificates, this is also used to store
+ // the public key of the previously pushed certificate.)
+ //
+ uint8_t readerPublicKey[EIC_PRESENTATION_MAX_READER_PUBLIC_KEY_SIZE];
+ size_t readerPublicKeySize;
+
+ // This is set to true only if eicPresentationValidateRequestMessage() successfully
+ // validated the requestMessage.
+ //
+ // Why even record this? Because there's no requirement the HAL actually calls that
+ // function and we validate ACPs before it's called... so it's possible that a
+ // compromised HAL could trick us into marking ACPs as authorized while they in fact
+ // aren't.
+ bool requestMessageValidated;
+ bool buildCbor;
+
+ // Set to true initialized as a test credential.
+ bool testCredential;
+
+ // These are bitmasks indicating which of the possible 32 access control profiles are
+ // authorized. They are built up by eicPresentationValidateAccessControlProfile().
+ //
+ uint32_t accessControlProfileMaskValidated; // True if the profile was validated.
+ uint32_t accessControlProfileMaskUsesReaderAuth; // True if the ACP is using reader auth
+ uint32_t accessControlProfileMaskFailedReaderAuth; // True if failed reader auth
+ uint32_t accessControlProfileMaskFailedUserAuth; // True if failed user auth
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+ EicCbor cbor;
+} EicPresentation;
+
+bool eicPresentationInit(EicPresentation* ctx, bool testCredential, const char* docType,
+ const uint8_t encryptedCredentialKeys[80]);
+
+bool eicPresentationGenerateSigningKeyPair(EicPresentation* ctx, const char* docType, time_t now,
+ uint8_t* publicKeyCert, size_t* publicKeyCertSize,
+ uint8_t signingKeyBlob[60]);
+
+// Create an ephemeral key-pair.
+//
+// The private key is stored in |ctx->ephemeralPrivateKey| and also returned in
+// |ephemeralPrivateKey|.
+//
+bool eicPresentationCreateEphemeralKeyPair(EicPresentation* ctx,
+ uint8_t ephemeralPrivateKey[EIC_P256_PRIV_KEY_SIZE]);
+
+// Returns a non-zero challenge in |authChallenge|.
+bool eicPresentationCreateAuthChallenge(EicPresentation* ctx, uint64_t* authChallenge);
+
+// Starts retrieveing entries.
+//
+bool eicPresentationStartRetrieveEntries(EicPresentation* ctx);
+
+// Sets the auth-token.
+bool eicPresentationSetAuthToken(EicPresentation* ctx, uint64_t challenge, uint64_t secureUserId,
+ uint64_t authenticatorId, int hardwareAuthenticatorType,
+ uint64_t timeStamp, const uint8_t* mac, size_t macSize,
+ uint64_t verificationTokenChallenge,
+ uint64_t verificationTokenTimeStamp,
+ int verificationTokenSecurityLevel,
+ const uint8_t* verificationTokenMac,
+ size_t verificationTokenMacSize);
+
+// Function to push certificates in the reader certificate chain.
+//
+// This should start with the root certificate (e.g. the last in the chain) and
+// continue up the chain, ending with the certificate for the reader.
+//
+// Calls to this function should be interleaved with calls to the
+// eicPresentationValidateAccessControlProfile() function, see below.
+//
+bool eicPresentationPushReaderCert(EicPresentation* ctx, const uint8_t* certX509,
+ size_t certX509Size);
+
+// Checks an access control profile.
+//
+// Returns false if an error occurred while checking the profile (e.g. MAC doesn't check out).
+//
+// Returns in |accessGranted| whether access is granted.
+//
+// If |readerCertificate| is non-empty and the public key of one of those
+// certificates appear in the chain presented by the reader, this function must
+// be called after pushing that certificate using
+// eicPresentationPushReaderCert().
+//
+bool eicPresentationValidateAccessControlProfile(EicPresentation* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, int timeoutMillis,
+ uint64_t secureUserId, const uint8_t mac[28],
+ bool* accessGranted);
+
+// Validates that the given requestMessage is signed by the public key in the
+// certificate last set with eicPresentationPushReaderCert().
+//
+// The format of the signature is the same encoding as the 'signature' field of
+// COSE_Sign1 - that is, it's the R and S integers both with the same length as
+// the key-size.
+//
+// Must be called after eicPresentationPushReaderCert() have been used to push
+// the final certificate. Which is the certificate of the reader itself.
+//
+bool eicPresentationValidateRequestMessage(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t* requestMessage, size_t requestMessageSize,
+ int coseSignAlg,
+ const uint8_t* readerSignatureOfToBeSigned,
+ size_t readerSignatureOfToBeSignedSize);
+
+typedef enum {
+ // Returned if access is granted.
+ EIC_ACCESS_CHECK_RESULT_OK,
+
+ // Returned if an error occurred checking for access.
+ EIC_ACCESS_CHECK_RESULT_FAILED,
+
+ // Returned if access was denied because item is configured without any
+ // access control profiles.
+ EIC_ACCESS_CHECK_RESULT_NO_ACCESS_CONTROL_PROFILES,
+
+ // Returned if access was denied because of user authentication.
+ EIC_ACCESS_CHECK_RESULT_USER_AUTHENTICATION_FAILED,
+
+ // Returned if access was denied because of reader authentication.
+ EIC_ACCESS_CHECK_RESULT_READER_AUTHENTICATION_FAILED,
+} EicAccessCheckResult;
+
+// Passes enough information to calculate the MACing key
+//
+bool eicPresentationCalcMacKey(EicPresentation* ctx, const uint8_t* sessionTranscript,
+ size_t sessionTranscriptSize,
+ const uint8_t readerEphemeralPublicKey[EIC_P256_PUB_KEY_SIZE],
+ const uint8_t signingKeyBlob[60], const char* docType,
+ unsigned int numNamespacesWithValues,
+ size_t expectedDeviceNamespacesSize);
+
+// The scratchSpace should be set to a buffer at least 512 bytes (ideally 1024
+// bytes, the bigger the better). It's done this way to avoid allocating stack
+// space.
+//
+EicAccessCheckResult eicPresentationStartRetrieveEntryValue(
+ EicPresentation* ctx, const char* nameSpace, const char* name,
+ unsigned int newNamespaceNumEntries, int32_t entrySize, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace, size_t scratchSpaceSize);
+
+// Note: |content| must be big enough to hold |encryptedContentSize| - 28 bytes.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicPresentationRetrieveEntryValue(EicPresentation* ctx, const uint8_t* encryptedContent,
+ size_t encryptedContentSize, uint8_t* content,
+ const char* nameSpace, const char* name,
+ const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// Returns the HMAC-SHA256 of |ToBeMaced| as per RFC 8051 "6.3. How to Compute
+// and Verify a MAC".
+bool eicPresentationFinishRetrieval(EicPresentation* ctx, uint8_t* digestToBeMaced,
+ size_t* digestToBeMacedSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfDeletion CBOR.
+//
+bool eicPresentationDeleteCredential(EicPresentation* ctx, const char* docType,
+ size_t proofOfDeletionCborSize,
+ uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PRESENTATION_H
diff --git a/identity/aidl/default/libeic/EicProvisioning.c b/identity/aidl/default/libeic/EicProvisioning.c
new file mode 100644
index 0000000..f16605c
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.c
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "EicProvisioning.h"
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential) {
+ eicMemSet(ctx, '\0', sizeof(EicProvisioning));
+ ctx->testCredential = testCredential;
+ if (!eicOpsRandom(ctx->storageKey, EIC_AES_128_KEY_SIZE)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize) {
+ if (!eicOpsCreateCredentialKey(ctx->credentialPrivateKey, challenge, challengeSize,
+ applicationId, applicationIdSize, ctx->testCredential,
+ publicKeyCert, publicKeyCertSize)) {
+ return false;
+ }
+ return true;
+}
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+ const int* entryCounts, size_t numEntryCounts,
+ const char* docType,
+ size_t expectedProofOfProvisioningSize) {
+ if (numEntryCounts >= EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ if (accessControlProfileCount >= EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS) {
+ return false;
+ }
+
+ ctx->numEntryCounts = numEntryCounts;
+ if (numEntryCounts > EIC_MAX_NUM_NAMESPACES) {
+ return false;
+ }
+ for (size_t n = 0; n < numEntryCounts; n++) {
+ if (entryCounts[n] >= 256) {
+ return false;
+ }
+ ctx->entryCounts[n] = entryCounts[n];
+ }
+ ctx->curNamespace = -1;
+ ctx->curNamespaceNumProcessed = 0;
+
+ eicCborInit(&ctx->cbor, NULL, 0);
+
+ // What we're going to sign is the COSE ToBeSigned structure which
+ // looks like the following:
+ //
+ // Sig_structure = [
+ // context : "Signature" / "Signature1" / "CounterSignature",
+ // body_protected : empty_or_serialized_map,
+ // ? sign_protected : empty_or_serialized_map,
+ // external_aad : bstr,
+ // payload : bstr
+ // ]
+ //
+ eicCborAppendArray(&ctx->cbor, 4);
+ eicCborAppendString(&ctx->cbor, "Signature1");
+
+ // The COSE Encoded protected headers is just a single field with
+ // COSE_LABEL_ALG (1) -> COSE_ALG_ECSDA_256 (-7). For simplicitly we just
+ // hard-code the CBOR encoding:
+ static const uint8_t coseEncodedProtectedHeaders[] = {0xa1, 0x01, 0x26};
+ eicCborAppendByteString(&ctx->cbor, coseEncodedProtectedHeaders,
+ sizeof(coseEncodedProtectedHeaders));
+
+ // We currently don't support Externally Supplied Data (RFC 8152 section 4.3)
+ // so external_aad is the empty bstr
+ static const uint8_t externalAad[0] = {};
+ eicCborAppendByteString(&ctx->cbor, externalAad, sizeof(externalAad));
+
+ // For the payload, the _encoded_ form follows here. We handle this by simply
+ // opening a bstr, and then writing the CBOR. This requires us to know the
+ // size of said bstr, ahead of time.
+ eicCborBegin(&ctx->cbor, EIC_CBOR_MAJOR_TYPE_BYTE_STRING, expectedProofOfProvisioningSize);
+ ctx->expectedCborSizeAtEnd = expectedProofOfProvisioningSize + ctx->cbor.size;
+
+ eicCborAppendArray(&ctx->cbor, 5);
+ eicCborAppendString(&ctx->cbor, "ProofOfProvisioning");
+ eicCborAppendString(&ctx->cbor, docType);
+
+ eicCborAppendArray(&ctx->cbor, accessControlProfileCount);
+
+ return true;
+}
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId, uint8_t outMac[28]) {
+ uint8_t cborBuffer[EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE];
+ EicCbor cborBuilder;
+
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis, secureUserId)) {
+ return false;
+ }
+
+ // Calculate and return MAC
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, NULL, 0, cborBuilder.buffer,
+ cborBuilder.size, outMac)) {
+ return false;
+ }
+
+ // The ACP CBOR in the provisioning receipt doesn't include secureUserId so build
+ // it again.
+ eicCborInit(&cborBuilder, cborBuffer, EIC_MAX_CBOR_SIZE_FOR_ACCESS_CONTROL_PROFILE);
+ if (!eicCborCalcAccessControl(&cborBuilder, id, readerCertificate, readerCertificateSize,
+ userAuthenticationRequired, timeoutMillis,
+ 0 /* secureUserId */)) {
+ return false;
+ }
+
+ // Append the CBOR from the local builder to the digester.
+ eicCborAppend(&ctx->cbor, cborBuilder.buffer, cborBuilder.size);
+
+ return true;
+}
+
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ // We'll need to calc and store a digest of additionalData to check that it's the same
+ // additionalData being passed in for every eicProvisioningAddEntryValue() call...
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ ctx->additionalDataSha256)) {
+ return false;
+ }
+
+ if (ctx->curNamespace == -1) {
+ ctx->curNamespace = 0;
+ ctx->curNamespaceNumProcessed = 0;
+ // Opens the main map: { * Namespace => [ + Entry ] }
+ eicCborAppendMap(&ctx->cbor, ctx->numEntryCounts);
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ if (ctx->curNamespaceNumProcessed == ctx->entryCounts[ctx->curNamespace]) {
+ ctx->curNamespace += 1;
+ ctx->curNamespaceNumProcessed = 0;
+ eicCborAppendString(&ctx->cbor, nameSpace);
+ // Opens the per-namespace array: [ + Entry ]
+ eicCborAppendArray(&ctx->cbor, ctx->entryCounts[ctx->curNamespace]);
+ }
+
+ eicCborAppendMap(&ctx->cbor, 3);
+ eicCborAppendString(&ctx->cbor, "name");
+ eicCborAppendString(&ctx->cbor, name);
+
+ ctx->curEntrySize = entrySize;
+ ctx->curEntryNumBytesReceived = 0;
+
+ eicCborAppendString(&ctx->cbor, "value");
+
+ ctx->curNamespaceNumProcessed += 1;
+ return true;
+}
+
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, const uint8_t* content, size_t contentSize,
+ uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+ size_t scratchSpaceSize) {
+ uint8_t* additionalDataCbor = scratchSpace;
+ const size_t additionalDataCborBufSize = scratchSpaceSize;
+ size_t additionalDataCborSize;
+
+ uint8_t calculatedSha256[EIC_SHA256_DIGEST_SIZE];
+ if (!eicCborCalcEntryAdditionalData(accessControlProfileIds, numAccessControlProfileIds,
+ nameSpace, name, additionalDataCbor,
+ additionalDataCborBufSize, &additionalDataCborSize,
+ calculatedSha256)) {
+ return false;
+ }
+ if (eicCryptoMemCmp(calculatedSha256, ctx->additionalDataSha256, EIC_SHA256_DIGEST_SIZE) != 0) {
+ eicDebug("SHA-256 mismatch of additionalData");
+ return false;
+ }
+
+ eicCborAppend(&ctx->cbor, content, contentSize);
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(ctx->storageKey, nonce, content, contentSize, additionalDataCbor,
+ additionalDataCborSize, outEncryptedContent)) {
+ return false;
+ }
+
+ // If done with this entry, close the map
+ ctx->curEntryNumBytesReceived += contentSize;
+ if (ctx->curEntryNumBytesReceived == ctx->curEntrySize) {
+ eicCborAppendString(&ctx->cbor, "accessControlProfiles");
+ eicCborAppendArray(&ctx->cbor, numAccessControlProfileIds);
+ for (size_t n = 0; n < numAccessControlProfileIds; n++) {
+ eicCborAppendNumber(&ctx->cbor, accessControlProfileIds[n]);
+ }
+ }
+ return true;
+}
+
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]) {
+ uint8_t cborSha256[EIC_SHA256_DIGEST_SIZE];
+
+ eicCborAppendBool(&ctx->cbor, ctx->testCredential);
+ eicCborFinal(&ctx->cbor, cborSha256);
+
+ // This verifies that the correct expectedProofOfProvisioningSize value was
+ // passed in at eicStartPersonalization() time.
+ if (ctx->cbor.size != ctx->expectedCborSizeAtEnd) {
+ eicDebug("CBOR size is %zd, was expecting %zd", ctx->cbor.size, ctx->expectedCborSizeAtEnd);
+ return false;
+ }
+
+ if (!eicOpsEcDsa(ctx->credentialPrivateKey, cborSha256, signatureOfToBeSigned)) {
+ eicDebug("Error signing proofOfProvisioning");
+ return false;
+ }
+
+ return true;
+}
+
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+ uint8_t encryptedCredentialKeys[80]) {
+ EicCbor cbor;
+ uint8_t cborBuf[52];
+
+ eicCborInit(&cbor, cborBuf, sizeof(cborBuf));
+ eicCborAppendArray(&cbor, 2);
+ eicCborAppendByteString(&cbor, ctx->storageKey, EIC_AES_128_KEY_SIZE);
+ eicCborAppendByteString(&cbor, ctx->credentialPrivateKey, EIC_P256_PRIV_KEY_SIZE);
+ if (cbor.size > sizeof(cborBuf)) {
+ eicDebug("Exceeded buffer size");
+ return false;
+ }
+
+ uint8_t nonce[12];
+ if (!eicOpsRandom(nonce, 12)) {
+ eicDebug("Error getting random");
+ return false;
+ }
+ if (!eicOpsEncryptAes128Gcm(
+ eicOpsGetHardwareBoundKey(ctx->testCredential), nonce, cborBuf, cbor.size,
+ // DocType is the additionalAuthenticatedData
+ (const uint8_t*)docType, eicStrLen(docType), encryptedCredentialKeys)) {
+ eicDebug("Error encrypting CredentialKeys");
+ return false;
+ }
+
+ return true;
+}
diff --git a/identity/aidl/default/libeic/EicProvisioning.h b/identity/aidl/default/libeic/EicProvisioning.h
new file mode 100644
index 0000000..836d16e
--- /dev/null
+++ b/identity/aidl/default/libeic/EicProvisioning.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(EIC_INSIDE_LIBEIC_H) && !defined(EIC_COMPILATION)
+#error "Never include this file directly, include libeic.h instead."
+#endif
+
+#ifndef ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+#define ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "EicCbor.h"
+
+#define EIC_MAX_NUM_NAMESPACES 32
+#define EIC_MAX_NUM_ACCESS_CONTROL_PROFILE_IDS 32
+
+typedef struct {
+ // Set by eicCreateCredentialKey.
+ uint8_t credentialPrivateKey[EIC_P256_PRIV_KEY_SIZE];
+
+ int numEntryCounts;
+ uint8_t entryCounts[EIC_MAX_NUM_NAMESPACES];
+
+ int curNamespace;
+ int curNamespaceNumProcessed;
+
+ size_t curEntrySize;
+ size_t curEntryNumBytesReceived;
+
+ uint8_t storageKey[EIC_AES_128_KEY_SIZE];
+
+ size_t expectedCborSizeAtEnd;
+
+ // SHA-256 for AdditionalData, updated for each entry.
+ uint8_t additionalDataSha256[EIC_SHA256_DIGEST_SIZE];
+
+ EicCbor cbor;
+
+ bool testCredential;
+} EicProvisioning;
+
+bool eicProvisioningInit(EicProvisioning* ctx, bool testCredential);
+
+bool eicProvisioningCreateCredentialKey(EicProvisioning* ctx, const uint8_t* challenge,
+ size_t challengeSize, const uint8_t* applicationId,
+ size_t applicationIdSize, uint8_t* publicKeyCert,
+ size_t* publicKeyCertSize);
+
+bool eicProvisioningStartPersonalization(EicProvisioning* ctx, int accessControlProfileCount,
+ const int* entryCounts, size_t numEntryCounts,
+ const char* docType,
+ size_t expectedProofOfProvisioningingSize);
+
+bool eicProvisioningAddAccessControlProfile(EicProvisioning* ctx, int id,
+ const uint8_t* readerCertificate,
+ size_t readerCertificateSize,
+ bool userAuthenticationRequired, uint64_t timeoutMillis,
+ uint64_t secureUserId, uint8_t outMac[28]);
+
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningBeginAddEntry(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, uint64_t entrySize, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// The outEncryptedContent array must be contentSize + 28 bytes long.
+//
+// The scratchSpace should be set to a buffer at least 512 bytes. It's done this way to
+// avoid allocating stack space.
+//
+bool eicProvisioningAddEntryValue(EicProvisioning* ctx, const int* accessControlProfileIds,
+ size_t numAccessControlProfileIds, const char* nameSpace,
+ const char* name, const uint8_t* content, size_t contentSize,
+ uint8_t* outEncryptedContent, uint8_t* scratchSpace,
+ size_t scratchSpaceSize);
+
+// The data returned in |signatureOfToBeSigned| contains the ECDSA signature of
+// the ToBeSigned CBOR from RFC 8051 "4.4. Signing and Verification Process"
+// where content is set to the ProofOfProvisioninging CBOR.
+//
+bool eicProvisioningFinishAddingEntries(
+ EicProvisioning* ctx, uint8_t signatureOfToBeSigned[EIC_ECDSA_P256_SIGNATURE_SIZE]);
+
+//
+//
+// The |encryptedCredentialKeys| array is set to AES-GCM-ENC(HBK, R, CredentialKeys, docType)
+// where
+//
+// CredentialKeys = [
+// bstr, ; storageKey, a 128-bit AES key
+// bstr ; credentialPrivKey, the private key for credentialKey
+// ]
+//
+// Since |storageKey| is 16 bytes and |credentialPrivKey| is 32 bytes, the
+// encoded CBOR for CredentialKeys is 52 bytes and consequently
+// |encryptedCredentialKeys| will be 52 + 28 = 80 bytes.
+//
+bool eicProvisioningFinishGetCredentialData(EicProvisioning* ctx, const char* docType,
+ uint8_t encryptedCredentialKeys[80]);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_EIC_PROVISIONING_H
diff --git a/identity/aidl/default/libeic/libeic.h b/identity/aidl/default/libeic/libeic.h
new file mode 100644
index 0000000..88abef8
--- /dev/null
+++ b/identity/aidl/default/libeic/libeic.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+#define ANDROID_HARDWARE_IDENTITY_LIBEIC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The EIC_INSIDE_LIBEIC_H preprocessor symbol is used to enforce
+ * library users to include only this file. All public interfaces, and
+ * only public interfaces, must be included here.
+ */
+#define EIC_INSIDE_LIBEIC_H
+#include "EicCbor.h"
+#include "EicOps.h"
+#include "EicPresentation.h"
+#include "EicProvisioning.h"
+#undef EIC_INSIDE_LIBEIC_H
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ANDROID_HARDWARE_IDENTITY_LIBEIC_H
diff --git a/identity/aidl/default/service.cpp b/identity/aidl/default/service.cpp
index bf95df5..c290c08 100644
--- a/identity/aidl/default/service.cpp
+++ b/identity/aidl/default/service.cpp
@@ -22,20 +22,26 @@
#include "IdentityCredentialStore.h"
+#include "FakeSecureHardwareProxy.h"
+
+using ::android::sp;
using ::android::base::InitLogging;
using ::android::base::StderrLogger;
-using aidl::android::hardware::identity::IdentityCredentialStore;
+using ::aidl::android::hardware::identity::IdentityCredentialStore;
+using ::android::hardware::identity::FakeSecureHardwareProxyFactory;
+using ::android::hardware::identity::SecureHardwareProxyFactory;
int main(int /*argc*/, char* argv[]) {
InitLogging(argv, StderrLogger);
+ sp<SecureHardwareProxyFactory> hwProxyFactory = new FakeSecureHardwareProxyFactory();
+
ABinderProcess_setThreadPoolMaxThreadCount(0);
std::shared_ptr<IdentityCredentialStore> store =
- ndk::SharedRefBase::make<IdentityCredentialStore>();
+ ndk::SharedRefBase::make<IdentityCredentialStore>(hwProxyFactory);
const std::string instance = std::string() + IdentityCredentialStore::descriptor + "/default";
- LOG(INFO) << "instance: " << instance;
binder_status_t status = AServiceManager_addService(store->asBinder().get(), instance.c_str());
CHECK(status == STATUS_OK);
diff --git a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
index 56e17ba..1629a0c 100644
--- a/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
+++ b/identity/aidl/vts/VtsIWritableIdentityCredentialTests.cpp
@@ -182,7 +182,7 @@
false /* testCredential */));
// Verify set a large number of profile count and entry count is ok
- const vector<int32_t> entryCounts = {3000};
+ const vector<int32_t> entryCounts = {255};
writableCredential->setExpectedProofOfProvisioningSize(123456);
result = writableCredential->startPersonalization(25, entryCounts);
EXPECT_TRUE(result.isOk()) << result.exceptionCode() << "; " << result.exceptionMessage()
diff --git a/keymaster/4.1/default/OWNERS b/keymaster/4.1/default/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/default/OWNERS
+++ b/keymaster/4.1/default/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/support/OWNERS b/keymaster/4.1/support/OWNERS
index a9efe66..2b2ad2a 100644
--- a/keymaster/4.1/support/OWNERS
+++ b/keymaster/4.1/support/OWNERS
@@ -1,3 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
-jbires@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/keymaster/4.1/vts/OWNERS b/keymaster/4.1/vts/OWNERS
index 335660d..2b2ad2a 100644
--- a/keymaster/4.1/vts/OWNERS
+++ b/keymaster/4.1/vts/OWNERS
@@ -1,2 +1,4 @@
+jbires@google.com
jdanis@google.com
swillden@google.com
+zeuthen@google.com
\ No newline at end of file
diff --git a/radio/1.0/vts/functional/vts_test_util.h b/radio/1.0/vts/functional/vts_test_util.h
index 1625f11..846148f 100644
--- a/radio/1.0/vts/functional/vts_test_util.h
+++ b/radio/1.0/vts/functional/vts_test_util.h
@@ -35,6 +35,8 @@
static constexpr const char* FEATURE_VOICE_CALL = "android.software.connectionservice";
+static constexpr const char* FEATURE_TELEPHONY = "android.hardware.telephony";
+
/*
* Generate random serial number for radio test
*/
diff --git a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
index 1b254a1..4dcf1f3 100644
--- a/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
+++ b/radio/1.4/vts/functional/radio_hidl_hal_api.cpp
@@ -34,6 +34,9 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY)) {
+ ALOGI("Skipping emergencyDial because telephony radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -86,6 +89,9 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY)) {
+ ALOGI("Skipping emergencyDial because telephony radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
@@ -138,6 +144,9 @@
if (!deviceSupportsFeature(FEATURE_VOICE_CALL)) {
ALOGI("Skipping emergencyDial because voice call is not supported in device");
return;
+ } else if (!deviceSupportsFeature(FEATURE_TELEPHONY)) {
+ ALOGI("Skipping emergencyDial because telephony radio is not supported in device");
+ return;
} else {
ALOGI("Running emergencyDial because voice call is supported in device");
}
diff --git a/tests/lazy/1.1/ILazy.hal b/tests/lazy/1.1/ILazy.hal
index a15e0e3..b0a6a2a 100644
--- a/tests/lazy/1.1/ILazy.hal
+++ b/tests/lazy/1.1/ILazy.hal
@@ -18,4 +18,12 @@
import android.hardware.tests.lazy@1.0;
-interface ILazy extends @1.0::ILazy {};
+interface ILazy extends @1.0::ILazy {
+ /**
+ * Ask the process hosting the service to install a callback that notifies
+ * it when the number of active (i.e. with clients) services changes.
+ * For testing purposes, this callback exercises the code to unregister/re-register
+ * the services and eventually shuts down the process.
+ */
+ setCustomActiveServicesCountCallback();
+};