/// <summary> /// Write a copy of the current encrypted stream. Used to change meta-data /// and encryption key(s) etc. /// </summary> /// <param name="outputStream"></param> public void CopyEncryptedTo(DocumentHeaders outputDocumentHeaders, Stream cipherStream, ProgressContext progress) { if (outputDocumentHeaders == null) { throw new ArgumentNullException("outputDocumentHeaders"); } if (cipherStream == null) { throw new ArgumentNullException("cipherStream"); } if (!cipherStream.CanSeek) { throw new ArgumentException("The output stream must support seek in order to back-track and write the HMAC."); } if (DocumentHeaders == null) { throw new InternalErrorException("Document headers are not loaded"); } using (HmacStream hmacStreamOutput = new HmacStream(outputDocumentHeaders.HmacSubkey.Key, cipherStream)) { outputDocumentHeaders.WriteWithHmac(hmacStreamOutput); using (AxCryptDataStream encryptedDataStream = _reader.CreateEncryptedDataStream(DocumentHeaders.HmacSubkey.Key, DocumentHeaders.CipherTextLength, progress)) { CopyToWithCount(encryptedDataStream, hmacStreamOutput, progress); if (_reader.Hmac != DocumentHeaders.Hmac) { throw new Axantum.AxCrypt.Core.Runtime.InvalidDataException("HMAC validation error in the input stream.", ErrorStatus.HmacValidationError); } } outputDocumentHeaders.Hmac = hmacStreamOutput.HmacResult; // Rewind and rewrite the headers, now with the updated HMAC outputDocumentHeaders.WriteWithoutHmac(cipherStream); cipherStream.Position = cipherStream.Length; } }
/// <summary> /// Encrypt a stream with a given set of headers and write to an output stream. The caller is responsible for consistency and completeness /// of the headers. Headers that are not known until encryption and compression are added here. /// </summary> /// <param name="outputDocumentHeaders"></param> /// <param name="inputStream"></param> /// <param name="outputStream"></param> public void EncryptTo(DocumentHeaders outputDocumentHeaders, Stream inputStream, Stream outputStream, AxCryptOptions options, ProgressContext progress) { if (outputDocumentHeaders == null) { throw new ArgumentNullException("outputDocumentHeaders"); } if (inputStream == null) { throw new ArgumentNullException("inputStream"); } if (outputStream == null) { throw new ArgumentNullException("outputStream"); } if (progress == null) { throw new ArgumentNullException("progress"); } if (!outputStream.CanSeek) { throw new ArgumentException("The output stream must support seek in order to back-track and write the HMAC."); } if (options.HasMask(AxCryptOptions.EncryptWithCompression) && options.HasMask(AxCryptOptions.EncryptWithoutCompression)) { throw new ArgumentException("Invalid options, cannot specify both with and without compression."); } if (!options.HasMask(AxCryptOptions.EncryptWithCompression) && !options.HasMask(AxCryptOptions.EncryptWithoutCompression)) { throw new ArgumentException("Invalid options, must specify either with or without compression."); } bool isCompressed = options.HasMask(AxCryptOptions.EncryptWithCompression); outputDocumentHeaders.IsCompressed = isCompressed; outputDocumentHeaders.WriteWithoutHmac(outputStream); using (ICryptoTransform encryptor = DataCrypto.CreateEncryptingTransform()) { long outputStartPosition = outputStream.Position; using (CryptoStream encryptingStream = new CryptoStream(new NonClosingStream(outputStream), encryptor, CryptoStreamMode.Write)) { if (isCompressed) { EncryptWithCompressionInternal(outputDocumentHeaders, inputStream, encryptingStream, progress); } else { outputDocumentHeaders.PlaintextLength = CopyToWithCount(inputStream, encryptingStream, progress); } } outputStream.Flush(); outputDocumentHeaders.CipherTextLength = outputStream.Position - outputStartPosition; using (HmacStream outputHmacStream = new HmacStream(outputDocumentHeaders.HmacSubkey.Key, outputStream)) { outputDocumentHeaders.WriteWithHmac(outputHmacStream); outputHmacStream.ReadFrom(outputStream); outputDocumentHeaders.Hmac = outputHmacStream.HmacResult; } // Rewind and rewrite the headers, now with the updated HMAC outputDocumentHeaders.WriteWithoutHmac(outputStream); outputStream.Position = outputStream.Length; } }
public AxCryptDataStream CreateEncryptedDataStream(AesKey hmacKey, long cipherTextLength, ProgressContext progress) { if (hmacKey == null) { throw new ArgumentNullException("hmacKey"); } if (progress == null) { throw new ArgumentNullException("progress"); } if (_disposed) { throw new ObjectDisposedException(GetType().FullName); } if (CurrentItemType != AxCryptItemType.Data) { throw new InvalidOperationException("GetEncryptedDataStream() was called when the reader is not positioned at the data."); } CurrentItemType = AxCryptItemType.EndOfStream; _hmacStream = new HmacStream(hmacKey); _hmacBufferStream.Position = 0; _hmacBufferStream.CopyTo(_hmacStream, OS.Current.StreamBufferSize); _expectedTotalHmacLength = _hmacBufferStream.Length + cipherTextLength; AxCryptDataStream encryptedDataStream = new AxCryptDataStream(_inputStream, _hmacStream, cipherTextLength); return encryptedDataStream; }
private void Dispose(bool disposing) { if (_disposed) { return; } if (disposing) { if (_inputStream != null) { _inputStream.Dispose(); _inputStream = null; } if (_hmacBufferStream != null) { _hmacBufferStream.Dispose(); _hmacBufferStream = null; } if (_hmacStream != null) { _hmacStream.Dispose(); _hmacStream = null; } _disposed = true; } }
public static void TestHmacStream() { Assert.Throws<ArgumentNullException>(() => { using (HmacStream hmacStream = new HmacStream(null)) { } }); AesKey key = new AesKey(new byte[16]); using (HmacStream hmacStream = new HmacStream(key)) { Assert.That(hmacStream.CanRead, Is.False, "HmacStream does not support reading."); Assert.That(hmacStream.CanSeek, Is.False, "HmacStream does not support seeking."); Assert.That(hmacStream.CanWrite, Is.True, "HmacStream does support writing."); Assert.Throws<NotSupportedException>(() => { byte[] buffer = new byte[5]; hmacStream.Read(buffer, 0, buffer.Length); }); Assert.Throws<NotSupportedException>(() => { hmacStream.Seek(0, SeekOrigin.Begin); }); Assert.Throws<NotSupportedException>(() => { hmacStream.SetLength(0); }); Assert.Throws<ArgumentNullException>(() => { hmacStream.ReadFrom(null); }); hmacStream.Write(new byte[10], 0, 10); using (Stream dataStream = new MemoryStream()) { dataStream.Write(new byte[10], 0, 10); dataStream.Position = 0; hmacStream.ReadFrom(dataStream); } Assert.That(hmacStream.Position, Is.EqualTo(20), "There are 20 bytes written so the position should be 20."); Assert.That(hmacStream.Length, Is.EqualTo(20), "There are 20 bytes written so the position should be 20."); hmacStream.Flush(); Assert.That(hmacStream.Position, Is.EqualTo(20), "Nothing should change after Flush(), this is not a buffered stream."); Assert.That(hmacStream.Length, Is.EqualTo(20), "Nothing should change after Flush(), this is not a buffered stream."); Assert.Throws<NotSupportedException>(() => { hmacStream.Position = 0; }, "Position is not supported."); DataHmac dataHmac = hmacStream.HmacResult; Assert.That(dataHmac.GetBytes(), Is.EquivalentTo(new byte[] { 0x62, 0x6f, 0x2c, 0x61, 0xc7, 0x68, 0x00, 0xb3, 0xa6, 0x8d, 0xf9, 0x55, 0x95, 0xbc, 0x1f, 0xd1 }), "The HMAC of 20 bytes of zero with 128-bit AesKey all zero should be this."); Assert.Throws<InvalidOperationException>(() => { hmacStream.Write(new byte[1], 0, 1); }, "Can't write to the stream after checking and thus finalizing the HMAC"); // This also implicitly covers double-dispose since we're in a using block. hmacStream.Dispose(); Assert.Throws<ObjectDisposedException>(() => { DataHmac invalidDataHmac = hmacStream.HmacResult; // Remove FxCop warning Object.Equals(invalidDataHmac, null); }); Assert.Throws<ObjectDisposedException>(() => { hmacStream.Write(new byte[1], 0, 1); }); Assert.Throws<ObjectDisposedException>(() => { using (Stream stream = new MemoryStream()) { hmacStream.ReadFrom(stream); } }); } }