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"
14 #include "CipherStrategy.h"
15 #include "mozilla/Assertions.h"
16 #include "mozilla/Span.h"
17 #include "mozilla/fallible.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);
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
48 nsresult rv
= (*mBaseStream
)->IsNonBlocking(&baseNonBlocking
);
49 MOZ_ASSERT(NS_SUCCEEDED(rv
));
50 if (baseNonBlocking
) {
51 nsCOMPtr
<nsIAsyncOutputStream
> async
=
52 do_QueryInterface((*mBaseStream
).get());
58 template <typename CipherStrategy
>
59 EncryptingOutputStream
<CipherStrategy
>::~EncryptingOutputStream() {
63 template <typename CipherStrategy
>
64 NS_IMETHODIMP EncryptingOutputStream
<CipherStrategy
>::Close() {
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
))) {
76 // XXX Maybe this Flush call can be removed, since the base stream is closed
78 rv
= (*mBaseStream
)->Flush();
79 if (NS_WARN_IF(NS_FAILED(rv
))) {
83 // XXX What if closing the base stream failed? Fail this method, or at least
85 (*mBaseStream
)->Close();
86 mBaseStream
.destroy();
89 mEncryptedBlock
.reset();
94 template <typename CipherStrategy
>
95 NS_IMETHODIMP EncryptingOutputStream
<CipherStrategy
>::Flush() {
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
))) {
115 return (*mBaseStream
)->Flush();
118 template <typename CipherStrategy
>
119 NS_IMETHODIMP EncryptingOutputStream
<CipherStrategy
>::StreamStatus() {
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;
133 return NS_BASE_STREAM_CLOSED
;
136 if (!EnsureBuffers()) {
137 return NS_ERROR_OUT_OF_MEMORY
;
140 const size_t plainBufferSize
= mEncryptedBlock
->MaxPayloadLength();
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
))) {
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;
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.
176 mNextByte
+= numRead
;
177 *aBytesWrittenOut
+= numRead
;
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(),
203 template <typename CipherStrategy
>
204 nsresult EncryptingOutputStream
<CipherStrategy
>::FlushToBaseStream() {
205 MOZ_ASSERT(mBaseStream
);
212 if (mNextByte
< mEncryptedBlock
->MaxPayloadLength()) {
213 if (!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
))) {
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
))) {
257 mEncryptedBlock
->SetActualPayloadLength(mNextByte
);
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(),
266 if (NS_WARN_IF(NS_FAILED(rv
))) {
269 MOZ_ASSERT(wholeBlock
.Length() == numWritten
);
274 } // namespace mozilla::dom::quota