Copyright | (c) 2013-2021 Brendan Hay |
---|---|
License | Mozilla Public License, v. 2.0. |
Maintainer | Brendan Hay <brendan.g.hay@gmail.com> |
Stability | provisional |
Portability | non-portable (GHC extensions) |
Safe Haskell | None |
Addons for 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.
Synopsis
- data Key
- kmsKey :: Text -> Key
- asymmetricKey :: PrivateKey -> Key
- symmetricKey :: MonadIO m => ByteString -> m Key
- newSecret :: MonadRandom m => m ByteString
- encrypt :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse
- decrypt :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse
- initiate :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart)
- encryptInstructions :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse
- decryptInstructions :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse
- initiateInstructions :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart)
- cleanupInstructions :: (MonadResource m, RemoveInstructions a) => Env -> a -> m (AWSResponse a)
- newtype Ext = Ext Text
- defaultExtension :: Ext
- data EncryptionError
- class AsEncryptionError r where
- _EncryptionError :: Prism' r EncryptionError
- _CipherFailure :: Prism' r CryptoError
- _PubKeyFailure :: Prism' r Error
- _IVInvalid :: Prism' r ByteString
- _EnvelopeMissing :: Prism' r (CI Text)
- _EnvelopeInvalid :: Prism' r (CI Text, String)
- _PlaintextUnavailable :: Prism' r ()
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 usingnewEnv
: e <- newEnv Frankfurt Discover runResourceT $ do -- To store an encrypted object,encrypt
is used inplace of where you would -- typically usesend
: _ <- 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 usesend
: rs <- decrypt (kmsKey "alias/master-key") e (getObject "bucket-name" "object-key") -- TheGetObjectResponse
here contains abody
that is decrypted during read: return rs
Specifying Master Keys
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.
The key used for encryption and decryption.
kmsKey :: Text -> Key Source #
Specify a KMS master key to use, with an initially empty material description.
See: description
, material
.
asymmetricKey :: PrivateKey -> Key Source #
Specify the asymmetric key used for RSA encryption.
See: description
, material
.
symmetricKey :: MonadIO m => ByteString -> m Key Source #
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
.
newSecret :: MonadRandom m => m ByteString Source #
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.
Request Encryption/Decryption
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).
encrypt :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse Source #
Encrypt an object, storing the encryption envelope in x-amz-meta-*
headers.
Throws EncryptionError
, Error
.
decrypt :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse Source #
Retrieve an object, parsing the envelope from any x-amz-meta-*
headers
and decrypting the response body.
Throws EncryptionError
, Error
.
initiate :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart) Source #
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
, Error
.
Instruction Files
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
) _ <-send
(b &piExtension
.~ ".envelope") -- Store the custom instruction file.send
a -- Store the actual encrypted object.
encryptInstructions :: MonadResource m => Key -> Env -> PutObject -> m PutObjectResponse Source #
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
, Error
.
decryptInstructions :: MonadResource m => Key -> Env -> GetObject -> m GetObjectResponse Source #
Retrieve an object and its adjacent instruction file. The instruction are retrieved and parsed first. Performs two HTTP requests.
Throws EncryptionError
, Error
.
initiateInstructions :: MonadResource m => Key -> Env -> CreateMultipartUpload -> m (CreateMultipartUploadResponse, UploadPart -> Encrypted UploadPart) Source #
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
, Error
.
cleanupInstructions :: (MonadResource m, RemoveInstructions a) => Env -> a -> m (AWSResponse a) Source #
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
, Error
.
Default Instruction Extension
An instructions file extension.
defaultExtension :: Ext Source #
Defaults to .instruction
Handling 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
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")) :: EitherEncryptionError
PutObjectResponse
data EncryptionError Source #
An error thrown when performing encryption or decryption.
CipherFailure CryptoError | Error initialising an AES cipher from a secret key. |
PubKeyFailure Error | Failure performing asymmetric encryption/decryption. |
IVInvalid ByteString | Failure creating an IV from some bytes. |
EnvelopeMissing (CI Text) | Required envelope field missing. |
EnvelopeInvalid (CI Text) String | Error parsing envelope. |
PlaintextUnavailable | KMS error when retrieving decrypted plaintext. |
Instances
Eq EncryptionError Source # | |
Defined in Amazonka.S3.Encryption.Types (==) :: EncryptionError -> EncryptionError -> Bool # (/=) :: EncryptionError -> EncryptionError -> Bool # | |
Show EncryptionError Source # | |
Defined in Amazonka.S3.Encryption.Types showsPrec :: Int -> EncryptionError -> ShowS # show :: EncryptionError -> String # showList :: [EncryptionError] -> ShowS # | |
Exception EncryptionError Source # | |
Defined in Amazonka.S3.Encryption.Types | |
AsEncryptionError EncryptionError Source # | |
Defined in Amazonka.S3.Encryption.Types _EncryptionError :: Prism' EncryptionError EncryptionError Source # _CipherFailure :: Prism' EncryptionError CryptoError Source # _PubKeyFailure :: Prism' EncryptionError Error Source # _IVInvalid :: Prism' EncryptionError ByteString Source # _EnvelopeMissing :: Prism' EncryptionError (CI Text) Source # _EnvelopeInvalid :: Prism' EncryptionError (CI Text, String) Source # |
class AsEncryptionError r where Source #
_EncryptionError :: Prism' r EncryptionError Source #
_CipherFailure :: Prism' r CryptoError Source #
_PubKeyFailure :: Prism' r Error Source #
_IVInvalid :: Prism' r ByteString Source #
_EnvelopeMissing :: Prism' r (CI Text) Source #
_EnvelopeInvalid :: Prism' r (CI Text, String) Source #
_PlaintextUnavailable :: Prism' r () Source #