-- | -- Module : Amazonka.S3.Encryption -- Copyright : (c) 2013-2023 Brendan Hay -- License : Mozilla Public License, v. 2.0. -- Maintainer : Brendan Hay <brendan.g.hay+amazonka.com> -- Stability : provisional -- Portability : non-portable (GHC extensions) -- -- Addons for <http://hackage.haskell.org/package/amazonka-s3 amazonka-s3> to -- support client-side encryption. -- -- Your client-side master keys and your unencrypted data are never sent to AWS; -- therefore, it is important that you safely manage your encryption keys. If -- you lose them, you won't be able to decrypt your data. -- When generating a symmetric key, you should ensure -- that the key length is compatible with the underlying 'AES256' cipher. -- -- The encryption procedure is: -- -- * A one-time-use symmetric key a.k.a. a data encryption key (or data key) and -- initialisation vector (IV) are generated locally. This data key and IV are used -- to encrypt the data of a single S3 object using an AES256 cipher in CBC mode, -- with PKCS5 padding. (For each object sent, a completely separate data key and IV are generated.) -- -- * The generated data encryption key used above is encrypted using a symmetric -- AES256 cipher in ECB mode, asymmetric RSA, or KMS facilities, depending on the -- client-side master key you provide. -- -- * The encrypted data is uploaded and the encrypted data key and material description -- are attached as object metadata (either headers or a separate instruction file). -- If KMS is used, the material description helps determine which client-side master -- key to later use for decryption, otherwise the configured client-side key at -- time of decryption is used. -- -- For decryption: -- -- The encrypted object is downloaded from Amazon S3 along with any metadata. -- If KMS was used to encrypt the data then the master key id is taken from the -- metadata material description, otherwise the client-side master key in the -- current environment is used to decrypt the data key, which in turn is used -- to decrypt the object data. -- -- The client-side master key you provide can be either a symmetric key, an -- asymmetric public/private key pair, or a KMS master key. -- -- The stored metadata format is designed to be compatible with the official Java -- AWS SDK (both V1 and V2 envelopes), but only a limited set of the possible -- encryption options are supported. Therefore assuming defaults, objects stored -- with this library should be retrievable by any of the other official SDKs, and -- vice versa. module Amazonka.S3.Encryption ( -- * Usage -- $usage -- * Specifying Master Keys -- $master-key Key (..), kmsKey, asymmetricKey, symmetricKey, newSecret, -- * Request Encryption/Decryption -- $requests encrypt, decrypt, initiate, -- ** Instruction Files -- $instructions encryptInstructions, decryptInstructions, initiateInstructions, cleanupInstructions, -- *** Default Instruction Extension Ext (..), defaultExtension, -- * Handling Errors -- $errors EncryptionError (..), AsEncryptionError (..), ) where import Amazonka as AWS import Amazonka.Prelude import Amazonka.S3 import Amazonka.S3.Encryption.Decrypt import Amazonka.S3.Encryption.Encrypt import Amazonka.S3.Encryption.Envelope import Amazonka.S3.Encryption.Instructions import Amazonka.S3.Encryption.Types import Control.Lens import Crypto.PubKey.RSA.Types as RSA import Crypto.Random import Data.Typeable (Typeable) -- | Specify a KMS master key to use, with an initially empty material description. -- -- /See:/ 'description', 'material'. kmsKey :: Text -> Key kmsKey :: Text -> Key kmsKey Text k = Text -> Description -> Key KMS Text k forall a. Monoid a => a mempty -- | Specify the asymmetric key used for RSA encryption. -- -- /See:/ 'description', 'material'. asymmetricKey :: PrivateKey -> Key asymmetricKey :: PrivateKey -> Key asymmetricKey PrivateKey k = KeyPair -> Description -> Key Asymmetric (PrivateKey -> KeyPair KeyPair PrivateKey k) forall a. Monoid a => a mempty -- | Specify the shared secret to use for symmetric key encryption. -- This must be compatible with the AES256 key size, 32 bytes. -- -- Throws 'EncryptionError', specifically 'CipherFailure'. -- -- /See:/ 'newSecret', 'description', 'material'. symmetricKey :: MonadIO m => ByteString -> m Key symmetricKey :: forall (m :: * -> *). MonadIO m => ByteString -> m Key symmetricKey = forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b fmap (AES256 -> Description -> Key `Symmetric` forall a. Monoid a => a mempty) forall b c a. (b -> c) -> (a -> b) -> a -> c . forall (m :: * -> *) a b. (MonadIO m, ByteArray a, Cipher b) => a -> m b createCipher -- | Generate a random shared secret that is of the correct length to use with -- 'symmetricKey'. This will need to be stored securely to enable decryption -- of any requests that are encrypted using this secret. newSecret :: MonadRandom m => m ByteString newSecret :: forall (m :: * -> *). MonadRandom m => m ByteString newSecret = forall (m :: * -> *) byteArray. (MonadRandom m, ByteArray byteArray) => Int -> m byteArray getRandomBytes Int aesKeySize -- | Encrypt an object, storing the encryption envelope in @x-amz-meta-*@ -- headers. -- -- Throws 'EncryptionError', 'AWS.Error'. encrypt :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse encrypt :: forall (m :: * -> *). MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse encrypt Key key Env env PutObject x = do (Encrypted PutObject a, PutInstructions _) <- forall (m :: * -> *) a. (MonadResource m, ToEncrypted a) => Key -> Env -> a -> m (Encrypted a, PutInstructions) encrypted Key key Env env PutObject x forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env (forall s t a b. ASetter s t a b -> b -> s -> t set forall a. Setter' (Encrypted a) Location location Location Metadata Encrypted PutObject a) -- | Encrypt an object, storing the encryption envelope in an adjacent instruction -- file with the same 'ObjectKey' and 'defaultExtension'. -- This makes two HTTP requests, storing the instruction file first and upon success, -- storing the actual object. -- -- Throws 'EncryptionError', 'AWS.Error'. encryptInstructions :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse encryptInstructions :: forall (m :: * -> *). MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse encryptInstructions Key key Env env PutObject x = do (Encrypted PutObject a, PutInstructions b) <- forall (m :: * -> *) a. (MonadResource m, ToEncrypted a) => Key -> Env -> a -> m (Encrypted a, PutInstructions) encrypted Key key Env env PutObject x PutObjectResponse _ <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env PutInstructions b forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env Encrypted PutObject a -- | Initiate an encrypted multipart upload, storing the encryption envelope -- in the @x-amz-meta-*@ headers. -- -- The returned 'UploadPart' @->@ 'Encrypted' 'UploadPart' function is used to encrypt -- each part of the object. The same caveats for multipart upload apply, it is -- assumed that each part is uploaded in order and each part needs to be -- individually encrypted. -- -- For example: -- -- @ -- (a', f) <- initiate (a :: CreateMultipartUpload) -- b' <- send (f b :: Encrypted UploadPart) -- @ -- -- Throws 'EncryptionError', 'AWS.Error'. initiate :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m ( CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart ) initiate :: forall (m :: * -> *). MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart) initiate Key key Env env CreateMultipartUpload x = do (Encrypted CreateMultipartUpload a, PutInstructions _) <- forall (m :: * -> *) a. (MonadResource m, ToEncrypted a) => Key -> Env -> a -> m (Encrypted a, PutInstructions) encrypted Key key Env env CreateMultipartUpload x CreateMultipartUploadResponse rs <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env (forall s t a b. ASetter s t a b -> b -> s -> t set forall a. Setter' (Encrypted a) Location location Location Metadata Encrypted CreateMultipartUpload a) forall (m :: * -> *) a. Monad m => a -> m a return (CreateMultipartUploadResponse rs, Encrypted CreateMultipartUpload -> UploadPart -> Encrypted UploadPart encryptPart Encrypted CreateMultipartUpload a) -- | Initiate an encrypted multipart upload, storing the encryption envelope -- in an adjacent instruction file with the same 'ObjectKey' and 'defaultExtension'. -- -- The returned 'UploadPart' @->@ 'Encrypted' 'UploadPart' function is used to encrypt -- each part of the object. The same caveats for multipart upload apply, it is -- assumed that each part is uploaded in order and each part needs to be -- individually encrypted. -- -- Throws 'EncryptionError', 'AWS.Error'. initiateInstructions :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m ( CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart ) initiateInstructions :: forall (m :: * -> *). MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart) initiateInstructions Key key Env env CreateMultipartUpload x = do (Encrypted CreateMultipartUpload a, PutInstructions b) <- forall (m :: * -> *) a. (MonadResource m, ToEncrypted a) => Key -> Env -> a -> m (Encrypted a, PutInstructions) encrypted Key key Env env CreateMultipartUpload x CreateMultipartUploadResponse rs <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env Encrypted CreateMultipartUpload a PutObjectResponse _ <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env PutInstructions b forall (m :: * -> *) a. Monad m => a -> m a return (CreateMultipartUploadResponse rs, Encrypted CreateMultipartUpload -> UploadPart -> Encrypted UploadPart encryptPart Encrypted CreateMultipartUpload a) -- | Retrieve an object, parsing the envelope from any @x-amz-meta-*@ headers -- and decrypting the response body. -- -- Throws 'EncryptionError', 'AWS.Error'. decrypt :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse decrypt :: forall (m :: * -> *). MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse decrypt Key key Env env GetObject x = do let (Decrypt GetObject a, GetInstructions _) = GetObject -> (Decrypt GetObject, GetInstructions) decrypted GetObject x Decrypted forall (m :: * -> *). MonadResource m => Key -> Env -> Maybe Envelope -> m GetObjectResponse f <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env Decrypt GetObject a forall (m :: * -> *). MonadResource m => Key -> Env -> Maybe Envelope -> m GetObjectResponse f Key key Env env forall a. Maybe a Nothing -- | Retrieve an object and its adjacent instruction file. The instruction -- are retrieved and parsed first. -- Performs two HTTP requests. -- -- Throws 'EncryptionError', 'AWS.Error'. decryptInstructions :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse decryptInstructions :: forall (m :: * -> *). MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse decryptInstructions Key key Env env GetObject x = do let (Decrypt GetObject a, GetInstructions b) = GetObject -> (Decrypt GetObject, GetInstructions) decrypted GetObject x Instructions forall (m :: * -> *). MonadResource m => Key -> Env -> m Envelope g <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env GetInstructions b Decrypted forall (m :: * -> *). MonadResource m => Key -> Env -> Maybe Envelope -> m GetObjectResponse f <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env Decrypt GetObject a forall (m :: * -> *). MonadResource m => Key -> Env -> m Envelope g Key key Env env forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b >>= forall (m :: * -> *). MonadResource m => Key -> Env -> Maybe Envelope -> m GetObjectResponse f Key key Env env forall b c a. (b -> c) -> (a -> b) -> a -> c . forall a. a -> Maybe a Just -- | Given a request to execute, such as 'AbortMultipartUpload' or 'DeleteObject', -- remove the adjacent instruction file, if it exists with the 'defaultExtension'. -- Performs two HTTP requests. -- -- Throws 'EncryptionError', 'AWS.Error'. cleanupInstructions :: ( MonadResource m, RemoveInstructions a, Typeable (AWSResponse a), Typeable a ) => Env -> a -> m (AWSResponse a) cleanupInstructions :: forall (m :: * -> *) a. (MonadResource m, RemoveInstructions a, Typeable (AWSResponse a), Typeable a) => Env -> a -> m (AWSResponse a) cleanupInstructions Env env a x = do AWSResponse a rs <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env a x DeleteObjectResponse _ <- forall (m :: * -> *) a. (MonadResource m, AWSRequest a, Typeable a, Typeable (AWSResponse a)) => Env -> a -> m (AWSResponse a) send Env env (forall a. RemoveInstructions a => a -> DeleteInstructions deleteInstructions a x) forall (m :: * -> *) a. Monad m => a -> m a return AWSResponse a rs -- $usage -- When sending requests that make use of a master key, an extension to the underlying -- 'AWS' environment is required. You can specify this environment as follows: -- -- @ -- import Amazonka -- import Amazonka.S3 -- import Amazonka.S3.Encryption -- import System.IO -- -- example :: Key -> IO GetObjectResponse -- example k = do -- -- A standard AWS environment with credentials is created using 'newEnv': -- e <- newEnv Frankfurt Discover -- -- runResourceT $ do -- -- To store an encrypted object, 'encrypt' is used inplace of where you would -- -- typically use 'AWS.send': -- _ <- encrypt (kmsKey "alias/master-key") e (putObject "bucket-name" "object-key" body) -- -- -- To retrieve a previously encrypted object, 'decrypt' is used, again similarly to -- -- how you'd use 'AWS.send': -- rs <- decrypt (kmsKey "alias/master-key") e (getObject "bucket-name" "object-key") -- -- -- The 'GetObjectResponse' here contains a 'body' that is decrypted during read: -- return rs -- @ -- $master-key -- You master key should be stored and secured by you alone (or KMS). The specific -- key that is used to encrypt an object is required to decrypt the same object. -- If you lose this key, you will not be able to decrypt the related objects. -- $errors -- Errors are thrown by the library using 'MonadThrow' and will consist of one of -- the branches from 'EncryptionError' for anything crypto related, or a disparate -- 'AWS.Error' anything related to the underlying 'AWS' service calls. -- -- You can catch errors and sub-errors via 'trying' etc. from "Control.Exception.Lens", -- and the appropriate 'AsEncryptionError' 'Prism': -- -- @ -- trying '_EncryptionError' (encrypt (putObject "bkt" "key")) :: Either 'EncryptionError' PutObjectResponse -- @ -- $requests -- Only a small number of S3 operations actually utilise encryption/decryption -- behaviour, namely 'PutObject', 'GetObject', and the related multipart upload -- operations. The following functions store the encryption envelope in object -- metadata (headers). -- $instructions -- An alternative method of storing the encryption envelope in an adjacent S3 -- object is provided for the case when metadata headers are reserved for other -- data. This method removes the metadata overhead at the expense of an additional -- HTTP request to perform encryption/decryption. -- The provided @*Instruction@ functions make the convenient assumption that -- the 'defaultExtension' is desired. If you wish to override the suffix\/extension, -- you can simply call the underlying plumbing to modify the -- 'PutInstructions' or 'GetInstructions' suffix before sending. -- -- An example of encryption with a non-default instruction extension: -- -- @ -- (a, b) <- 'encrypted' (x :: 'PutObject') -- _ <- 'AWS.send' (b & 'piExtension' .~ ".envelope") -- Store the custom instruction file. -- 'AWS.send' a -- Store the actual encrypted object. -- @