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