libZSamazonka-s3-encryptionZSamazonka-s3-encryption
Copyright(c) 2013-2021 Brendan Hay
LicenseMozilla Public License, v. 2.0.
MaintainerBrendan Hay <brendan.g.hay@gmail.com>
Stabilityprovisional
Portabilitynon-portable (GHC extensions)
Safe HaskellNone

Amazonka.S3.Encryption

Description

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

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 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 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

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.

data Key Source #

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

newtype Ext Source #

An instructions file extension.

Constructors

Ext Text 

Instances

Instances details
Eq Ext Source # 
Instance details

Defined in Amazonka.S3.Encryption.Types

Methods

(==) :: Ext -> Ext -> Bool #

(/=) :: Ext -> Ext -> Bool #

Show Ext Source # 
Instance details

Defined in Amazonka.S3.Encryption.Types

Methods

showsPrec :: Int -> Ext -> ShowS #

show :: Ext -> String #

showList :: [Ext] -> ShowS #

IsString Ext Source # 
Instance details

Defined in Amazonka.S3.Encryption.Types

Methods

fromString :: String -> Ext #

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")) :: Either EncryptionError PutObjectResponse

data EncryptionError Source #

An error thrown when performing encryption or decryption.

Constructors

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.

class AsEncryptionError r where Source #

Minimal complete definition

_EncryptionError