Bug 1641178 - Add NSSCipherStrategy. r=dom-workers-and-storage-reviewers,jcj,janv

Differential Revision: https://phabricator.services.mozilla.com/D73290
This commit is contained in:
Simon Giesecke 2021-02-24 13:18:24 +00:00
parent 4852d25512
commit e09b49aa3d
6 changed files with 251 additions and 2 deletions

View File

@ -7,10 +7,10 @@
#ifndef mozilla_dom_quota_IPCStreamCipherStrategy_h
#define mozilla_dom_quota_IPCStreamCipherStrategy_h
#include "mozilla/dom/quota/DummyCipherStrategy.h"
#include "mozilla/dom/quota/NSSCipherStrategy.h"
namespace mozilla::dom::quota {
using IPCStreamCipherStrategy = DummyCipherStrategy;
using IPCStreamCipherStrategy = NSSCipherStrategy;
}
#endif

View File

@ -0,0 +1,153 @@
/* -*- 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/. */
#include "NSSCipherStrategy.h"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <type_traits>
#include <utility>
#include "mozilla/Assertions.h"
#include "mozilla/ResultExtensions.h"
// NSS includes
#include "blapit.h"
#include "pk11pub.h"
#include "pkcs11t.h"
#include "seccomon.h"
#include "secmodt.h"
namespace mozilla::dom::quota {
static_assert(sizeof(NSSCipherStrategy::KeyType) == 32);
static_assert(NSSCipherStrategy::BlockPrefixLength == 32);
static_assert(NSSCipherStrategy::BasicBlockSize == 16);
Result<NSSCipherStrategy::KeyType, nsresult> NSSCipherStrategy::GenerateKey() {
const auto slot = UniquePK11SlotInfo{PK11_GetInternalSlot()};
if (slot == nullptr) {
return Err(NS_ERROR_FAILURE);
}
const auto symKey = UniquePK11SymKey{PK11_KeyGen(
slot.get(), CKM_CHACHA20_KEY_GEN, nullptr, sizeof(KeyType), nullptr)};
if (symKey == nullptr) {
return Err(NS_ERROR_FAILURE);
}
if (PK11_ExtractKeyValue(symKey.get()) != SECSuccess) {
return Err(NS_ERROR_FAILURE);
}
// No need to free keyData as it is a buffer managed by symKey.
SECItem* keyData = PK11_GetKeyData(symKey.get());
if (keyData == nullptr) {
return Err(NS_ERROR_FAILURE);
}
KeyType key;
MOZ_RELEASE_ASSERT(keyData->len == key.size());
std::copy(keyData->data, keyData->data + key.size(), key.data());
return key;
}
nsresult NSSCipherStrategy::Init(const CipherMode aMode,
const Span<const uint8_t> aKey,
const Span<const uint8_t> aInitialIv) {
MOZ_ASSERT_IF(CipherMode::Encrypt == aMode, aInitialIv.Length() == 32);
mMode.init(aMode);
mIv.AppendElements(aInitialIv);
const auto slot = UniquePK11SlotInfo{PK11_GetInternalSlot()};
if (slot == nullptr) {
return NS_ERROR_FAILURE;
}
SECItem keyItem;
keyItem.data = const_cast<uint8_t*>(aKey.Elements());
keyItem.len = aKey.Length();
const auto symKey = UniquePK11SymKey{
PK11_ImportSymKey(slot.get(), CKM_CHACHA20_POLY1305, PK11_OriginUnwrap,
CKA_ENCRYPT, &keyItem, nullptr)};
if (symKey == nullptr) {
return NS_ERROR_FAILURE;
}
SECItem empty = {siBuffer, nullptr, 0};
auto pk11Context = UniquePK11Context{PK11_CreateContextBySymKey(
CKM_CHACHA20_POLY1305,
CKA_NSS_MESSAGE |
(CipherMode::Encrypt == aMode ? CKA_ENCRYPT : CKA_DECRYPT),
symKey.get(), &empty)};
if (pk11Context == nullptr) {
return NS_ERROR_FAILURE;
}
mPK11Context.init(std::move(pk11Context));
return NS_OK;
}
nsresult NSSCipherStrategy::Cipher(const Span<uint8_t> aIv,
const Span<const uint8_t> aIn,
const Span<uint8_t> aOut) {
if (CipherMode::Encrypt == *mMode) {
MOZ_RELEASE_ASSERT(aIv.Length() == mIv.Length());
memcpy(aIv.Elements(), mIv.Elements(), aIv.Length());
}
// XXX make tag a separate parameter
constexpr size_t tagLen = 16;
const auto tag = Span{aIv}.Last(tagLen);
// tag is const on decrypt, but returned on encrypt
const auto iv = Span{aIv}.First(12);
MOZ_ASSERT(tag.Length() + iv.Length() <= aIv.Length());
int outLen;
// aIn and aOut may not overlap resp. be the same, so we can't do this
// in-place.
const SECStatus rv = PK11_AEADOp(
mPK11Context->get(), CKG_GENERATE_COUNTER,
/* TODO use this for the discriminator */ 0, iv.Elements(), iv.Length(),
nullptr, 0, aOut.Elements(), &outLen, aOut.Length(), tag.Elements(),
tag.Length(), aIn.Elements(), aIn.Length());
if (CipherMode::Encrypt == *mMode) {
memcpy(mIv.Elements(), aIv.Elements(), aIv.Length());
}
return MapSECStatus(rv);
}
template <size_t N>
static std::array<uint8_t, N> MakeRandomData() {
std::array<uint8_t, N> res;
const auto rv = PK11_GenerateRandom(res.data(), res.size());
/// XXX Allow return of error code to handle this gracefully.
MOZ_RELEASE_ASSERT(rv == SECSuccess);
return res;
}
std::array<uint8_t, NSSCipherStrategy::BlockPrefixLength>
NSSCipherStrategy::MakeBlockPrefix() {
return MakeRandomData<BlockPrefixLength>();
}
Span<const uint8_t> NSSCipherStrategy::SerializeKey(const KeyType& aKey) {
return Span(aKey);
}
NSSCipherStrategy::KeyType NSSCipherStrategy::DeserializeKey(
const Span<const uint8_t>& aSerializedKey) {
KeyType res;
MOZ_ASSERT(res.size() == aSerializedKey.size());
std::copy(aSerializedKey.cbegin(), aSerializedKey.cend(), res.begin());
return res;
}
} // namespace mozilla::dom::quota

