Add RSA encryption and decryption support.
This change was already reviewed, merged and reverted, so I'm skipping
the review step this time.
Change-Id: Ie5b7dba86a7ae7f62eedbdb6eec7b61ef83d0c73
diff --git a/google_keymaster.cpp b/google_keymaster.cpp
index da606e5..47f22cf 100644
--- a/google_keymaster.cpp
+++ b/google_keymaster.cpp
@@ -93,10 +93,12 @@
response->error = KM_ERROR_UNSUPPORTED_BLOCK_MODE;
}
+keymaster_padding_t supported_rsa_crypt_padding[] = {KM_PAD_RSA_OAEP, KM_PAD_RSA_PKCS1_1_5_ENCRYPT};
+keymaster_padding_t supported_rsa_sign_padding[] = {KM_PAD_NONE};
keymaster_padding_t supported_padding[] = {KM_PAD_NONE};
void GoogleKeymaster::SupportedPaddingModes(
- keymaster_algorithm_t algorithm, keymaster_purpose_t /* purpose */,
+ keymaster_algorithm_t algorithm, keymaster_purpose_t purpose,
SupportedResponse<keymaster_padding_t>* response) const {
if (response == NULL || !check_supported(algorithm, response))
return;
@@ -104,6 +106,17 @@
response->error = KM_ERROR_OK;
switch (algorithm) {
case KM_ALGORITHM_RSA:
+ switch (purpose) {
+ case KM_PURPOSE_ENCRYPT:
+ case KM_PURPOSE_DECRYPT:
+ response->SetResults(supported_rsa_crypt_padding);
+ break;
+ case KM_PURPOSE_SIGN:
+ case KM_PURPOSE_VERIFY:
+ response->SetResults(supported_rsa_sign_padding);
+ break;
+ }
+ break;
case KM_ALGORITHM_DSA:
case KM_ALGORITHM_ECDSA:
response->SetResults(supported_padding);
diff --git a/google_keymaster_test.cpp b/google_keymaster_test.cpp
index a124126..810f1f3 100644
--- a/google_keymaster_test.cpp
+++ b/google_keymaster_test.cpp
@@ -1089,5 +1089,208 @@
EXPECT_EQ(0, rsp.subminor_ver);
}
+/**
+ * Test class that provides some infrastructure for generating keys and encrypting messages.
+ */
+class EncryptionOperationsTest : public KeymasterTest {
+ protected:
+ void GenerateKey(keymaster_algorithm_t algorithm, keymaster_padding_t padding,
+ uint32_t key_size) {
+ keymaster_key_param_t params[] = {
+ Authorization(TAG_PURPOSE, KM_PURPOSE_ENCRYPT),
+ Authorization(TAG_PURPOSE, KM_PURPOSE_DECRYPT), Authorization(TAG_ALGORITHM, algorithm),
+ Authorization(TAG_KEY_SIZE, key_size), Authorization(TAG_USER_ID, 7),
+ Authorization(TAG_USER_AUTH_ID, 8), Authorization(TAG_APPLICATION_ID, "app_id", 6),
+ Authorization(TAG_AUTH_TIMEOUT, 300),
+ };
+ GenerateKeyRequest generate_request;
+ generate_request.key_description.Reinitialize(params, array_length(params));
+ if (static_cast<int>(padding) != -1)
+ generate_request.key_description.push_back(TAG_PADDING, padding);
+ device.GenerateKey(generate_request, &generate_response_);
+ EXPECT_EQ(KM_ERROR_OK, generate_response_.error);
+ }
+
+ keymaster_error_t BeginOperation(keymaster_purpose_t purpose,
+ const keymaster_key_blob_t& key_blob, uint64_t* op_handle) {
+ BeginOperationRequest begin_request;
+ begin_request.SetKeyMaterial(key_blob);
+ begin_request.purpose = purpose;
+ AddClientParams(&begin_request.additional_params);
+
+ BeginOperationResponse begin_response;
+ device.BeginOperation(begin_request, &begin_response);
+ *op_handle = begin_response.op_handle;
+ return begin_response.error;
+ }
+
+ keymaster_error_t UpdateOperation(uint64_t op_handle, const void* message, size_t size,
+ string* output) {
+ UpdateOperationRequest update_request;
+ update_request.op_handle = op_handle;
+ update_request.input.Reinitialize(message, size);
+
+ UpdateOperationResponse update_response;
+ device.UpdateOperation(update_request, &update_response);
+ if (update_response.error == KM_ERROR_OK)
+ output->append(reinterpret_cast<const char*>(update_response.output.peek_read()),
+ update_response.output.available_read());
+ return update_response.error;
+ }
+
+ keymaster_error_t FinishOperation(uint64_t op_handle, string* output) {
+ FinishOperationRequest finish_request;
+ finish_request.op_handle = op_handle;
+ FinishOperationResponse finish_response;
+ device.FinishOperation(finish_request, &finish_response);
+ if (finish_response.error == KM_ERROR_OK)
+ output->append(reinterpret_cast<const char*>(finish_response.output.peek_read()),
+ finish_response.output.available_read());
+ return finish_response.error;
+ }
+
+ string ProcessMessage(keymaster_purpose_t purpose, const keymaster_key_blob_t& key_blob,
+ const void* message, size_t size) {
+ uint64_t op_handle;
+ EXPECT_EQ(KM_ERROR_OK, BeginOperation(purpose, key_blob, &op_handle));
+
+ string result;
+ EXPECT_EQ(KM_ERROR_OK, UpdateOperation(op_handle, message, size, &result));
+ EXPECT_EQ(KM_ERROR_OK, FinishOperation(op_handle, &result));
+ return result;
+ }
+
+ string EncryptMessage(const void* message, size_t size) {
+ return ProcessMessage(KM_PURPOSE_ENCRYPT, generate_response_.key_blob, message, size);
+ }
+
+ string DecryptMessage(const void* ciphertext, size_t size) {
+ return ProcessMessage(KM_PURPOSE_DECRYPT, generate_response_.key_blob, ciphertext, size);
+ }
+
+ void AddClientParams(AuthorizationSet* set) { set->push_back(TAG_APPLICATION_ID, "app_id", 6); }
+
+ const keymaster_key_blob_t& key_blob() { return generate_response_.key_blob; }
+
+ const keymaster_key_blob_t& corrupt_key_blob() {
+ uint8_t* tmp = const_cast<uint8_t*>(generate_response_.key_blob.key_material);
+ ++tmp[generate_response_.key_blob.key_material_size / 2];
+ return generate_response_.key_blob;
+ }
+
+ protected:
+ GenerateKeyResponse generate_response_;
+};
+
+TEST_F(EncryptionOperationsTest, RsaOaepSuccess) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_OAEP, 512);
+ const char message[] = "Hello World!";
+ string ciphertext1 = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext1.size());
+
+ string ciphertext2 = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext2.size());
+
+ // OAEP randomizes padding so every result should be different.
+ EXPECT_NE(ciphertext1, ciphertext2);
+}
+
+TEST_F(EncryptionOperationsTest, RsaOaepRoundTrip) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_OAEP, 512);
+ const char message[] = "Hello World!";
+ string ciphertext = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext.size());
+
+ string plaintext = DecryptMessage(ciphertext.data(), ciphertext.size());
+ EXPECT_EQ(message, plaintext);
+}
+
+TEST_F(EncryptionOperationsTest, RsaOaepTooLarge) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_OAEP, 512);
+ const char message[] = "12345678901234567890123";
+ uint64_t op_handle;
+ string result;
+
+ EXPECT_EQ(KM_ERROR_OK,
+ BeginOperation(KM_PURPOSE_ENCRYPT, generate_response_.key_blob, &op_handle));
+ EXPECT_EQ(KM_ERROR_OK, UpdateOperation(op_handle, message, array_size(message), &result));
+ EXPECT_EQ(KM_ERROR_INVALID_INPUT_LENGTH, FinishOperation(op_handle, &result));
+ EXPECT_EQ(0, result.size());
+}
+
+TEST_F(EncryptionOperationsTest, RsaOaepCorruptedDecrypt) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_OAEP, 512);
+ const char message[] = "Hello World!";
+ string ciphertext = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext.size());
+
+ // Corrupt the ciphertext
+ ciphertext[512 / 8 / 2]++;
+
+ uint64_t op_handle;
+ string result;
+ EXPECT_EQ(KM_ERROR_OK,
+ BeginOperation(KM_PURPOSE_DECRYPT, generate_response_.key_blob, &op_handle));
+ EXPECT_EQ(KM_ERROR_OK,
+ UpdateOperation(op_handle, ciphertext.data(), ciphertext.size(), &result));
+ EXPECT_EQ(KM_ERROR_UNKNOWN_ERROR, FinishOperation(op_handle, &result));
+ EXPECT_EQ(0, result.size());
+}
+
+TEST_F(EncryptionOperationsTest, RsaPkcs1Success) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_PKCS1_1_5_ENCRYPT, 512);
+ const char message[] = "Hello World!";
+ string ciphertext1 = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext1.size());
+
+ string ciphertext2 = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext2.size());
+
+ // PKCS1 v1.5 randomizes padding so every result should be different.
+ EXPECT_NE(ciphertext1, ciphertext2);
+}
+
+TEST_F(EncryptionOperationsTest, RsaPkcs1RoundTrip) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_PKCS1_1_5_ENCRYPT, 512);
+ const char message[] = "Hello World!";
+ string ciphertext = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext.size());
+
+ string plaintext = DecryptMessage(ciphertext.data(), ciphertext.size());
+ EXPECT_EQ(message, plaintext);
+}
+
+TEST_F(EncryptionOperationsTest, RsaPkcs1TooLarge) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_PKCS1_1_5_ENCRYPT, 512);
+ const char message[] = "1234567890123456789012345678901234567890123456789012";
+ uint64_t op_handle;
+ string result;
+
+ EXPECT_EQ(KM_ERROR_OK,
+ BeginOperation(KM_PURPOSE_ENCRYPT, generate_response_.key_blob, &op_handle));
+ EXPECT_EQ(KM_ERROR_OK, UpdateOperation(op_handle, message, array_size(message), &result));
+ EXPECT_EQ(KM_ERROR_INVALID_INPUT_LENGTH, FinishOperation(op_handle, &result));
+ EXPECT_EQ(0, result.size());
+}
+
+TEST_F(EncryptionOperationsTest, RsaPkcs1CorruptedDecrypt) {
+ GenerateKey(KM_ALGORITHM_RSA, KM_PAD_RSA_PKCS1_1_5_ENCRYPT, 512);
+ const char message[] = "Hello World!";
+ string ciphertext = EncryptMessage(message, strlen(message));
+ EXPECT_EQ(512 / 8, ciphertext.size());
+
+ // Corrupt the ciphertext
+ ciphertext[512 / 8 / 2]++;
+
+ uint64_t op_handle;
+ string result;
+ EXPECT_EQ(KM_ERROR_OK,
+ BeginOperation(KM_PURPOSE_DECRYPT, generate_response_.key_blob, &op_handle));
+ EXPECT_EQ(KM_ERROR_OK,
+ UpdateOperation(op_handle, ciphertext.data(), ciphertext.size(), &result));
+ EXPECT_EQ(KM_ERROR_UNKNOWN_ERROR, FinishOperation(op_handle, &result));
+ EXPECT_EQ(0, result.size());
+}
+
} // namespace test
} // namespace keymaster
diff --git a/rsa_key.cpp b/rsa_key.cpp
index db50930..c904c19 100644
--- a/rsa_key.cpp
+++ b/rsa_key.cpp
@@ -128,30 +128,44 @@
}
Operation* RsaKey::CreateOperation(keymaster_purpose_t purpose, keymaster_error_t* error) {
- keymaster_digest_t digest = KM_DIGEST_NONE;
- if (!authorizations().GetTagValue(TAG_DIGEST, &digest) || digest != KM_DIGEST_NONE) {
- *error = KM_ERROR_UNSUPPORTED_DIGEST;
- return NULL;
- }
- keymaster_padding_t padding = KM_PAD_NONE;
- if (!authorizations().GetTagValue(TAG_PADDING, &padding) || padding != KM_PAD_NONE) {
+ *error = KM_ERROR_OK;
+
+ keymaster_padding_t padding = static_cast<keymaster_padding_t>(-1);
+ authorizations().GetTagValue(TAG_PADDING, &padding);
+ if (!SupportedMode(purpose, padding)) {
*error = KM_ERROR_UNSUPPORTED_PADDING_MODE;
return NULL;
}
- Operation* op;
- switch (purpose) {
- case KM_PURPOSE_SIGN:
- op = new RsaSignOperation(purpose, logger_, digest, padding, rsa_key_.release());
- break;
- case KM_PURPOSE_VERIFY:
- op = new RsaVerifyOperation(purpose, logger_, digest, padding, rsa_key_.release());
- break;
- default:
- *error = KM_ERROR_UNIMPLEMENTED;
+ keymaster_digest_t digest = static_cast<keymaster_digest_t>(-1);
+ authorizations().GetTagValue(TAG_DIGEST, &digest);
+ if (!SupportedMode(purpose, digest)) {
+ *error = KM_ERROR_UNSUPPORTED_DIGEST;
return NULL;
}
- *error = op ? KM_ERROR_OK : KM_ERROR_MEMORY_ALLOCATION_FAILED;
+
+ Operation* op = NULL;
+ switch (purpose) {
+ case KM_PURPOSE_SIGN:
+ op = new RsaSignOperation(logger_, digest, padding, rsa_key_.release());
+ break;
+ case KM_PURPOSE_VERIFY:
+ op = new RsaVerifyOperation(logger_, digest, padding, rsa_key_.release());
+ break;
+ case KM_PURPOSE_ENCRYPT:
+ op = new RsaEncryptOperation(logger_, padding, rsa_key_.release());
+ break;
+ case KM_PURPOSE_DECRYPT:
+ op = new RsaDecryptOperation(logger_, padding, rsa_key_.release());
+ break;
+ default:
+ *error = KM_ERROR_UNSUPPORTED_PURPOSE;
+ return NULL;
+ }
+
+ if (!op)
+ *error = KM_ERROR_MEMORY_ALLOCATION_FAILED;
+
return op;
}
@@ -164,4 +178,32 @@
return EVP_PKEY_set1_RSA(pkey, rsa_key_.get()) == 1;
}
+bool RsaKey::SupportedMode(keymaster_purpose_t purpose, keymaster_padding_t padding) {
+ switch (purpose) {
+ case KM_PURPOSE_SIGN:
+ case KM_PURPOSE_VERIFY:
+ return padding == KM_PAD_NONE;
+ break;
+ case KM_PURPOSE_ENCRYPT:
+ case KM_PURPOSE_DECRYPT:
+ return padding == KM_PAD_RSA_OAEP || padding == KM_PAD_RSA_PKCS1_1_5_ENCRYPT;
+ break;
+ };
+ return false;
+}
+
+bool RsaKey::SupportedMode(keymaster_purpose_t purpose, keymaster_digest_t digest) {
+ switch (purpose) {
+ case KM_PURPOSE_SIGN:
+ case KM_PURPOSE_VERIFY:
+ return digest == KM_DIGEST_NONE;
+ break;
+ case KM_PURPOSE_ENCRYPT:
+ case KM_PURPOSE_DECRYPT:
+ /* Don't care */
+ break;
+ };
+ return true;
+}
+
} // namespace keymaster
diff --git a/rsa_key.h b/rsa_key.h
index 4002996..cb341e2 100644
--- a/rsa_key.h
+++ b/rsa_key.h
@@ -41,6 +41,9 @@
virtual bool InternalToEvp(EVP_PKEY* pkey) const;
virtual bool EvpToInternal(const EVP_PKEY* pkey);
+ bool SupportedMode(keymaster_purpose_t purpose, keymaster_padding_t padding);
+ bool SupportedMode(keymaster_purpose_t purpose, keymaster_digest_t digest);
+
struct RSA_Delete {
void operator()(RSA* p) { RSA_free(p); }
};
diff --git a/rsa_operation.cpp b/rsa_operation.cpp
index a013b78..df39006 100644
--- a/rsa_operation.cpp
+++ b/rsa_operation.cpp
@@ -14,8 +14,11 @@
* limitations under the License.
*/
-#include <openssl/rsa.h>
+#include <limits.h>
+
+#include <openssl/err.h>
#include <openssl/evp.h>
+#include <openssl/rsa.h>
#include "rsa_operation.h"
#include "openssl_utils.h"
@@ -37,6 +40,8 @@
return KM_ERROR_UNIMPLEMENTED;
case KM_PURPOSE_SIGN:
case KM_PURPOSE_VERIFY:
+ case KM_PURPOSE_ENCRYPT:
+ case KM_PURPOSE_DECRYPT:
return StoreData(input);
}
}
@@ -61,13 +66,16 @@
keymaster_error_t RsaVerifyOperation::Finish(const Buffer& signature, Buffer* /* output */) {
#if defined(OPENSSL_IS_BORINGSSL)
- if (data_.available_read() != RSA_size(rsa_key_))
- return KM_ERROR_INVALID_INPUT_LENGTH;
+ size_t message_size = data_.available_read();
#else
- if ((int)data_.available_read() != RSA_size(rsa_key_))
+ if (data_.available_read() > INT_MAX)
return KM_ERROR_INVALID_INPUT_LENGTH;
+ int message_size = (int)data_.available_read();
#endif
+ if (message_size != RSA_size(rsa_key_))
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+
if (data_.available_read() != signature.available_read())
return KM_ERROR_VERIFICATION_FAILED;
@@ -83,4 +91,81 @@
return KM_ERROR_VERIFICATION_FAILED;
}
+const int OAEP_PADDING_OVERHEAD = 41;
+const int PKCS1_PADDING_OVERHEAD = 11;
+
+keymaster_error_t RsaEncryptOperation::Finish(const Buffer& /* signature */, Buffer* output) {
+ int openssl_padding;
+
+#if defined(OPENSSL_IS_BORINGSSL)
+ size_t message_size = data_.available_read();
+#else
+ if (data_.available_read() > INT_MAX)
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+ int message_size = (int)data_.available_read();
+#endif
+
+ switch (padding_) {
+ case KM_PAD_RSA_OAEP:
+ openssl_padding = RSA_PKCS1_OAEP_PADDING;
+ if (message_size >= RSA_size(rsa_key_) - OAEP_PADDING_OVERHEAD) {
+ logger().error("Cannot encrypt %d bytes with %d-byte key and OAEP padding",
+ data_.available_read(), RSA_size(rsa_key_));
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+ }
+ break;
+ case KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ openssl_padding = RSA_PKCS1_PADDING;
+ if (message_size >= RSA_size(rsa_key_) - PKCS1_PADDING_OVERHEAD) {
+ logger().error("Cannot encrypt %d bytes with %d-byte key and PKCS1 padding",
+ data_.available_read(), RSA_size(rsa_key_));
+ return KM_ERROR_INVALID_INPUT_LENGTH;
+ }
+ break;
+ default:
+ logger().error("Padding mode %d not supported", padding_);
+ return KM_ERROR_UNSUPPORTED_PADDING_MODE;
+ }
+
+ output->Reinitialize(RSA_size(rsa_key_));
+ int bytes_encrypted = RSA_public_encrypt(data_.available_read(), data_.peek_read(),
+ output->peek_write(), rsa_key_, openssl_padding);
+
+ if (bytes_encrypted < 0) {
+ logger().error("Error %d encrypting data with RSA", ERR_get_error());
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+ assert(bytes_encrypted == RSA_size(rsa_key_));
+ output->advance_write(bytes_encrypted);
+
+ return KM_ERROR_OK;
+}
+
+keymaster_error_t RsaDecryptOperation::Finish(const Buffer& /* signature */, Buffer* output) {
+ int openssl_padding;
+ switch (padding_) {
+ case KM_PAD_RSA_OAEP:
+ openssl_padding = RSA_PKCS1_OAEP_PADDING;
+ break;
+ case KM_PAD_RSA_PKCS1_1_5_ENCRYPT:
+ openssl_padding = RSA_PKCS1_PADDING;
+ break;
+ default:
+ logger().error("Padding mode %d not supported", padding_);
+ return KM_ERROR_UNSUPPORTED_PADDING_MODE;
+ }
+
+ output->Reinitialize(RSA_size(rsa_key_));
+ int bytes_decrypted = RSA_private_decrypt(data_.available_read(), data_.peek_read(),
+ output->peek_write(), rsa_key_, openssl_padding);
+
+ if (bytes_decrypted < 0) {
+ logger().error("Error %d decrypting data with RSA", ERR_get_error());
+ return KM_ERROR_UNKNOWN_ERROR;
+ }
+ output->advance_write(bytes_decrypted);
+
+ return KM_ERROR_OK;
+}
+
} // namespace keymaster
diff --git a/rsa_operation.h b/rsa_operation.h
index 4697866..fb417a7 100644
--- a/rsa_operation.h
+++ b/rsa_operation.h
@@ -27,9 +27,9 @@
class RsaOperation : public Operation {
public:
- RsaOperation(keymaster_purpose_t purpose, const Logger& logger, keymaster_digest_t digest,
- keymaster_padding_t padding, RSA* key)
- : Operation(purpose, logger), rsa_key_(key), digest_(digest), padding_(padding) {}
+ RsaOperation(keymaster_purpose_t purpose, const Logger& logger, keymaster_padding_t padding,
+ RSA* key)
+ : Operation(purpose, logger), rsa_key_(key), padding_(padding) {}
~RsaOperation();
virtual keymaster_error_t Begin() { return KM_ERROR_OK; }
@@ -40,24 +40,43 @@
keymaster_error_t StoreData(const Buffer& input);
RSA* rsa_key_;
- keymaster_digest_t digest_;
keymaster_padding_t padding_;
Buffer data_;
};
class RsaSignOperation : public RsaOperation {
public:
- RsaSignOperation(keymaster_purpose_t purpose, const Logger& logger, keymaster_digest_t digest,
- keymaster_padding_t padding, RSA* key)
- : RsaOperation(purpose, logger, digest, padding, key) {}
+ RsaSignOperation(const Logger& logger, keymaster_digest_t digest, keymaster_padding_t padding,
+ RSA* key)
+ : RsaOperation(KM_PURPOSE_SIGN, logger, padding, key), digest_(digest) {}
virtual keymaster_error_t Finish(const Buffer& signature, Buffer* output);
+
+ private:
+ keymaster_digest_t digest_;
};
class RsaVerifyOperation : public RsaOperation {
public:
- RsaVerifyOperation(keymaster_purpose_t purpose, const Logger& logger, keymaster_digest_t digest,
- keymaster_padding_t padding, RSA* key)
- : RsaOperation(purpose, logger, digest, padding, key) {}
+ RsaVerifyOperation(const Logger& logger, keymaster_digest_t digest, keymaster_padding_t padding,
+ RSA* key)
+ : RsaOperation(KM_PURPOSE_VERIFY, logger, padding, key), digest_(digest) {}
+ virtual keymaster_error_t Finish(const Buffer& signature, Buffer* output);
+
+ private:
+ keymaster_digest_t digest_;
+};
+
+class RsaEncryptOperation : public RsaOperation {
+ public:
+ RsaEncryptOperation(const Logger& logger, keymaster_padding_t padding, RSA* key)
+ : RsaOperation(KM_PURPOSE_ENCRYPT, logger, padding, key) {}
+ virtual keymaster_error_t Finish(const Buffer& signature, Buffer* output);
+};
+
+class RsaDecryptOperation : public RsaOperation {
+ public:
+ RsaDecryptOperation(const Logger& logger, keymaster_padding_t padding, RSA* key)
+ : RsaOperation(KM_PURPOSE_DECRYPT, logger, padding, key) {}
virtual keymaster_error_t Finish(const Buffer& signature, Buffer* output);
};