Exemplo n.º 1
0
        /// <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)));
            }
        }
Exemplo n.º 2
0
        /// <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);
        }