View File

@ -0,0 +1,56 @@
/* -*- 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/. */
#ifndef mozilla_dom_quota_NSSCipherStrategy_h
#define mozilla_dom_quota_NSSCipherStrategy_h
#include "CipherStrategy.h"
#include <cstddef>
#include <cstdint>
#include "mozilla/InitializedOnce.h"
#include "mozilla/Result.h"
#include "mozilla/Span.h"
#include "ErrorList.h"
#include "nsTArray.h"
#include "ScopedNSSTypes.h"
#include <array>
namespace mozilla::dom::quota {
struct NSSCipherStrategy {
// Use numeric literals here to avoid having to include NSS headers here.
// static_assert's in the cpp file check their consistency.
using KeyType = std::array<uint8_t, 32>;
static constexpr size_t BlockPrefixLength = 32;
static constexpr size_t BasicBlockSize = 16;
static Result<KeyType, nsresult> GenerateKey();
nsresult Init(CipherMode aCipherMode, Span<const uint8_t> aKey,
Span<const uint8_t> aInitialIv = Span<const uint8_t>{});
nsresult Cipher(Span<uint8_t> aIv, Span<const uint8_t> aIn,
Span<uint8_t> aOut);
static std::array<uint8_t, BlockPrefixLength> MakeBlockPrefix();
static Span<const uint8_t> SerializeKey(const KeyType& aKey);
static KeyType DeserializeKey(const Span<const uint8_t>& aSerializedKey);
private:
// XXX Remove EarlyDestructible, remove moving of the CipherStrategy.
LazyInitializedOnceEarlyDestructible<const CipherMode> mMode;
LazyInitializedOnceEarlyDestructible<const UniquePK11Context> mPK11Context;
nsTArray<uint8_t> mIv;
};
} // namespace mozilla::dom::quota
#endif

