blob: a559c1773415d1c83e55967ed21660e75df6e6cb [file] [log] [blame]
// Copyright 2020 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/optional.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys_service.h"
#include <cert.h>
#include <certdb.h>
#include <cryptohi.h>
#include <keyhi.h>
#include <pk11pub.h>
#include <pkcs11t.h>
#include <secder.h>
#include <secerr.h>
#include <stddef.h>
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_helpers.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chromeos/net/client_cert_store_chromeos.h"
#include "chrome/browser/chromeos/platform_keys/platform_keys.h"
#include "chrome/browser/extensions/api/enterprise_platform_keys/enterprise_platform_keys_api.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_key_util.h"
#include "crypto/openssl_util.h"
#include "crypto/scoped_nss_types.h"
#include "net/base/net_errors.h"
#include "net/cert/asn1_util.h"
#include "net/cert/cert_database.h"
#include "net/cert/nss_cert_database.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/cert/x509_util_nss.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/cros_system_api/constants/pkcs11_custom_attributes.h"
using content::BrowserContext;
using content::BrowserThread;
namespace {
// The current maximal RSA modulus length that ChromeOS's TPM supports for key
// generation.
const unsigned int kMaxRSAModulusLengthBits = 2048;
} // namespace
namespace chromeos {
namespace platform_keys {
namespace {
using ServiceWeakPtr = base::WeakPtr<PlatformKeysServiceImpl>;
// Base class to store state that is common to all NSS database operations and
// to provide convenience methods to call back.
// Keeps track of the originating task runner.
class NSSOperationState {
public:
explicit NSSOperationState(ServiceWeakPtr weak_ptr)
: service_weak_ptr_(weak_ptr),
origin_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
virtual ~NSSOperationState() = default;
// Called if an error occurred during the execution of the NSS operation
// described by this object.
virtual void OnError(const base::Location& from, Status status) = 0;
static void RunCallback(base::OnceClosure callback, ServiceWeakPtr weak_ptr) {
if (weak_ptr) {
std::move(callback).Run();
}
}
crypto::ScopedPK11Slot slot_;
// Weak pointer to the PlatformKeysServiceImpl that created this state. Used
// to check if the callback should be still called.
ServiceWeakPtr service_weak_ptr_;
// The task runner on which the NSS operation was called. Any reply must be
// posted to this runner.
scoped_refptr<base::SingleThreadTaskRunner> origin_task_runner_;
private:
DISALLOW_COPY_AND_ASSIGN(NSSOperationState);
};
using GetCertDBCallback =
base::OnceCallback<void(net::NSSCertDatabase* cert_db)>;
// Called on the UI thread with certificate database.
void DidGetCertDbOnUiThread(base::Optional<TokenId> token_id,
GetCertDBCallback callback,
NSSOperationState* state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!cert_db) {
LOG(ERROR) << "Couldn't get NSSCertDatabase.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
if (token_id) {
switch (token_id.value()) {
case TokenId::kUser:
state->slot_ = cert_db->GetPrivateSlot();
break;
case TokenId::kSystem:
state->slot_ = cert_db->GetSystemSlot();
break;
}
if (!state->slot_) {
LOG(ERROR) << "Slot for token id '" << static_cast<int>(token_id.value())
<< "' not available.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
}
// Sets |slot_| of |state| accordingly and calls |callback| on the IO thread
// if the database was successfully retrieved.
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), cert_db));
}
// Asynchronously fetches the NSSCertDatabase using |delegate| and, if
// |token_id| is not empty, the slot for |token_id|. Stores the slot in |state|
// and passes the database to |callback|. Will run |callback| on the IO thread.
void GetCertDatabase(base::Optional<TokenId> token_id,
GetCertDBCallback callback,
PlatformKeysServiceImplDelegate* delegate,
NSSOperationState* state) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
delegate->GetNSSCertDatabase(base::BindOnce(&DidGetCertDbOnUiThread, token_id,
std::move(callback), state));
}
class GenerateRSAKeyState : public NSSOperationState {
public:
GenerateRSAKeyState(ServiceWeakPtr weak_ptr,
unsigned int modulus_length_bits,
GenerateKeyCallback callback)
: NSSOperationState(weak_ptr),
modulus_length_bits_(modulus_length_bits),
callback_(std::move(callback)) {}
~GenerateRSAKeyState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*public_key_spki_der=*/std::string(), status);
}
void OnSuccess(const base::Location& from,
const std::string& public_key_spki_der) {
CallBack(from, public_key_spki_der, Status::kSuccess);
}
const unsigned int modulus_length_bits_;
private:
void CallBack(const base::Location& from,
const std::string& public_key_spki_der,
Status status) {
UMA_HISTOGRAM_BOOLEAN("ChromeOS.PlatformKeysService.GenerateKey.RSA",
status == Status::kSuccess);
auto bound_callback =
base::BindOnce(std::move(callback_), public_key_spki_der, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GenerateKeyCallback callback_;
};
class GenerateECKeyState : public NSSOperationState {
public:
GenerateECKeyState(ServiceWeakPtr weak_ptr,
const std::string& named_curve,
GenerateKeyCallback callback)
: NSSOperationState(weak_ptr),
named_curve_(named_curve),
callback_(std::move(callback)) {}
~GenerateECKeyState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*public_key_spki_der=*/std::string(), status);
}
void OnSuccess(const base::Location& from,
const std::string& public_key_spki_der) {
CallBack(from, public_key_spki_der, Status::kSuccess);
}
const std::string named_curve_;
private:
void CallBack(const base::Location& from,
const std::string& public_key_spki_der,
Status status) {
UMA_HISTOGRAM_BOOLEAN("ChromeOS.PlatformKeysService.GenerateKey.EC",
status == Status::kSuccess);
auto bound_callback =
base::BindOnce(std::move(callback_), public_key_spki_der, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GenerateKeyCallback callback_;
};
class SignState : public NSSOperationState {
public:
SignState(ServiceWeakPtr weak_ptr,
const std::string& data,
const std::string& public_key_spki_der,
bool raw_pkcs1,
HashAlgorithm hash_algorithm,
const KeyType key_type,
SignCallback callback)
: NSSOperationState(weak_ptr),
data_(data),
public_key_spki_der_(public_key_spki_der),
raw_pkcs1_(raw_pkcs1),
hash_algorithm_(hash_algorithm),
key_type_(key_type),
callback_(std::move(callback)) {}
~SignState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*signature=*/std::string(), status);
}
void OnSuccess(const base::Location& from, const std::string& signature) {
CallBack(from, signature, Status::kSuccess);
}
// The data that will be signed.
const std::string data_;
// Must be the DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
// If true, |data_| will not be hashed before signing. Only PKCS#1 v1.5
// padding will be applied before signing.
// If false, |hash_algorithm_| is set to a value != NONE.
const bool raw_pkcs1_;
// Determines the hash algorithm that is used to digest |data| before signing.
// Ignored if |raw_pkcs1_| is true.
const HashAlgorithm hash_algorithm_;
// Determines the type of the key that should be used for signing. This is
// specified by the state creator.
// Note: This can be different from the type of |public_key_spki_der|. In such
// case, a runtime error should be thrown.
const KeyType key_type_;
private:
void CallBack(const base::Location& from,
const std::string& signature,
Status status) {
EmitOperationStatusToHistogram(status == Status::kSuccess);
auto bound_callback =
base::BindOnce(std::move(callback_), signature, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
void EmitOperationStatusToHistogram(bool success) const {
switch (key_type_) {
case KeyType::kRsassaPkcs1V15:
UMA_HISTOGRAM_BOOLEAN("ChromeOS.PlatformKeysService.SignKey.RSA",
success);
break;
case KeyType::kEcdsa:
UMA_HISTOGRAM_BOOLEAN("ChromeOS.PlatformKeysService.SignKey.EC",
success);
break;
}
}
// Must be called on origin thread, therefore use CallBack().
SignCallback callback_;
};
class SelectCertificatesState : public NSSOperationState {
public:
SelectCertificatesState(
ServiceWeakPtr weak_ptr,
const scoped_refptr<net::SSLCertRequestInfo>& cert_request_info,
SelectCertificatesCallback callback)
: NSSOperationState(weak_ptr),
cert_request_info_(cert_request_info),
callback_(std::move(callback)) {}
~SelectCertificatesState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, std::unique_ptr<net::CertificateList>() /* no matches */,
status);
}
void OnSuccess(const base::Location& from,
std::unique_ptr<net::CertificateList> matches) {
CallBack(from, std::move(matches), Status::kSuccess);
}
scoped_refptr<net::SSLCertRequestInfo> cert_request_info_;
std::unique_ptr<net::ClientCertStore> cert_store_;
private:
void CallBack(const base::Location& from,
std::unique_ptr<net::CertificateList> matches,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), std::move(matches), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
SelectCertificatesCallback callback_;
};
class GetCertificatesState : public NSSOperationState {
public:
GetCertificatesState(ServiceWeakPtr weak_ptr,
GetCertificatesCallback callback)
: NSSOperationState(weak_ptr), callback_(std::move(callback)) {}
~GetCertificatesState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from,
std::unique_ptr<net::CertificateList>() /* no certificates */,
status);
}
void OnSuccess(const base::Location& from,
std::unique_ptr<net::CertificateList> certs) {
CallBack(from, std::move(certs), Status::kSuccess);
}
net::ScopedCERTCertificateList certs_;
private:
void CallBack(const base::Location& from,
std::unique_ptr<net::CertificateList> certs,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), std::move(certs), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GetCertificatesCallback callback_;
};
class GetAllKeysState : public NSSOperationState {
public:
GetAllKeysState(ServiceWeakPtr weak_ptr, GetAllKeysCallback callback)
: NSSOperationState(weak_ptr), callback_(std::move(callback)) {}
~GetAllKeysState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*public_key_spki_der_list=*/std::vector<std::string>(),
status);
}
void OnSuccess(const base::Location& from,
std::vector<std::string> public_key_spki_der_list) {
CallBack(from, std::move(public_key_spki_der_list), Status::kSuccess);
}
private:
void CallBack(const base::Location& from,
std::vector<std::string> public_key_spki_der_list,
Status status) {
auto bound_callback = base::BindOnce(
std::move(callback_), std::move(public_key_spki_der_list), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GetAllKeysCallback callback_;
};
class ImportCertificateState : public NSSOperationState {
public:
ImportCertificateState(ServiceWeakPtr weak_ptr,
const scoped_refptr<net::X509Certificate>& certificate,
ImportCertificateCallback callback)
: NSSOperationState(weak_ptr),
certificate_(certificate),
callback_(std::move(callback)) {}
~ImportCertificateState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, status);
}
void OnSuccess(const base::Location& from) {
CallBack(from, Status::kSuccess);
}
scoped_refptr<net::X509Certificate> certificate_;
private:
void CallBack(const base::Location& from, Status status) {
auto bound_callback = base::BindOnce(std::move(callback_), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
ImportCertificateCallback callback_;
};
class RemoveCertificateState : public NSSOperationState {
public:
RemoveCertificateState(ServiceWeakPtr weak_ptr,
const scoped_refptr<net::X509Certificate>& certificate,
RemoveCertificateCallback callback)
: NSSOperationState(weak_ptr),
certificate_(certificate),
callback_(std::move(callback)) {}
~RemoveCertificateState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, status);
}
void OnSuccess(const base::Location& from) {
CallBack(from, Status::kSuccess);
}
scoped_refptr<net::X509Certificate> certificate_;
private:
void CallBack(const base::Location& from, Status status) {
auto bound_callback = base::BindOnce(std::move(callback_), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
RemoveCertificateCallback callback_;
};
class RemoveKeyState : public NSSOperationState {
public:
RemoveKeyState(ServiceWeakPtr weak_ptr,
const std::string& public_key_spki_der,
RemoveKeyCallback callback)
: NSSOperationState(weak_ptr),
public_key_spki_der_(public_key_spki_der),
callback_(std::move(callback)) {}
~RemoveKeyState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, status);
}
void OnSuccess(const base::Location& from) {
CallBack(from, Status::kSuccess);
}
// Must be a DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
private:
void CallBack(const base::Location& from, Status status) {
auto bound_callback = base::BindOnce(std::move(callback_), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
RemoveKeyCallback callback_;
};
class GetTokensState : public NSSOperationState {
public:
GetTokensState(ServiceWeakPtr weak_ptr, GetTokensCallback callback)
: NSSOperationState(weak_ptr), callback_(std::move(callback)) {}
~GetTokensState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, std::unique_ptr<std::vector<TokenId>>() /* no token ids */,
status);
}
void OnSuccess(const base::Location& from,
std::unique_ptr<std::vector<TokenId>> token_ids) {
CallBack(from, std::move(token_ids), Status::kSuccess);
}
private:
void CallBack(const base::Location& from,
std::unique_ptr<std::vector<TokenId>> token_ids,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), std::move(token_ids), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GetTokensCallback callback_;
};
class GetKeyLocationsState : public NSSOperationState {
public:
GetKeyLocationsState(ServiceWeakPtr weak_ptr,
const std::string& public_key_spki_der,
GetKeyLocationsCallback callback)
: NSSOperationState(weak_ptr),
public_key_spki_der_(public_key_spki_der),
callback_(std::move(callback)) {}
~GetKeyLocationsState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*token_ids=*/std::vector<TokenId>(), status);
}
void OnSuccess(const base::Location& from,
const std::vector<TokenId>& token_ids) {
CallBack(from, token_ids, Status::kSuccess);
}
// Must be a DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
private:
void CallBack(const base::Location& from,
const std::vector<TokenId>& token_ids,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), token_ids, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GetKeyLocationsCallback callback_;
};
class SetAttributeForKeyState : public NSSOperationState {
public:
SetAttributeForKeyState(ServiceWeakPtr weak_ptr,
const std::string& public_key_spki_der,
CK_ATTRIBUTE_TYPE attribute_type,
const std::string& attribute_value,
SetAttributeForKeyCallback callback)
: NSSOperationState(weak_ptr),
public_key_spki_der_(public_key_spki_der),
attribute_type_(attribute_type),
attribute_value_(attribute_value),
callback_(std::move(callback)) {}
~SetAttributeForKeyState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, status);
}
void OnSuccess(const base::Location& from) {
CallBack(from, Status::kSuccess);
}
// Must be a DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
const CK_ATTRIBUTE_TYPE attribute_type_;
const std::string attribute_value_;
private:
void CallBack(const base::Location& from, Status status) {
auto bound_callback = base::BindOnce(std::move(callback_), status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
SetAttributeForKeyCallback callback_;
};
class GetAttributeForKeyState : public NSSOperationState {
public:
GetAttributeForKeyState(ServiceWeakPtr weak_ptr,
const std::string& public_key_spki_der,
CK_ATTRIBUTE_TYPE attribute_type,
GetAttributeForKeyCallback callback)
: NSSOperationState(weak_ptr),
public_key_spki_der_(public_key_spki_der),
attribute_type_(attribute_type),
callback_(std::move(callback)) {}
~GetAttributeForKeyState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*attribute_value=*/base::nullopt, status);
}
void OnSuccess(const base::Location& from,
const base::Optional<std::string>& attribute_value) {
CallBack(from, attribute_value, Status::kSuccess);
}
// Must be a DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
const CK_ATTRIBUTE_TYPE attribute_type_;
private:
void CallBack(const base::Location& from,
const base::Optional<std::string>& attribute_value,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), attribute_value, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
GetAttributeForKeyCallback callback_;
};
class IsKeyOnTokenState : public NSSOperationState {
public:
IsKeyOnTokenState(ServiceWeakPtr weak_ptr,
const std::string& public_key_spki_der,
IsKeyOnTokenCallback callback)
: NSSOperationState(weak_ptr),
public_key_spki_der_(public_key_spki_der),
callback_(std::move(callback)) {}
~IsKeyOnTokenState() override = default;
void OnError(const base::Location& from, Status status) override {
CallBack(from, /*on_token=*/base::nullopt, status);
}
void OnSuccess(const base::Location& from, bool on_token) {
CallBack(from, on_token, Status::kSuccess);
}
// Must be a DER encoding of a SubjectPublicKeyInfo.
const std::string public_key_spki_der_;
private:
void CallBack(const base::Location& from,
base::Optional<bool> on_token,
Status status) {
auto bound_callback =
base::BindOnce(std::move(callback_), on_token, status);
origin_task_runner_->PostTask(
from, base::BindOnce(&NSSOperationState::RunCallback,
std::move(bound_callback), service_weak_ptr_));
}
// Must be called on origin thread, therefore use CallBack().
IsKeyOnTokenCallback callback_;
};
// Returns the private key corresponding to the der-encoded
// |public_key_spki_der| if found in |slot|. If |slot| is nullptr, the
// private key will be searched in all slots.
crypto::ScopedSECKEYPrivateKey GetPrivateKey(
const std::string& public_key_spki_der,
PK11SlotInfo* slot) {
auto public_key_bytes = base::as_bytes(base::make_span(public_key_spki_der));
if (slot) {
return crypto::FindNSSKeyFromPublicKeyInfoInSlot(public_key_bytes, slot);
}
return crypto::FindNSSKeyFromPublicKeyInfo(public_key_bytes);
}
// Does the actual RSA key generation on a worker thread. Used by
// GenerateRSAKeyWithDB().
void GenerateRSAKeyOnWorkerThread(std::unique_ptr<GenerateRSAKeyState> state) {
if (!state->slot_) {
LOG(ERROR) << "No slot.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
if (!crypto::GenerateRSAKeyPairNSS(
state->slot_.get(), state->modulus_length_bits_, true /* permanent */,
&public_key, &private_key)) {
LOG(ERROR) << "Couldn't create key.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
crypto::ScopedSECItem public_key_der(
SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get()));
if (!public_key_der) {
// TODO(https://crbug.com/1044368): Remove private_key and public_key from
// storage.
LOG(ERROR) << "Couldn't export public key.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
state->OnSuccess(
FROM_HERE,
std::string(reinterpret_cast<const char*>(public_key_der->data),
public_key_der->len));
}
// Does the actual EC key generation on a worker thread. Used by
// GenerateECKeyWithDB().
void GenerateECKeyOnWorkerThread(std::unique_ptr<GenerateECKeyState> state) {
if (!state->slot_) {
LOG(ERROR) << "No slot.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
if (state->named_curve_ != "P-256") {
LOG(ERROR) << "Only P-256 named curve is supported.";
state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
}
crypto::ScopedSECKEYPublicKey public_key;
crypto::ScopedSECKEYPrivateKey private_key;
if (!crypto::GenerateECKeyPairNSS(
state->slot_.get(), SEC_OID_ANSIX962_EC_PRIME256V1,
true /* permanent */, &public_key, &private_key)) {
LOG(ERROR) << "Couldn't create key.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
crypto::ScopedSECItem public_key_der(
SECKEY_EncodeDERSubjectPublicKeyInfo(public_key.get()));
if (!public_key_der) {
// TODO(https://crbug.com/1044368): Remove private_key and public_key from
// storage.
LOG(ERROR) << "Couldn't export public key.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
state->OnSuccess(
FROM_HERE,
std::string(reinterpret_cast<const char*>(public_key_der->data),
public_key_der->len));
}
// Continues generating a RSA key with the obtained NSSCertDatabase. Used by
// GenerateRSAKey().
void GenerateRSAKeyWithDB(std::unique_ptr<GenerateRSAKeyState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
// This task interacts with the TPM, hence MayBlock().
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&GenerateRSAKeyOnWorkerThread, std::move(state)));
}
// Continues generating an EC key with the obtained NSSCertDatabase. Used by
// GenerateECKey().
void GenerateECKeyWithDB(std::unique_ptr<GenerateECKeyState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
// This task interacts with the TPM, hence MayBlock().
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&GenerateECKeyOnWorkerThread, std::move(state)));
}
// Performs "raw" PKCS1 v1.5 padding + signing by calling PK11_Sign on
// |rsa_key|.
void SignRSAPKCS1RawOnWorkerThread(std::unique_ptr<SignState> state,
crypto::ScopedSECKEYPrivateKey rsa_key) {
static_assert(
sizeof(*state->data_.data()) == sizeof(char),
"Can't reinterpret data if it's characters are not 8 bit large.");
SECItem input = {
siBuffer,
reinterpret_cast<unsigned char*>(const_cast<char*>(state->data_.data())),
state->data_.size()};
// Compute signature of hash.
int signature_len = PK11_SignatureLen(rsa_key.get());
if (signature_len <= 0) {
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
std::vector<unsigned char> signature(signature_len);
SECItem signature_output = {siBuffer, signature.data(), signature.size()};
if (PK11_Sign(rsa_key.get(), &signature_output, &input) != SECSuccess) {
LOG(ERROR) << "Couldn't sign.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
std::string signature_str(signature.begin(), signature.end());
state->OnSuccess(FROM_HERE, signature_str);
}
// Does the actual RSA signing on a worker thread.
void SignRSAOnWorkerThread(std::unique_ptr<SignState> state) {
crypto::ScopedSECKEYPrivateKey rsa_key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
// Fail if the key was not found or is of the wrong type.
if (!rsa_key || SECKEY_GetPrivateKeyType(rsa_key.get()) != rsaKey) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
if (state->raw_pkcs1_) {
SignRSAPKCS1RawOnWorkerThread(std::move(state), std::move(rsa_key));
return;
}
SECOidTag sign_alg_tag = SEC_OID_UNKNOWN;
switch (state->hash_algorithm_) {
case HASH_ALGORITHM_SHA1:
sign_alg_tag = SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION;
break;
case HASH_ALGORITHM_SHA256:
sign_alg_tag = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION;
break;
case HASH_ALGORITHM_SHA384:
sign_alg_tag = SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION;
break;
case HASH_ALGORITHM_SHA512:
sign_alg_tag = SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION;
break;
case HASH_ALGORITHM_NONE:
NOTREACHED();
break;
}
crypto::ScopedSECItem sign_result(SECITEM_AllocItem(nullptr, nullptr, 0));
if (SEC_SignData(sign_result.get(),
reinterpret_cast<const unsigned char*>(state->data_.data()),
state->data_.size(), rsa_key.get(),
sign_alg_tag) != SECSuccess) {
LOG(ERROR) << "Couldn't sign.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
std::string signature_str(sign_result->data,
sign_result->data + sign_result->len);
state->OnSuccess(FROM_HERE, signature_str);
}
// Does the actual EC Signing on a worker thread.
void SignECOnWorkerThread(std::unique_ptr<SignState> state) {
crypto::ScopedSECKEYPrivateKey ec_key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
// Fail if the key was not found or is of the wrong type.
if (!ec_key || SECKEY_GetPrivateKeyType(ec_key.get()) != ecKey) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
DCHECK(!state->raw_pkcs1_);
// Only SHA-256 algorithm is supported for ECDSA.
if (state->hash_algorithm_ != HASH_ALGORITHM_SHA256) {
state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
return;
}
std::string signature_str;
SECOidTag sign_alg_tag = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE;
crypto::ScopedSECItem sign_result(SECITEM_AllocItem(nullptr, nullptr, 0));
if (SEC_SignData(sign_result.get(),
reinterpret_cast<const unsigned char*>(state->data_.data()),
state->data_.size(), ec_key.get(),
sign_alg_tag) != SECSuccess) {
LOG(ERROR) << "Couldn't sign using elliptic curve key.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
int signature_len = PK11_SignatureLen(ec_key.get());
crypto::ScopedSECItem web_crypto_signature(
DSAU_DecodeDerSigToLen(sign_result.get(), signature_len));
if (!web_crypto_signature || web_crypto_signature->len == 0) {
LOG(ERROR) << "Couldn't convert signature to WebCrypto format.";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
signature_str.assign(web_crypto_signature->data,
web_crypto_signature->data + web_crypto_signature->len);
state->OnSuccess(FROM_HERE, signature_str);
}
// Decides which signing algorithm will be used. Used by SignWithDB().
void SignOnWorkerThread(std::unique_ptr<SignState> state) {
crypto::ScopedSECKEYPrivateKey key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
if (!key) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
switch (state->key_type_) {
case KeyType::kRsassaPkcs1V15:
SignRSAOnWorkerThread(std::move(state));
break;
case KeyType::kEcdsa:
SignECOnWorkerThread(std::move(state));
break;
}
}
// Continues signing with the obtained NSSCertDatabase. Used by Sign().
void SignWithDB(std::unique_ptr<SignState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Only the slot and not the NSSCertDatabase is required. Ignore |cert_db|.
// This task interacts with the TPM, hence MayBlock().
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&SignOnWorkerThread, std::move(state)));
}
// Called when ClientCertStoreChromeOS::GetClientCerts is done. Builds the list
// of net::CertificateList and calls back. Used by SelectCertificates().
void DidSelectCertificates(std::unique_ptr<SelectCertificatesState> state,
net::ClientCertIdentityList identities) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Convert the ClientCertIdentityList to a CertificateList since returning
// ClientCertIdentities would require changing the platformKeys extension
// api. This assumes that the necessary keys can be found later with
// crypto::FindNSSKeyFromPublicKeyInfo.
auto certs = std::make_unique<net::CertificateList>();
for (const std::unique_ptr<net::ClientCertIdentity>& identity : identities)
certs->push_back(identity->certificate());
// DidSelectCertificates() may be called synchronously, so run the callback on
// a separate event loop iteration to avoid potential reentrancy bugs.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&SelectCertificatesState::OnSuccess,
std::move(state), FROM_HERE, std::move(certs)));
}
// Filters the obtained certificates on a worker thread. Used by
// DidGetCertificates().
void FilterCertificatesOnWorkerThread(
std::unique_ptr<GetCertificatesState> state) {
std::unique_ptr<net::CertificateList> client_certs(new net::CertificateList);
for (net::ScopedCERTCertificateList::const_iterator it =
state->certs_.begin();
it != state->certs_.end(); ++it) {
CERTCertificate* cert_handle = it->get();
crypto::ScopedPK11Slot cert_slot(PK11_KeyForCertExists(cert_handle,
nullptr, // keyPtr
nullptr)); // wincx
// Keep only user certificates, i.e. certs for which the private key is
// present and stored in the queried slot.
if (cert_slot != state->slot_)
continue;
// Allow UTF-8 inside PrintableStrings in client certificates. See
// crbug.com/770323 and crbug.com/788655.
net::X509Certificate::UnsafeCreateOptions options;
options.printable_string_is_utf8 = true;
scoped_refptr<net::X509Certificate> cert =
net::x509_util::CreateX509CertificateFromCERTCertificate(cert_handle,
{}, options);
if (!cert)
continue;
client_certs->push_back(std::move(cert));
}
state->OnSuccess(FROM_HERE, std::move(client_certs));
}
// Passes the obtained certificates to the worker thread for filtering. Used by
// GetCertificatesWithDB().
void DidGetCertificates(std::unique_ptr<GetCertificatesState> state,
net::ScopedCERTCertificateList all_certs) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
state->certs_ = std::move(all_certs);
// This task interacts with the TPM, hence MayBlock().
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&FilterCertificatesOnWorkerThread, std::move(state)));
}
// Continues getting certificates with the obtained NSSCertDatabase. Used by
// GetCertificates().
void GetCertificatesWithDB(std::unique_ptr<GetCertificatesState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// Get the pointer to slot before base::Passed releases |state|.
PK11SlotInfo* slot = state->slot_.get();
cert_db->ListCertsInSlot(
base::BindOnce(&DidGetCertificates, std::move(state)), slot);
}
// Returns true if |public_key| is relevant as a "platform key" that should be
// visible to chrome extensions / subsystems.
bool ShouldIncludePublicKey(SECKEYPublicKey* public_key) {
crypto::ScopedSECItem cka_id(SECITEM_AllocItem(/*arena=*/nullptr,
/*item=*/nullptr,
/*len=*/0));
if (PK11_ReadRawAttribute(
/*objType=*/PK11_TypePubKey, public_key, CKA_ID, cka_id.get()) !=
SECSuccess) {
return false;
}
base::StringPiece cka_id_str(reinterpret_cast<char*>(cka_id->data),
cka_id->len);
// Only keys generated/stored by extensions/Chrome should be visible to
// extensions. Oemcrypto stores its key in the TPM, but that should not
// be exposed. Look at exposing additional attributes or changing the slot
// that Oemcrypto stores keys, so that it can be done based on properties
// of the key. See https://crbug/com/1136396
if (cka_id_str == "arc-oemcrypto") {
VLOG(0) << "Filtered out arc-oemcrypto public key.";
return false;
}
return true;
}
// Does the actual retrieval of the SubjectPublicKeyInfo string on a worker
// thread. Used by GetAllKeysWithDb().
void GetAllKeysOnWorkerThread(std::unique_ptr<GetAllKeysState> state) {
DCHECK(state->slot_.get());
std::vector<std::string> public_key_spki_der_list;
// This assumes that all public keys on the slots are actually key pairs with
// private + public keys, so it's sufficient to get the public keys (and also
// not necessary to check that a private key for that public key really
// exists).
crypto::ScopedSECKEYPublicKeyList public_keys(
PK11_ListPublicKeysInSlot(state->slot_.get(), /*nickname=*/nullptr));
if (!public_keys) {
state->OnSuccess(FROM_HERE, std::move(public_key_spki_der_list));
return;
}
for (SECKEYPublicKeyListNode* node = PUBKEY_LIST_HEAD(public_keys);
!PUBKEY_LIST_END(node, public_keys); node = PUBKEY_LIST_NEXT(node)) {
if (!ShouldIncludePublicKey(node->key)) {
continue;
}
crypto::ScopedSECItem subject_public_key_info(
SECKEY_EncodeDERSubjectPublicKeyInfo(node->key));
if (!subject_public_key_info) {
LOG(WARNING) << "Could not encode subject public key info.";
continue;
}
if (subject_public_key_info->len > 0) {
public_key_spki_der_list.push_back(std::string(
subject_public_key_info->data,
subject_public_key_info->data + subject_public_key_info->len));
}
}
state->OnSuccess(FROM_HERE, std::move(public_key_spki_der_list));
}
// Continues the retrieval of the SubjectPublicKeyInfo list with |cert_db|.
// Used by GetAllKeys().
void GetAllKeysWithDb(std::unique_ptr<GetAllKeysState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&GetAllKeysOnWorkerThread, std::move(state)));
}
// Does the actual certificate importing on the IO thread. Used by
// ImportCertificate().
void ImportCertificateWithDB(std::unique_ptr<ImportCertificateState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!state->certificate_) {
state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
return;
}
if (state->certificate_->HasExpired()) {
state->OnError(FROM_HERE, Status::kNetErrorCertificateDateInvalid);
return;
}
net::ScopedCERTCertificate nss_cert =
net::x509_util::CreateCERTCertificateFromX509Certificate(
state->certificate_.get());
if (!nss_cert) {
state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
return;
}
// Check that the private key is in the correct slot.
crypto::ScopedPK11Slot slot(
PK11_KeyForCertExists(nss_cert.get(), nullptr, nullptr));
if (slot.get() != state->slot_.get()) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
const net::Error import_status =
static_cast<net::Error>(cert_db->ImportUserCert(nss_cert.get()));
if (import_status != net::OK) {
LOG(ERROR) << "Could not import certificate: "
<< net::ErrorToString(import_status);
state->OnError(FROM_HERE, Status::kNetErrorAddUserCertFailed);
return;
}
state->OnSuccess(FROM_HERE);
}
// Called on IO thread after the certificate removal is finished.
void DidRemoveCertificate(std::unique_ptr<RemoveCertificateState> state,
bool certificate_found,
bool success) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// CertificateNotFound error has precedence over an internal error.
if (!certificate_found) {
state->OnError(FROM_HERE, Status::kErrorCertificateNotFound);
return;
}
if (!success) {
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
state->OnSuccess(FROM_HERE);
}
// Does the actual certificate removal on the IO thread. Used by
// RemoveCertificate().
void RemoveCertificateWithDB(std::unique_ptr<RemoveCertificateState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
net::ScopedCERTCertificate nss_cert =
net::x509_util::CreateCERTCertificateFromX509Certificate(
state->certificate_.get());
if (!nss_cert) {
state->OnError(FROM_HERE, Status::kNetErrorCertificateInvalid);
return;
}
bool certificate_found = nss_cert->isperm;
cert_db->DeleteCertAndKeyAsync(
std::move(nss_cert),
base::BindOnce(&DidRemoveCertificate, base::Passed(&state),
certificate_found));
}
// Does the actual key pair removal on a worker thread. Used by
// RemoveKeyWithDb().
void RemoveKeyOnWorkerThread(std::unique_ptr<RemoveKeyState> state) {
DCHECK(state->slot_.get());
crypto::ScopedSECKEYPrivateKey private_key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
if (!private_key) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
crypto::ScopedSECKEYPublicKey public_key(
SECKEY_ConvertToPublicKey(private_key.get()));
// PK11_DeleteTokenPrivateKey function frees the privKey structure
// unconditionally, and thus releasing the ownership of the passed private
// key.
// |force| is set to false so as not to delete the key if there are any
// matching certificates.
if (PK11_DeleteTokenPrivateKey(/*privKey=*/private_key.release(),
/*force=*/false) != SECSuccess) {
LOG(ERROR) << "Cannot delete private key";
state->OnError(FROM_HERE, Status::kErrorInternal);
return;
}
// PK11_DeleteTokenPublicKey function frees the pubKey structure
// unconditionally, and thus releasing the ownership of the passed private
// key.
if (PK11_DeleteTokenPublicKey(/*pubKey=*/public_key.release()) !=
SECSuccess) {
LOG(WARNING) << "Cannot delete public key";
}
state->OnSuccess(FROM_HERE);
}
// Continues removing the key pair with the obtained |cert_db|. Called by
// RemoveKey().
void RemoveKeyWithDb(std::unique_ptr<RemoveKeyState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&RemoveKeyOnWorkerThread, std::move(state)));
}
// Does the actual work to determine which tokens are available.
void GetTokensWithDB(std::unique_ptr<GetTokensState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
auto token_ids = std::make_unique<std::vector<TokenId>>();
// The user token will be unavailable in case of no logged in user in this
// profile.
if (cert_db->GetPrivateSlot())
token_ids->push_back(TokenId::kUser);
if (cert_db->GetSystemSlot())
token_ids->push_back(TokenId::kSystem);
DCHECK(!token_ids->empty());
state->OnSuccess(FROM_HERE, std::move(token_ids));
}
// Does the actual work to determine which key is on which token.
void GetKeyLocationsWithDB(std::unique_ptr<GetKeyLocationsState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::vector<TokenId> token_ids;
const uint8_t* public_key_uint8 =
reinterpret_cast<const uint8_t*>(state->public_key_spki_der_.data());
std::vector<uint8_t> public_key_vector(
public_key_uint8, public_key_uint8 + state->public_key_spki_der_.size());
if (cert_db->GetPrivateSlot().get()) {
crypto::ScopedSECKEYPrivateKey rsa_key =
crypto::FindNSSKeyFromPublicKeyInfoInSlot(
public_key_vector, cert_db->GetPrivateSlot().get());
if (rsa_key)
token_ids.push_back(TokenId::kUser);
}
// The "system" NSSCertDatabaseChromeOS instance reuses its "system slot" as
// "public slot", but that doesn't mean it's a user-specific slot.
if (token_ids.empty() && cert_db->GetPublicSlot().get() &&
cert_db->GetPublicSlot().get() != cert_db->GetSystemSlot().get()) {
crypto::ScopedSECKEYPrivateKey rsa_key =
crypto::FindNSSKeyFromPublicKeyInfoInSlot(
public_key_vector, cert_db->GetPublicSlot().get());
if (rsa_key)
token_ids.push_back(TokenId::kUser);
}
if (cert_db->GetSystemSlot().get()) {
crypto::ScopedSECKEYPrivateKey rsa_key =
crypto::FindNSSKeyFromPublicKeyInfoInSlot(
public_key_vector, cert_db->GetSystemSlot().get());
if (rsa_key)
token_ids.push_back(TokenId::kSystem);
}
state->OnSuccess(FROM_HERE, std::move(token_ids));
}
// Translates |type| to one of the NSS softoken module's predefined key
// attributes which are used in tests.
CK_ATTRIBUTE_TYPE TranslateKeyAttributeTypeForSoftoken(KeyAttributeType type) {
switch (type) {
case KeyAttributeType::kCertificateProvisioningId:
return CKA_START_DATE;
case KeyAttributeType::kKeyPermissions:
return CKA_END_DATE;
}
}
// If |map_to_softoken_attrs| is true, translates |type| to one of the softoken
// module predefined key attributes. Otherwise, applies normal translation.
CK_ATTRIBUTE_TYPE TranslateKeyAttributeType(KeyAttributeType type,
bool map_to_softoken_attrs) {
if (map_to_softoken_attrs) {
return TranslateKeyAttributeTypeForSoftoken(type);
}
switch (type) {
case KeyAttributeType::kCertificateProvisioningId:
return pkcs11_custom_attributes::kCkaChromeOsBuiltinProvisioningProfileId;
case KeyAttributeType::kKeyPermissions:
return pkcs11_custom_attributes::kCkaChromeOsKeyPermissions;
}
}
// Does the actual attribute value setting with the obtained |cert_db|.
// Called by SetAttributeForKey().
void SetAttributeForKeyWithDb(std::unique_ptr<SetAttributeForKeyState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(state->slot_.get());
crypto::ScopedSECKEYPrivateKey private_key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
if (!private_key) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
// This SECItem will point to data owned by |state| so it is not necessary to
// use ScopedSECItem.
SECItem attribute_value;
attribute_value.data = reinterpret_cast<unsigned char*>(
const_cast<char*>(state->attribute_value_.data()));
attribute_value.len = state->attribute_value_.size();
if (PK11_WriteRawAttribute(
/*objType=*/PK11_TypePrivKey, private_key.get(),
state->attribute_type_, &attribute_value) != SECSuccess) {
state->OnError(FROM_HERE, Status::kErrorKeyAttributeSettingFailed);
return;
}
state->OnSuccess(FROM_HERE);
}
// Does the actual attribute value retrieval with the obtained |cert_db|.
// Called by GetAttributeForKey().
void GetAttributeForKeyWithDb(std::unique_ptr<GetAttributeForKeyState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(state->slot_.get());
crypto::ScopedSECKEYPrivateKey private_key =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get());
if (!private_key) {
state->OnError(FROM_HERE, Status::kErrorKeyNotFound);
return;
}
crypto::ScopedSECItem attribute_value(SECITEM_AllocItem(/*arena=*/nullptr,
/*item=*/nullptr,
/*len=*/0));
DCHECK(attribute_value.get());
if (PK11_ReadRawAttribute(
/*objType=*/PK11_TypePrivKey, private_key.get(),
state->attribute_type_, attribute_value.get()) != SECSuccess) {
// CKR_ATTRIBUTE_TYPE_INVALID is a cryptoki function return value which is
// returned by Chaps if the attribute was not set before for the key. NSS
// maps this error to SEC_ERROR_BAD_DATA. This error is captured here so as
// not to return an |error| in cases of retrieving unset key attributes and
// to return nullopt |attribute_value| instead.
int error = PORT_GetError();
if (error == SEC_ERROR_BAD_DATA) {
state->OnSuccess(FROM_HERE, /*attribute_value=*/base::nullopt);
return;
}
state->OnError(FROM_HERE, Status::kErrorKeyAttributeRetrievalFailed);
return;
}
std::string attribute_value_str;
if (attribute_value->len > 0) {
attribute_value_str.assign(attribute_value->data,
attribute_value->data + attribute_value->len);
}
state->OnSuccess(FROM_HERE, attribute_value_str);
}
void IsKeyOnTokenWithDb(std::unique_ptr<IsKeyOnTokenState> state,
net::NSSCertDatabase* cert_db) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(state->slot_.get());
bool key_on_slot =
GetPrivateKey(state->public_key_spki_der_, state->slot_.get()) != nullptr;
state->OnSuccess(FROM_HERE, key_on_slot);
}
} // namespace
void PlatformKeysServiceImpl::GenerateRSAKey(TokenId token_id,
unsigned int modulus_length_bits,
GenerateKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GenerateRSAKeyState>(
weak_factory_.GetWeakPtr(), modulus_length_bits, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
if (modulus_length_bits > kMaxRSAModulusLengthBits) {
state->OnError(FROM_HERE, Status::kErrorAlgorithmNotSupported);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
GetCertDatabase(token_id,
base::BindOnce(&GenerateRSAKeyWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::GenerateECKey(TokenId token_id,
const std::string& named_curve,
GenerateKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GenerateECKeyState>(
weak_factory_.GetWeakPtr(), named_curve, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
GetCertDatabase(token_id,
base::BindOnce(&GenerateECKeyWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SignRSAPKCS1Digest(
base::Optional<TokenId> token_id,
const std::string& data,
const std::string& public_key_spki_der,
HashAlgorithm hash_algorithm,
SignCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<SignState>(
weak_factory_.GetWeakPtr(), data, public_key_spki_der,
/*raw_pkcs1=*/false, hash_algorithm,
/*key_type=*/KeyType::kRsassaPkcs1V15, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes and we can double check that we
// use a key of the correct token.
GetCertDatabase(token_id, base::BindOnce(&SignWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SignRSAPKCS1Raw(
base::Optional<TokenId> token_id,
const std::string& data,
const std::string& public_key_spki_der,
SignCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<SignState>(
weak_factory_.GetWeakPtr(), data, public_key_spki_der,
/*raw_pkcs1=*/true, HASH_ALGORITHM_NONE,
/*key_type=*/KeyType::kRsassaPkcs1V15, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes and we can double check that we
// use a key of the correct token.
GetCertDatabase(token_id, base::BindOnce(&SignWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SignECDSADigest(
base::Optional<TokenId> token_id,
const std::string& data,
const std::string& public_key_spki_der,
HashAlgorithm hash_algorithm,
SignCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<SignState>(
weak_factory_.GetWeakPtr(), data, public_key_spki_der,
/*raw_pkcs1=*/false, hash_algorithm,
/*key_type=*/KeyType::kEcdsa, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes and we can double check that we
// use a key of the correct token.
GetCertDatabase(token_id, base::BindOnce(&SignWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SelectClientCertificates(
const std::vector<std::string>& certificate_authorities,
SelectCertificatesCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto cert_request_info = base::MakeRefCounted<net::SSLCertRequestInfo>();
// Currently we do not pass down the requested certificate type to the net
// layer, as it does not support filtering certificates by type. Rather, we
// do not constrain the certificate type here, instead the caller has to apply
// filtering afterwards.
cert_request_info->cert_authorities = certificate_authorities;
auto state = std::make_unique<SelectCertificatesState>(
weak_factory_.GetWeakPtr(), cert_request_info, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
state->cert_store_ = delegate_->CreateClientCertStore();
// Note DidSelectCertificates() may be called synchronously.
SelectCertificatesState* state_ptr = state.get();
state_ptr->cert_store_->GetClientCerts(
*state_ptr->cert_request_info_,
base::BindOnce(&DidSelectCertificates, std::move(state)));
}
std::string GetSubjectPublicKeyInfo(
const scoped_refptr<net::X509Certificate>& certificate) {
base::StringPiece spki_bytes;
if (!net::asn1::ExtractSPKIFromDERCert(
net::x509_util::CryptoBufferAsStringPiece(certificate->cert_buffer()),
&spki_bytes))
return {};
return spki_bytes.as_string();
}
// Extracts the public exponent out of an EVP_PKEY and verifies if it is equal
// to 65537 (Fermat number with n=4). This values is enforced by
// platform_keys::GetPublicKey() and platform_keys::GetPublicKeyBySpki().
// The caller of this function needs to have an OpenSSLErrStackTracer or
// otherwise clean up the error stack on failure.
bool VerifyRSAPublicExponent(EVP_PKEY* pkey) {
RSA* rsa = EVP_PKEY_get0_RSA(pkey);
if (!rsa) {
LOG(WARNING) << "Could not get RSA from PKEY.";
return false;
}
const BIGNUM* public_exponent = nullptr;
RSA_get0_key(rsa, nullptr /* out_n */, &public_exponent, nullptr /* out_d */);
if (BN_get_word(public_exponent) != 65537L) {
LOG(ERROR) << "Rejecting RSA public exponent that is unequal 65537.";
return false;
}
return true;
}
bool GetPublicKey(const scoped_refptr<net::X509Certificate>& certificate,
net::X509Certificate::PublicKeyType* key_type,
size_t* key_size_bits) {
net::X509Certificate::PublicKeyType key_type_tmp =
net::X509Certificate::kPublicKeyTypeUnknown;
size_t key_size_bits_tmp = 0;
net::X509Certificate::GetPublicKeyInfo(certificate->cert_buffer(),
&key_size_bits_tmp, &key_type_tmp);
if (key_type_tmp == net::X509Certificate::kPublicKeyTypeUnknown) {
LOG(WARNING) << "Could not extract public key of certificate.";
return false;
}
if (key_type_tmp != net::X509Certificate::kPublicKeyTypeRSA &&
key_type_tmp != net::X509Certificate::kPublicKeyTypeECDSA) {
LOG(WARNING) << "Keys of other types than RSA and EC are not supported.";
return false;
}
std::string spki = GetSubjectPublicKeyInfo(certificate);
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(spki.data()), spki.size());
bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
if (!pkey) {
LOG(WARNING) << "Could not extract public key of certificate.";
return false;
}
switch (EVP_PKEY_type(pkey->type)) {
case EVP_PKEY_RSA: {
if (!VerifyRSAPublicExponent(pkey.get())) {
return false;
}
break;
}
case EVP_PKEY_EC: {
EC_KEY* ec = EVP_PKEY_get0_EC_KEY(pkey.get());
if (!ec) {
LOG(WARNING) << "Could not get EC from PKEY.";
return false;
}
if (EC_GROUP_get_curve_name(EC_KEY_get0_group(ec)) !=
NID_X9_62_prime256v1) {
LOG(WARNING) << "Only P-256 named curve is supported.";
return false;
}
break;
}
default: {
LOG(WARNING) << "Only RSA and EC keys are supported.";
return false;
}
}
*key_type = key_type_tmp;
*key_size_bits = key_size_bits_tmp;
return true;
}
bool GetPublicKeyBySpki(const std::string& spki,
net::X509Certificate::PublicKeyType* key_type,
size_t* key_size_bits) {
net::X509Certificate::PublicKeyType key_type_tmp =
net::X509Certificate::kPublicKeyTypeUnknown;
size_t key_size_bits_tmp = 0;
crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE);
CBS cbs;
CBS_init(&cbs, reinterpret_cast<const uint8_t*>(spki.data()), spki.size());
bssl::UniquePtr<EVP_PKEY> pkey(EVP_parse_public_key(&cbs));
if (!pkey) {
LOG(WARNING) << "Could not extract public key from SPKI.";
return false;
}
int type = EVP_PKEY_type(pkey->type);
if (type == EVP_PKEY_RSA) {
key_type_tmp = net::X509Certificate::kPublicKeyTypeRSA;
} else {
LOG(WARNING) << "Keys of other type than RSA are not supported.";
return false;
}
key_size_bits_tmp = base::saturated_cast<size_t>(EVP_PKEY_bits(pkey.get()));
if (!VerifyRSAPublicExponent(pkey.get())) {
return false;
}
*key_type = key_type_tmp;
*key_size_bits = key_size_bits_tmp;
return true;
}
void PlatformKeysServiceImpl::GetCertificates(
TokenId token_id,
GetCertificatesCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GetCertificatesState>(
weak_factory_.GetWeakPtr(), std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
GetCertDatabase(token_id,
base::BindOnce(&GetCertificatesWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::GetAllKeys(TokenId token_id,
GetAllKeysCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GetAllKeysState>(weak_factory_.GetWeakPtr(),
std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
NSSOperationState* state_ptr = state.get();
GetCertDatabase(token_id,
base::BindOnce(&GetAllKeysWithDb, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::ImportCertificate(
TokenId token_id,
const scoped_refptr<net::X509Certificate>& certificate,
ImportCertificateCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<ImportCertificateState>(
weak_factory_.GetWeakPtr(), certificate, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes and we can double check that we
// use a key of the correct token.
GetCertDatabase(
token_id, base::BindOnce(&ImportCertificateWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::RemoveCertificate(
TokenId token_id,
const scoped_refptr<net::X509Certificate>& certificate,
RemoveCertificateCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<RemoveCertificateState>(
weak_factory_.GetWeakPtr(), certificate, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes.
GetCertDatabase(
token_id, base::BindOnce(&RemoveCertificateWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::RemoveKey(TokenId token_id,
const std::string& public_key_spki_der,
RemoveKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<RemoveKeyState>(
weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. But in case it's not available
// we would get more informative status codes.
GetCertDatabase(token_id,
base::BindOnce(&RemoveKeyWithDb, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::GetTokens(GetTokensCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GetTokensState>(weak_factory_.GetWeakPtr(),
std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
GetCertDatabase(/*token_id=*/base::nullopt /* don't get any specific slot */,
base::Bind(&GetTokensWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::GetKeyLocations(
const std::string& public_key_spki_der,
GetKeyLocationsCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<GetKeyLocationsState>(
weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
NSSOperationState* state_ptr = state.get();
GetCertDatabase(
/*token_id=*/base::nullopt /* don't get any specific slot */,
base::BindRepeating(&GetKeyLocationsWithDB, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SetAttributeForKey(
TokenId token_id,
const std::string& public_key_spki_der,
KeyAttributeType attribute_type,
const std::string& attribute_value,
SetAttributeForKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CK_ATTRIBUTE_TYPE ck_attribute_type = TranslateKeyAttributeType(
attribute_type,
/*map_to_softoken_attrs=*/IsSetMapToSoftokenAttrsForTesting());
auto state = std::make_unique<SetAttributeForKeyState>(
weak_factory_.GetWeakPtr(), public_key_spki_der, ck_attribute_type,
attribute_value, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. Only setting the state slot is
// required.
GetCertDatabase(
token_id, base::BindOnce(&SetAttributeForKeyWithDb, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::GetAttributeForKey(
TokenId token_id,
const std::string& public_key_spki_der,
KeyAttributeType attribute_type,
GetAttributeForKeyCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
CK_ATTRIBUTE_TYPE ck_attribute_type = TranslateKeyAttributeType(
attribute_type,
/*map_to_softoken_attrs=*/IsSetMapToSoftokenAttrsForTesting());
auto state = std::make_unique<GetAttributeForKeyState>(
weak_factory_.GetWeakPtr(), public_key_spki_der, ck_attribute_type,
std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. Only setting the state slot is
// required.
GetCertDatabase(
token_id, base::BindOnce(&GetAttributeForKeyWithDb, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::IsKeyOnToken(
TokenId token_id,
const std::string& public_key_spki_der,
IsKeyOnTokenCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
auto state = std::make_unique<IsKeyOnTokenState>(
weak_factory_.GetWeakPtr(), public_key_spki_der, std::move(callback));
if (delegate_->IsShutDown()) {
state->OnError(FROM_HERE, Status::kErrorShutDown);
return;
}
// Get the pointer to |state| before base::Passed releases |state|.
NSSOperationState* state_ptr = state.get();
// The NSSCertDatabase object is not required. Only setting the state slot is
// required.
GetCertDatabase(token_id,
base::BindOnce(&IsKeyOnTokenWithDb, base::Passed(&state)),
delegate_.get(), state_ptr);
}
void PlatformKeysServiceImpl::SetMapToSoftokenAttrsForTesting(
bool map_to_softoken_attrs_for_testing) {
map_to_softoken_attrs_for_testing_ = map_to_softoken_attrs_for_testing;
}
bool PlatformKeysServiceImpl::IsSetMapToSoftokenAttrsForTesting() {
return map_to_softoken_attrs_for_testing_;
}
} // namespace platform_keys
} // namespace chromeos