Add HMAC-SHA256 support.
Change-Id: I64c7bdf77388e3cb491b702c52c6746d32f317b0
diff --git a/Android.mk b/Android.mk
index 3c5fab1..78aed7e 100644
--- a/Android.mk
+++ b/Android.mk
@@ -57,6 +57,8 @@
google_keymaster.cpp \
google_keymaster_messages.cpp \
google_keymaster_utils.cpp \
+ hmac_key.cpp \
+ hmac_operation.cpp \
key.cpp \
key_blob.cpp \
ocb.c \
diff --git a/Makefile b/Makefile
index 6f675d3..b06e51d 100644
--- a/Makefile
+++ b/Makefile
@@ -39,6 +39,8 @@
google_keymaster_test.cpp \
google_keymaster_test_utils.cpp \
google_keymaster_utils.cpp \
+ hmac_key.cpp \
+ hmac_operation.cpp \
key.cpp \
key_blob.cpp \
key_blob_test.cpp \
@@ -139,6 +141,8 @@
google_keymaster_messages.o \
google_keymaster_test_utils.o \
google_keymaster_utils.o \
+ hmac_key.o \
+ hmac_operation.o \
key.o \
key_blob.o \
ocb.o \
diff --git a/google_keymaster_test.cpp b/google_keymaster_test.cpp
index 596aeed..1ee9c02 100644
--- a/google_keymaster_test.cpp
+++ b/google_keymaster_test.cpp
@@ -85,6 +85,14 @@
keymaster_device* device() { return reinterpret_cast<keymaster_device*>(device_.hw_device()); }
+ void GenerateKey(AuthorizationSet* params) {
+ FreeKeyBlob();
+ FreeCharacteristics();
+ AddClientParams(params);
+ EXPECT_EQ(KM_ERROR_OK, device()->generate_key(device(), params->data(), params->size(),
+ &blob_, &characteristics_));
+ }
+
keymaster_error_t BeginOperation(keymaster_purpose_t purpose,
const keymaster_key_blob_t& key_blob) {
return device()->begin(device(), purpose, &key_blob, client_params_,
@@ -133,6 +141,8 @@
blob_.key_material = NULL;
}
+ void AddClientParams(AuthorizationSet* set) { set->push_back(TAG_APPLICATION_ID, "app_id", 6); }
+
const keymaster_key_blob_t& key_blob() { return blob_; }
SoftKeymasterDevice device_;
@@ -488,7 +498,6 @@
Authorization(TAG_MAC_LENGTH, 16), Authorization(TAG_PADDING, KM_PAD_NONE),
};
params_.Reinitialize(params, array_length(params));
- params_.Reinitialize(params, array_length(params));
EXPECT_EQ(KM_ERROR_OK, device()->generate_key(device(), params_.data(), params_.size(), &blob_,
&characteristics_));
@@ -531,6 +540,16 @@
}
}
+TEST_F(NewKeyGeneration, HmacSha256) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_PURPOSE, KM_PURPOSE_VERIFY),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_HMAC), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_MAC_LENGTH, 16), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ };
+ EXPECT_EQ(KM_ERROR_OK, device()->generate_key(device(), params, array_length(params), &blob_,
+ &characteristics_));
+}
+
typedef KeymasterTest GetKeyCharacteristics;
TEST_F(GetKeyCharacteristics, SimpleRsa) {
keymaster_key_param_t params[] = {
@@ -564,6 +583,9 @@
FreeSignature();
}
+ // TODO(swillden): Refactor and move common test utils to KeymasterTest
+ using KeymasterTest::GenerateKey;
+
void GenerateKey(keymaster_algorithm_t algorithm, keymaster_digest_t digest,
keymaster_padding_t padding, uint32_t key_size) {
params_.push_back(Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN));
@@ -602,6 +624,8 @@
signature_ = NULL;
}
+ const keymaster_key_blob_t& key_blob() { return blob_; }
+
void corrupt_key_blob() {
uint8_t* tmp = const_cast<uint8_t*>(blob_.key_material);
++tmp[blob_.key_material_size / 2];
@@ -669,6 +693,54 @@
EXPECT_EQ(KM_ERROR_INVALID_OPERATION_HANDLE, device()->abort(device(), op_handle_));
}
+TEST_F(SigningOperationsTest, HmacSha256Success) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_PURPOSE, KM_PURPOSE_VERIFY),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_HMAC), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_MAC_LENGTH, 32), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ Authorization(TAG_USER_ID, 7), Authorization(TAG_USER_AUTH_ID, 8),
+ Authorization(TAG_AUTH_TIMEOUT, 300),
+ };
+ params_.Reinitialize(params, array_length(params));
+ GenerateKey(¶ms_);
+ const char message[] = "12345678901234567890123456789012";
+ string signature;
+ SignMessage(message, array_size(message) - 1);
+ ASSERT_EQ(32, signature_length_);
+}
+
+// TODO(swillden): Add an HMACSHA256 test that validates against the test vectors from RFC4231.
+// Doing that requires being able to import keys, rather than just generate them
+// randomly.
+
+TEST_F(SigningOperationsTest, HmacSha256NoTag) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_PURPOSE, KM_PURPOSE_VERIFY),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_HMAC), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_MAC_LENGTH, 16), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ Authorization(TAG_USER_ID, 7), Authorization(TAG_USER_AUTH_ID, 8),
+ Authorization(TAG_AUTH_TIMEOUT, 300),
+ };
+ params_.Reinitialize(params, array_length(params));
+ GenerateKey(¶ms_);
+ const char message[] = "12345678901234567890123456789012";
+ string signature;
+ SignMessage(message, array_size(message) - 1);
+}
+
+TEST_F(SigningOperationsTest, HmacSha256TooLargeTag) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_PURPOSE, KM_PURPOSE_VERIFY),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_HMAC), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_MAC_LENGTH, 33), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ Authorization(TAG_USER_ID, 7), Authorization(TAG_USER_AUTH_ID, 8),
+ Authorization(TAG_AUTH_TIMEOUT, 300),
+ };
+ params_.Reinitialize(params, array_length(params));
+ GenerateKey(¶ms_);
+ ASSERT_EQ(KM_ERROR_UNSUPPORTED_MAC_LENGTH, BeginOperation(KM_PURPOSE_SIGN, key_blob()));
+}
+
TEST_F(SigningOperationsTest, RsaTooShortMessage) {
GenerateKey(KM_ALGORITHM_RSA, KM_DIGEST_NONE, KM_PAD_NONE, 256 /* key size */);
ASSERT_EQ(KM_ERROR_OK, BeginOperation(KM_PURPOSE_SIGN, key_blob()));
@@ -718,6 +790,22 @@
VerifyMessage(message, array_size(message) - 1);
}
+TEST_F(VerificationOperationsTest, HmacSha256Success) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_SIGN), Authorization(TAG_PURPOSE, KM_PURPOSE_VERIFY),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_HMAC), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_MAC_LENGTH, 16), Authorization(TAG_DIGEST, KM_DIGEST_SHA_2_256),
+ Authorization(TAG_USER_ID, 7), Authorization(TAG_USER_AUTH_ID, 8),
+ Authorization(TAG_AUTH_TIMEOUT, 300),
+ };
+ params_.Reinitialize(params, array_length(params));
+ GenerateKey(¶ms_);
+ const char message[] = "123456789012345678901234567890123456789012345678";
+ string signature;
+ SignMessage(message, array_size(message) - 1);
+ VerifyMessage(message, array_size(message) - 1);
+}
+
typedef VerificationOperationsTest ExportKeyTest;
TEST_F(ExportKeyTest, RsaSuccess) {
GenerateKey(KM_ALGORITHM_RSA, KM_DIGEST_NONE, KM_PAD_NONE, 256 /* key size */);
@@ -900,13 +988,8 @@
*/
class EncryptionOperationsTest : public KeymasterTest {
protected:
- void GenerateKey(AuthorizationSet* params) {
- FreeKeyBlob();
- FreeCharacteristics();
- AddClientParams(params);
- EXPECT_EQ(KM_ERROR_OK, device()->generate_key(device(), params->data(), params->size(),
- &blob_, &characteristics_));
- }
+ // TODO(swillden): Refactor and move common test utils to KeymasterTest
+ using KeymasterTest::GenerateKey;
void GenerateKey(keymaster_algorithm_t algorithm, keymaster_padding_t padding,
uint32_t key_size) {
@@ -962,8 +1045,6 @@
return ProcessMessage(KM_PURPOSE_DECRYPT, blob_, ciphertext.c_str(), ciphertext.length());
}
- void AddClientParams(AuthorizationSet* set) { set->push_back(TAG_APPLICATION_ID, "app_id", 6); }
-
const void corrupt_key_blob() {
uint8_t* tmp = const_cast<uint8_t*>(blob_.key_material);
++tmp[blob_.key_material_size / 2];
diff --git a/hmac_key.cpp b/hmac_key.cpp
new file mode 100644
index 0000000..40ecfe6
--- /dev/null
+++ b/hmac_key.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014 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 "hmac_key.h"
+
+#include <openssl/err.h>
+#include <openssl/rand.h>
+
+#include "hmac_operation.h"
+
+namespace keymaster {
+
+Operation* HmacKey::CreateOperation(keymaster_purpose_t purpose, keymaster_error_t* error) {
+ *error = KM_ERROR_OK;
+
+ uint32_t tag_length;
+ if (!authorizations().GetTagValue(TAG_MAC_LENGTH, &tag_length))
+ *error = KM_ERROR_UNSUPPORTED_MAC_LENGTH;
+
+ keymaster_digest_t digest;
+ if (!authorizations().GetTagValue(TAG_DIGEST, &digest) || digest != KM_DIGEST_SHA_2_256)
+ *error = KM_ERROR_UNSUPPORTED_DIGEST;
+
+ if (*error != KM_ERROR_OK)
+ return NULL;
+
+ UniquePtr<Operation> op;
+ switch (purpose) {
+ case KM_PURPOSE_SIGN:
+ case KM_PURPOSE_VERIFY:
+ op.reset(
+ new HmacOperation(purpose, logger_, key_data(), key_data_size(), digest, tag_length));
+ break;
+ default:
+ *error = KM_ERROR_UNSUPPORTED_PURPOSE;
+ return NULL;
+ }
+
+ if (!op.get())
+ *error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
+
+ return op.release();
+}
+
+} // namespace keymaster
diff --git a/hmac_key.h b/hmac_key.h
new file mode 100644
index 0000000..d60c848
--- /dev/null
+++ b/hmac_key.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014 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 SYSTEM_KEYMASTER_HMAC_KEY_H_
+#define SYSTEM_KEYMASTER_HMAC_KEY_H_
+
+#include "symmetric_key.h"
+
+namespace keymaster {
+
+class HmacKey : public SymmetricKey {
+ public:
+ HmacKey(const AuthorizationSet& auths, const Logger& logger) : SymmetricKey(auths, logger) {}
+ HmacKey(const UnencryptedKeyBlob& blob, const Logger& logger, keymaster_error_t* error)
+ : SymmetricKey(blob, logger, error) {}
+
+ virtual Operation* CreateOperation(keymaster_purpose_t purpose, keymaster_error_t* error);
+};
+
+} // namespace keymaster
+
+#endif // SYSTEM_KEYMASTER_HMAC_KEY_H_
diff --git a/hmac_operation.cpp b/hmac_operation.cpp
new file mode 100644
index 0000000..1620105
--- /dev/null
+++ b/hmac_operation.cpp
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014 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 "hmac_operation.h"
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+
+namespace keymaster {
+
+HmacOperation::HmacOperation(keymaster_purpose_t purpose, const Logger& logger,
+ const uint8_t* key_data, size_t key_data_size,
+ keymaster_digest_t digest, size_t tag_length)
+ : Operation(purpose, logger), error_(KM_ERROR_OK), tag_length_(tag_length) {
+ // Initialize CTX first, so dtor won't crash even if we error out later.
+ HMAC_CTX_init(&ctx_);
+
+ const EVP_MD* md;
+ switch (digest) {
+ case KM_DIGEST_SHA_2_256:
+ md = EVP_sha256();
+ break;
+ default:
+ error_ = KM_ERROR_UNSUPPORTED_DIGEST;
+ return;
+ }
+
+ if ((int)tag_length_ > EVP_MD_size(md)) {
+ error_ = KM_ERROR_UNSUPPORTED_MAC_LENGTH;
+ return;
+ }
+
+ HMAC_Init_ex(&ctx_, key_data, key_data_size, md, NULL /* engine */);
+}
+
+HmacOperation::~HmacOperation() {
+ HMAC_CTX_cleanup(&ctx_);
+}
+
+keymaster_error_t HmacOperation::Begin() {
+ return error_;
+}
+
+keymaster_error_t HmacOperation::Update(const Buffer& input, Buffer* /* output */,
+ size_t* input_consumed) {
+ if (!HMAC_Update(&ctx_, input.peek_read(), input.available_read()))
+ return KM_ERROR_UNKNOWN_ERROR;
+ *input_consumed = input.available_read();
+ return KM_ERROR_OK;
+}
+
+keymaster_error_t HmacOperation::Abort() {
+ return KM_ERROR_OK;
+}
+
+keymaster_error_t HmacOperation::Finish(const Buffer& signature, Buffer* output) {
+ uint8_t digest[EVP_MAX_MD_SIZE];
+ unsigned int digest_len;
+ if (!HMAC_Final(&ctx_, digest, &digest_len))
+ return KM_ERROR_UNKNOWN_ERROR;
+
+ switch (purpose()) {
+ case KM_PURPOSE_SIGN:
+ output->reserve(tag_length_);
+ output->write(digest, tag_length_);
+ return KM_ERROR_OK;
+ case KM_PURPOSE_VERIFY:
+ if (signature.available_read() != tag_length_)
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+ if (memcmp(signature.peek_read(), digest, tag_length_) != 0)
+ return KM_ERROR_VERIFICATION_FAILED;
+ return KM_ERROR_OK;
+ default:
+ return KM_ERROR_UNSUPPORTED_PURPOSE;
+ }
+}
+
+} // namespace keymaster
diff --git a/hmac_operation.h b/hmac_operation.h
new file mode 100644
index 0000000..9299b82
--- /dev/null
+++ b/hmac_operation.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014 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 SYSTEM_KEYMASTER_HMAC_OPERATION_H_
+#define SYSTEM_KEYMASTER_HMAC_OPERATION_H_
+
+#include "operation.h"
+
+#include <openssl/hmac.h>
+
+namespace keymaster {
+
+class HmacOperation : public Operation {
+ public:
+ HmacOperation(keymaster_purpose_t purpose, const Logger& logger, const uint8_t* key_data,
+ size_t key_data_size, keymaster_digest_t digest, size_t tag_length);
+ ~HmacOperation();
+
+ virtual keymaster_error_t Begin();
+ virtual keymaster_error_t Update(const Buffer& input, Buffer* output, size_t* input_consumed);
+ virtual keymaster_error_t Abort();
+ virtual keymaster_error_t Finish(const Buffer& signature, Buffer* output);
+
+ private:
+ HMAC_CTX ctx_;
+ keymaster_error_t error_;
+ const size_t tag_length_;
+};
+
+} // namespace keymaster
+
+#endif // SYSTEM_KEYMASTER_HMAC_OPERATION_H_
diff --git a/key.cpp b/key.cpp
index a5a0457..053c2d5 100644
--- a/key.cpp
+++ b/key.cpp
@@ -44,6 +44,7 @@
case KM_ALGORITHM_ECDSA:
return new EcdsaKey(blob, logger, error);
case KM_ALGORITHM_AES:
+ case KM_ALGORITHM_HMAC:
return SymmetricKey::CreateKey(blob.algorithm(), blob, logger, error);
default:
*error = KM_ERROR_UNSUPPORTED_ALGORITHM;
@@ -66,6 +67,7 @@
case KM_ALGORITHM_ECDSA:
return EcdsaKey::GenerateKey(key_description, logger, error);
case KM_ALGORITHM_AES:
+ case KM_ALGORITHM_HMAC:
return SymmetricKey::GenerateKey(algorithm, key_description, logger, error);
default:
*error = KM_ERROR_UNSUPPORTED_ALGORITHM;
diff --git a/symmetric_key.cpp b/symmetric_key.cpp
index 5105c7d..9678f9d 100644
--- a/symmetric_key.cpp
+++ b/symmetric_key.cpp
@@ -22,6 +22,7 @@
#include <openssl/rand.h>
#include "aes_key.h"
+#include "hmac_key.h"
#include "unencrypted_key_blob.h"
namespace keymaster {
@@ -40,6 +41,9 @@
case KM_ALGORITHM_AES:
key.reset(new AesKey(key_description, logger));
break;
+ case KM_ALGORITHM_HMAC:
+ key.reset(new HmacKey(key_description, logger));
+ break;
default:
*error = KM_ERROR_UNSUPPORTED_ALGORITHM;
return NULL;
@@ -74,6 +78,8 @@
switch (algorithm) {
case KM_ALGORITHM_AES:
return new AesKey(blob, logger, error);
+ case KM_ALGORITHM_HMAC:
+ return new HmacKey(blob, logger, error);
default:
*error = KM_ERROR_UNSUPPORTED_ALGORITHM;
return NULL;