blob: 1db15dbd24a981b181e2d8e2ae93dcf904870f31 [file] [log] [blame]
// Copyright 2014 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 "content/child/webcrypto/openssl/rsa_hashed_algorithm_openssl.h"
#include <openssl/evp.h>
#include "base/logging.h"
#include "base/stl_util.h"
#include "content/child/webcrypto/crypto_data.h"
#include "content/child/webcrypto/generate_key_result.h"
#include "content/child/webcrypto/jwk.h"
#include "content/child/webcrypto/openssl/key_openssl.h"
#include "content/child/webcrypto/openssl/util_openssl.h"
#include "content/child/webcrypto/status.h"
#include "content/child/webcrypto/webcrypto_util.h"
#include "crypto/openssl_util.h"
#include "crypto/scoped_openssl_types.h"
#include "third_party/WebKit/public/platform/WebCryptoAlgorithmParams.h"
#include "third_party/WebKit/public/platform/WebCryptoKeyAlgorithm.h"
namespace content {
namespace webcrypto {
namespace {
// Creates a blink::WebCryptoAlgorithm having the modulus length and public
// exponent of |key|.
Status CreateRsaHashedKeyAlgorithm(
blink::WebCryptoAlgorithmId rsa_algorithm,
blink::WebCryptoAlgorithmId hash_algorithm,
EVP_PKEY* key,
blink::WebCryptoKeyAlgorithm* key_algorithm) {
DCHECK_EQ(EVP_PKEY_RSA, EVP_PKEY_id(key));
crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(key));
if (!rsa.get())
return Status::ErrorUnexpected();
unsigned int modulus_length_bits = BN_num_bits(rsa.get()->n);
// Convert the public exponent to big-endian representation.
std::vector<uint8_t> e(BN_num_bytes(rsa.get()->e));
if (e.size() == 0)
return Status::ErrorUnexpected();
if (e.size() != BN_bn2bin(rsa.get()->e, &e[0]))
return Status::ErrorUnexpected();
*key_algorithm = blink::WebCryptoKeyAlgorithm::createRsaHashed(
rsa_algorithm, modulus_length_bits, &e[0], e.size(), hash_algorithm);
return Status::Success();
}
// Creates a WebCryptoKey that wraps |private_key|.
Status CreateWebCryptoRsaPrivateKey(
crypto::ScopedEVP_PKEY private_key,
const blink::WebCryptoAlgorithmId rsa_algorithm_id,
const blink::WebCryptoAlgorithm& hash,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) {
blink::WebCryptoKeyAlgorithm key_algorithm;
Status status = CreateRsaHashedKeyAlgorithm(
rsa_algorithm_id, hash.id(), private_key.get(), &key_algorithm);
if (status.IsError())
return status;
return CreateWebCryptoPrivateKey(private_key.Pass(), key_algorithm,
extractable, usages, key);
}
// Creates a WebCryptoKey that wraps |public_key|.
Status CreateWebCryptoRsaPublicKey(
crypto::ScopedEVP_PKEY public_key,
const blink::WebCryptoAlgorithmId rsa_algorithm_id,
const blink::WebCryptoAlgorithm& hash,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) {
blink::WebCryptoKeyAlgorithm key_algorithm;
Status status = CreateRsaHashedKeyAlgorithm(rsa_algorithm_id, hash.id(),
public_key.get(), &key_algorithm);
if (status.IsError())
return status;
return CreateWebCryptoPublicKey(public_key.Pass(), key_algorithm, extractable,
usages, key);
}
Status ImportRsaPrivateKey(const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
const JwkRsaInfo& params,
blink::WebCryptoKey* key) {
crypto::ScopedRSA rsa(RSA_new());
rsa->n = CreateBIGNUM(params.n);
rsa->e = CreateBIGNUM(params.e);
rsa->d = CreateBIGNUM(params.d);
rsa->p = CreateBIGNUM(params.p);
rsa->q = CreateBIGNUM(params.q);
rsa->dmp1 = CreateBIGNUM(params.dp);
rsa->dmq1 = CreateBIGNUM(params.dq);
rsa->iqmp = CreateBIGNUM(params.qi);
if (!rsa->n || !rsa->e || !rsa->d || !rsa->p || !rsa->q || !rsa->dmp1 ||
!rsa->dmq1 || !rsa->iqmp) {
return Status::OperationError();
}
// TODO(eroman): This should really be a DataError, however for compatibility
// with NSS it is an OperationError.
if (!RSA_check_key(rsa.get()))
return Status::OperationError();
// Create a corresponding EVP_PKEY.
crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
return Status::OperationError();
return CreateWebCryptoRsaPrivateKey(pkey.Pass(), algorithm.id(),
algorithm.rsaHashedImportParams()->hash(),
extractable, usages, key);
}
Status ImportRsaPublicKey(const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
const CryptoData& n,
const CryptoData& e,
blink::WebCryptoKey* key) {
crypto::ScopedRSA rsa(RSA_new());
rsa->n = BN_bin2bn(n.bytes(), n.byte_length(), NULL);
rsa->e = BN_bin2bn(e.bytes(), e.byte_length(), NULL);
if (!rsa->n || !rsa->e)
return Status::OperationError();
// Create a corresponding EVP_PKEY.
crypto::ScopedEVP_PKEY pkey(EVP_PKEY_new());
if (!pkey || !EVP_PKEY_set1_RSA(pkey.get(), rsa.get()))
return Status::OperationError();
return CreateWebCryptoRsaPublicKey(pkey.Pass(), algorithm.id(),
algorithm.rsaHashedImportParams()->hash(),
extractable, usages, key);
}
} // namespace
Status RsaHashedAlgorithm::GenerateKey(
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask combined_usages,
GenerateKeyResult* result) const {
blink::WebCryptoKeyUsageMask public_usages = 0;
blink::WebCryptoKeyUsageMask private_usages = 0;
Status status = GetUsagesForGenerateAsymmetricKey(
combined_usages, all_public_key_usages_, all_private_key_usages_,
&public_usages, &private_usages);
if (status.IsError())
return status;
const blink::WebCryptoRsaHashedKeyGenParams* params =
algorithm.rsaHashedKeyGenParams();
unsigned int public_exponent = 0;
unsigned int modulus_length_bits = 0;
status =
GetRsaKeyGenParameters(params, &public_exponent, &modulus_length_bits);
if (status.IsError())
return status;
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
// Generate an RSA key pair.
crypto::ScopedRSA rsa_private_key(RSA_new());
crypto::ScopedBIGNUM bn(BN_new());
if (!rsa_private_key.get() || !bn.get() ||
!BN_set_word(bn.get(), public_exponent)) {
return Status::OperationError();
}
if (!RSA_generate_key_ex(rsa_private_key.get(), modulus_length_bits, bn.get(),
NULL)) {
return Status::OperationError();
}
// Construct an EVP_PKEY for the private key.
crypto::ScopedEVP_PKEY private_pkey(EVP_PKEY_new());
if (!private_pkey ||
!EVP_PKEY_set1_RSA(private_pkey.get(), rsa_private_key.get())) {
return Status::OperationError();
}
// Construct an EVP_PKEY for the public key.
crypto::ScopedRSA rsa_public_key(RSAPublicKey_dup(rsa_private_key.get()));
crypto::ScopedEVP_PKEY public_pkey(EVP_PKEY_new());
if (!public_pkey ||
!EVP_PKEY_set1_RSA(public_pkey.get(), rsa_public_key.get())) {
return Status::OperationError();
}
blink::WebCryptoKey public_key;
blink::WebCryptoKey private_key;
// Note that extractable is unconditionally set to true. This is because per
// the WebCrypto spec generated public keys are always extractable.
status = CreateWebCryptoRsaPublicKey(public_pkey.Pass(), algorithm.id(),
params->hash(), true, public_usages,
&public_key);
if (status.IsError())
return status;
status = CreateWebCryptoRsaPrivateKey(private_pkey.Pass(), algorithm.id(),
params->hash(), extractable,
private_usages, &private_key);
if (status.IsError())
return status;
result->AssignKeyPair(public_key, private_key);
return Status::Success();
}
Status RsaHashedAlgorithm::VerifyKeyUsagesBeforeImportKey(
blink::WebCryptoKeyFormat format,
blink::WebCryptoKeyUsageMask usages) const {
return VerifyUsagesBeforeImportAsymmetricKey(format, all_public_key_usages_,
all_private_key_usages_, usages);
}
Status RsaHashedAlgorithm::ImportKeyPkcs8(
const CryptoData& key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::ScopedEVP_PKEY private_key;
Status status =
ImportUnverifiedPkeyFromPkcs8(key_data, EVP_PKEY_RSA, &private_key);
if (status.IsError())
return status;
// Verify the parameters of the key.
crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(private_key.get()));
if (!rsa.get())
return Status::ErrorUnexpected();
if (!RSA_check_key(rsa.get()))
return Status::DataError();
// TODO(eroman): Validate the algorithm OID against the webcrypto provided
// hash. http://crbug.com/389400
return CreateWebCryptoRsaPrivateKey(private_key.Pass(), algorithm.id(),
algorithm.rsaHashedImportParams()->hash(),
extractable, usages, key);
}
Status RsaHashedAlgorithm::ImportKeySpki(
const CryptoData& key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::ScopedEVP_PKEY public_key;
Status status =
ImportUnverifiedPkeyFromSpki(key_data, EVP_PKEY_RSA, &public_key);
if (status.IsError())
return status;
// TODO(eroman): Validate the algorithm OID against the webcrypto provided
// hash. http://crbug.com/389400
return CreateWebCryptoRsaPublicKey(public_key.Pass(), algorithm.id(),
algorithm.rsaHashedImportParams()->hash(),
extractable, usages, key);
}
Status RsaHashedAlgorithm::ImportKeyJwk(
const CryptoData& key_data,
const blink::WebCryptoAlgorithm& algorithm,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
blink::WebCryptoKey* key) const {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
const char* jwk_algorithm =
GetJwkAlgorithm(algorithm.rsaHashedImportParams()->hash().id());
if (!jwk_algorithm)
return Status::ErrorUnexpected();
JwkRsaInfo jwk;
Status status =
ReadRsaKeyJwk(key_data, jwk_algorithm, extractable, usages, &jwk);
if (status.IsError())
return status;
// Once the key type is known, verify the usages.
status = CheckKeyCreationUsages(
jwk.is_private_key ? all_private_key_usages_ : all_public_key_usages_,
usages, !jwk.is_private_key);
if (status.IsError())
return status;
return jwk.is_private_key
? ImportRsaPrivateKey(algorithm, extractable, usages, jwk, key)
: ImportRsaPublicKey(algorithm, extractable, usages,
CryptoData(jwk.n), CryptoData(jwk.e), key);
}
Status RsaHashedAlgorithm::ExportKeyPkcs8(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.type() != blink::WebCryptoKeyTypePrivate)
return Status::ErrorUnexpectedKeyType();
*buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
return Status::Success();
}
Status RsaHashedAlgorithm::ExportKeySpki(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
if (key.type() != blink::WebCryptoKeyTypePublic)
return Status::ErrorUnexpectedKeyType();
*buffer = AsymKeyOpenSsl::Cast(key)->serialized_key_data();
return Status::Success();
}
Status RsaHashedAlgorithm::ExportKeyJwk(const blink::WebCryptoKey& key,
std::vector<uint8_t>* buffer) const {
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
EVP_PKEY* pkey = AsymKeyOpenSsl::Cast(key)->key();
crypto::ScopedRSA rsa(EVP_PKEY_get1_RSA(pkey));
if (!rsa.get())
return Status::ErrorUnexpected();
const char* jwk_algorithm =
GetJwkAlgorithm(key.algorithm().rsaHashedParams()->hash().id());
if (!jwk_algorithm)
return Status::ErrorUnexpected();
switch (key.type()) {
case blink::WebCryptoKeyTypePublic:
WriteRsaPublicKeyJwk(CryptoData(BIGNUMToVector(rsa->n)),
CryptoData(BIGNUMToVector(rsa->e)), jwk_algorithm,
key.extractable(), key.usages(), buffer);
return Status::Success();
case blink::WebCryptoKeyTypePrivate:
WriteRsaPrivateKeyJwk(CryptoData(BIGNUMToVector(rsa->n)),
CryptoData(BIGNUMToVector(rsa->e)),
CryptoData(BIGNUMToVector(rsa->d)),
CryptoData(BIGNUMToVector(rsa->p)),
CryptoData(BIGNUMToVector(rsa->q)),
CryptoData(BIGNUMToVector(rsa->dmp1)),
CryptoData(BIGNUMToVector(rsa->dmq1)),
CryptoData(BIGNUMToVector(rsa->iqmp)),
jwk_algorithm, key.extractable(), key.usages(),
buffer);
return Status::Success();
default:
return Status::ErrorUnexpected();
}
}
Status RsaHashedAlgorithm::SerializeKeyForClone(
const blink::WebCryptoKey& key,
blink::WebVector<uint8_t>* key_data) const {
key_data->assign(AsymKeyOpenSsl::Cast(key)->serialized_key_data());
return Status::Success();
}
// TODO(eroman): Defer import to the crypto thread. http://crbug.com/430763
Status RsaHashedAlgorithm::DeserializeKeyForClone(
const blink::WebCryptoKeyAlgorithm& algorithm,
blink::WebCryptoKeyType type,
bool extractable,
blink::WebCryptoKeyUsageMask usages,
const CryptoData& key_data,
blink::WebCryptoKey* key) const {
blink::WebCryptoAlgorithm import_algorithm = CreateRsaHashedImportAlgorithm(
algorithm.id(), algorithm.rsaHashedParams()->hash().id());
Status status;
switch (type) {
case blink::WebCryptoKeyTypePublic:
status =
ImportKeySpki(key_data, import_algorithm, extractable, usages, key);
break;
case blink::WebCryptoKeyTypePrivate:
status =
ImportKeyPkcs8(key_data, import_algorithm, extractable, usages, key);
break;
default:
return Status::ErrorUnexpected();
}
// There is some duplicated information in the serialized format used by
// structured clone (since the KeyAlgorithm is serialized separately from the
// key data). Use this extra information to further validate what was
// deserialized from the key data.
if (algorithm.id() != key->algorithm().id())
return Status::ErrorUnexpected();
if (key->type() != type)
return Status::ErrorUnexpected();
if (algorithm.rsaHashedParams()->modulusLengthBits() !=
key->algorithm().rsaHashedParams()->modulusLengthBits()) {
return Status::ErrorUnexpected();
}
if (algorithm.rsaHashedParams()->publicExponent().size() !=
key->algorithm().rsaHashedParams()->publicExponent().size() ||
0 !=
memcmp(algorithm.rsaHashedParams()->publicExponent().data(),
key->algorithm().rsaHashedParams()->publicExponent().data(),
key->algorithm().rsaHashedParams()->publicExponent().size())) {
return Status::ErrorUnexpected();
}
return Status::Success();
}
} // namespace webcrypto
} // namespace content