blob: 3fb6800a677049f2b5e8c1d1e0476f5b04c1ba84 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/android/jni_android.h"
#include "base/android/jni_array.h"
#include "base/android/scoped_java_ref.h"
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "crypto/openssl_util.h"
#include "net/android/keystore.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_platform_key_android.h"
#include "net/ssl/ssl_private_key.h"
#include "net/test/cert_test_util.h"
#include "net/test/jni/AndroidKeyStoreTestUtil_jni.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/digest.h"
#include "third_party/boringssl/src/include/openssl/ecdsa.h"
#include "third_party/boringssl/src/include/openssl/err.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/pem.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/boringssl/src/include/openssl/x509.h"
namespace net {
namespace {
typedef base::android::ScopedJavaLocalRef<jobject> ScopedJava;
// Resize a string to |size| bytes of data, then return its data buffer address
// cast as an 'uint8_t*', as expected by OpenSSL functions.
// |str| the target string.
// |size| the number of bytes to write into the string.
// Return the string's new buffer in memory, as an 'uint8_t*' pointer.
uint8_t* OpenSSLWriteInto(std::string* str, size_t size) {
return reinterpret_cast<uint8_t*>(base::WriteInto(str, size + 1));
}
bool ReadTestFile(const char* filename, std::string* pkcs8) {
base::FilePath certs_dir = GetTestCertsDirectory();
base::FilePath file_path = certs_dir.AppendASCII(filename);
return base::ReadFileToString(file_path, pkcs8);
}
// Parses a PKCS#8 key into an OpenSSL private key object.
bssl::UniquePtr<EVP_PKEY> ImportPrivateKeyOpenSSL(const std::string& pkcs8) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(pkcs8.data()), pkcs8.size());
return bssl::UniquePtr<EVP_PKEY>(EVP_parse_private_key(&cbs));
}
// Retrieve a JNI local ref from encoded PKCS#8 data.
ScopedJava GetPKCS8PrivateKeyJava(android::PrivateKeyType key_type,
const std::string& pkcs8_key) {
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jbyteArray> bytes(
base::android::ToJavaByteArray(
env, reinterpret_cast<const uint8_t*>(pkcs8_key.data()),
pkcs8_key.size()));
ScopedJava key(Java_AndroidKeyStoreTestUtil_createPrivateKeyFromPKCS8(
env, key_type, bytes));
return key;
}
bool VerifyWithOpenSSL(const EVP_MD* md,
const base::StringPiece& digest,
EVP_PKEY* key,
const base::StringPiece& signature) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
if (!ctx || !EVP_PKEY_verify_init(ctx.get()) ||
!EVP_PKEY_CTX_set_signature_md(ctx.get(), md) ||
!EVP_PKEY_verify(
ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), reinterpret_cast<const uint8_t*>(digest.data()),
digest.size())) {
return false;
}
return true;
}
bool SignWithOpenSSL(const EVP_MD* md,
const base::StringPiece& digest,
EVP_PKEY* key,
std::string* result) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
size_t sig_len;
bssl::UniquePtr<EVP_PKEY_CTX> ctx(EVP_PKEY_CTX_new(key, nullptr));
if (!ctx || !EVP_PKEY_sign_init(ctx.get()) ||
!EVP_PKEY_CTX_set_signature_md(ctx.get(), md) ||
!EVP_PKEY_sign(ctx.get(), OpenSSLWriteInto(result, EVP_PKEY_size(key)),
&sig_len, reinterpret_cast<const uint8_t*>(digest.data()),
digest.size())) {
return false;
}
result->resize(sig_len);
return true;
}
void OnSignComplete(base::RunLoop* loop,
Error* out_error,
std::string* out_signature,
Error error,
const std::vector<uint8_t>& signature) {
*out_error = error;
out_signature->assign(signature.begin(), signature.end());
loop->Quit();
}
void DoKeySigningWithWrapper(SSLPrivateKey* key,
SSLPrivateKey::Hash hash,
const base::StringPiece& message,
std::string* result) {
Error error;
base::RunLoop loop;
key->SignDigest(
hash, message,
base::Bind(OnSignComplete, base::Unretained(&loop),
base::Unretained(&error), base::Unretained(result)));
loop.Run();
ASSERT_EQ(OK, error);
}
static const struct {
const char* name;
int nid;
SSLPrivateKey::Hash hash;
} kHashes[] = {
{"MD5-SHA1", NID_md5_sha1, SSLPrivateKey::Hash::MD5_SHA1},
{"SHA-1", NID_sha1, SSLPrivateKey::Hash::SHA1},
{"SHA-256", NID_sha256, SSLPrivateKey::Hash::SHA256},
{"SHA-384", NID_sha384, SSLPrivateKey::Hash::SHA384},
{"SHA-512", NID_sha512, SSLPrivateKey::Hash::SHA512},
};
struct TestKey {
const char* cert_file;
const char* key_file;
android::PrivateKeyType android_key_type;
SSLPrivateKey::Type key_type;
};
static const TestKey kTestKeys[] = {
{"client_1.pem", "client_1.pk8", android::PRIVATE_KEY_TYPE_RSA,
SSLPrivateKey::Type::RSA},
{"client_4.pem", "client_4.pk8", android::PRIVATE_KEY_TYPE_ECDSA,
SSLPrivateKey::Type::ECDSA_P256},
{"client_5.pem", "client_5.pk8", android::PRIVATE_KEY_TYPE_ECDSA,
SSLPrivateKey::Type::ECDSA_P384},
{"client_6.pem", "client_6.pk8", android::PRIVATE_KEY_TYPE_ECDSA,
SSLPrivateKey::Type::ECDSA_P521},
};
} // namespace
class SSLPlatformKeyAndroidTest : public testing::TestWithParam<TestKey> {};
TEST_P(SSLPlatformKeyAndroidTest, SignHashes) {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
const TestKey& test_key = GetParam();
scoped_refptr<X509Certificate> cert =
ImportCertFromFile(GetTestCertsDirectory(), test_key.cert_file);
ASSERT_TRUE(cert);
std::string key_bytes;
ASSERT_TRUE(ReadTestFile(test_key.key_file, &key_bytes));
ScopedJava java_key =
GetPKCS8PrivateKeyJava(test_key.android_key_type, key_bytes);
ASSERT_FALSE(java_key.is_null());
scoped_refptr<SSLPrivateKey> wrapper_key =
WrapJavaPrivateKey(cert.get(), java_key);
ASSERT_TRUE(wrapper_key);
bssl::UniquePtr<EVP_PKEY> openssl_key = ImportPrivateKeyOpenSSL(key_bytes);
ASSERT_TRUE(openssl_key);
// Check that the wrapper key returns the correct length and type.
EXPECT_EQ(test_key.key_type, wrapper_key->GetType());
EXPECT_EQ(static_cast<size_t>(EVP_PKEY_size(openssl_key.get())),
wrapper_key->GetMaxSignatureLengthInBytes());
// Test signing against each hash.
for (const auto& hash : kHashes) {
// Only RSA signs MD5-SHA1.
if (test_key.key_type != SSLPrivateKey::Type::RSA &&
hash.nid == NID_md5_sha1) {
continue;
}
SCOPED_TRACE(hash.name);
const EVP_MD* md = EVP_get_digestbynid(hash.nid);
ASSERT_TRUE(md);
std::string digest(EVP_MD_size(md), 'a');
std::string signature;
DoKeySigningWithWrapper(wrapper_key.get(), hash.hash, digest, &signature);
EXPECT_TRUE(VerifyWithOpenSSL(md, digest, openssl_key.get(), signature));
// RSA signing is deterministic, so further check the signature matches.
if (test_key.key_type == SSLPrivateKey::Type::RSA) {
std::string openssl_signature;
ASSERT_TRUE(
SignWithOpenSSL(md, digest, openssl_key.get(), &openssl_signature));
EXPECT_EQ(openssl_signature, signature);
}
}
}
INSTANTIATE_TEST_CASE_P(,
SSLPlatformKeyAndroidTest,
testing::ValuesIn(kTestKeys));
} // namespace net