EncryptingOutputStream_impl.h (8563B)
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 <algorithm> 11 #include <utility> 12 13 #include "CipherStrategy.h" 14 #include "EncryptingOutputStream.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 #include "nsIAsyncOutputStream.h" 21 #include "nsIRandomGenerator.h" 22 #include "nsServiceManagerUtils.h" 23 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())); 34 35 MOZ_ASSERT(mBlockSize > 0); 36 MOZ_ASSERT(mBlockSize % CipherStrategy::BasicBlockSize == 0); 37 static_assert( 38 CipherStrategy::BlockPrefixLength % CipherStrategy::BasicBlockSize == 0); 39 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 46 #ifdef DEBUG 47 bool baseNonBlocking; 48 nsresult rv = (*mBaseStream)->IsNonBlocking(&baseNonBlocking); 49 MOZ_ASSERT(NS_SUCCEEDED(rv)); 50 if (baseNonBlocking) { 51 nsCOMPtr<nsIAsyncOutputStream> async = 52 do_QueryInterface((*mBaseStream).get()); 53 MOZ_ASSERT(!async); 54 } 55 #endif 56 } 57 58 template <typename CipherStrategy> 59 EncryptingOutputStream<CipherStrategy>::~EncryptingOutputStream() { 60 Close(); 61 } 62 63 template <typename CipherStrategy> 64 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Close() { 65 if (!mBaseStream) { 66 return NS_OK; 67 } 68 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))) { 73 return rv; 74 } 75 76 // XXX Maybe this Flush call can be removed, since the base stream is closed 77 // afterwards anyway. 78 rv = (*mBaseStream)->Flush(); 79 if (NS_WARN_IF(NS_FAILED(rv))) { 80 return rv; 81 } 82 83 // XXX What if closing the base stream failed? Fail this method, or at least 84 // log a warning? 85 (*mBaseStream)->Close(); 86 mBaseStream.destroy(); 87 88 mBuffer.Clear(); 89 mEncryptedBlock.reset(); 90 91 return NS_OK; 92 } 93 94 template <typename CipherStrategy> 95 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::Flush() { 96 if (!mBaseStream) { 97 return NS_BASE_STREAM_CLOSED; 98 } 99 100 if (!EnsureBuffers()) { 101 return NS_ERROR_OUT_OF_MEMORY; 102 } 103 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))) { 111 return rv; 112 } 113 } 114 115 return (*mBaseStream)->Flush(); 116 } 117 118 template <typename CipherStrategy> 119 NS_IMETHODIMP EncryptingOutputStream<CipherStrategy>::StreamStatus() { 120 if (!mBaseStream) { 121 return NS_BASE_STREAM_CLOSED; 122 } 123 return (*mBaseStream)->StreamStatus(); 124 } 125 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; 131 132 if (!mBaseStream) { 133 return NS_BASE_STREAM_CLOSED; 134 } 135 136 if (!EnsureBuffers()) { 137 return NS_ERROR_OUT_OF_MEMORY; 138 } 139 140 const size_t plainBufferSize = mEncryptedBlock->MaxPayloadLength(); 141 142 while (aCount > 0) { 143 // Determine how much space is left in our flat, plain buffer. 144 MOZ_ASSERT(mNextByte <= plainBufferSize); 145 uint32_t remaining = plainBufferSize - mNextByte; 146 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))) { 151 return rv; 152 } 153 154 // Now the entire buffer should be available for copying. 155 MOZ_ASSERT(!mNextByte); 156 remaining = plainBufferSize; 157 } 158 159 uint32_t numToRead = std::min(remaining, aCount); 160 uint32_t numRead = 0; 161 162 nsresult rv = 163 aReader(this, aClosure, reinterpret_cast<char*>(&mBuffer[mNextByte]), 164 *aBytesWrittenOut, numToRead, &numRead); 165 166 // As defined in nsIOutputStream.idl, do not pass reader func errors. 167 if (NS_FAILED(rv)) { 168 return NS_OK; 169 } 170 171 // End-of-file 172 if (numRead == 0) { 173 return NS_OK; 174 } 175 176 mNextByte += numRead; 177 *aBytesWrittenOut += numRead; 178 aCount -= numRead; 179 } 180 181 return NS_OK; 182 } 183 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()); 193 194 if (NS_WARN_IF(!mBuffer.SetLength(mEncryptedBlock->MaxPayloadLength(), 195 fallible))) { 196 return false; 197 } 198 } 199 200 return true; 201 } 202 203 template <typename CipherStrategy> 204 nsresult EncryptingOutputStream<CipherStrategy>::FlushToBaseStream() { 205 MOZ_ASSERT(mBaseStream); 206 207 if (!mNextByte) { 208 // Nothing to do. 209 return NS_OK; 210 } 211 212 if (mNextByte < mEncryptedBlock->MaxPayloadLength()) { 213 if (!mRandomGenerator) { 214 mRandomGenerator = 215 do_GetService("@mozilla.org/security/random-generator;1"); 216 if (NS_WARN_IF(!mRandomGenerator)) { 217 return NS_ERROR_FAILURE; 218 } 219 } 220 221 const auto payload = mEncryptedBlock->MutablePayload(); 222 223 const auto unusedPayload = payload.From(mNextByte); 224 225 nsresult rv = mRandomGenerator->GenerateRandomBytesInto( 226 unusedPayload.Elements(), unusedPayload.Length()); 227 if (NS_WARN_IF(NS_FAILED(rv))) { 228 return rv; 229 } 230 } 231 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. 237 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()); 243 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))) { 254 return rv; 255 } 256 257 mEncryptedBlock->SetActualPayloadLength(mNextByte); 258 259 mNextByte = 0; 260 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(), 265 &numWritten); 266 if (NS_WARN_IF(NS_FAILED(rv))) { 267 return rv; 268 } 269 MOZ_ASSERT(wholeBlock.Length() == numWritten); 270 271 return NS_OK; 272 } 273 274 } // namespace mozilla::dom::quota 275 276 #endif