/// <summary> /// Determine the index of the next stream to use in an I/O operation /// (whether to completion or otherwise, depending on implementation). /// </summary> /// <remarks>May be overriden in a derived class to provide for advanced stream selection logic.</remarks> /// <returns>The next stream index.</returns> protected virtual void NextSource() { if (++Index == PayloadItems.Count) { Index = 0; } Debug.Print(DebugUtility.CreateReportString("PayloadMux", "NextSource", "Generated index", Index)); }
/// <summary> /// If variable striping mode is enabled, advances the state of the selection CSPRNG, /// and returns the output to be used as the length of the next operation. /// Otherwise, fixed length is returned. /// </summary> /// <returns>Operation length to perform.</returns> private int NextOperationLength() { int operationLength = _stripeMode == FabricStripeMode.VariableLength ? EntropySource.Next(_minStripe, _maxStripe) : _maxStripe; Debug.Print(DebugUtility.CreateReportString("FabricPayloadMux", "NextOperationLength", "Generated stripe length value", operationLength)); return(operationLength); }
private int NextPaddingLength() { int paddingLength = (_paddingMode == FrameshiftPaddingMode.VariableLength) ? EntropySource.NextPositive(_minPadding, _maxPadding) : _maxPadding; Debug.Print(DebugUtility.CreateReportString("FrameshiftPayloadMux", "NextPaddingLength", "Generated length value", paddingLength)); return(paddingLength); }
/// <summary> /// Executes multiplexing operations until source(s) are exhausted. /// </summary> public void Execute() { while (ItemsCompleted < PayloadItems.Count) { do { NextSource(); } while (ItemsCompleted < PayloadItems.Count && ItemCompletionRegister[Index]); Debug.Print(DebugUtility.CreateReportString("PayloadMux", "Execute", "Selected stream", Index)); ExecuteOperation(); } }
/// <summary> /// Generate a verified output of a function given the correct key, to be used as a key confirmation. /// Uses confirmation canary. /// </summary> /// <param name="configuration">Configuration of the verification function.</param> /// <param name="senderKeypair">Sender keypair to generate a confirmation output verification for.</param> /// <param name="recipientKey">Recipient key to generate a confirmation output verification for.</param> /// <returns>Output of the verification function, given the correct key.</returns> /// <exception cref="ArgumentException">Key is null or zero-length.</exception> /// <seealso cref="SymmetricKey"/> /// <seealso cref="ECKeypair"/> /// <seealso cref="IPossessConfirmationCanary"/> public static byte[] GenerateVerifiedOutput(AuthenticationConfiguration configuration, ECKeypair senderKeypair, ECKey recipientKey) { Func <byte[], byte[]> validator = GetValidator(configuration, TagConstantBytes, configuration.SerialiseDto()); byte[] canary = XorCanaryBytes(senderKeypair.ConfirmationCanary, recipientKey.ConfirmationCanary); byte[] verifiedOutput = validator(canary); Debug.Print(DebugUtility.CreateReportString("ConfirmationUtility", "GenerateVerifiedOutput", "Verified output", verifiedOutput.ToHexString())); return(verifiedOutput); }
protected override void ExecuteOperation() { PayloadItem item = PayloadItems[Index]; bool skip = ItemSkipRegister != null && ItemSkipRegister.Contains(item.Identifier); if (skip == false) { CipherStream itemEncryptor; MacStream itemAuthenticator; CreateEtMDecorator(item, out itemEncryptor, out itemAuthenticator); if (Writing) { EmitHeader(itemAuthenticator); } else { ConsumeHeader(itemAuthenticator); } if (Writing) { int iterIn; do { iterIn = item.StreamBinding.Read(Buffer, 0, BufferSize); itemEncryptor.Write(Buffer, 0, iterIn); } while (iterIn > 0); } else { itemEncryptor.ReadExactly(item.StreamBinding, item.InternalLength, true); } FinishItem(item, itemEncryptor, itemAuthenticator); } else { // Skipping long skipLength = GetHeaderLength() + item.InternalLength + GetTrailerLength(); PayloadStream.Seek(skipLength, SeekOrigin.Current); // Mark the item as completed in the register ItemCompletionRegister[Index] = true; ItemsCompleted++; Debug.Print(DebugUtility.CreateReportString("SimplePayloadMux", "ExecuteOperation", "[*** SKIPPED ITEM", String.Format("{0} ({1}) ***]", Index, item.Identifier))); } }
/// <summary> /// Generate a verified output of a function given the correct canary, to be used as a key confirmation. /// </summary> /// <param name="configuration">Configuration of the verification function.</param> /// <param name="canary">Confirmation canary to generate a confirmation output verification for.</param> /// <returns>Output of the verification function, given the correct canary.</returns> /// <exception cref="ArgumentException">Key is null or zero-length.</exception> /// <seealso cref="SymmetricKey"/> /// <seealso cref="ECKeypair"/> /// <seealso cref="IPossessConfirmationCanary"/> public static byte[] GenerateVerifiedOutput(AuthenticationConfiguration configuration, byte[] canary) { if (canary.IsNullOrZeroLength()) { throw new ArgumentException("Canary is null or zero-length.", "canary"); } Func <byte[], byte[]> validator = GetValidator(configuration, TagConstantBytes, configuration.SerialiseDto()); byte[] verifiedOutput = validator(canary); Debug.Print(DebugUtility.CreateReportString("ConfirmationUtility", "GenerateVerifiedOutput", "Verified output", verifiedOutput.ToHexString())); return(verifiedOutput); }
public void GenerateSeed(byte[] buffer, int offset, int count, bool fast) { _counter = 0; _stop = false; int last = 0; int end = fast ? count : count * 8; ThreadPool.QueueUserWorkItem(Run); for (int i = 0; i < end; i++) { while (_counter == last) { try { Thread.Sleep(1); } catch (Exception e) { Debug.Print(DebugUtility.CreateReportString("ThreadedSeedRng", "GenerateSeed", "Thread sleep threw exception", e.ToString())); } } last = _counter; if (fast) { buffer[offset + i] = (byte)last; } else { int bytepos = i / 8; buffer[offset + bytepos] = (byte)((buffer[offset + bytepos] << 1) | (last & 1)); } } _stop = true; }
/// <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; } } }
/// <summary> /// Advances and returns the index of the next stream to use in an I/O operation (whether to completion or just a /// buffer-full). /// </summary> /// <remarks>May be overriden in a derived class to provide for advanced stream selection logic.</remarks> /// <returns>The next stream index.</returns> protected override sealed void NextSource() { Index = EntropySource.NextPositive(0, PayloadItems.Count - 1); Debug.Print(DebugUtility.CreateReportString("SimplePayloadMux", "NextSource", "Generated index", Index)); }
/// <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))); }
/// <summary> /// Read the payload. /// </summary> /// <remarks> /// All payload items to be read must have have valid stream bindings /// (<see cref="PayloadItem.StreamBinding" />) prior to calling this. /// </remarks> /// <param name="payloadKeys">Potential keys for payload items (optional).</param> /// <exception cref="KeyConfirmationException">Key confirmation for payload items failed.</exception> /// <exception cref="ConfigurationInvalidException">Payload layout scheme malformed/missing.</exception> /// <exception cref="InvalidDataException">Package data structure malformed.</exception> /// <exception cref="EndOfStreamException">Package is truncated or otherwise shorter than expected.</exception> private void ReadPayload(IEnumerable <SymmetricKey> payloadKeys = null) { if (_readingPayloadStreamOffset != 0 && _readingStream.Position != _readingPayloadStreamOffset) { _readingStream.Seek(_readingPayloadStreamOffset, SeekOrigin.Begin); } Debug.Print(DebugUtility.CreateReportString("PackageReader", "Read", "Payload offset (absolute)", _readingStream.Position)); // Check that all payload items have decryption keys - if they do not, confirm them from potentials try { ConfirmItemPreKeys(payloadKeys); } catch (Exception e) { throw new KeyConfirmationException("Error in key confirmation of payload items.", e); } // Read the payload PayloadMux mux; try { var payloadScheme = _manifest.PayloadConfiguration.SchemeName.ToEnum <PayloadLayoutScheme>(); mux = PayloadMuxFactory.CreatePayloadMultiplexer(payloadScheme, false, _readingStream, _manifest.PayloadItems, _itemPreKeys, _manifest.PayloadConfiguration); } catch (EnumerationParsingException e) { throw new ConfigurationInvalidException( "Payload layout scheme specified is unsupported/unknown or missing.", e); } catch (Exception e) { throw new Exception("Error in creation of payload demultiplexer.", e); } // Demux the payload try { mux.Execute(); } catch (Exception e) { // Catch different kinds of exception in future throw new Exception("Error in demultiplexing payload.", e); } // Read the trailer byte[] referenceTrailerTag = Athena.Packaging.GetPackageTrailerTag(); var trailerTag = new byte[referenceTrailerTag.Length]; Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadPayload", "Trailer offset (absolute)", _readingStream.Position)); int trailerBytesRead = _readingStream.Read(trailerTag, 0, trailerTag.Length); if (trailerTag.SequenceEqualShortCircuiting(referenceTrailerTag) == false) { const string pragmatist = "It would appear, however, that the package has unpacked successfully despite this."; if (trailerBytesRead != referenceTrailerTag.Length) { throw new EndOfStreamException("Insufficient data to read package trailer tag. " + pragmatist); } throw new InvalidDataException("Package is malformed. Trailer tag is either absent or malformed." + pragmatist); } Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadPayload", "[* PACKAGE END *] Offset (absolute)", _readingStream.Position)); }
/// <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); }
/// <summary> /// Reads a package manifest header from a stream. /// </summary> /// <param name="sourceStream">Stream to read the header from.</param> /// <param name="cryptoScheme">Manifest cryptography scheme parsed from the header.</param> /// <param name="cryptoConfig">Manifest cryptography configuration deserialised from the header.</param> /// <returns>Package manifest header.</returns> /// <exception cref="DataLengthException">End of stream encountered unexpectedly (contents truncated).</exception> /// <exception cref="InvalidDataException">Package data structure is out of specification or otherwise malformed.</exception> /// <exception cref="NotSupportedException">Version format specified is unknown to the local version.</exception> private static ManifestHeader ReadManifestHeader(Stream sourceStream, out ManifestCryptographyScheme cryptoScheme, out IManifestCryptographySchemeConfiguration cryptoConfig) { Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifestHeader", "[* PACKAGE START* ] Header offset (absolute)", sourceStream.Position)); byte[] referenceHeaderTag = Athena.Packaging.GetPackageHeaderTag(); var readHeaderTag = new byte[referenceHeaderTag.Length]; int headerTagBytesRead = sourceStream.Read(readHeaderTag, 0, readHeaderTag.Length); if (readHeaderTag.SequenceEqualShortCircuiting(referenceHeaderTag) == false) { if (headerTagBytesRead != referenceHeaderTag.Length) { throw new DataLengthException("Insufficient data to read package header tag."); } throw new InvalidDataException( "Package is malformed. Expected header tag is either absent or malformed."); } Debug.Print(DebugUtility.CreateReportString("PackageReader", "ReadManifestHeader", "Manifest header offset (absolute)", sourceStream.Position)); var manifestHeader = StratCom.DeserialiseDataTransferObject <ManifestHeader>(sourceStream, true); if (manifestHeader.FormatVersion <= 0) { throw new InvalidDataException("Package format descriptor is 0 or less (must be 1 or more)."); } if (manifestHeader.FormatVersion > Athena.Packaging.PackageFormatVersion) { throw new NotSupportedException( String.Format("Package version {0} as specified by the manifest header is unsupported/unknown.\n" + "The local version of ObscurCore supports up to version {1}.", manifestHeader.FormatVersion, Athena.Packaging.PackageFormatVersion)); // In later versions, can redirect to diff. behaviour (and DTO objects) for diff. versions. } cryptoScheme = manifestHeader.CryptographyScheme; switch (cryptoScheme) { case ManifestCryptographyScheme.SymmetricOnly: cryptoConfig = manifestHeader.CryptographySchemeConfiguration .DeserialiseDto <SymmetricManifestCryptographyConfiguration>(); break; case ManifestCryptographyScheme.Um1Hybrid: cryptoConfig = manifestHeader.CryptographySchemeConfiguration .DeserialiseDto <Um1HybridManifestCryptographyConfiguration>(); break; default: throw new NotSupportedException(String.Format( "Package manifest cryptography scheme \"{0}\" as specified by the manifest header is unsupported.", manifestHeader.CryptographyScheme)); } return(manifestHeader); }