mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 21:01:08 +00:00
2282fb5a6a
Differential Revision: https://phabricator.services.mozilla.com/D94011
238 lines
7.1 KiB
C++
238 lines
7.1 KiB
C++
/* -*- 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_EncryptingOutputStream_impl_h
|
|
#define mozilla_dom_quota_EncryptingOutputStream_impl_h
|
|
|
|
#include "EncryptingOutputStream.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
#include "CipherStrategy.h"
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Span.h"
|
|
#include "mozilla/fallible.h"
|
|
#include "nsDebug.h"
|
|
#include "nsError.h"
|
|
|
|
namespace mozilla::dom::quota {
|
|
template <typename CipherStrategy>
|
|
EncryptingOutputStream<CipherStrategy>::EncryptingOutputStream(
|
|
nsCOMPtr<nsIOutputStream> aBaseStream, size_t aBlockSize,
|
|
typename CipherStrategy::KeyType aKey)
|
|
: EncryptingOutputStreamBase(std::move(aBaseStream), aBlockSize) {
|
|
// XXX Move this to a fallible init function.
|
|
MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Encrypt,
|
|
CipherStrategy::SerializeKey(aKey),
|
|
CipherStrategy::MakeBlockPrefix()));
|
|
|
|
MOZ_ASSERT(mBlockSize > 0);
|
|
MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0);
|
|
static_assert(
|
|
CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0);
|
|
|
|
// This implementation only supports sync base streams. Verify this in debug
|
|
// builds.
|
|
#ifdef DEBUG
|
|
bool baseNonBlocking;
|
|
nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
MOZ_ASSERT(!baseNonBlocking);
|
|
#endif
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() {
|
|
Close();
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() {
|
|
if (!mBaseStream) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// When closing, flush to the base stream unconditionally, i.e. even if the
|
|
// buffer is not completely full.
|
|
nsresult rv = FlushToBaseStream();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// XXX Maybe this Flush call can be removed, since the base stream is closed
|
|
// afterwards anyway.
|
|
rv = (*mBaseStream)->Flush();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// XXX What if closing the base stream failed? Fail this method, or at least
|
|
// log a warning?
|
|
(*mBaseStream)->Close();
|
|
mBaseStream.destroy();
|
|
|
|
mBuffer.Clear();
|
|
mEncryptedBlock.reset();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() {
|
|
if (!mBaseStream) {
|
|
return NS_BASE_STREAM_CLOSED;
|
|
}
|
|
|
|
if (!EnsureBuffers()) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// We cannot call FlushBaseStream() here if the buffer is not completely
|
|
// full, we would write an incomplete page, which might be read sequentially,
|
|
// but we want to support random accesses in DecryptingInputStream, which
|
|
// would no longer be feasible.
|
|
if (mNextByte && mNextByte == mEncryptedBlock->MaxPayloadLength()) {
|
|
nsresult rv = FlushToBaseStream();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return (*mBaseStream)->Flush();
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::WriteSegments(
|
|
nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
|
|
uint32_t* aBytesWrittenOut) {
|
|
*aBytesWrittenOut = 0;
|
|
|
|
if (!mBaseStream) {
|
|
return NS_BASE_STREAM_CLOSED;
|
|
}
|
|
|
|
if (!EnsureBuffers()) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength();
|
|
|
|
while (aCount > 0) {
|
|
// Determine how much space is left in our flat, plain buffer.
|
|
MOZ_ASSERT(mNextByte <= plainBufferSize);
|
|
uint32_t remaining = plainBufferSize - mNextByte;
|
|
|
|
// If it is full, then encrypt and flush the data to the base stream.
|
|
if (remaining == 0) {
|
|
nsresult rv = FlushToBaseStream();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
// Now the entire buffer should be available for copying.
|
|
MOZ_ASSERT(!mNextByte);
|
|
remaining = plainBufferSize;
|
|
}
|
|
|
|
uint32_t numToRead = std::min(remaining, aCount);
|
|
uint32_t numRead = 0;
|
|
|
|
nsresult rv =
|
|
aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]),
|
|
*aBytesWrittenOut, numToRead, &numRead);
|
|
|
|
// As defined in nsIOutputStream.idl, do not pass reader func errors.
|
|
if (NS_FAILED(rv)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// End-of-file
|
|
if (numRead == 0) {
|
|
return NS_OK;
|
|
}
|
|
|
|
mNextByte += numRead;
|
|
*aBytesWrittenOut += numRead;
|
|
aCount -= numRead;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
bool EncryptingOutputStream<CipherStrategy>::EnsureBuffers() {
|
|
// Lazily create the encrypted buffer on our first flush. This
|
|
// allows us to report OOM during stream operation. This buffer
|
|
// will then get re-used until the stream is closed.
|
|
if (!mEncryptedBlock) {
|
|
// XXX Do we need to do this fallible (as the comment above suggests)?
|
|
mEncryptedBlock.emplace(mBlockSize);
|
|
MOZ_ASSERT(mBuffer.IsEmpty());
|
|
|
|
if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
|
|
fallible))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template <typename CipherStrategy>
|
|
nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() {
|
|
MOZ_ASSERT(mBaseStream);
|
|
|
|
if (!mNextByte) {
|
|
// Nothing to do.
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX The compressing stream implementation this was based on wrote a stream
|
|
// identifier, containing e.g. the block size. Should we do something like
|
|
// that as well? At the moment, we don't need it, but maybe this were
|
|
// convenient if we use this for persistent files in the future across version
|
|
// updates, which might change such parameters.
|
|
|
|
const auto iv = mCipherStrategy.MakeBlockPrefix();
|
|
static_assert(iv.size() * sizeof(decltype(*iv.begin())) ==
|
|
CipherStrategy::BlockPrefixLength);
|
|
std::copy(iv.cbegin(), iv.cend(),
|
|
mEncryptedBlock->MutableCipherPrefix().begin());
|
|
|
|
// Encrypt the data to our internal encrypted buffer.
|
|
// XXX Do we need to know the actual encrypted size?
|
|
nsresult rv = mCipherStrategy.Cipher(
|
|
mEncryptedBlock->MutableCipherPrefix(),
|
|
mozilla::Span(reinterpret_cast<uint8_t*>(mBuffer.Elements()),
|
|
((mNextByte + (CipherStrategy::BasicBlockSize - 1)) /
|
|
CipherStrategy::BasicBlockSize) *
|
|
CipherStrategy::BasicBlockSize),
|
|
mEncryptedBlock->MutablePayload());
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
mEncryptedBlock->SetActualPayloadLength(mNextByte);
|
|
|
|
mNextByte = 0;
|
|
|
|
// Write the encrypted buffer out to the base stream.
|
|
uint32_t numWritten = 0;
|
|
const auto& wholeBlock = mEncryptedBlock->WholeBlock();
|
|
rv = WriteAll(AsChars(wholeBlock).Elements(), wholeBlock.Length(),
|
|
&numWritten);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
MOZ_ASSERT(wholeBlock.Length() == numWritten);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace mozilla::dom::quota
|
|
|
|
#endif
|