From e09b49aa3d867ac403e336330b3c690e82ddda0b Mon Sep 17 00:00:00 2001 From: Simon Giesecke Date: Wed, 24 Feb 2021 13:18:24 +0000 Subject: [PATCH] Bug 1641178 - Add NSSCipherStrategy. r=dom-workers-and-storage-reviewers,jcj,janv Differential Revision: https://phabricator.services.mozilla.com/D73290 --- dom/quota/IPCStreamCipherStrategy.h | 4 +- dom/quota/NSSCipherStrategy.cpp | 153 +++++++++++++++++++ dom/quota/NSSCipherStrategy.h | 56 +++++++ dom/quota/moz.build | 2 + dom/quota/test/gtest/TestEncryptedStream.cpp | 37 +++++ security/nss.symbols | 1 + 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 dom/quota/NSSCipherStrategy.cpp create mode 100644 dom/quota/NSSCipherStrategy.h diff --git a/dom/quota/IPCStreamCipherStrategy.h b/dom/quota/IPCStreamCipherStrategy.h index e694358b0c83..c66c644827c7 100644 --- a/dom/quota/IPCStreamCipherStrategy.h +++ b/dom/quota/IPCStreamCipherStrategy.h @@ -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 diff --git a/dom/quota/NSSCipherStrategy.cpp b/dom/quota/NSSCipherStrategy.cpp new file mode 100644 index 000000000000..bd72c80be7d9 --- /dev/null +++ b/dom/quota/NSSCipherStrategy.cpp @@ -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 +#include +#include +#include +#include +#include +#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::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 aKey, + const Span 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(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 aIv, + const Span aIn, + const Span 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 +static std::array MakeRandomData() { + std::array 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 +NSSCipherStrategy::MakeBlockPrefix() { + return MakeRandomData(); +} + +Span NSSCipherStrategy::SerializeKey(const KeyType& aKey) { + return Span(aKey); +} + +NSSCipherStrategy::KeyType NSSCipherStrategy::DeserializeKey( + const Span& aSerializedKey) { + KeyType res; + MOZ_ASSERT(res.size() == aSerializedKey.size()); + std::copy(aSerializedKey.cbegin(), aSerializedKey.cend(), res.begin()); + return res; +} + +} // namespace mozilla::dom::quota diff --git a/dom/quota/NSSCipherStrategy.h b/dom/quota/NSSCipherStrategy.h new file mode 100644 index 000000000000..31b3eb03e296 --- /dev/null +++ b/dom/quota/NSSCipherStrategy.h @@ -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 +#include +#include "mozilla/InitializedOnce.h" +#include "mozilla/Result.h" +#include "mozilla/Span.h" +#include "ErrorList.h" +#include "nsTArray.h" + +#include "ScopedNSSTypes.h" + +#include + +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; + static constexpr size_t BlockPrefixLength = 32; + static constexpr size_t BasicBlockSize = 16; + + static Result GenerateKey(); + + nsresult Init(CipherMode aCipherMode, Span aKey, + Span aInitialIv = Span{}); + + nsresult Cipher(Span aIv, Span aIn, + Span aOut); + + static std::array MakeBlockPrefix(); + + static Span SerializeKey(const KeyType& aKey); + + static KeyType DeserializeKey(const Span& aSerializedKey); + + private: + // XXX Remove EarlyDestructible, remove moving of the CipherStrategy. + LazyInitializedOnceEarlyDestructible mMode; + LazyInitializedOnceEarlyDestructible mPK11Context; + nsTArray mIv; +}; + +} // namespace mozilla::dom::quota + +#endif diff --git a/dom/quota/moz.build b/dom/quota/moz.build index 91c92cd16863..dc9e98d7af60 100644 --- a/dom/quota/moz.build +++ b/dom/quota/moz.build @@ -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", diff --git a/dom/quota/test/gtest/TestEncryptedStream.cpp b/dom/quota/test/gtest/TestEncryptedStream.cpp index 651307e9f304..625be7310570 100644 --- a/dom/quota/test/gtest/TestEncryptedStream.cpp +++ b/dom/quota/test/gtest/TestEncryptedStream.cpp @@ -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 DoRoundtripTest( return baseOutputStream; } +TEST_P(ParametrizedCryptTest, NSSCipherStrategy) { + using CipherStrategy = NSSCipherStrategy; + const TestParams& testParams = GetParam(); + + auto keyOrErr = CipherStrategy::GenerateKey(); + ASSERT_FALSE(keyOrErr.isErr()); + + DoRoundtripTest( + testParams.DataSize(), testParams.EffectiveWriteChunkSize(), + testParams.EffectiveReadChunkSize(), testParams.BlockSize(), + keyOrErr.unwrap(), testParams.FlushMode()); +} + TEST_P(ParametrizedCryptTest, DummyCipherStrategy_CheckOutput) { using CipherStrategy = DummyCipherStrategy; const TestParams& testParams = GetParam(); diff --git a/security/nss.symbols b/security/nss.symbols index e35a1bbed91a..a46d31bead17 100644 --- a/security/nss.symbols +++ b/security/nss.symbols @@ -280,6 +280,7 @@ NSSUTIL_Quote #ifdef XP_WIN _NSSUTIL_UTF8ToWide #endif +PK11_AEADOp PK11_AlgtagToMechanism PK11_Authenticate PK11_ChangePW