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/. */
6 
7 #ifndef mozilla_dom_quota_EncryptingOutputStream_impl_h
8 #define mozilla_dom_quota_EncryptingOutputStream_impl_h
9 
10 #include "EncryptingOutputStream.h"
11 
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 
21 namespace mozilla::dom::quota {
22 template <typename CipherStrategy>
EncryptingOutputStream(nsCOMPtr<nsIOutputStream> aBaseStream,size_t aBlockSize,typename CipherStrategy::KeyType aKey)23 EncryptingOutputStream<CipherStrategy>::EncryptingOutputStream(
24     nsCOMPtr<nsIOutputStream> aBaseStream, size_t aBlockSize,
25     typename CipherStrategy::KeyType aKey)
26     : EncryptingOutputStreamBase(std::move(aBaseStream), aBlockSize) {
27   // XXX Move this to a fallible init function.
28   MOZ_ALWAYS_SUCCEEDS(mCipherStrategy.Init(CipherMode::Encrypt,
29                                            CipherStrategy::SerializeKey(aKey),
30                                            CipherStrategy::MakeBlockPrefix()));
31 
32   MOZ_ASSERT(mBlockSize > 0);
33   MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0);
34   static_assert(
35       CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0);
36 
37   // This implementation only supports sync base streams.  Verify this in debug
38   // builds.
39 #ifdef DEBUG
40   bool baseNonBlocking;
41   nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking);
42   MOZ_ASSERT(NS_SUCCEEDED(rv));
43   MOZ_ASSERT(!baseNonBlocking);
44 #endif
45 }
46 
47 template <typename CipherStrategy>
~EncryptingOutputStream()48 EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() {
49   Close();
50 }
51 
52 template <typename CipherStrategy>
Close()53 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() {
54   if (!mBaseStream) {
55     return NS_OK;
56   }
57 
58   // When closing, flush to the base stream unconditionally, i.e. even if the
59   // buffer is not completely full.
60   nsresult rv = FlushToBaseStream();
61   if (NS_WARN_IF(NS_FAILED(rv))) {
62     return rv;
63   }
64 
65   // XXX Maybe this Flush call can be removed, since the base stream is closed
66   // afterwards anyway.
67   rv = (*mBaseStream)->Flush();
68   if (NS_WARN_IF(NS_FAILED(rv))) {
69     return rv;
70   }
71 
72   // XXX What if closing the base stream failed? Fail this method, or at least
73   // log a warning?
74   (*mBaseStream)->Close();
75   mBaseStream.destroy();
76 
77   mBuffer.Clear();
78   mEncryptedBlock.reset();
79 
80   return NS_OK;
81 }
82 
83 template <typename CipherStrategy>
Flush()84 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() {
85   if (!mBaseStream) {
86     return NS_BASE_STREAM_CLOSED;
87   }
88 
89   if (!EnsureBuffers()) {
90     return NS_ERROR_OUT_OF_MEMORY;
91   }
92 
93   // We cannot call FlushBaseStream() here if the buffer is not completely
94   // full, we would write an incomplete page, which might be read sequentially,
95   // but we want to support random accesses in DecryptingInputStream, which
96   // would no longer be feasible.
97   if (mNextByte && mNextByte == mEncryptedBlock->MaxPayloadLength()) {
98     nsresult rv = FlushToBaseStream();
99     if (NS_WARN_IF(NS_FAILED(rv))) {
100       return rv;
101     }
102   }
103 
104   return (*mBaseStream)->Flush();
105 }
106 
107 template <typename CipherStrategy>
WriteSegments(nsReadSegmentFun aReader,void * aClosure,uint32_t aCount,uint32_t * aBytesWrittenOut)108 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::WriteSegments(
109     nsReadSegmentFun aReader, void* aClosure, uint32_t aCount,
110     uint32_t* aBytesWrittenOut) {
111   *aBytesWrittenOut = 0;
112 
113   if (!mBaseStream) {
114     return NS_BASE_STREAM_CLOSED;
115   }
116 
117   if (!EnsureBuffers()) {
118     return NS_ERROR_OUT_OF_MEMORY;
119   }
120 
121   const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength();
122 
123   while (aCount > 0) {
124     // Determine how much space is left in our flat, plain buffer.
125     MOZ_ASSERT(mNextByte <= plainBufferSize);
126     uint32_t remaining = plainBufferSize - mNextByte;
127 
128     // If it is full, then encrypt and flush the data to the base stream.
129     if (remaining == 0) {
130       nsresult rv = FlushToBaseStream();
131       if (NS_WARN_IF(NS_FAILED(rv))) {
132         return rv;
133       }
134 
135       // Now the entire buffer should be available for copying.
136       MOZ_ASSERT(!mNextByte);
137       remaining = plainBufferSize;
138     }
139 
140     uint32_t numToRead = std::min(remaining, aCount);
141     uint32_t numRead = 0;
142 
143     nsresult rv =
144         aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]),
145                 *aBytesWrittenOut, numToRead, &numRead);
146 
147     // As defined in nsIOutputStream.idl, do not pass reader func errors.
148     if (NS_FAILED(rv)) {
149       return NS_OK;
150     }
151 
152     // End-of-file
153     if (numRead == 0) {
154       return NS_OK;
155     }
156 
157     mNextByte += numRead;
158     *aBytesWrittenOut += numRead;
159     aCount -= numRead;
160   }
161 
162   return NS_OK;
163 }
164 
165 template <typename CipherStrategy>
EnsureBuffers()166 bool EncryptingOutputStream<CipherStrategy>::EnsureBuffers() {
167   // Lazily create the encrypted buffer on our first flush.  This
168   // allows us to report OOM during stream operation.  This buffer
169   // will then get re-used until the stream is closed.
170   if (!mEncryptedBlock) {
171     // XXX Do we need to do this fallible (as the comment above suggests)?
172     mEncryptedBlock.emplace(mBlockSize);
173     MOZ_ASSERT(mBuffer.IsEmpty());
174 
175     if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(),
176                                       fallible))) {
177       return false;
178     }
179   }
180 
181   return true;
182 }
183 
184 template <typename CipherStrategy>
FlushToBaseStream()185 nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() {
186   MOZ_ASSERT(mBaseStream);
187 
188   if (!mNextByte) {
189     // Nothing to do.
190     return NS_OK;
191   }
192 
193   // XXX The compressing stream implementation this was based on wrote a stream
194   // identifier, containing e.g. the block size. Should we do something like
195   // that as well? At the moment, we don't need it, but maybe this were
196   // convenient if we use this for persistent files in the future across version
197   // updates, which might change such parameters.
198 
199   const auto iv = mCipherStrategy.MakeBlockPrefix();
200   static_assert(iv.size() * sizeof(decltype(*iv.begin())) ==
201                 CipherStrategy::BlockPrefixLength);
202   std::copy(iv.cbegin(), iv.cend(),
203             mEncryptedBlock->MutableCipherPrefix().begin());
204 
205   // Encrypt the data to our internal encrypted buffer.
206   // XXX Do we need to know the actual encrypted size?
207   nsresult rv = mCipherStrategy.Cipher(
208       mEncryptedBlock->MutableCipherPrefix(),
209       mozilla::Span(reinterpret_cast<uint8_t*>(mBuffer.Elements()),
210                     ((mNextByte + (CipherStrategy::BasicBlockSize - 1)) /
211                      CipherStrategy::BasicBlockSize) *
212                         CipherStrategy::BasicBlockSize),
213       mEncryptedBlock->MutablePayload());
214   if (NS_WARN_IF(NS_FAILED(rv))) {
215     return rv;
216   }
217 
218   mEncryptedBlock->SetActualPayloadLength(mNextByte);
219 
220   mNextByte = 0;
221 
222   // Write the encrypted buffer out to the base stream.
223   uint32_t numWritten = 0;
224   const auto& wholeBlock = mEncryptedBlock->WholeBlock();
225   rv = WriteAll(AsChars(wholeBlock).Elements(), wholeBlock.Length(),
226                 &numWritten);
227   if (NS_WARN_IF(NS_FAILED(rv))) {
228     return rv;
229   }
230   MOZ_ASSERT(wholeBlock.Length() == numWritten);
231 
232   return NS_OK;
233 }
234 
235 }  // namespace mozilla::dom::quota
236 
237 #endif
238