Add AES key generation support.
Change-Id: I4a519ffd679f46c7212f72ab538f71cd6cb3cc29
diff --git a/Makefile b/Makefile
index ae6e2dd..c308942 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,7 @@
LDLIBS=-lcrypto -lpthread -lstdc++
CPPSRCS=\
+ aes_key.cpp \
asymmetric_key.cpp \
authorization_set.cpp \
authorization_set_test.cpp \
@@ -125,6 +126,7 @@
$(GTEST)/src/gtest-all.o
google_keymaster_test: google_keymaster_test.o \
+ aes_key.o \
asymmetric_key.o \
authorization_set.o \
dsa_key.o \
diff --git a/aes_key.cpp b/aes_key.cpp
new file mode 100644
index 0000000..2031054
--- /dev/null
+++ b/aes_key.cpp
@@ -0,0 +1,124 @@
+/*
+ * 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 <openssl/err.h>
+#include <openssl/rand.h>
+
+#include "aes_key.h"
+
+namespace keymaster {
+
+AesKey* AesKey::GenerateKey(const AuthorizationSet& key_description, const Logger& logger,
+ keymaster_error_t* error) {
+ if (!error)
+ return NULL;
+
+ AuthorizationSet authorizations(key_description);
+ uint32_t key_size_bits;
+ if (!authorizations.GetTagValue(TAG_KEY_SIZE, &key_size_bits) ||
+ !size_is_supported(key_size_bits)) {
+ *error = KM_ERROR_UNSUPPORTED_KEY_SIZE;
+ return NULL;
+ }
+
+ keymaster_block_mode_t block_mode;
+ if (!authorizations.GetTagValue(TAG_BLOCK_MODE, &block_mode) ||
+ !block_mode_is_supported(block_mode)) {
+ *error = KM_ERROR_UNSUPPORTED_BLOCK_MODE;
+ return NULL;
+ }
+
+ uint32_t chunk_length = 0;
+ if (block_mode >= KM_MODE_FIRST_AUTHENTICATED && block_mode < KM_MODE_FIRST_MAC) {
+ // Chunk length is required.
+ if (!authorizations.GetTagValue(TAG_CHUNK_LENGTH, &chunk_length) ||
+ !chunk_length_is_supported(chunk_length)) {
+ // TODO(swillden): Add a better error code for this.
+ *error = KM_ERROR_INVALID_INPUT_LENGTH;
+ return NULL;
+ }
+ }
+
+ // Padding is optional
+ keymaster_padding_t padding;
+ if (authorizations.GetTagValue(TAG_PADDING, &padding) && !padding_is_supported(padding)) {
+ *error = KM_ERROR_UNSUPPORTED_PADDING_MODE;
+ return NULL;
+ }
+
+ // Verify purpose is compatible with block mode.
+ if (!ModeAndPurposesAreCompatible(authorizations, block_mode, logger)) {
+ *error = KM_ERROR_INCOMPATIBLE_BLOCK_MODE;
+ return NULL;
+ }
+
+ // All required tags seem to be present and valid. Generate the key bits.
+ size_t key_data_size = key_size_bits / 8;
+ UniquePtr<uint8_t[]> key_data(new uint8_t[key_data_size]);
+ if (!key_data.get()) {
+ *error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
+ return NULL;
+ }
+
+ if (!RAND_bytes(key_data.get(), key_data_size)) {
+ logger.error("Error %ul generating %d bit AES key", ERR_get_error(), key_size_bits);
+ *error = KM_ERROR_UNKNOWN_ERROR;
+ return NULL;
+ }
+
+ *error = KM_ERROR_OK;
+ return new AesKey(key_data.release(), key_data_size, authorizations, logger);
+}
+
+bool AesKey::ModeAndPurposesAreCompatible(const AuthorizationSet& authorizations,
+ keymaster_block_mode_t block_mode, const Logger& logger) {
+ keymaster_purpose_t purpose;
+ for (size_t i = 0; authorizations.GetTagValue(TAG_PURPOSE, i, &purpose); ++i) {
+ switch (purpose) {
+ case KM_PURPOSE_SIGN:
+ case KM_PURPOSE_VERIFY:
+ if (block_mode < KM_MODE_FIRST_AUTHENTICATED) {
+ logger.error("Only MACing or authenticated modes are supported for signing and "
+ "verification purposes.");
+ return false;
+ }
+ break;
+
+ case KM_PURPOSE_ENCRYPT:
+ case KM_PURPOSE_DECRYPT:
+ if (block_mode >= KM_MODE_FIRST_MAC) {
+ logger.error("MACing modes not supported for encryption and decryption purposes.");
+ return false;
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+AesKey::AesKey(uint8_t* key_data, size_t key_data_size, AuthorizationSet& auths,
+ const Logger& logger)
+ : Key(auths, logger), key_data_(key_data), key_data_size_(key_data_size) {
+}
+
+keymaster_error_t AesKey::key_material(UniquePtr<uint8_t[]>* key_material, size_t* size) const {
+ *size = key_data_size_;
+ key_material->reset(new uint8_t[*size]);
+ memcpy(key_material->get(), key_data_.get(), *size);
+ return KM_ERROR_OK;
+}
+
+} // namespace keymaster
diff --git a/aes_key.h b/aes_key.h
new file mode 100644
index 0000000..126a06e
--- /dev/null
+++ b/aes_key.h
@@ -0,0 +1,74 @@
+/*
+ * 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_AES_KEY_H_
+#define SYSTEM_KEYMASTER_AES_KEY_H_
+
+#include <openssl/aes.h>
+
+#include "key.h"
+
+namespace keymaster {
+
+const uint32_t MAX_AES_CHUNK_LENGTH = 64 * 1024;
+
+class AesKey : public Key {
+ public:
+ static AesKey* GenerateKey(const AuthorizationSet& key_description, const Logger& logger,
+ keymaster_error_t* error);
+
+ static bool size_is_supported(size_t key_size_in_bits) {
+ return (key_size_in_bits == 128 || key_size_in_bits == 192 || key_size_in_bits == 256);
+ };
+
+ static bool block_mode_is_supported(keymaster_block_mode_t block_mode) {
+ return (block_mode == KM_MODE_OCB);
+ }
+
+ static bool chunk_length_is_supported(uint32_t chunk_length) {
+ return (chunk_length <= MAX_AES_CHUNK_LENGTH);
+ }
+
+ static bool padding_is_supported(keymaster_padding_t padding) {
+ return (padding == KM_PAD_NONE);
+ }
+
+ virtual Operation* CreateOperation(keymaster_purpose_t, keymaster_error_t* error) {
+ *error = KM_ERROR_UNIMPLEMENTED;
+ return NULL;
+ }
+
+ virtual keymaster_error_t key_material(UniquePtr<uint8_t[]>* key_material, size_t* size) const;
+
+ virtual keymaster_error_t formatted_key_material(keymaster_key_format_t, UniquePtr<uint8_t[]>*,
+ size_t*) const {
+ return KM_ERROR_UNIMPLEMENTED;
+ }
+
+ private:
+ AesKey(uint8_t* key_data, size_t key_data_size, AuthorizationSet& auths, const Logger& logger);
+
+ static bool ModeAndPurposesAreCompatible(const AuthorizationSet& auths,
+ keymaster_block_mode_t block_mode,
+ const Logger& logger);
+
+ UniquePtr<const uint8_t[]> key_data_;
+ const size_t key_data_size_;
+};
+
+} // namespace keymaster
+
+#endif // SYSTEM_KEYMASTER_AES_KEY_H_
diff --git a/google_keymaster.cpp b/google_keymaster.cpp
index 1da78f3..d1280a6 100644
--- a/google_keymaster.cpp
+++ b/google_keymaster.cpp
@@ -51,8 +51,14 @@
};
typedef UniquePtr<ae_ctx, AE_CTX_Delete> Unique_ae_ctx;
+// TODO(swillden): Unify support analysis. Right now, we have per-keytype methods that determine if
+// specific modes, padding, etc. are supported for that key type, and GoogleKeymaster also has
+// methods that return the same information. They'll get out of sync. Best to put the knowledge in
+// the keytypes and provide some mechanism for GoogleKeymaster to query the keytypes for the
+// information.
+
keymaster_algorithm_t supported_algorithms[] = {
- KM_ALGORITHM_RSA, KM_ALGORITHM_DSA, KM_ALGORITHM_ECDSA,
+ KM_ALGORITHM_RSA, KM_ALGORITHM_DSA, KM_ALGORITHM_ECDSA, KM_ALGORITHM_AES,
};
template <typename T>
diff --git a/google_keymaster_test.cpp b/google_keymaster_test.cpp
index ff21f45..4dca97a 100644
--- a/google_keymaster_test.cpp
+++ b/google_keymaster_test.cpp
@@ -97,10 +97,11 @@
SupportedResponse<keymaster_algorithm_t> response;
device.SupportedAlgorithms(&response);
EXPECT_EQ(KM_ERROR_OK, response.error);
- EXPECT_EQ(3U, response.results_length);
+ EXPECT_EQ(4U, response.results_length);
EXPECT_EQ(KM_ALGORITHM_RSA, response.results[0]);
EXPECT_EQ(KM_ALGORITHM_DSA, response.results[1]);
EXPECT_EQ(KM_ALGORITHM_ECDSA, response.results[2]);
+ EXPECT_EQ(KM_ALGORITHM_AES, response.results[3]);
}
TEST_F(CheckSupported, SupportedBlockModes) {
@@ -118,7 +119,7 @@
EXPECT_EQ(KM_ERROR_UNSUPPORTED_BLOCK_MODE, response.error);
device.SupportedBlockModes(KM_ALGORITHM_AES, KM_PURPOSE_ENCRYPT, &response);
- EXPECT_EQ(KM_ERROR_UNSUPPORTED_ALGORITHM, response.error);
+ EXPECT_EQ(KM_ERROR_UNSUPPORTED_BLOCK_MODE, response.error);
}
TEST_F(CheckSupported, SupportedPaddingModes) {
@@ -136,7 +137,7 @@
ExpectResponseContains(KM_PAD_NONE, response);
device.SupportedPaddingModes(KM_ALGORITHM_AES, KM_PURPOSE_SIGN, &response);
- EXPECT_EQ(KM_ERROR_UNSUPPORTED_ALGORITHM, response.error);
+ ExpectEmptyResponse(response);
}
TEST_F(CheckSupported, SupportedDigests) {
@@ -154,7 +155,7 @@
ExpectResponseContains(KM_DIGEST_NONE, response);
device.SupportedDigests(KM_ALGORITHM_AES, KM_PURPOSE_SIGN, &response);
- EXPECT_EQ(KM_ERROR_UNSUPPORTED_ALGORITHM, response.error);
+ ExpectEmptyResponse(response);
}
TEST_F(CheckSupported, SupportedImportFormats) {
@@ -172,7 +173,7 @@
ExpectResponseContains(KM_KEY_FORMAT_PKCS8, response);
device.SupportedImportFormats(KM_ALGORITHM_AES, &response);
- EXPECT_EQ(KM_ERROR_UNSUPPORTED_ALGORITHM, response.error);
+ ExpectEmptyResponse(response);
}
TEST_F(CheckSupported, SupportedExportFormats) {
@@ -190,7 +191,7 @@
ExpectResponseContains(KM_KEY_FORMAT_X509, response);
device.SupportedExportFormats(KM_ALGORITHM_AES, &response);
- EXPECT_EQ(KM_ERROR_UNSUPPORTED_ALGORITHM, response.error);
+ ExpectEmptyResponse(response);
}
keymaster_key_param_t key_generation_base_params[] = {
@@ -384,6 +385,89 @@
}
}
+TEST_F(NewKeyGeneration, AesOcb) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_OCB), Authorization(TAG_CHUNK_LENGTH, 4096),
+ Authorization(TAG_PADDING, KM_PAD_NONE),
+ };
+ req_.key_description.Reinitialize(params, array_length(params));
+ device.GenerateKey(req_, &rsp_);
+ EXPECT_EQ(KM_ERROR_OK, rsp_.error);
+}
+
+TEST_F(NewKeyGeneration, AesOcbInvalidKeySize) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES), Authorization(TAG_KEY_SIZE, 129),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_OCB), Authorization(TAG_CHUNK_LENGTH, 4096),
+ Authorization(TAG_PADDING, KM_PAD_NONE),
+ };
+ req_.key_description.Reinitialize(params, array_length(params));
+ device.GenerateKey(req_, &rsp_);
+ EXPECT_EQ(KM_ERROR_UNSUPPORTED_KEY_SIZE, rsp_.error);
+}
+
+TEST_F(NewKeyGeneration, AesOcbAllValidSizes) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_OCB),
+ Authorization(TAG_CHUNK_LENGTH, 4096),
+ Authorization(TAG_PADDING, KM_PAD_NONE),
+ };
+
+ size_t valid_sizes[] = {128, 192, 256};
+ for (size_t size : valid_sizes) {
+ GenerateKeyResponse rsp;
+ req_.key_description.Reinitialize(params, array_length(params));
+ req_.key_description.push_back(Authorization(TAG_KEY_SIZE, size));
+ device.GenerateKey(req_, &rsp);
+ EXPECT_EQ(KM_ERROR_OK, rsp.error) << "Failed to generate size: " << size;
+ }
+}
+
+TEST_F(NewKeyGeneration, AesOcbNoChunkLength) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_OCB), Authorization(TAG_PADDING, KM_PAD_NONE),
+ };
+ req_.key_description.Reinitialize(params, array_length(params));
+ device.GenerateKey(req_, &rsp_);
+ EXPECT_EQ(KM_ERROR_INVALID_INPUT_LENGTH, rsp_.error);
+}
+
+TEST_F(NewKeyGeneration, AesEcbUnsupported) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_ECB), Authorization(TAG_PADDING, KM_PAD_NONE),
+ };
+ req_.key_description.Reinitialize(params, array_length(params));
+ device.GenerateKey(req_, &rsp_);
+ EXPECT_EQ(KM_ERROR_UNSUPPORTED_BLOCK_MODE, rsp_.error);
+}
+
+TEST_F(NewKeyGeneration, AesOcbPaddingUnsupported) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT),
+ Authorization(TAG_ALGORITHM, KM_ALGORITHM_AES), Authorization(TAG_KEY_SIZE, 128),
+ Authorization(TAG_BLOCK_MODE, KM_MODE_OCB), Authorization(TAG_CHUNK_LENGTH, 4096),
+ Authorization(TAG_PADDING, KM_PAD_ZERO),
+ };
+ req_.key_description.Reinitialize(params, array_length(params));
+ device.GenerateKey(req_, &rsp_);
+ EXPECT_EQ(KM_ERROR_UNSUPPORTED_PADDING_MODE, rsp_.error);
+}
+
typedef KeymasterTest GetKeyCharacteristics;
TEST_F(GetKeyCharacteristics, SimpleRsa) {
keymaster_key_param_t params[] = {
diff --git a/include/keymaster/keymaster_defs.h b/include/keymaster/keymaster_defs.h
index c10a8b6..ce5cbc9 100644
--- a/include/keymaster/keymaster_defs.h
+++ b/include/keymaster/keymaster_defs.h
@@ -165,25 +165,31 @@
/**
* Symmetric block cipher modes that may be provided by keymaster implementations. Those that must
* be provided by all implementations are tagged as "required". This type is new in 0_4.
+ *
+ * KM_MODE_FIRST_UNAUTHENTICATED, KM_MODE_FIRST_AUTHENTICATED and KM_MODE_FIRST_MAC are not modes,
+ * but markers used to separate the available modes into classes.
*/
typedef enum {
/* Unauthenticated modes, usable only for encryption/decryption and not generally recommended
* except for compatibility with existing other protocols. */
- KM_MODE_ECB = 1, /* required */
- KM_MODE_CBC = 2, /* required */
- KM_MODE_CBC_CTS = 3, /* recommended */
- KM_MODE_CTR = 4, /* recommended */
+ KM_MODE_FIRST_UNAUTHENTICATED = 1,
+ KM_MODE_ECB = KM_MODE_FIRST_UNAUTHENTICATED, /* required */
+ KM_MODE_CBC = 2, /* required */
+ KM_MODE_CBC_CTS = 3, /* recommended */
+ KM_MODE_CTR = 4, /* recommended */
KM_MODE_OFB = 5,
KM_MODE_CFB = 6,
KM_MODE_XTS = 7, /* Note: requires double-length keys */
/* Authenticated modes, usable for encryption/decryption and signing/verification. Recommended
* over unauthenticated modes for all purposes. One of KM_MODE_GCM and KM_MODE_OCB is
* required. */
- KM_MODE_GCM = 32,
+ KM_MODE_FIRST_AUTHENTICATED = 32,
+ KM_MODE_GCM = KM_MODE_FIRST_AUTHENTICATED,
KM_MODE_OCB = 33,
KM_MODE_CCM = 34,
/* MAC modes -- only for signing/verification */
- KM_MODE_CMAC = 128,
+ KM_MODE_FIRST_MAC = 128,
+ KM_MODE_CMAC = KM_MODE_FIRST_MAC,
KM_MODE_POLY1305 = 129,
} keymaster_block_mode_t;
diff --git a/key.cpp b/key.cpp
index 596f428..8ee0656 100644
--- a/key.cpp
+++ b/key.cpp
@@ -16,6 +16,7 @@
#include <openssl/x509.h>
+#include "aes_key.h"
#include "dsa_key.h"
#include "ecdsa_key.h"
#include "openssl_utils.h"
@@ -67,6 +68,8 @@
return DsaKey::GenerateKey(key_description, logger, error);
case KM_ALGORITHM_ECDSA:
return EcdsaKey::GenerateKey(key_description, logger, error);
+ case KM_ALGORITHM_AES:
+ return AesKey::GenerateKey(key_description, logger, error);
default:
*error = KM_ERROR_UNSUPPORTED_ALGORITHM;
return NULL;