Bug 1874684 - Part 6: Limit day length calculations to safe integers. r=mgaudet
[gecko.git] / dom / quota / EncryptingOutputStream_impl.h
blob09e479dbe80120d50593d8b2f66eac8fdf220f72
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifndef mozilla_dom_quota_EncryptingOutputStream_impl_h
8 #define mozilla_dom_quota_EncryptingOutputStream_impl_h
10 #include "EncryptingOutputStream.h"
12 #include <algorithm>
13 #include <utility>
14 #include "CipherStrategy.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/Span.h"
17 #include "mozilla/fallible.h"
18 #include "nsDebug.h"
19 #include "nsError.h"
20 #include "nsIAsyncOutputStream.h"
21 #include "nsIRandomGenerator.h"
22 #include "nsServiceManagerUtils.h"
24 namespace mozilla::dom::quota {
25 template <typename CipherStrategy>
26 EncryptingOutputStream<CipherStrategy>::EncryptingOutputStream(
27 nsCOMPtr<nsIOutputStream> aBaseStream, size_t aBlockSize,
28 typename CipherStrategy::KeyType aKey)
29 : EncryptingOutputStreamBase(std::move(aBaseStream), aBlockSize) {
30 // XXX Move this to a fallible init function.
31 MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Encrypt,
32 CipherStrategy::SerializeKey(aKey),
33 CipherStrategy::MakeBlockPrefix()));
35 MOZ_ASSERT(mBlockSize > 0);
36 MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0);
37 static_assert(
38 CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0);
40 // This implementation only supports sync base streams. Verify this in debug
41 // builds. Note, this is a bit complicated because the streams we support
42 // advertise different capabilities:
43 // - nsFileOutputStream - blocking and sync
44 // - FixedBufferOutputStream - non-blocking and sync
45 // - nsPipeOutputStream - can be blocking, but provides async interface
46 #ifdef DEBUG
47 bool baseNonBlocking;
48 nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
49 MOZ_ASSERT(NS_SUCCEEDED(rv));
50 if (baseNonBlocking) {
51 nsCOMPtr<nsIAsyncOutputStream> async =
52 do_QueryInterface((*mBaseStream).get());
53 MOZ_ASSERT(!async);
55 #endif
58 template <typename CipherStrategy>
59 EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() {
60 Close();
63 template <typename CipherStrategy>
64 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() {
65 if (!mBaseStream) {
66 return NS_OK;
69 // When closing, flush to the base stream unconditionally, i.e. even if the
70 // buffer is not completely full.
71 nsresult rv = FlushToBaseStream();
72 if (NS_WARN_IF(NS_FAILED(rv))) {
73 return rv;
76 // XXX Maybe this Flush call can be removed, since the base stream is closed
77 // afterwards anyway.
78 rv = (*mBaseStream)->Flush();
79 if (NS_WARN_IF(NS_FAILED(rv))) {
80 return rv;
83 // XXX What if closing the base stream failed? Fail this method, or at least
84 // log a warning?
85 (*mBaseStream)->Close();
86 mBaseStream.destroy();
88 mBuffer.Clear();
89 mEncryptedBlock.reset();
91 return NS_OK;
94 template <typename CipherStrategy>
95 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() {
96 if (!mBaseStream) {
97 return NS_BASE_STREAM_CLOSED;
100 if (!EnsureBuffers()) {
101 return NS_ERROR_OUT_OF_MEMORY;
104 // We cannot call FlushBaseStream() here if the buffer is not completely
105 // full, we would write an incomplete page, which might be read sequentially,
106 // but we want to support random accesses in DecryptingInputStream, which
107 // would no longer be feasible.
108 if (mNextByte && mNextByte == mEncryptedBlock->MaxPayloadLength()) {
109 nsresult rv = FlushToBaseStream();
110 if (NS_WARN_IF(NS_FAILED(rv))) {
111 return rv;
115 return (*mBaseStream)->Flush();
118 template <typename CipherStrategy>
119 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::StreamStatus() {
120 if (!mBaseStream) {
121 return NS_BASE_STREAM_CLOSED;
123 return (*mBaseStream)->StreamStatus();
126 template <typename CipherStrategy>
127 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::WriteSegments(
128 nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
129 uint32_t* aBytesWrittenOut) {
130 *aBytesWrittenOut = 0;
132 if (!mBaseStream) {
133 return NS_BASE_STREAM_CLOSED;
136 if (!EnsureBuffers()) {
137 return NS_ERROR_OUT_OF_MEMORY;
140 const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength();
142 while (aCount > 0) {
143 // Determine how much space is left in our flat, plain buffer.
144 MOZ_ASSERT(mNextByte <= plainBufferSize);
145 uint32_t remaining = plainBufferSize - mNextByte;
147 // If it is full, then encrypt and flush the data to the base stream.
148 if (remaining == 0) {
149 nsresult rv = FlushToBaseStream();
150 if (NS_WARN_IF(NS_FAILED(rv))) {
151 return rv;
154 // Now the entire buffer should be available for copying.
155 MOZ_ASSERT(!mNextByte);
156 remaining = plainBufferSize;
159 uint32_t numToRead = std::min(remaining, aCount);
160 uint32_t numRead = 0;
162 nsresult rv =
163 aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]),
164 *aBytesWrittenOut, numToRead, &numRead);
166 // As defined in nsIOutputStream.idl, do not pass reader func errors.
167 if (NS_FAILED(rv)) {
168 return NS_OK;
171 // End-of-file
172 if (numRead == 0) {
173 return NS_OK;
176 mNextByte += numRead;
177 *aBytesWrittenOut += numRead;
178 aCount -= numRead;
181 return NS_OK;
184 template <typename CipherStrategy>
185 bool EncryptingOutputStream<CipherStrategy>::EnsureBuffers() {
186 // Lazily create the encrypted buffer on our first flush. This
187 // allows us to report OOM during stream operation. This buffer
188 // will then get re-used until the stream is closed.
189 if (!mEncryptedBlock) {
190 // XXX Do we need to do this fallible (as the comment above suggests)?
191 mEncryptedBlock.emplace(mBlockSize);
192 MOZ_ASSERT(mBuffer.IsEmpty());
194 if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
195 fallible))) {
196 return false;
200 return true;
203 template <typename CipherStrategy>
204 nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() {
205 MOZ_ASSERT(mBaseStream);
207 if (!mNextByte) {
208 // Nothing to do.
209 return NS_OK;
212 if (mNextByte < mEncryptedBlock->MaxPayloadLength()) {
213 if (!mRandomGenerator) {
214 mRandomGenerator =
215 do_GetService("@mozilla.org/security/random-generator;1");
216 if (NS_WARN_IF(!mRandomGenerator)) {
217 return NS_ERROR_FAILURE;
221 const auto payload = mEncryptedBlock->MutablePayload();
223 const auto unusedPayload = payload.From(mNextByte);
225 nsresult rv = mRandomGenerator->GenerateRandomBytesInto(
226 unusedPayload.Elements(), unusedPayload.Length());
227 if (NS_WARN_IF(NS_FAILED(rv))) {
228 return rv;
232 // XXX The compressing stream implementation this was based on wrote a stream
233 // identifier, containing e.g. the block size. Should we do something like
234 // that as well? At the moment, we don't need it, but maybe this were
235 // convenient if we use this for persistent files in the future across version
236 // updates, which might change such parameters.
238 const auto iv = mCipherStrategy.MakeBlockPrefix();
239 static_assert(iv.size() * sizeof(decltype(*iv.begin())) ==
240 CipherStrategy::BlockPrefixLength);
241 std::copy(iv.cbegin(), iv.cend(),
242 mEncryptedBlock->MutableCipherPrefix().begin());
244 // Encrypt the data to our internal encrypted buffer.
245 // XXX Do we need to know the actual encrypted size?
246 nsresult rv = mCipherStrategy.Cipher(
247 mEncryptedBlock->MutableCipherPrefix(),
248 mozilla::Span(reinterpret_cast<uint8_t*>(mBuffer.Elements()),
249 ((mNextByte + (CipherStrategy::BasicBlockSize - 1)) /
250 CipherStrategy::BasicBlockSize) *
251 CipherStrategy::BasicBlockSize),
252 mEncryptedBlock->MutablePayload());
253 if (NS_WARN_IF(NS_FAILED(rv))) {
254 return rv;
257 mEncryptedBlock->SetActualPayloadLength(mNextByte);
259 mNextByte = 0;
261 // Write the encrypted buffer out to the base stream.
262 uint32_t numWritten = 0;
263 const auto& wholeBlock = mEncryptedBlock->WholeBlock();
264 rv = WriteAll(AsChars(wholeBlock).Elements(), wholeBlock.Length(),
265 &numWritten);
266 if (NS_WARN_IF(NS_FAILED(rv))) {
267 return rv;
269 MOZ_ASSERT(wholeBlock.Length() == numWritten);
271 return NS_OK;
274 } // namespace mozilla::dom::quota
276 #endif