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_DecryptingInputStream_impl_h
8 #define mozilla_dom_quota_DecryptingInputStream_impl_h
10 #include "DecryptingInputStream.h"
14 #include <type_traits>
16 #include "CipherStrategy.h"
17 #include "mozilla/Assertions.h"
18 #include "mozilla/RefPtr.h"
19 #include "mozilla/Result.h"
20 #include "mozilla/ResultExtensions.h"
21 #include "mozilla/Span.h"
22 #include "mozilla/fallible.h"
25 #include "nsFileStreams.h"
27 #include "nsIFileStreams.h"
29 namespace mozilla::dom::quota
{
31 template <typename CipherStrategy
>
32 DecryptingInputStream
<CipherStrategy
>::DecryptingInputStream(
33 MovingNotNull
<nsCOMPtr
<nsIInputStream
>> aBaseStream
, size_t aBlockSize
,
34 typename
CipherStrategy::KeyType aKey
)
35 : DecryptingInputStreamBase(std::move(aBaseStream
), aBlockSize
),
37 // XXX Move this to a fallible init function.
38 MOZ_ALWAYS_SUCCEEDS(mCipherStrategy
.Init(CipherMode::Decrypt
,
39 CipherStrategy::SerializeKey(aKey
)));
41 // This implementation only supports sync base streams. Verify this in debug
45 nsresult rv
= (*mBaseStream
)->IsNonBlocking(&baseNonBlocking
);
46 MOZ_ASSERT(NS_SUCCEEDED(rv
));
47 MOZ_ASSERT(!baseNonBlocking
);
51 template <typename CipherStrategy
>
52 DecryptingInputStream
<CipherStrategy
>::~DecryptingInputStream() {
56 template <typename CipherStrategy
>
57 DecryptingInputStream
<CipherStrategy
>::DecryptingInputStream()
58 : DecryptingInputStreamBase
{} {}
60 template <typename CipherStrategy
>
61 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Close() {
66 (*mBaseStream
)->Close();
67 mBaseStream
.destroy();
70 mEncryptedBlock
.reset();
75 template <typename CipherStrategy
>
76 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Available(
77 uint64_t* aLengthOut
) {
79 return NS_BASE_STREAM_CLOSED
;
82 int64_t oldPos
, endPos
;
83 nsresult rv
= Tell(&oldPos
);
84 if (NS_WARN_IF(NS_FAILED(rv
))) {
88 rv
= Seek(SEEK_END
, 0);
89 if (NS_WARN_IF(NS_FAILED(rv
))) {
94 if (NS_WARN_IF(NS_FAILED(rv
))) {
98 rv
= Seek(SEEK_SET
, oldPos
);
99 if (NS_WARN_IF(NS_FAILED(rv
))) {
103 *aLengthOut
= endPos
- oldPos
;
107 template <typename CipherStrategy
>
108 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::StreamStatus() {
109 return mBaseStream
? NS_OK
: NS_BASE_STREAM_CLOSED
;
112 template <typename CipherStrategy
>
113 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::ReadSegments(
114 nsWriteSegmentFun aWriter
, void* aClosure
, uint32_t aCount
,
115 uint32_t* aBytesReadOut
) {
119 return NS_BASE_STREAM_CLOSED
;
124 // Do not try to use the base stream's ReadSegments here. Its very
125 // unlikely we will get a single buffer that contains all of the encrypted
126 // data and therefore would have to copy into our own buffer anyways.
127 // Instead, focus on making efficient use of the Read() interface.
130 // We have some decrypted data in our buffer. Provide it to the callers
132 if (mPlainBytes
> 0) {
133 MOZ_ASSERT(!mPlainBuffer
.IsEmpty());
134 uint32_t remaining
= PlainLength();
135 uint32_t numToWrite
= std::min(aCount
, remaining
);
137 rv
= aWriter(this, aClosure
,
138 reinterpret_cast<const char*>(&mPlainBuffer
[mNextByte
]),
139 *aBytesReadOut
, numToWrite
, &numWritten
);
141 // As defined in nsIInputputStream.idl, do not pass writer func errors.
147 if (numWritten
== 0) {
151 *aBytesReadOut
+= numWritten
;
152 mNextByte
+= numWritten
;
153 MOZ_ASSERT(mNextByte
<= mPlainBytes
);
155 if (mNextByte
== mPlainBytes
) {
157 mLastBlockLength
= mPlainBytes
;
161 aCount
-= numWritten
;
166 // Otherwise decrypt the next chunk and loop. Any resulting data
167 // will set mPlainBytes which we check at the top of the loop.
169 rv
= ParseNextChunk(&bytesRead
);
174 // If we couldn't read anything and there is no more data to provide
175 // to the caller, then this is eof.
176 if (bytesRead
== 0 && mPlainBytes
== 0) {
180 mPlainBytes
+= bytesRead
;
186 template <typename CipherStrategy
>
187 nsresult DecryptingInputStream
<CipherStrategy
>::ParseNextChunk(
188 uint32_t* const aBytesReadOut
) {
189 // There must not be any plain data already in mPlainBuffer.
190 MOZ_ASSERT(mPlainBytes
== 0);
191 MOZ_ASSERT(mNextByte
== 0);
195 if (!EnsureBuffers()) {
196 return NS_ERROR_OUT_OF_MEMORY
;
199 // Read the data to our internal encrypted buffer.
200 auto wholeBlock
= mEncryptedBlock
->MutableWholeBlock();
202 ReadAll(AsWritableChars(wholeBlock
).Elements(), wholeBlock
.Length(),
203 wholeBlock
.Length(), aBytesReadOut
);
204 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) {
208 // XXX Do we need to know the actual decrypted size?
209 rv
= mCipherStrategy
.Cipher(mEncryptedBlock
->MutableCipherPrefix(),
210 mEncryptedBlock
->Payload(),
211 AsWritableBytes(Span
{mPlainBuffer
}));
212 if (NS_WARN_IF(NS_FAILED(rv
))) {
216 *aBytesReadOut
= mEncryptedBlock
->ActualPayloadLength();
221 template <typename CipherStrategy
>
222 nsresult DecryptingInputStream
<CipherStrategy
>::ReadAll(
223 char* aBuf
, uint32_t aCount
, uint32_t aMinValidCount
,
224 uint32_t* aBytesReadOut
) {
225 MOZ_ASSERT(aCount
>= aMinValidCount
);
226 MOZ_ASSERT(mBaseStream
);
232 uint32_t bytesRead
= 0;
233 nsresult rv
= (*mBaseStream
)->Read(aBuf
+ offset
, aCount
, &bytesRead
);
234 if (NS_WARN_IF(NS_FAILED(rv
))) {
238 // EOF, but don't immediately return. We need to validate min read bytes
240 if (bytesRead
== 0) {
244 *aBytesReadOut
+= bytesRead
;
249 // Reading zero bytes is not an error. Its the expected EOF condition.
250 // Only compare to the minimum valid count if we read at least one byte.
251 if (*aBytesReadOut
!= 0 && *aBytesReadOut
< aMinValidCount
) {
252 return NS_ERROR_CORRUPTED_CONTENT
;
258 template <typename CipherStrategy
>
259 bool DecryptingInputStream
<CipherStrategy
>::EnsureBuffers() {
260 // Lazily create our two buffers so we can report OOM during stream
261 // operation. These allocations only happens once. The buffers are reused
262 // until the stream is closed.
263 if (!mEncryptedBlock
) {
264 // XXX Do we need to do this fallible (as the comment above suggests)?
265 mEncryptedBlock
.emplace(*mBlockSize
);
267 MOZ_ASSERT(mPlainBuffer
.IsEmpty());
268 if (NS_WARN_IF(!mPlainBuffer
.SetLength(mEncryptedBlock
->MaxPayloadLength(),
277 template <typename CipherStrategy
>
278 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Tell(
279 int64_t* const aRetval
) {
283 return NS_BASE_STREAM_CLOSED
;
286 if (!EnsureBuffers()) {
287 return NS_ERROR_OUT_OF_MEMORY
;
290 int64_t basePosition
;
291 nsresult rv
= (*mBaseSeekableStream
)->Tell(&basePosition
);
292 if (NS_WARN_IF(NS_FAILED(rv
))) {
296 const auto fullBlocks
= basePosition
/ *mBlockSize
;
297 MOZ_ASSERT(0 == basePosition
% *mBlockSize
);
299 *aRetval
= (fullBlocks
- ((mPlainBytes
|| mLastBlockLength
) ? 1 : 0)) *
300 mEncryptedBlock
->MaxPayloadLength() +
301 mNextByte
+ (mNextByte
? 0 : mLastBlockLength
);
305 template <typename CipherStrategy
>
306 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Seek(const int32_t aWhence
,
309 return NS_BASE_STREAM_CLOSED
;
312 if (!EnsureBuffers()) {
313 return NS_ERROR_OUT_OF_MEMORY
;
316 int64_t baseBlocksOffset
;
317 int64_t nextByteOffset
;
320 // XXX Simplify this without using Tell.
323 nsresult rv
= Tell(¤t
);
324 if (NS_WARN_IF(NS_FAILED(rv
))) {
335 // XXX Simplify this without using Seek/Tell.
337 // XXX The size of the stream could also be queried and stored once
339 nsresult rv
= (*mBaseSeekableStream
)->Seek(NS_SEEK_SET
, 0);
340 if (NS_WARN_IF(NS_FAILED(rv
))) {
344 uint64_t baseStreamSize
;
345 rv
= (*mBaseStream
)->Available(&baseStreamSize
);
346 if (NS_WARN_IF(NS_FAILED(rv
))) {
350 auto decryptedStreamSizeOrErr
= [baseStreamSize
,
351 this]() -> Result
<int64_t, nsresult
> {
352 if (!baseStreamSize
) {
357 (*mBaseSeekableStream
)
358 ->Seek(NS_SEEK_END
, -static_cast<int64_t>(*mBlockSize
));
359 if (NS_WARN_IF(NS_FAILED(rv
))) {
367 rv
= ParseNextChunk(&bytesRead
);
368 if (NS_WARN_IF(NS_FAILED(rv
))) {
371 MOZ_ASSERT(bytesRead
);
373 // XXX Shouldn't ParseNextChunk better update mPlainBytes?
374 mPlainBytes
= bytesRead
;
376 mNextByte
= bytesRead
;
380 if (NS_WARN_IF(NS_FAILED(rv
))) {
387 if (decryptedStreamSizeOrErr
.isErr()) {
388 return decryptedStreamSizeOrErr
.unwrapErr();
391 aOffset
+= decryptedStreamSizeOrErr
.unwrap();
396 return NS_ERROR_ILLEGAL_VALUE
;
399 baseBlocksOffset
= aOffset
/ mEncryptedBlock
->MaxPayloadLength();
400 nextByteOffset
= aOffset
% mEncryptedBlock
->MaxPayloadLength();
402 // XXX If we remain in the same block as before, we can skip this.
404 (*mBaseSeekableStream
)->Seek(NS_SEEK_SET
, baseBlocksOffset
* *mBlockSize
);
405 if (NS_WARN_IF(NS_FAILED(rv
))) {
413 rv
= ParseNextChunk(&readBytes
);
414 if (NS_WARN_IF(NS_FAILED(rv
))) {
415 // XXX Do we need to do more here? Restore any previous state?
419 // We positioned after the last block, we must read that to know its size.
420 // XXX We could know earlier if we positioned us after the last block.
422 if (baseBlocksOffset
== 0) {
423 // The stream is empty.
424 return aOffset
== 0 ? NS_OK
: NS_ERROR_ILLEGAL_VALUE
;
427 nsresult rv
= (*mBaseSeekableStream
)->Seek(NS_SEEK_CUR
, -*mBlockSize
);
428 if (NS_WARN_IF(NS_FAILED(rv
))) {
432 rv
= ParseNextChunk(&readBytes
);
433 if (NS_WARN_IF(NS_FAILED(rv
))) {
434 // XXX Do we need to do more here? Restore any previous state?
439 mPlainBytes
= readBytes
;
440 mNextByte
= nextByteOffset
;
445 template <typename CipherStrategy
>
446 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Clone(
447 nsIInputStream
** _retval
) {
449 return NS_BASE_STREAM_CLOSED
;
452 if (!(*mBaseCloneableInputStream
)->GetCloneable()) {
453 return NS_ERROR_FAILURE
;
456 nsCOMPtr
<nsIInputStream
> clonedStream
;
458 (*mBaseCloneableInputStream
)->Clone(getter_AddRefs(clonedStream
));
459 if (NS_WARN_IF(NS_FAILED(rv
))) {
463 *_retval
= MakeAndAddRef
<DecryptingInputStream
>(
464 WrapNotNull(std::move(clonedStream
)), *mBlockSize
, *mKey
)
470 template <typename CipherStrategy
>
471 void DecryptingInputStream
<CipherStrategy
>::Serialize(
472 mozilla::ipc::InputStreamParams
& aParams
, uint32_t aMaxSize
,
473 uint32_t* aSizeUsed
) {
474 MOZ_ASSERT(mBaseStream
);
475 MOZ_ASSERT(mBaseIPCSerializableInputStream
);
477 mozilla::ipc::InputStreamParams baseStreamParams
;
478 (*mBaseIPCSerializableInputStream
)
479 ->Serialize(baseStreamParams
, aMaxSize
, aSizeUsed
);
481 MOZ_ASSERT(baseStreamParams
.type() ==
482 mozilla::ipc::InputStreamParams::TFileInputStreamParams
);
484 mozilla::ipc::EncryptedFileInputStreamParams encryptedFileInputStreamParams
;
485 encryptedFileInputStreamParams
.fileInputStreamParams() =
486 std::move(baseStreamParams
);
487 encryptedFileInputStreamParams
.key().AppendElements(
488 mCipherStrategy
.SerializeKey(*mKey
));
489 encryptedFileInputStreamParams
.blockSize() = *mBlockSize
;
491 aParams
= std::move(encryptedFileInputStreamParams
);
494 template <typename CipherStrategy
>
495 bool DecryptingInputStream
<CipherStrategy
>::Deserialize(
496 const mozilla::ipc::InputStreamParams
& aParams
) {
497 MOZ_ASSERT(aParams
.type() ==
498 mozilla::ipc::InputStreamParams::TEncryptedFileInputStreamParams
);
499 const auto& params
= aParams
.get_EncryptedFileInputStreamParams();
501 nsCOMPtr
<nsIFileInputStream
> stream
;
502 nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream
),
503 getter_AddRefs(stream
));
504 nsCOMPtr
<nsIIPCSerializableInputStream
> baseSerializable
=
505 do_QueryInterface(stream
);
508 !baseSerializable
->Deserialize(params
.fileInputStreamParams()))) {
512 Init(WrapNotNull
<nsCOMPtr
<nsIInputStream
>>(std::move(stream
)),
515 auto key
= mCipherStrategy
.DeserializeKey(params
.key());
516 if (NS_WARN_IF(!key
)) {
522 NS_FAILED(mCipherStrategy
.Init(CipherMode::Decrypt
, params
.key())))) {
529 } // namespace mozilla::dom::quota