/// <summary> /// Create decorator streams implementing the Encrypt-then-MAC scheme (CipherStream bound to a MacStream). /// </summary> /// <param name="item">Item to create resources for.</param> /// <param name="encryptor">Cipher stream (output).</param> /// <param name="authenticator">MAC stream (output).</param> protected void CreateEtMDecorator(PayloadItem item, out CipherStream encryptor, out MacStream authenticator) { byte[] encryptionKey, authenticationKey; if (item.SymmetricCipherKey.IsNullOrZeroLength() == false && item.AuthenticationKey.IsNullOrZeroLength() == false) { encryptionKey = item.SymmetricCipherKey; authenticationKey = item.AuthenticationKey; } else if (PayloadItemPreKeys.ContainsKey(item.Identifier)) { if (item.Authentication.KeySizeBits.HasValue == false) { throw new ConfigurationInvalidException( "Payload item authentication configuration is missing size specification of MAC key."); } KeyStretchingUtility.DeriveWorkingKeys(PayloadItemPreKeys[item.Identifier], item.SymmetricCipher.KeySizeBits / 8, item.Authentication.KeySizeBits.Value / 8, item.KeyDerivation, out encryptionKey, out authenticationKey); } else { throw new ItemKeyMissingException(item); } authenticator = new MacStream(PayloadStream, Writing, item.Authentication, authenticationKey, false); encryptor = new CipherStream(authenticator, Writing, item.SymmetricCipher, encryptionKey, false); }
/// <inheritdoc /> protected override void EmitHeader(MacStream authenticator) { int paddingLength = GetHeaderLength(); StratCom.EntropySupplier.NextBytes(_paddingBuffer, 0, paddingLength); authenticator.Write(_paddingBuffer, 0, paddingLength); Overhead += paddingLength; }
public CmsAuthenticatedDataOutputStream( MacStream macStream, BerSequenceGenerator cGen, BerSequenceGenerator authGen, BerSequenceGenerator eiGen) { this.macStream = macStream; this.cGen = cGen; this.authGen = authGen; this.eiGen = eiGen; }
private byte[] MacTest2(string FileName, byte[] IKm) { using (FileStream inStream = new FileStream(FileName, FileMode.Open)) { using (MacStream mac = new MacStream(new HMAC(new SHA512(), IKm))) { mac.Initialize(inStream); mac.IsConcurrent = false; return(mac.ComputeMac()); } } }
public MuxItemResourceContainer(CipherStream encryptor, MacStream authenticator, int?bufferCapacity) { Encryptor = encryptor; Authenticator = authenticator; if (bufferCapacity != null) { Buffer = new Lazy <RingBufferStream>(() => new RingBufferStream(bufferCapacity.Value, false)); } else { SkippedLength = 0; } }
protected void RunMacTest(MacFunction function, byte[] config = null, byte[] nonce = null, byte[] overrideKey = null, byte[] overrideSalt = null) { byte[] outputMac; var sw = new Stopwatch(); using (var output = new MemoryStream((int)LargeBinaryFile.Length)) { using (var macS = new MacStream(output, true, function, out outputMac, overrideKey ?? Key, overrideSalt ?? Salt, config, nonce, false)) { sw.Start(); LargeBinaryFile.CopyTo(macS); sw.Stop(); } } Debug.Print(outputMac.ToHexString()); Assert.Pass("{0:N0} ms ({1:N2} MB/s)", sw.ElapsedMilliseconds, ((double)LargeBinaryFile.Length / 1048576) / sw.Elapsed.TotalSeconds); }
/// <inheritdoc /> protected override void ConsumeHeader(MacStream authenticator) { int paddingLength = GetHeaderLength(); int bytesRead = authenticator.Read(_paddingBuffer, 0, paddingLength); if (bytesRead < paddingLength) { #if DEBUG string exStr = String.Format("Unable to read frameshift padding bytes ({0} bytes returned of a requested {1}).", bytesRead, paddingLength); #else string exStr = "Unable to read frameshift padding bytes."; #endif throw new IOException(exStr); } Overhead += paddingLength; }
private bool MacTest3(byte[] IKm) { byte[] data = new CSPPrng().GetBytes(33033); byte[] hash1; byte[] hash2; using (MacStream mac1 = new MacStream(new HMAC(new SHA512(), IKm))) { mac1.Initialize(new MemoryStream(data)); mac1.IsConcurrent = false; hash1 = mac1.ComputeMac(); } using (HMAC mac2 = new HMAC(new SHA512(), IKm)) hash2 = mac2.ComputeMac(data); return(Evaluate.AreEqual(hash1, hash2)); }
private void HmacDescriptionTest() { CSPPrng rng = new CSPPrng(); byte[] data = rng.GetBytes(rng.Next(100, 400)); byte[] key = rng.GetBytes(64); HMAC mac = new HMAC(Digests.SHA256); mac.Initialize(key); byte[] c1 = mac.ComputeMac(data); MacDescription mds = new MacDescription(64, Digests.SHA256); MacStream mst = new MacStream(mds, new KeyParams(key)); mst.Initialize(new MemoryStream(data)); byte[] c2 = mst.ComputeMac(); if (!Evaluate.AreEqual(c1, c2)) { throw new Exception("MacStreamTest: HMAC code arrays are not equal!"); } }
private void CmacDescriptionTest() { CSPPrng rng = new CSPPrng(); byte[] data = rng.GetBytes(rng.Next(100, 400)); byte[] key = rng.GetBytes(32); byte[] iv = rng.GetBytes(16); CMAC mac = new CMAC(BlockCiphers.Rijndael); mac.Initialize(key, iv); byte[] c1 = mac.ComputeMac(data); MacDescription mds = new MacDescription(32, BlockCiphers.Rijndael, IVSizes.V128, BlockSizes.B128, RoundCounts.R14); MacStream mst = new MacStream(mds, new KeyParams(key, iv)); mst.Initialize(new MemoryStream(data)); byte[] c2 = mst.ComputeMac(); if (!Evaluate.AreEqual(c1, c2)) { throw new Exception("MacStreamTest: CMAC code arrays are not equal!"); } }
/// <summary> /// Test the MacStream class implementation /// <para>Throws an Exception on failure</</para> /// </summary> public static void StreamMacTest() { byte[] data; byte[] key; MemoryStream instrm; MemoryStream outstrm = new MemoryStream(); using (KeyGenerator kg = new KeyGenerator()) { data = kg.GetBytes(512); key = kg.GetBytes(64); } // data to digest instrm = new MemoryStream(data); byte[] code1; byte[] code2; using (MacStream sm = new MacStream(new HMAC(new SHA512(), key))) { sm.Initialize(instrm); code1 = sm.ComputeMac(); } using (HMAC hm = new HMAC(new SHA512())) { hm.Initialize(key); code2 = hm.ComputeMac(data); } // compare the hash codes if (!Evaluate.AreEqual(code1, code2)) { throw new Exception(); } }
/// <inheritdoc /> protected override void FinishItem(PayloadItem item, CipherStream encryptor, MacStream authenticator) { if (Writing) { if (item.ExternalLength > 0 && encryptor.BytesIn != item.ExternalLength) { throw new InvalidDataException("Length written is not equal to predefined item external length."); } } else { if (encryptor.BytesIn != item.InternalLength) { throw new InvalidDataException("Length read is not equal to item internal length."); } if (encryptor.BytesOut != item.ExternalLength) { throw new InvalidDataException("Demultiplexed and decrypted length is not equal to specified item external length."); } encryptor.Close(); } if (Writing) { // Commit the determined internal length to item in payload manifest item.InternalLength = encryptor.BytesOut; EmitTrailer(authenticator); } else { ConsumeTrailer(authenticator); } // Final stages of Encrypt-then-MAC authentication scheme PayloadItem itemDto = item.CreateAuthenticatibleClone(); byte[] itemDtoAuthBytes = itemDto.SerialiseDto(); Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "FinishItem", "Item DTO length", itemDtoAuthBytes.Length)); if (Writing) { authenticator.Update(itemDtoAuthBytes, 0, itemDtoAuthBytes.Length); authenticator.Close(); // Commit the MAC to item in payload manifest item.AuthenticationVerifiedOutput = authenticator.Mac.DeepCopy(); } else { authenticator.Update(itemDtoAuthBytes, 0, itemDtoAuthBytes.Length); authenticator.Close(); // Verify the authenticity of the item ciphertext and configuration if (authenticator.Mac.SequenceEqual_ConstantTime(item.AuthenticationVerifiedOutput) == false) { // Verification failed! throw new CiphertextAuthenticationException("Payload item not authenticated."); } } // Release the item's resources (implicitly - no references remain) _activeItemResources.Remove(item.Identifier); // Mark the item as completed in the register ItemCompletionRegister[Index] = true; ItemsCompleted++; // Close the source/destination item.StreamBinding.Close(); Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "FinishItem", "[*** END OF ITEM", Index + " ***]")); }
protected override void ExecuteOperation() { Debug.Assert(ItemCompletionRegister[Index] == false); PayloadItem item = PayloadItems[Index]; Guid itemIdentifier = item.Identifier; bool skip = ItemSkipRegister != null && ItemSkipRegister.Contains(itemIdentifier); MuxItemResourceContainer itemContainer; bool activeResource = _activeItemResources.ContainsKey(itemIdentifier); if (activeResource) { itemContainer = _activeItemResources[itemIdentifier]; } else { if (skip == false) { itemContainer = CreateEtMSchemeResources(item); if (Writing) { EmitHeader(itemContainer.Authenticator); } else { ConsumeHeader(itemContainer.Authenticator); } } else { itemContainer = new MuxItemResourceContainer(null, null, null); } _activeItemResources.Add(itemIdentifier, itemContainer); } int opLength = NextOperationLength(); if (skip == false) { CipherStream itemEncryptor = itemContainer.Encryptor; MacStream itemAuthenticator = itemContainer.Authenticator; if (Writing) { // Writing/multiplexing if (itemEncryptor.BytesIn + opLength < item.ExternalLength) { // Normal operation itemEncryptor.WriteExactly(item.StreamBinding, opLength); } else { // Final operation, or just prior to if (itemContainer.Buffer.IsValueCreated == false) { // Redirect final ciphertext to buffer to account for possible expansion itemAuthenticator.ReassignBinding(itemContainer.Buffer.Value, false, finish: false); } var remaining = (int)(item.ExternalLength - itemEncryptor.BytesIn); if (remaining > 0) { while (remaining > 0) { int toRead = Math.Min(remaining, BufferSize); int iterIn = item.StreamBinding.Read(Buffer, 0, toRead); if (iterIn < toRead) { throw new EndOfStreamException(); } itemEncryptor.Write(Buffer, 0, iterIn); // Writing into recently-lazy-inited buffer remaining -= iterIn; } itemEncryptor.Close(); } var toWrite = (int)Math.Min(opLength, itemContainer.Buffer.Value.Length); Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "ExecuteOperation", "Multiplexing item: final stripe length", toWrite)); itemContainer.Buffer.Value.ReadTo(PayloadStream, toWrite); } } else { // Reading/demultiplexing long readRemaining = item.InternalLength - itemEncryptor.BytesIn; bool finalOp = false; if (readRemaining <= opLength) { // Final operation opLength = (int)readRemaining; finalOp = true; Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "ExecuteOperation", "Demultiplexing item: final stripe length", opLength)); } itemEncryptor.ReadExactly(item.StreamBinding, opLength, finalOp); } if ((Writing && itemEncryptor.BytesIn >= item.ExternalLength && itemContainer.Buffer.Value.Length == 0) || (Writing == false && itemEncryptor.BytesIn >= item.InternalLength)) { // Now that we're finished we need to do some extra things, then clean up FinishItem(item, itemEncryptor, itemAuthenticator); } } else { // Skipping Debug.Assert(Writing == false, "Should not be skipping when writing!"); if (itemContainer.SkippedLength == 0) { // Start of item PayloadStream.Seek(opLength, SeekOrigin.Current); itemContainer.SkippedLength += opLength; } else if (itemContainer.SkippedLength + opLength >= item.InternalLength) { int remainingToSkip = (int)(item.InternalLength - itemContainer.SkippedLength); itemContainer.SkippedLength += remainingToSkip; PayloadStream.Seek(remainingToSkip + GetTrailerLength(), SeekOrigin.Current); // "Finish" item _activeItemResources.Remove(item.Identifier); // Mark the item as completed in the register ItemCompletionRegister[Index] = true; ItemsCompleted++; Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "ExecuteOperation", "[*** SKIPPED ITEM", Index + " ***]")); } else { PayloadStream.Seek(opLength, SeekOrigin.Current); itemContainer.SkippedLength += opLength; } } }
/// <inheritdoc /> protected override void ConsumeTrailer(MacStream authenticator) { ConsumeHeader(authenticator); }
/// <inheritdoc /> protected override void EmitTrailer(MacStream authenticator) { EmitHeader(authenticator); }
/// <summary> /// Finish processing the current item. /// </summary> /// <param name="item">Payload item to finish.</param> /// <param name="encryptor">Item encryptor/cipher.</param> /// <param name="authenticator">Item authenticator/MAC.</param> protected abstract void FinishItem(PayloadItem item, CipherStream encryptor, MacStream authenticator);
internal CmsTypedStream GetContentFromSessionKey( KeyParameter sKey) { try { Stream content = data; if (encAlg != null) { IBufferedCipher cipher = CipherUtilities.GetCipher(encAlg.ObjectID); Asn1Encodable asn1Enc = encAlg.Parameters; Asn1Object asn1Params = asn1Enc == null ? null : asn1Enc.ToAsn1Object(); ICipherParameters cipherParameters = sKey; if (asn1Params != null && !(asn1Params is Asn1Null)) { cipherParameters = ParameterUtilities.GetCipherParameters( encAlg.ObjectID, cipherParameters, asn1Params); } else { string alg = encAlg.ObjectID.Id; if (alg.Equals(CmsEnvelopedDataGenerator.DesEde3Cbc) || alg.Equals(CmsEnvelopedDataGenerator.IdeaCbc) || alg.Equals(CmsEnvelopedDataGenerator.Cast5Cbc)) { cipherParameters = new ParametersWithIV(cipherParameters, new byte[8]); } } cipher.Init(false, cipherParameters); content = new CipherStream(content, cipher, null); } // If authenticated, need to wrap in MacStream to calculate MAC if (macAlg != null) { content = this.macStream = CreateMacStream(macAlg, sKey, content); } if (authEncAlg != null) { // TODO Create AEAD cipher instance to decrypt and calculate tag ( MAC) throw new CmsException("AuthEnveloped data decryption not yet implemented"); // RFC 5084 ASN.1 Module // -- Parameters for AigorithmIdentifier // // CCMParameters ::= SEQUENCE { // aes-nonce OCTET STRING (SIZE(7..13)), // aes-ICVlen AES-CCM-ICVlen DEFAULT 12 } // // AES-CCM-ICVlen ::= INTEGER (4 | 6 | 8 | 10 | 12 | 14 | 16) // // GCMParameters ::= SEQUENCE { // aes-nonce OCTET STRING, -- recommended size is 12 octets // aes-ICVlen AES-GCM-ICVlen DEFAULT 12 } // // AES-GCM-ICVlen ::= INTEGER (12 | 13 | 14 | 15 | 16) } return(new CmsTypedStream(content)); } catch (SecurityUtilityException e) { throw new CmsException("couldn't create cipher.", e); } catch (InvalidKeyException e) { throw new CmsException("key invalid in message.", e); } catch (IOException e) { throw new CmsException("error decoding algorithm parameters.", e); } }
protected Stream Open( Stream outStr, AlgorithmIdentifier macAlgId, ICipherParameters cipherParameters, Asn1EncodableVector recipientInfos) { try { // // ContentInfo // BerSequenceGenerator cGen = new BerSequenceGenerator(outStr); cGen.AddObject(CmsObjectIdentifiers.AuthenticatedData); // // Authenticated Data // BerSequenceGenerator authGen = new BerSequenceGenerator( cGen.GetRawOutputStream(), 0, true); authGen.AddObject(new DerInteger(AuthenticatedData.CalculateVersion(null))); Stream authRaw = authGen.GetRawOutputStream(); Asn1Generator recipGen = _berEncodeRecipientSet ? (Asn1Generator) new BerSetGenerator(authRaw) : new DerSetGenerator(authRaw); foreach (Asn1Encodable ae in recipientInfos) { recipGen.AddObject(ae); } recipGen.Close(); authGen.AddObject(macAlgId); BerSequenceGenerator eiGen = new BerSequenceGenerator(authRaw); eiGen.AddObject(CmsObjectIdentifiers.Data); Stream octetOutputStream = CmsUtilities.CreateBerOctetOutputStream( eiGen.GetRawOutputStream(), 0, false, _bufferSize); IMac mac = MacUtilities.GetMac(macAlgId.ObjectID); // TODO Confirm no ParametersWithRandom needed mac.Init(cipherParameters); MacStream mOut = new MacStream(octetOutputStream, null, mac); return(new CmsAuthenticatedDataOutputStream(mOut, cGen, authGen, eiGen)); } catch (SecurityUtilityException e) { throw new CmsException("couldn't create cipher.", e); } catch (InvalidKeyException e) { throw new CmsException("key invalid in message.", e); } catch (IOException e) { throw new CmsException("exception decoding algorithm parameters.", e); } }
/// <summary> /// Read an item trailer from the payload stream. /// </summary> /// <param name="authenticator"> /// Authenticator for the item, if trailer is to be authenticated. /// </param> protected virtual void ConsumeTrailer(MacStream authenticator) { // Unused in this version // Could throw an exception in an implementation where a trailer must be present }
/// <summary> /// Generate and write an item trailer into the payload stream. /// </summary> /// <param name="authenticator"> /// Authenticator for the item, if trailer is to be authenticated. /// </param> protected virtual void EmitTrailer(MacStream authenticator) { // Unused in this version }
/// <summary> /// Close the item decorator, check lengths, authenticate the item (emit or verify), /// and if writing, commit the authentication value to the payload item DTO. /// </summary> /// <param name="item">Payload item to finish.</param> /// <param name="encryptor">Item encryptor/cipher.</param> /// <param name="authenticator">Item authenticator/MAC.</param> protected override void FinishItem(PayloadItem item, CipherStream encryptor, MacStream authenticator) { try { encryptor.Close(); } catch (Exception e) { throw new Exception("Unknown error when finalising/closing cipher.", e); } try { if (Writing) { EmitTrailer(authenticator); } else { ConsumeTrailer(authenticator); } } catch (Exception e) { throw new Exception(String.Format("Unknown error when {0} item trailer.", Writing ? "emitting" : "consuming"), e); } // Length checks & commits if (Writing) { // Check if pre-stated length matches what was actually written if (item.ExternalLength > 0 && encryptor.BytesIn != item.ExternalLength) { throw new InvalidDataException( "Mismatch between stated item external length and actual input length."); } // Commit the determined internal length to item in payload manifest item.InternalLength = encryptor.BytesOut; } else { if (encryptor.BytesIn != item.InternalLength) { throw new InvalidOperationException("Probable decorator stack malfunction."); } if (encryptor.BytesOut != item.ExternalLength) { throw new InvalidDataException( "Mismatch between stated item external length and actual output length."); } } // Final stages of Encrypt-then-MAC authentication scheme PayloadItem itemDto = item.CreateAuthenticatibleClone(); byte[] itemDtoAuthBytes = itemDto.SerialiseDto(); #if PRINT_DTO_LENGTH Debug.Print(DebugUtility.CreateReportString("SimplePayloadMux", "FinishItem", "Payload item DTO length", itemDtoAuthBytes.Length)); #endif authenticator.Update(itemDtoAuthBytes, 0, itemDtoAuthBytes.Length); authenticator.Close(); // Authentication if (Writing) { // Commit the MAC to item in payload manifest item.AuthenticationVerifiedOutput = authenticator.Mac.DeepCopy(); } else { // Verify the authenticity of the item ciphertext and configuration if (authenticator.Mac.SequenceEqual_ConstantTime(item.AuthenticationVerifiedOutput) == false) { // Verification failed! throw new CiphertextAuthenticationException("Payload item not authenticated."); } } // Close the source/destination item.StreamBinding.Close(); // Mark the item as completed in the register ItemCompletionRegister[Index] = true; ItemsCompleted++; Debug.Print(DebugUtility.CreateReportString("SimplePayloadMux", "ExecuteOperation", "[*** END OF ITEM", String.Format("{0} ({1}) ***]", Index, item.Identifier))); }
/** * generate an enveloped object that contains an CMS Enveloped Data * object using the given provider and the passed in key generator. */ private CmsAuthenticatedData Generate( CmsProcessable content, string macOid, CipherKeyGenerator keyGen) { AlgorithmIdentifier macAlgId; KeyParameter encKey; Asn1OctetString encContent; Asn1OctetString macResult; try { // FIXME Will this work for macs? byte[] encKeyBytes = keyGen.GenerateKey(); encKey = ParameterUtilities.CreateKeyParameter(macOid, encKeyBytes); Asn1Encodable asn1Params = GenerateAsn1Parameters(macOid, encKeyBytes); ICipherParameters cipherParameters; macAlgId = GetAlgorithmIdentifier( macOid, encKey, asn1Params, out cipherParameters); IMac mac = MacUtilities.GetMac(macOid); // TODO Confirm no ParametersWithRandom needed // FIXME Only passing key at the moment // mac.Init(cipherParameters); mac.Init(encKey); MemoryStream bOut = new MemoryStream(); MacStream mOut = new MacStream(bOut, null, mac); content.Write(mOut); mOut.Close(); bOut.Close(); encContent = new BerOctetString(bOut.ToArray()); byte[] macOctets = MacUtilities.DoFinal(mOut.WriteMac()); macResult = new DerOctetString(macOctets); } catch (SecurityUtilityException e) { throw new CmsException("couldn't create cipher.", e); } catch (InvalidKeyException e) { throw new CmsException("key invalid in message.", e); } catch (IOException e) { throw new CmsException("exception decoding algorithm parameters.", e); } Asn1EncodableVector recipientInfos = new Asn1EncodableVector(); foreach (RecipientInfoGenerator rig in recipientInfoGenerators) { try { recipientInfos.Add(rig.Generate(encKey, rand)); } catch (InvalidKeyException e) { throw new CmsException("key inappropriate for algorithm.", e); } catch (GeneralSecurityException e) { throw new CmsException("error making encrypted content.", e); } } ContentInfo eci = new ContentInfo(CmsObjectIdentifiers.Data, encContent); ContentInfo contentInfo = new ContentInfo( CmsObjectIdentifiers.AuthenticatedData, new AuthenticatedData(null, new DerSet(recipientInfos), macAlgId, null, eci, null, macResult, null)); return(new CmsAuthenticatedData(contentInfo)); }
/// <summary> /// Reads the manifest from the package. /// </summary> /// <remarks> /// Call method, supplying (all of) only the keys associated with the sender and the context. /// This maximises the chance that: <br /> /// <list type="number"> /// <item> /// <description> /// The package will be successfully decrypted if multiple /// keys are in use by both parties. /// </description> /// </item> /// <item> /// <description> /// Minimises the time spent validating potential key pairs. /// </description> /// </item> /// </list> /// </remarks> /// <param name="keyProvider">Provider to get possible keys for the manifest from.</param> /// <param name="manifestScheme">Cryptography scheme used in the manifest.</param> /// <returns>Package manifest object.</returns> /// <exception cref="ArgumentException">Key provider absent or could not supply any keys.</exception> /// <exception cref="NotSupportedException">Manifest cryptography scheme unsupported/unknown or missing.</exception> /// <exception cref="CryptoException"> /// A cryptographic operation failed (additional data maybe available in <see cref="CryptoException.InnerException" /> /// ). /// </exception> /// <exception cref="KeyConfirmationException"> /// Key confirmation failed to determine a key, or failed unexpectedly /// (additional data maybe available in <see cref="KeyConfirmationException.InnerException" />) /// </exception> /// <exception cref="InvalidDataException"> /// Deserialisation of manifest failed unexpectedly (manifest malformed, or incorrect key). /// </exception> /// <exception cref="CiphertextAuthenticationException">Manifest not authenticated.</exception> private Manifest ReadManifest(IKeyProvider keyProvider, ManifestCryptographyScheme manifestScheme) { // Determine the pre-key for the package manifest decryption (different schemes use different approaches) byte[] preMKey = null; switch (manifestScheme) { case ManifestCryptographyScheme.SymmetricOnly: { if (keyProvider.SymmetricKeys.Any() == false) { throw new ArgumentException("No symmetric keys available for decryption of this manifest.", "keyProvider"); } SymmetricKey symmetricKey = null; if (_manifestCryptoConfig.KeyConfirmation != null) { try { symmetricKey = ConfirmationUtility.ConfirmKeyFromCanary( ((SymmetricManifestCryptographyConfiguration)_manifestCryptoConfig).KeyConfirmation, _manifestCryptoConfig.KeyConfirmationVerifiedOutput, keyProvider.SymmetricKeys); } catch (Exception e) { throw new KeyConfirmationException("Key confirmation failed in an unexpected way.", e); } } else { if (keyProvider.SymmetricKeys.Count() > 1) { // Possibly allow to proceed anyway and just look for a serialisation failure? (not implemented) throw new ArgumentException( "Multiple symmetric keys are available, but confirmation is unavailable.", "keyProvider", new ConfigurationInvalidException("Package manifest includes no key confirmation data.")); } preMKey = keyProvider.SymmetricKeys.First().Key; } if (symmetricKey != null) { preMKey = symmetricKey.Key; } break; } case ManifestCryptographyScheme.Um1Hybrid: { ECKey um1SenderKey; ECKeypair um1RecipientKeypair; ECKey um1EphemeralKey = ((Um1HybridManifestCryptographyConfiguration)_manifestCryptoConfig).EphemeralKey; if (_manifestCryptoConfig.KeyConfirmation != null) { try { ConfirmationUtility.ConfirmKeyFromCanary(_manifestCryptoConfig.KeyConfirmation, _manifestCryptoConfig.KeyConfirmationVerifiedOutput, keyProvider.ForeignEcKeys, um1EphemeralKey, keyProvider.EcKeypairs, out um1SenderKey, out um1RecipientKeypair); } catch (Exception e) { throw new KeyConfirmationException("Key confirmation failed in an unexpected way.", e); } } else { // No key confirmation capability available if (keyProvider.ForeignEcKeys.Count() > 1 || keyProvider.EcKeypairs.Count() > 1) { throw new KeyConfirmationException( "Multiple EC keys have been provided where the package provides no key confirmation capability."); } um1SenderKey = keyProvider.ForeignEcKeys.First(); um1RecipientKeypair = keyProvider.EcKeypairs.First(); } // Perform the UM1 key agreement try { preMKey = Um1Exchange.Respond(um1SenderKey, um1RecipientKeypair.GetPrivateKey(), um1EphemeralKey); } catch (Exception e) { throw new CryptoException("Unexpected error in UM1 key agreement.", e); } break; } default: throw new NotSupportedException( String.Format("Manifest cryptography scheme \"{0}\" is unsupported/unknown.", manifestScheme)); } if (preMKey.IsNullOrZeroLength()) { throw new KeyConfirmationException(String.Format( "None of the keys provided to decrypt the manifest (cryptographic scheme: {0}) were confirmed as being able to do so.", manifestScheme)); } Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest pre-key", preMKey.ToHexString())); // Derive working manifest encryption & authentication keys from the manifest pre-key byte[] workingManifestCipherKey, workingManifestMacKey; try { int cipherKeySizeBytes = _manifestCryptoConfig.SymmetricCipher.KeySizeBits.BitsToBytes(); if (_manifestCryptoConfig.Authentication.KeySizeBits.HasValue == false) { throw new ConfigurationInvalidException("Manifest authentication key size is missing."); } int macKeySizeBytes = _manifestCryptoConfig.Authentication.KeySizeBits.Value.BitsToBytes(); // Derive working cipher and MAC keys from the pre-key KeyStretchingUtility.DeriveWorkingKeys( preMKey, cipherKeySizeBytes, macKeySizeBytes, _manifestCryptoConfig.KeyDerivation, out workingManifestCipherKey, out workingManifestMacKey); } catch (Exception e) { throw new CryptoException("Unexpected error in manifest key derivation.", e); // TODO: make a specialised exception to communicate the failure type } // Clear the manifest pre-key preMKey.SecureWipe(); Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest MAC working key", workingManifestMacKey.ToHexString())); Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest cipher working key", workingManifestCipherKey.ToHexString())); Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest length prefix offset (absolute)", _readingStream.Position)); // Read manifest length prefix var manifestLengthLe = new byte[sizeof(UInt32)]; // in little-endian form int manifestLengthBytesRead = _readingStream.Read(manifestLengthLe, 0, sizeof(UInt32)); if (manifestLengthBytesRead != sizeof(UInt32)) { throw new DataLengthException("Manifest length prefix could not be read. Insufficient data."); } manifestLengthLe.XorInPlaceInternal(0, workingManifestMacKey, 0, sizeof(UInt32)); // deobfuscate length UInt32 mlUInt = manifestLengthLe.LittleEndianToUInt32(); var manifestLength = (int)mlUInt; Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest length", manifestLength)); Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifest", "Manifest offset (absolute)", _readingStream.Position)); /* Read manifest */ Manifest manifest; using (var decryptedManifestStream = new MemoryStream(manifestLength)) { byte[] manifestMac; try { using ( var authenticator = new MacStream(_readingStream, false, _manifestCryptoConfig.Authentication, out manifestMac, workingManifestMacKey, false)) { using (var cs = new CipherStream(authenticator, false, _manifestCryptoConfig.SymmetricCipher, workingManifestCipherKey, false)) { cs.ReadExactly(decryptedManifestStream, manifestLength, true); } // Authenticate manifest length tag authenticator.Update(manifestLengthLe, 0, manifestLengthLe.Length); Contract.Assert(authenticator.BytesIn == manifestLength); byte[] manifestCryptoDtoForAuth; switch (manifestScheme) { case ManifestCryptographyScheme.SymmetricOnly: manifestCryptoDtoForAuth = ((SymmetricManifestCryptographyConfiguration)_manifestCryptoConfig) .CreateAuthenticatibleClone().SerialiseDto(); break; case ManifestCryptographyScheme.Um1Hybrid: manifestCryptoDtoForAuth = ((Um1HybridManifestCryptographyConfiguration)_manifestCryptoConfig) .CreateAuthenticatibleClone().SerialiseDto(); break; default: throw new NotSupportedException(); } // Authenticate manifest cryptography configuration (from manifest header) authenticator.Update(manifestCryptoDtoForAuth, 0, manifestCryptoDtoForAuth.Length); } } catch (Exception e) { throw new CryptoException("Unexpected error in manifest decrypt-then-MAC operation.", e); } // Verify that manifest authenticated successfully if (manifestMac.SequenceEqual_ConstantTime(_manifestCryptoConfig.AuthenticationVerifiedOutput) == false) { throw new CiphertextAuthenticationException("Manifest failed authentication."); } decryptedManifestStream.Seek(0, SeekOrigin.Begin); try { manifest = decryptedManifestStream.DeserialiseDto <Manifest>(false); } catch (Exception e) { throw new InvalidDataException("Manifest failed to deserialise.", e); } } _readingPayloadStreamOffset = _readingStream.Position; // Clear the manifest encryption & authentication keys workingManifestCipherKey.SecureWipe(); workingManifestMacKey.SecureWipe(); return(manifest); }