/// <summary>
        /// Fetches a known system key from metastore and decrypts it using the key management service.
        /// </summary>
        ///
        /// <returns>The decrypted system key.</returns>
        ///
        /// <param name="systemKeyMeta">The <see cref="KeyMeta"/> of the system key.</param>
        /// <exception cref="MetadataMissingException">If the system key is not found.</exception>
        internal virtual CryptoKey GetSystemKey(KeyMeta systemKeyMeta)
        {
            EnvelopeKeyRecord systemKeyRecord = LoadKeyRecord(systemKeyMeta.KeyId, systemKeyMeta.Created);

            return(keyManagementService.DecryptKey(
                       systemKeyRecord.EncryptedKey, systemKeyRecord.Created, systemKeyRecord.Revoked.IfNone(false)));
        }
        /// <inheritdoc/>
        public virtual byte[] DecryptDataRowRecord(JObject dataRowRecord)
        {
            using (MetricsUtil.MetricsInstance.Measure.Timer.Time(DecryptTimerOptions))
            {
                Json dataRowRecordJson = new Json(dataRowRecord);

                Json   keyDocument      = dataRowRecordJson.GetJson("Key");
                byte[] payloadEncrypted = dataRowRecordJson.GetBytes("Data");

                EnvelopeKeyRecord dataRowKeyRecord = new EnvelopeKeyRecord(keyDocument);

                KeyMeta keyMeta = dataRowKeyRecord.ParentKeyMeta.IfNone(() =>
                                                                        throw new MetadataMissingException("Could not find parentKeyMeta {IK} for dataRowKey"));

                if (!partition.IsValidIntermediateKeyId(keyMeta.KeyId))
                {
                    throw new MetadataMissingException("Could not find parentKeyMeta {IK} for dataRowKey");
                }

                byte[] decryptedPayload = WithIntermediateKeyForRead(
                    keyMeta,
                    intermediateCryptoKey =>
                    crypto.EnvelopeDecrypt(
                        payloadEncrypted, dataRowKeyRecord.EncryptedKey, dataRowKeyRecord.Created, intermediateCryptoKey));

                return(decryptedPayload);
            }
        }
        /// <summary>
        /// Fetches a known intermediate key from metastore and decrypts it using its associated system key.
        /// </summary>
        ///
        /// <returns>The decrypted intermediate key.</returns>
        ///
        /// <param name="intermediateKeyCreated">The creation time of intermediate key.</param>
        /// <exception cref="MetadataMissingException">If the intermediate key is not found, or it has missing system
        /// key info.</exception>
        internal virtual CryptoKey GetIntermediateKey(DateTimeOffset intermediateKeyCreated)
        {
            EnvelopeKeyRecord intermediateKeyRecord = LoadKeyRecord(partition.IntermediateKeyId, intermediateKeyCreated);

            return(WithExistingSystemKey(
                       intermediateKeyRecord.ParentKeyMeta.IfNone(() =>
                                                                  throw new MetadataMissingException("Could not find parentKeyMeta (SK) for intermediateKey")),
                       false,
                       key => DecryptKey(intermediateKeyRecord, key)));
        }
        /// <inheritdoc/>
        public virtual JObject EncryptPayload(byte[] payload)
        {
            using (MetricsUtil.MetricsInstance.Measure.Timer.Time(EncryptTimerOptions))
            {
                EnvelopeEncryptResult result = WithIntermediateKeyForWrite(intermediateCryptoKey => crypto.EnvelopeEncrypt(
                                                                               payload,
                                                                               intermediateCryptoKey,
                                                                               new KeyMeta(partition.IntermediateKeyId, intermediateCryptoKey.GetCreated())));

                KeyMeta parentKeyMeta = (KeyMeta)result.UserState;

                EnvelopeKeyRecord keyRecord =
                    new EnvelopeKeyRecord(DateTimeOffset.UtcNow, parentKeyMeta, result.EncryptedKey);

                Json wrapperDocument = new Json();
                wrapperDocument.Put("Key", keyRecord.ToJson());
                wrapperDocument.Put("Data", result.CipherText);

                return(wrapperDocument.ToJObject());
            }
        }
 internal virtual bool IsKeyExpiredOrRevoked(EnvelopeKeyRecord envelopeKeyRecord)
 {
     return(cryptoPolicy.IsKeyExpired(envelopeKeyRecord.Created) || envelopeKeyRecord.Revoked.IfNone(false));
 }
 /// <summary>
 /// Decrypts the <paramref name="keyRecord"/>'s encrypted key using the provided key.
 /// </summary>
 ///
 /// <returns>The decrypted key contained in the <paramref name="keyRecord"/>.</returns>
 ///
 /// <param name="keyRecord">The key to decrypt.</param>
 /// <param name="keyEncryptionKey">Encryption key to use for decryption.</param>
 internal virtual CryptoKey DecryptKey(EnvelopeKeyRecord keyRecord, CryptoKey keyEncryptionKey)
 {
     return(crypto.DecryptKey(
                keyRecord.EncryptedKey, keyRecord.Created, keyEncryptionKey, keyRecord.Revoked.IfNone(false)));
 }
        internal virtual CryptoKey GetLatestOrCreateSystemKey()
        {
            Option <EnvelopeKeyRecord> newestSystemKeyRecord = LoadLatestKeyRecord(partition.SystemKeyId);

            if (newestSystemKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestSystemKeyRecord;

                // If the key we just got back isn't expired, then just use it
                if (!IsKeyExpiredOrRevoked(keyRecord))
                {
                    return(keyManagementService.DecryptKey(
                               keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
                }

                // If we're here we know we have an expired key.
                // If we're doing queued rotation, flag it and continue to use the expired key
                if (cryptoPolicy.IsQueuedKeyRotation())
                {
                    // TODO : Queued rotation
                    Logger.LogDebug("Queuing up SK {keyId} for rotation", partition.SystemKeyId);
                    return(keyManagementService.DecryptKey(
                               keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
                }

                // If we're here then we're doing inline rotation and have an expired key.
                // Fall through as if we didn't have the key
            }

            DateTimeOffset systemKeyCreated = cryptoPolicy.TruncateToSystemKeyPrecision(DateTimeOffset.UtcNow);
            CryptoKey      systemKey        = crypto.GenerateKey(systemKeyCreated);

            try
            {
                EnvelopeKeyRecord newSystemKeyRecord = new EnvelopeKeyRecord(
                    systemKey.GetCreated(), null, keyManagementService.EncryptKey(systemKey), false);

                Logger.LogDebug(
                    "Attempting to store new SK {keyId} for created {created}",
                    partition.SystemKeyId,
                    newSystemKeyRecord.Created);
                if (metastore.Store(partition.SystemKeyId, newSystemKeyRecord.Created, newSystemKeyRecord.ToJson()))
                {
                    return(systemKey);
                }
                else
                {
                    Logger.LogDebug(
                        "Attempted to store new SK {keyId} but detected duplicate for created {created}, disposing newly created SK",
                        partition.SystemKeyId,
                        systemKey.GetCreated());
                    DisposeKey(systemKey, null);
                }
            }
            catch (Exception e)
            {
                DisposeKey(systemKey, e);
                throw new AppEncryptionException("Unable to store new System Key", e);
            }

            // If we're here, storing of the newly generated key failed above which means we attempted to
            // save a duplicate key to the metastore. If that's the case, then we know a valid key exists
            // in the metastore, so let's grab it and return it.
            newestSystemKeyRecord = LoadLatestKeyRecord(partition.SystemKeyId);

            if (newestSystemKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestSystemKeyRecord;
                return(keyManagementService.DecryptKey(
                           keyRecord.EncryptedKey, keyRecord.Created, keyRecord.Revoked.IfNone(false)));
            }
            else
            {
                throw new AppEncryptionException("SystemKey not present after LoadLatestKeyRecord retry");
            }
        }
        internal virtual CryptoKey GetLatestOrCreateIntermediateKey()
        {
            Option <EnvelopeKeyRecord> newestIntermediateKeyRecord = LoadLatestKeyRecord(partition.IntermediateKeyId);

            if (newestIntermediateKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)newestIntermediateKeyRecord;

                // If the key we just got back isn't expired, then just use it
                if (!IsKeyExpiredOrRevoked(keyRecord))
                {
                    try
                    {
                        return(WithExistingSystemKey(
                                   keyRecord.ParentKeyMeta.IfNone(() => throw new MetadataMissingException(
                                                                      "Could not find parentKeyMeta (SK) for intermediateKey ")),
                                   true,
                                   key => DecryptKey(keyRecord, key)));
                    }
                    catch (MetadataMissingException e)
                    {
                        Logger.LogDebug(
                            e,
                            "The SK for the IK ({keyId}, {created}) is missing or in an invalid state. Will create new IK instead.",
                            partition.IntermediateKeyId,
                            keyRecord.Created);
                    }
                }

                // If we're here we know we have an expired key.
                // If we're doing queued rotation, flag it and continue to use the expired key
                if (cryptoPolicy.IsQueuedKeyRotation())
                {
                    // TODO : Queued rotation
                    Logger.LogDebug("Queuing up IK {keyId} for rotation", partition.IntermediateKeyId);
                    try
                    {
                        return(WithExistingSystemKey(
                                   keyRecord.ParentKeyMeta.IfNone(() =>
                                                                  throw new MetadataMissingException(
                                                                      "Could not find parentKeyMeta (SK) for intermediateKey")),
                                   true,
                                   key => DecryptKey(keyRecord, key)));
                    }
                    catch (MetadataMissingException e)
                    {
                        Logger.LogDebug(
                            e,
                            "The SK for the IK ({keyId}, {created}) is missing or in an invalid state. Will create new IK instead.",
                            partition.IntermediateKeyId,
                            keyRecord.Created);
                    }
                }

                // If we're here then we're doing inline rotation and have an expired key.
                // Fall through as if we didn't have the key
            }

            DateTimeOffset intermediateKeyCreated = cryptoPolicy.TruncateToIntermediateKeyPrecision(DateTimeOffset.UtcNow);
            CryptoKey      intermediateKey        = crypto.GenerateKey(intermediateKeyCreated);

            try
            {
                EnvelopeKeyRecord newIntermediateKeyRecord = WithSystemKeyForWrite(systemCryptoKey =>
                                                                                   new EnvelopeKeyRecord(
                                                                                       intermediateKey.GetCreated(),
                                                                                       new KeyMeta(partition.SystemKeyId, systemCryptoKey.GetCreated()),
                                                                                       crypto.EncryptKey(intermediateKey, systemCryptoKey),
                                                                                       false));

                Logger.LogDebug(
                    "Attempting to store new IK {keyId}, for created {created}",
                    partition.IntermediateKeyId,
                    newIntermediateKeyRecord.Created);

                if (metastore.Store(
                        partition.IntermediateKeyId, newIntermediateKeyRecord.Created, newIntermediateKeyRecord.ToJson()))
                {
                    return(intermediateKey);
                }
                else
                {
                    Logger.LogDebug(
                        "Attempted to store new IK {keyId} but detected duplicate for created {created}, disposing newly created IK",
                        partition.IntermediateKeyId,
                        intermediateKey.GetCreated());
                    DisposeKey(intermediateKey, null);
                }
            }
            catch (Exception e)
            {
                DisposeKey(intermediateKey, e);
                throw new AppEncryptionException("Unable to store new Intermediate Key", e);
            }

            // If we're here, storing of the newly generated key failed above which means we attempted to
            // save a duplicate key to the metastore. If that's the case, then we know a valid key exists
            // in the metastore, so let's grab it and return it.

            // Using a new variable instead of the one above because the WithSystemKeyForWrite use above wants finality
            Option <EnvelopeKeyRecord> actualLatestIntermediateKeyRecord = LoadLatestKeyRecord(partition.IntermediateKeyId);

            if (actualLatestIntermediateKeyRecord.IsSome)
            {
                EnvelopeKeyRecord keyRecord = (EnvelopeKeyRecord)actualLatestIntermediateKeyRecord;

                // NOTE: Not wrapping this in try/catch to allow errors to bubble up. If we're missing meta in this flow, something's wrong.
                return(WithExistingSystemKey(
                           keyRecord.ParentKeyMeta.IfNone(() =>
                                                          throw new MetadataMissingException("Could not find parentKeyMeta (SK) for intermediateKey")),
                           true,
                           key => DecryptKey(keyRecord, key)));
            }
            else
            {
                throw new AppEncryptionException("IntermediateKey not present after LoadLatestKeyRecord retry");
            }
        }