/// <summary> /// Performs key confirmation and derivation on each payload item. /// </summary> /// <param name="payloadKeysSymmetric">Potential symmetric keys for payload items.</param> /// <exception cref="AggregateException"> /// Consisting of ItemKeyMissingException, indicating items missing cryptographic keys. /// </exception> private void ConfirmItemPreKeys(IEnumerable <SymmetricKey> payloadKeysSymmetric = null) { List <SymmetricKey> keys = payloadKeysSymmetric != null?payloadKeysSymmetric.ToList() : new List <SymmetricKey>(); var keylessItems = new List <PayloadItem>(); IEnumerable <PayloadItem> itemsToConfirm = _manifest.PayloadItems.AsQueryExpr() .Where(item => item.SymmetricCipherKey.IsNullOrZeroLength() || item.AuthenticationKey.IsNullOrZeroLength()) .Run(); Parallel.ForEach(itemsToConfirm, item => { if (item.KeyConfirmation != null && item.KeyDerivation != null) { // We will derive the key from one supplied as a potential SymmetricKey symmetricKey; try { symmetricKey = ConfirmationUtility.ConfirmKeyFromCanary( ((SymmetricManifestCryptographyConfiguration)_manifestCryptoConfig).KeyConfirmation, _manifestCryptoConfig.KeyConfirmationVerifiedOutput, keys); } catch (Exception e) { throw new KeyConfirmationException("Key confirmation failed in an unexpected way.", e); } if (symmetricKey != null) { if (symmetricKey.Key.IsNullOrZeroLength()) { throw new ArgumentException("Supplied symmetric key is null or zero-length.", "payloadKeysSymmetric"); } _itemPreKeys.Add(item.Identifier, symmetricKey.Key); } else { keylessItems.Add(item); } } else { keylessItems.Add(item); } }); if (keylessItems.Count > 0) { throw new AggregateException(keylessItems.Select(item => new ItemKeyMissingException(item))); } }
/// <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); }