/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Template taken from security/nss/lib/util/templates.c // This (or SGN_EncodeDigestInfo) would ideally be exported // by NSS and until that happens we have to keep our own copy.
MOZ_GLOBINIT const SEC_ASN1Template SGN_DigestInfoTemplate[] = {
{SEC_ASN1_SEQUENCE, 0, NULL, sizeof(SGNDigestInfo)},
{SEC_ASN1_INLINE, offsetof(SGNDigestInfo, digestAlgorithm),
SEC_ASN1_GET(SECOID_AlgorithmIDTemplate)},
{SEC_ASN1_OCTET_STRING, offsetof(SGNDigestInfo, digest)},
{
0,
}};
namespace mozilla::dom {
// Pre-defined identifiers for telemetry histograms
if (aAlgorithm.IsString()) { // If string, then treat as algorithm name
aName.Assign(aAlgorithm.GetAsString());
} else { // Coerce to algorithm and extract name
JS::Rooted<JS::Value> value(aCx,
JS::ObjectValue(*aAlgorithm.GetAsObject()));
Algorithm alg;
if (!alg.Init(aCx, value)) { return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
}
aName = alg.mName;
}
if (!NormalizeToken(aName, aName)) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
// Read HMAC key length from given algorithm object or // determine key length as the block size of the given hash. if (algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
RootedDictionary<HmacDerivedKeyParams> params(aCx); if (NS_FAILED(Coerce(aCx, params, aAlgorithm))) { return NS_ERROR_DOM_SYNTAX_ERR;
}
// Return the passed length, if any. if (params.mLength.WasPassed()) {
aLength.emplace(params.mLength.Value()); return NS_OK;
}
nsString hashName; if (NS_FAILED(GetAlgorithmName(aCx, params.mHash, hashName))) { return NS_ERROR_DOM_SYNTAX_ERR;
}
// Return the given hash algorithm's block size as the key length.
size_t blockSize = MapHashAlgorithmNameToBlockSize(hashName); if (blockSize == 0) { return NS_ERROR_DOM_SYNTAX_ERR;
}
// Fail if an error was set during the constructor
MAYBE_EARLY_FAIL(mEarlyRv)
// Perform pre-NSS operations, and fail if they fail
mEarlyRv = BeforeCrypto();
MAYBE_EARLY_FAIL(mEarlyRv)
// Skip dispatch if we're already done. Otherwise launch a CryptoTask if (mEarlyComplete) {
CallCallback(mEarlyRv); return;
}
// Store calling thread
mOriginalEventTarget = GetCurrentSerialEventTarget();
// If we are running on a worker thread we must hold the worker // alive while we work on the thread pool. Otherwise the worker // private may get torn down before we dispatch back to complete // the transaction. if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
NS_IMETHODIMP
WebCryptoTask::Run() { // Run heavy crypto operations on the thread pool, off the original thread. if (!IsOnOriginalThread()) {
mRv = CalculateResult();
// Back to the original thread, i.e. continue below.
mOriginalEventTarget->Dispatch(this, NS_DISPATCH_NORMAL); return NS_OK;
}
// We're now back on the calling thread.
CallCallback(mRv);
// Stop holding the worker thread alive now that the async work has // been completed.
mWorkerRef = nullptr;
if (aRv == NS_ERROR_DOM_TYPE_MISMATCH_ERR) {
mResultPromise->MaybeRejectWithTypeError( "The operation could not be performed.");
} else { // Blindly convert nsresult to DOMException // Individual tasks must ensure they pass the right values
mResultPromise->MaybeReject(aRv);
} // Manually release mResultPromise while we're on the main thread
mResultPromise = nullptr;
mWorkerRef = nullptr;
Cleanup();
}
// Manually release mResultPromise while we're on the main thread
mResultPromise = nullptr;
Cleanup();
}
// Some generic utility classes
class FailureTask : public WebCryptoTask { public: explicit FailureTask(nsresult aRv) { mEarlyRv = aRv; }
};
class ReturnArrayBufferViewTask : public WebCryptoTask { protected:
CryptoBuffer mResult;
private: // Returns mResult as an ArrayBufferView, or an error virtualvoid Resolve() override {
TypedArrayCreator<ArrayBuffer> ret(mResult);
mResultPromise->MaybeResolve(ret);
}
};
if (!mSymKey.Assign(aKey.GetSymKey())) {
mEarlyRv = NS_ERROR_OUT_OF_MEMORY; return;
}
// Check that we got a reasonable key if ((mSymKey.Length() != 16) && (mSymKey.Length() != 24) &&
(mSymKey.Length() != 32)) {
mEarlyRv = NS_ERROR_DOM_DATA_ERR; return;
}
// Cache parameters depending on the specific algorithm
TelemetryAlgorithm telemetryAlg; if (algName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC)) {
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_AES_CBC);
// Check whether the integer addition would overflow. if (std::numeric_limits<CryptoBuffer::size_type>::max() - 16 <
mData.Length()) { return NS_ERROR_DOM_DATA_ERR;
}
// Initialize the output buffer (enough space for padding / a full tag) if (!mResult.SetLength(mData.Length() + 16, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR;
}
// This class looks like an encrypt/decrypt task, like AesTask, // but it is only exposed to wrapKey/unwrapKey, not encrypt/decrypt class AesKwTask : public ReturnArrayBufferViewTask, public DeferredData { public:
AesKwTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, bool aEncrypt)
: mMechanism(CKM_NSS_AES_KEY_WRAP), mEncrypt(aEncrypt) {
Init(aCx, aAlgorithm, aKey, aEncrypt);
}
// Import the data to a SECItem
SECItem dataItem = {siBuffer, nullptr, 0};
ATTEMPT_BUFFER_TO_SECITEM(arena.get(), &dataItem, mData);
// Parameters for the fake keys
CK_MECHANISM_TYPE fakeMechanism = CKM_SHA_1_HMAC;
CK_ATTRIBUTE_TYPE fakeOperation = CKA_SIGN;
if (mEncrypt) { // Import the data into a fake PK11SymKey structure
UniquePK11SymKey keyToWrap(
PK11_ImportSymKey(slot.get(), fakeMechanism, PK11_OriginUnwrap,
fakeOperation, &dataItem, nullptr)); if (!keyToWrap) { return NS_ERROR_DOM_OPERATION_ERR;
}
// Encrypt and return the wrapped key // AES-KW encryption results in a wrapped key 64 bits longer if (!mResult.SetLength(mData.Length() + 8, fallible)) { return NS_ERROR_DOM_OPERATION_ERR;
}
SECItem resultItem = {siBuffer, mResult.Elements(),
(unsignedint)mResult.Length()};
rv = MapSECStatus(PK11_WrapSymKey(mMechanism, nullptr, symKey.get(),
keyToWrap.get(), &resultItem));
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
} else { // Decrypt the ciphertext into a temporary PK11SymKey // Unwrapped key should be 64 bits shorter int keySize = mData.Length() - 8;
UniquePK11SymKey unwrappedKey(
PK11_UnwrapSymKey(symKey.get(), mMechanism, nullptr, &dataItem,
fakeMechanism, fakeOperation, keySize)); if (!unwrappedKey) { return NS_ERROR_DOM_OPERATION_ERR;
}
// Export the key to get the cleartext
rv = MapSECStatus(PK11_ExtractKeyValue(unwrappedKey.get())); if (NS_FAILED(rv)) { return NS_ERROR_DOM_UNKNOWN_ERR;
}
ATTEMPT_BUFFER_ASSIGN(mResult, PK11_GetKeyData(unwrappedKey.get()));
}
return rv;
}
};
class RsaOaepTask : public ReturnArrayBufferViewTask, public DeferredData { public:
RsaOaepTask(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey, bool aEncrypt)
: mPrivKey(aKey.GetPrivateKey()),
mPubKey(aKey.GetPublicKey()),
mEncrypt(aEncrypt) {
Init(aCx, aAlgorithm, aKey, aEncrypt);
}
if (mEncrypt) { if (!mPubKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; return;
}
mStrength = SECKEY_PublicKeyStrength(mPubKey.get());
} else { if (!mPrivKey) {
mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR; return;
}
mStrength = PK11_GetPrivateModulusLen(mPrivKey.get());
}
// The algorithm could just be given as a string // in which case there would be no label specified. if (!aAlgorithm.IsString()) {
RootedDictionary<RsaOaepParams> params(aCx);
mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return;
}
if (params.mLabel.WasPassed()) {
ATTEMPT_BUFFER_INIT(mLabel, params.mLabel.Value());
}
} // Otherwise mLabel remains the empty octet string, as intended
if (!mDataIsSet) { return NS_ERROR_DOM_OPERATION_ERR;
}
// Ciphertext is an integer mod the modulus, so it will be // no longer than mStrength octets if (!mResult.SetLength(mStrength, fallible)) { return NS_ERROR_DOM_UNKNOWN_ERR;
}
uint32_t outLen = 0; if (mEncrypt) { // PK11_PubEncrypt() checks the plaintext's length and fails if it is too // long to encrypt, i.e. if it is longer than (k - 2hLen - 2) with 'k' // being the length in octets of the RSA modulus n and 'hLen' being the // output length in octets of the chosen hash function. // <https://tools.ietf.org/html/rfc3447#section-7.1>
rv = MapSECStatus(PK11_PubEncrypt(
mPubKey.get(), CKM_RSA_PKCS_OAEP, ¶m, mResult.Elements(), &outLen,
mResult.Length(), mData.Elements(), mData.Length(), nullptr));
} else {
rv = MapSECStatus(PK11_PrivDecrypt(
mPrivKey.get(), CKM_RSA_PKCS_OAEP, ¶m, mResult.Elements(),
&outLen, mResult.Length(), mData.Elements(), mData.Length()));
}
NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_OPERATION_ERR);
// Returns mResult as an ArrayBufferView, or an error virtualvoid Resolve() override { if (mSign) { // Return the computed MAC
TypedArrayCreator<ArrayBuffer> ret(mResult);
mResultPromise->MaybeResolve(ret);
} else { // Compare the MAC to the provided signature // No truncation allowed bool equal = (mResult.Length() == mSignature.Length()); if (equal) { int cmp = NSS_SecureMemcmp(mSignature.Elements(), mResult.Elements(),
mSignature.Length());
equal = (cmp == 0);
}
mResultPromise->MaybeResolve(equal);
}
}
};
// For ECDSA, the hash name comes from the algorithm parameter
RootedDictionary<EcdsaParams> params(aCx);
mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return;
}
mEarlyRv = GetAlgorithmName(aCx, params.mHash, hashAlgName); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; return;
}
} elseif (algName.EqualsLiteral(WEBCRYPTO_ALG_ED25519)) {
mAlgorithm = Algorithm::ED25519;
glean::webcrypto::alg.AccumulateSingleSample(TA_ED25519);
CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ED25519);
} else { // This shouldn't happen; CreateSignVerifyTask shouldn't create // one of these unless it's for the above algorithms.
MOZ_ASSERT(false);
}
// Must have a valid algorithm by now.
MOZ_ASSERT(mAlgorithm != Algorithm::UNKNOWN);
// Determine hash algorithm to use.
mOidTag = MapHashAlgorithmNameToOID(hashAlgName);
// The signature algorithm to use. enumclass Algorithm : uint8_t {
ECDSA,
RSA_PKCS1,
RSA_PSS,
ED25519,
UNKNOWN
};
Algorithm mAlgorithm;
bool AlgorithmRequiresHashing(Algorithm aAlgorithm) {
MOZ_ASSERT(aAlgorithm != Algorithm::UNKNOWN); /* Currently, only ED25519 does not require hashing.*/ switch (aAlgorithm) { case Algorithm::ED25519: returnfalse; case Algorithm::ECDSA: case Algorithm::RSA_PKCS1: case Algorithm::RSA_PSS: // Impossible case Algorithm::UNKNOWN: returntrue;
} /*Also impossible, as all the algorithm options should be managed in the
* switch. */ returntrue;
}
// Set up parameters for RSA-PSS. if (mAlgorithm == Algorithm::RSA_PSS) {
rsaPssParams.hashAlg = mHashMechanism;
rsaPssParams.mgf = mMgfMechanism;
rsaPssParams.sLen = mSaltLength;
if (AlgorithmRequiresHashing(mAlgorithm)) { // Compute digest over given data.
hash.reset(::SECITEM_AllocItem(nullptr, nullptr,
HASH_ResultLenByOidTag(mOidTag)));
// Allocate SECItem to hold the signature.
uint32_t len = mSign ? PK11_SignatureLen(mPrivKey.get()) : 0;
UniqueSECItem sig(::SECITEM_AllocItem(nullptr, nullptr, len)); if (!sig) { return NS_ERROR_DOM_OPERATION_ERR;
}
// Buffer for signature/verification input.
SECItem dataToOperateOn; if (mSign) { if (AlgorithmRequiresHashing(mAlgorithm)) {
dataToOperateOn = {siBuffer, hash->data, hash->len};
} else {
dataToOperateOn = {siBuffer, mData.Elements(), static_cast<unsignedint>(mData.Length())};
}
// This stuff pretty much always happens, so we'll do it here
mKey = new CryptoKey(aGlobal);
mKey->SetExtractable(aExtractable);
mKey->ClearUsages(); for (uint32_t i = 0; i < aKeyUsages.Length(); ++i) {
mEarlyRv = mKey->AddUsage(aKeyUsages[i]); if (NS_FAILED(mEarlyRv)) { return;
}
}
// This task only supports raw and JWK format. if (!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK) &&
!mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_RAW)) {
mEarlyRv = NS_ERROR_DOM_NOT_SUPPORTED_ERR; return;
}
// If this is an HMAC key, import the hash name if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) {
RootedDictionary<HmacImportParams> params(aCx);
mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return;
}
mEarlyRv = GetAlgorithmName(aCx, params.mHash, mHashName); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_SYNTAX_ERR; return;
}
}
}
virtual nsresult BeforeCrypto() override {
nsresult rv; // If we're doing a JWK import, import the key data if (mDataIsJwk) { if (!mJwk.mK.WasPassed()) { return NS_ERROR_DOM_DATA_ERR;
}
// Import the key material
rv = mKeyData.FromJwkBase64(mJwk.mK.Value()); if (NS_FAILED(rv)) { return NS_ERROR_DOM_DATA_ERR;
}
} // Check that we have valid key data. if (mKeyData.Length() == 0 &&
(!mAlgName.EqualsLiteral(WEBCRYPTO_ALG_PBKDF2) &&
!mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HKDF))) { return NS_ERROR_DOM_DATA_ERR;
}
// Construct an appropriate KeyAlgorithm, // and verify that usages are appropriate if (mKeyData.Length() > UINT32_MAX / 8) { return NS_ERROR_DOM_DATA_ERR;
}
uint32_t length = 8 * mKeyData.Length(); // bytes to bits if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CBC) ||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_CTR) ||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_GCM) ||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW)) { if (mKey->HasUsageOtherThan(CryptoKey::ENCRYPT | CryptoKey::DECRYPT |
CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) { return NS_ERROR_DOM_SYNTAX_ERR;
}
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_AES_KW) &&
mKey->HasUsageOtherThan(CryptoKey::WRAPKEY | CryptoKey::UNWRAPKEY)) { return NS_ERROR_DOM_SYNTAX_ERR;
}
if (mDataIsJwk && mJwk.mUse.WasPassed()) { // There is not a 'use' value consistent with PBKDF or HKDF return NS_ERROR_DOM_DATA_ERR;
};
} elseif (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_HMAC)) { if (mKey->HasUsageOtherThan(CryptoKey::SIGN | CryptoKey::VERIFY)) { return NS_ERROR_DOM_SYNTAX_ERR;
}
mKey->Algorithm().MakeHmac(length, mHashName);
if (mKey->Algorithm().Mechanism() == UNKNOWN_CK_MECHANISM) { return NS_ERROR_DOM_SYNTAX_ERR;
}
// If this is RSA with a hash, cache the hash name if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
RootedDictionary<RsaHashedImportParams> params(aCx);
mEarlyRv = Coerce(aCx, params, aAlgorithm); if (NS_FAILED(mEarlyRv)) {
mEarlyRv = NS_ERROR_DOM_DATA_ERR; return;
}
if (NS_FAILED(mKey->SetPrivateKey(privKey.get()))) { return NS_ERROR_DOM_OPERATION_ERR;
}
mKey->SetType(CryptoKey::PRIVATE);
pubKey = UniqueSECKEYPublicKey(SECKEY_ConvertToPublicKey(privKey.get())); if (!pubKey) { return NS_ERROR_DOM_UNKNOWN_ERR;
}
} else { // Invalid key format return NS_ERROR_DOM_SYNTAX_ERR;
}
if (pubKey->keyType != rsaKey) { return NS_ERROR_DOM_DATA_ERR;
} // Extract relevant information from the public key
mModulusLength = 8 * pubKey->u.rsa.modulus.len; if (!mPublicExponent.Assign(&pubKey->u.rsa.publicExponent)) { return NS_ERROR_DOM_OPERATION_ERR;
}
if (mKey->GetKeyType() == CryptoKey::PRIVATE && !mKey->HasAnyUsage()) { return NS_ERROR_DOM_SYNTAX_ERR;
}
// Set an appropriate KeyAlgorithm if (!mKey->Algorithm().MakeRsa(mAlgName, mModulusLength, mPublicExponent,
mHashName)) { return NS_ERROR_DOM_OPERATION_ERR;
}
if (mDataIsJwk && !JwkCompatible(mJwk, mKey)) { return NS_ERROR_DOM_DATA_ERR;
}
// Checking the 'crv' consistency if (mFormat.EqualsLiteral(WEBCRYPTO_KEY_FORMAT_JWK)) { // the curve stated in 'crv field'
nsString namedCurveFromCrv; if (!NormalizeToken(mJwk.mCrv.Value(), namedCurveFromCrv)) { return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.