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 nsresult DecryptingInputStream
<CipherStrategy
>::BaseStreamStatus() {
114 return mBaseStream
? (*mBaseStream
)->StreamStatus() : NS_BASE_STREAM_CLOSED
;
117 template <typename CipherStrategy
>
118 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::ReadSegments(
119 nsWriteSegmentFun aWriter
, void* aClosure
, uint32_t aCount
,
120 uint32_t* aBytesReadOut
) {
124 return NS_BASE_STREAM_CLOSED
;
129 // Do not try to use the base stream's ReadSegments here. Its very
130 // unlikely we will get a single buffer that contains all of the encrypted
131 // data and therefore would have to copy into our own buffer anyways.
132 // Instead, focus on making efficient use of the Read() interface.
135 // We have some decrypted data in our buffer. Provide it to the callers
137 if (mNextByte
< mPlainBytes
) {
138 MOZ_ASSERT(!mPlainBuffer
.IsEmpty());
139 uint32_t remaining
= PlainLength();
140 uint32_t numToWrite
= std::min(aCount
, remaining
);
142 rv
= aWriter(this, aClosure
,
143 reinterpret_cast<const char*>(&mPlainBuffer
[mNextByte
]),
144 *aBytesReadOut
, numToWrite
, &numWritten
);
146 // As defined in nsIInputputStream.idl, do not pass writer func errors.
152 if (numWritten
== 0) {
156 *aBytesReadOut
+= numWritten
;
157 mNextByte
+= numWritten
;
158 MOZ_ASSERT(mNextByte
<= mPlainBytes
);
160 aCount
-= numWritten
;
165 // Otherwise decrypt the next chunk and loop. Any resulting data will set
166 // mPlainBytes and mNextByte which we check at the top of the loop.
168 rv
= ParseNextChunk(false /* aCheckAvailableBytes */, &bytesRead
);
173 // If we couldn't read anything, then this is eof.
174 if (bytesRead
== 0) {
178 mPlainBytes
= bytesRead
;
185 template <typename CipherStrategy
>
186 nsresult DecryptingInputStream
<CipherStrategy
>::ParseNextChunk(
187 bool aCheckAvailableBytes
, uint32_t* const aBytesReadOut
) {
190 if (!EnsureBuffers()) {
191 return NS_ERROR_OUT_OF_MEMORY
;
194 // Read the data to our internal encrypted buffer.
195 auto wholeBlock
= mEncryptedBlock
->MutableWholeBlock();
197 ReadAll(AsWritableChars(wholeBlock
).Elements(), wholeBlock
.Length(),
198 wholeBlock
.Length(), aCheckAvailableBytes
, aBytesReadOut
);
199 if (NS_WARN_IF(NS_FAILED(rv
)) || *aBytesReadOut
== 0) {
203 // XXX Do we need to know the actual decrypted size?
204 rv
= mCipherStrategy
.Cipher(mEncryptedBlock
->MutableCipherPrefix(),
205 mEncryptedBlock
->Payload(),
206 AsWritableBytes(Span
{mPlainBuffer
}));
207 if (NS_WARN_IF(NS_FAILED(rv
))) {
211 *aBytesReadOut
= mEncryptedBlock
->ActualPayloadLength();
216 template <typename CipherStrategy
>
217 nsresult DecryptingInputStream
<CipherStrategy
>::ReadAll(
218 char* aBuf
, uint32_t aCount
, uint32_t aMinValidCount
,
219 bool aCheckAvailableBytes
, uint32_t* aBytesReadOut
) {
220 MOZ_ASSERT(aCount
>= aMinValidCount
);
221 MOZ_ASSERT(mBaseStream
);
228 Maybe
<uint64_t> availableBytes
;
229 if (aCheckAvailableBytes
) {
231 rv
= (*mBaseStream
)->Available(&available
);
232 if (NS_WARN_IF(NS_FAILED(rv
))) {
233 if (rv
== NS_BASE_STREAM_CLOSED
) {
239 if (available
== 0) {
243 availableBytes
= Some(available
);
246 uint32_t bytesRead
= 0;
247 rv
= (*mBaseStream
)->Read(aBuf
+ offset
, aCount
, &bytesRead
);
248 if (NS_WARN_IF(NS_FAILED(rv
))) {
252 // EOF, but don't immediately return. We need to validate min read bytes
254 if (bytesRead
== 0) {
258 MOZ_DIAGNOSTIC_ASSERT(!availableBytes
|| bytesRead
<= *availableBytes
);
260 *aBytesReadOut
+= bytesRead
;
265 // Reading zero bytes is not an error. Its the expected EOF condition.
266 // Only compare to the minimum valid count if we read at least one byte.
267 if (*aBytesReadOut
!= 0 && *aBytesReadOut
< aMinValidCount
) {
268 return NS_ERROR_CORRUPTED_CONTENT
;
274 template <typename CipherStrategy
>
275 bool DecryptingInputStream
<CipherStrategy
>::EnsureBuffers() {
276 // Lazily create our two buffers so we can report OOM during stream
277 // operation. These allocations only happens once. The buffers are reused
278 // until the stream is closed.
279 if (!mEncryptedBlock
) {
280 // XXX Do we need to do this fallible (as the comment above suggests)?
281 mEncryptedBlock
.emplace(*mBlockSize
);
283 MOZ_ASSERT(mPlainBuffer
.IsEmpty());
284 if (NS_WARN_IF(!mPlainBuffer
.SetLength(mEncryptedBlock
->MaxPayloadLength(),
293 template <typename CipherStrategy
>
294 nsresult DecryptingInputStream
<CipherStrategy
>::EnsureDecryptedStreamSize() {
295 if (mDecryptedStreamSize
) {
299 auto decryptedStreamSizeOrErr
= [this]() -> Result
<int64_t, nsresult
> {
300 nsresult rv
= (*mBaseSeekableStream
)->Seek(NS_SEEK_SET
, 0);
301 if (NS_WARN_IF(NS_FAILED(rv
))) {
305 uint64_t baseStreamSize
;
306 rv
= (*mBaseStream
)->Available(&baseStreamSize
);
307 if (NS_WARN_IF(NS_FAILED(rv
))) {
311 if (!baseStreamSize
) {
315 rv
= (*mBaseSeekableStream
)
316 ->Seek(NS_SEEK_END
, -static_cast<int64_t>(*mBlockSize
));
317 if (NS_WARN_IF(NS_FAILED(rv
))) {
322 rv
= ParseNextChunk(true /* aCheckAvailableBytes */, &bytesRead
);
323 if (NS_WARN_IF(NS_FAILED(rv
))) {
326 MOZ_ASSERT(bytesRead
);
328 mPlainBytes
= bytesRead
;
330 mNextByte
= bytesRead
;
334 if (NS_WARN_IF(NS_FAILED(rv
))) {
341 if (decryptedStreamSizeOrErr
.isErr()) {
342 return decryptedStreamSizeOrErr
.unwrapErr();
345 mDecryptedStreamSize
.init(decryptedStreamSizeOrErr
.inspect());
350 template <typename CipherStrategy
>
351 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Tell(
352 int64_t* const aRetval
) {
356 return NS_BASE_STREAM_CLOSED
;
359 if (!EnsureBuffers()) {
360 return NS_ERROR_OUT_OF_MEMORY
;
363 int64_t basePosition
;
364 nsresult rv
= (*mBaseSeekableStream
)->Tell(&basePosition
);
365 if (NS_WARN_IF(NS_FAILED(rv
))) {
369 if (basePosition
== 0) {
374 MOZ_ASSERT(0 == basePosition
% *mBlockSize
);
376 const auto fullBlocks
= basePosition
/ *mBlockSize
;
377 MOZ_ASSERT(fullBlocks
);
379 *aRetval
= (fullBlocks
- 1) * mEncryptedBlock
->MaxPayloadLength() + mNextByte
;
383 template <typename CipherStrategy
>
384 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Seek(const int32_t aWhence
,
387 return NS_BASE_STREAM_CLOSED
;
390 if (!EnsureBuffers()) {
391 return NS_ERROR_OUT_OF_MEMORY
;
395 nsresult rv
= (*mBaseSeekableStream
)->Tell(&baseCurrent
);
396 if (NS_WARN_IF(NS_FAILED(rv
))) {
400 // Can't call this just in NS_SEEK_CUR case, because ensuring the decrypted
401 // size below may change the current position.
404 if (NS_WARN_IF(NS_FAILED(rv
))) {
408 // If there's a failure we need to restore any previous state.
409 auto autoRestorePreviousState
=
410 MakeScopeExit([baseSeekableStream
= *mBaseSeekableStream
,
411 savedBaseCurrent
= baseCurrent
,
412 savedPlainBytes
= mPlainBytes
, savedNextByte
= mNextByte
,
413 &plainBytes
= mPlainBytes
, &nextByte
= mNextByte
] {
414 nsresult rv
= baseSeekableStream
->Seek(NS_SEEK_SET
, savedBaseCurrent
);
415 Unused
<< NS_WARN_IF(NS_FAILED(rv
));
416 plainBytes
= savedPlainBytes
;
417 nextByte
= savedNextByte
;
420 rv
= EnsureDecryptedStreamSize();
421 if (NS_WARN_IF(NS_FAILED(rv
))) {
425 int64_t baseBlocksOffset
;
426 int64_t nextByteOffset
;
429 // XXX Simplify this without using Tell.
437 // XXX Simplify this without using Seek/Tell.
438 aOffset
+= *mDecryptedStreamSize
;
442 return NS_ERROR_ILLEGAL_VALUE
;
445 if (aOffset
< 0 || aOffset
> *mDecryptedStreamSize
) {
446 return NS_ERROR_ILLEGAL_VALUE
;
449 baseBlocksOffset
= aOffset
/ mEncryptedBlock
->MaxPayloadLength();
450 nextByteOffset
= aOffset
% mEncryptedBlock
->MaxPayloadLength();
452 // XXX If we remain in the same block as before, we can skip this.
454 (*mBaseSeekableStream
)->Seek(NS_SEEK_SET
, baseBlocksOffset
* *mBlockSize
);
455 if (NS_WARN_IF(NS_FAILED(rv
))) {
460 rv
= ParseNextChunk(true /* aCheckAvailableBytes */, &readBytes
);
461 if (NS_WARN_IF(NS_FAILED(rv
))) {
465 if (readBytes
== 0 && baseBlocksOffset
!= 0) {
466 mPlainBytes
= mEncryptedBlock
->MaxPayloadLength();
467 mNextByte
= mEncryptedBlock
->MaxPayloadLength();
469 mPlainBytes
= readBytes
;
470 mNextByte
= nextByteOffset
;
473 autoRestorePreviousState
.release();
478 template <typename CipherStrategy
>
479 NS_IMETHODIMP DecryptingInputStream
<CipherStrategy
>::Clone(
480 nsIInputStream
** _retval
) {
482 return NS_BASE_STREAM_CLOSED
;
485 if (!(*mBaseCloneableInputStream
)->GetCloneable()) {
486 return NS_ERROR_FAILURE
;
489 nsCOMPtr
<nsIInputStream
> clonedStream
;
491 (*mBaseCloneableInputStream
)->Clone(getter_AddRefs(clonedStream
));
492 if (NS_WARN_IF(NS_FAILED(rv
))) {
496 *_retval
= MakeAndAddRef
<DecryptingInputStream
>(
497 WrapNotNull(std::move(clonedStream
)), *mBlockSize
, *mKey
)
503 template <typename CipherStrategy
>
504 void DecryptingInputStream
<CipherStrategy
>::Serialize(
505 mozilla::ipc::InputStreamParams
& aParams
, uint32_t aMaxSize
,
506 uint32_t* aSizeUsed
) {
507 MOZ_ASSERT(mBaseStream
);
508 MOZ_ASSERT(mBaseIPCSerializableInputStream
);
510 mozilla::ipc::InputStreamParams baseStreamParams
;
511 (*mBaseIPCSerializableInputStream
)
512 ->Serialize(baseStreamParams
, aMaxSize
, aSizeUsed
);
514 MOZ_ASSERT(baseStreamParams
.type() ==
515 mozilla::ipc::InputStreamParams::TFileInputStreamParams
);
517 mozilla::ipc::EncryptedFileInputStreamParams encryptedFileInputStreamParams
;
518 encryptedFileInputStreamParams
.fileInputStreamParams() =
519 std::move(baseStreamParams
);
520 encryptedFileInputStreamParams
.key().AppendElements(
521 mCipherStrategy
.SerializeKey(*mKey
));
522 encryptedFileInputStreamParams
.blockSize() = *mBlockSize
;
524 aParams
= std::move(encryptedFileInputStreamParams
);
527 template <typename CipherStrategy
>
528 bool DecryptingInputStream
<CipherStrategy
>::Deserialize(
529 const mozilla::ipc::InputStreamParams
& aParams
) {
530 MOZ_ASSERT(aParams
.type() ==
531 mozilla::ipc::InputStreamParams::TEncryptedFileInputStreamParams
);
532 const auto& params
= aParams
.get_EncryptedFileInputStreamParams();
534 nsCOMPtr
<nsIFileInputStream
> stream
;
535 nsFileInputStream::Create(NS_GET_IID(nsIFileInputStream
),
536 getter_AddRefs(stream
));
537 nsCOMPtr
<nsIIPCSerializableInputStream
> baseSerializable
=
538 do_QueryInterface(stream
);
541 !baseSerializable
->Deserialize(params
.fileInputStreamParams()))) {
545 Init(WrapNotNull
<nsCOMPtr
<nsIInputStream
>>(std::move(stream
)),
548 auto key
= mCipherStrategy
.DeserializeKey(params
.key());
549 if (NS_WARN_IF(!key
)) {
555 NS_FAILED(mCipherStrategy
.Init(CipherMode::Decrypt
, params
.key())))) {
562 } // namespace mozilla::dom::quota