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