/// <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 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); } }); } }