View File

@ -40,6 +40,7 @@ EXPORTS.mozilla.dom.quota += [
"InitializationTypes.h",
"IPCStreamCipherStrategy.h",
"MemoryOutputStream.h",
"NSSCipherStrategy.h",
"OriginMetadata.h",
"OriginScope.h",
"PersistenceType.h",
@ -66,6 +67,7 @@ UNIFIED_SOURCES += [
"FileStreams.cpp",
"MemoryOutputStream.cpp",
"nsIndexedDBProtocolHandler.cpp",
"NSSCipherStrategy.cpp",
"PersistenceType.cpp",
"QuotaCommon.cpp",
"QuotaManagerService.cpp",

View File

@ -22,6 +22,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/NotNull.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Scoped.h"
#include "mozilla/Span.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/dom/SafeRefPtr.h"
@ -30,6 +31,7 @@
#include "mozilla/dom/quota/EncryptedBlock.h"
#include "mozilla/dom/quota/EncryptingOutputStream_impl.h"
#include "mozilla/dom/quota/MemoryOutputStream.h"
#include "mozilla/dom/quota/NSSCipherStrategy.h"
#include "mozilla/fallible.h"
#include "nsCOMPtr.h"
#include "nsError.h"
@ -44,6 +46,7 @@
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nscore.h"
#include "nss.h"
namespace mozilla::dom::quota {
@ -221,11 +224,32 @@ NS_IMETHODIMP ArrayBufferInputStream::Clone(nsIInputStream** _retval) {
}
} // namespace mozilla::dom::quota
namespace mozilla {
MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedNSSContext, NSSInitContext,
NSS_ShutdownContext);
} // namespace mozilla
using namespace mozilla;
using namespace mozilla::dom::quota;
class DOM_Quota_EncryptedStream : public ::testing::Test {
public:
static void SetUpTestCase() {
// Do this only once, do not tear it down per test case.
if (!sNssContext) {
sNssContext =
NSS_InitContext("", "", "", "", nullptr,
NSS_INIT_READONLY | NSS_INIT_NOCERTDB |
NSS_INIT_NOMODDB | NSS_INIT_FORCEOPEN |
NSS_INIT_OPTIMIZESPACE | NSS_INIT_NOROOTINIT);
}
}
static void TearDownTestCase() { sNssContext = nullptr; }
private:
inline static ScopedNSSContext sNssContext = ScopedNSSContext{};
};
enum struct FlushMode { AfterEachChunk, Never };
@ -422,6 +446,19 @@ static RefPtr<dom::quota::MemoryOutputStream> DoRoundtripTest(
return baseOutputStream;
}
TEST_P(ParametrizedCryptTest, NSSCipherStrategy) {
using CipherStrategy = NSSCipherStrategy;
const TestParams& testParams = GetParam();
auto keyOrErr = CipherStrategy::GenerateKey();
ASSERT_FALSE(keyOrErr.isErr());
DoRoundtripTest<CipherStrategy>(
testParams.DataSize(), testParams.EffectiveWriteChunkSize(),
testParams.EffectiveReadChunkSize(), testParams.BlockSize(),
keyOrErr.unwrap(), testParams.FlushMode());
}
TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) {
using CipherStrategy = DummyCipherStrategy;
const TestParams& testParams = GetParam();

View File

@ -280,6 +280,7 @@ NSSUTIL_Quote
#ifdef XP_WIN
_NSSUTIL_UTF8ToWide
#endif
PK11_AEADOp
PK11_AlgtagToMechanism
PK11_Authenticate
PK11_ChangePW