cryptostore

This package allows to read and write cryptographic objects to/from ASN.1.
Currently the following is implemented:
-
Reading and writing private keys with optional encryption (this extends
x509-store API)
-
Reading and writing public keys, certificates and CRLs
-
PKCS #12 container format (password-based only)
Please have a look at the examples below as well as some warnings about
cryptographic algorithms.
Private Keys
The API to read and write private keys is available in module
Crypto.Store.PKCS8. When encrypting, some types and functions from module
Crypto.Store.PKCS5 are also necessary.
Reading a private key from disk:
> :set -XOverloadedStrings
> :m Crypto.Store.PKCS8
> (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key
> recover "mypassword" key
Right (keyPairFromPrivKey (PrivKeyRSA ...))
Generating a private key and writing to disk, without encryption:
> :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509
> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
> let keyPair = keyPairFromPrivKey privKey
> writeKeyFile PKCS8Format "/path/to/newkey.pem" [keyPair]
Generating a private key and writing to disk, with password-based encryption:
> :set -XOverloadedStrings
> :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5
> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
> let keyPair = keyPairFromPrivKey privKey
> salt <- generateSalt 16
> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
> encParams <- generateCBCParams AES256
> let pbes = PBES2 (PBES2Parameter kdf encParams)
> writeEncryptedKeyFile "/path/to/newkey.pem" pbes "mypassword" keyPair
Right ()
Parameters used in this example are AES-256-CBC as cipher, PBKDF2 as
key-derivation function, with a 16-byte salt, 200,000 iterations and SHA-256
as pseudorandom function.
Public Keys and Signed Objects
Module Crypto.Store.X509 provides functions to read/write PEM files containing
public keys, X.509 certificates and CRLs. These files are never encrypted.
Reading a public key and certificate from disk:
> :m Data.X509 Crypto.Store.X509
> readPubKeyFile "/path/to/pubkey.pem"
[PubKeyRSA ...]
> readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]
[SignedExact ...]
Writing back to disk:
> :m Crypto.Store.X509
> writePubKeyFile "/path/to/pubkey.pem" [pubKey]
> writeSignedObject "/path/to/cert.pem" [cert]
PKCS #12
PKCS #12 is a complex format with multiple layers of protection, providing
usually both privacy and integrity, with a single password for all or not. The
API to read PKCS #12 files requires some password at each layer. This API is
available in module Crypto.Store.PKCS12.
Reading a binary PKCS #12 file using a single password doing both integrity
and privacy (usual case):
> :set -XOverloadedStrings
> :m Crypto.Store.PKCS12
> Right p12 <- readP12File "/path/to/file.p12"
> let Right (password, pkcs12) = recoverAuthenticated "mypassword" p12
> let Right contents = recover password (unPKCS12 pkcs12)
> getAllSafeX509Certs contents
[SignedExact {getSigned = ...}]
> recover password (getAllSafeKeys contents)
Right [keyPairFromPrivKey (PrivKeyRSA ...)]
Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
> :set -XOverloadedStrings
> :m Crypto.Store.PKCS12
> Right p12 <- readP12File "/path/to/other.p12"
> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
> let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12)
> getAllSafeX509Certs contents
[SignedExact {getSigned = ...}]
> recover "myprivacypassword" (getAllSafeKeys contents)
Right [keyPairFromPrivKey (PrivKeyRSA ...)]
Generating a PKCS #12 file containing a private key:
> :set -XOverloadedStrings
-- Generate a private key
> :m Crypto.PubKey.RSA Data.X509
> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-- Put the key inside a bag
> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5
> let keyPair = keyPairFromPrivKey privKey
> let attrs = setFriendlyName "Some Key" []
> keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs
> contents = SafeContents [keyBag]
-- Encrypt the contents
> salt <- generateSalt 16
> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
> encParams <- generateCBCParams AES256
> let pbes = PBES2 (PBES2Parameter kdf encParams)
> Right pkcs12 = encrypted pbes "mypassword" contents
-- Save to PKCS #12 with integrity protection (same password)
> salt' <- generateSalt 16
> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt' 200000)
> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
Right ()
The API also provides functions to generate/extract a pair containing a private
key and a certificate chain. This pair is the type alias Credential in tls.
> :set -XOverloadedStrings
> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5
-- Read PKCS #12 content as credential
> Right p12 <- readP12File "/path/to/other.p12"
> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
> let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)
> cred
(CertificateChain [...], PrivKeyRSA (...))
-- Scheme to reencrypt the key
> saltK <- generateSalt 16
> let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256
> encParamsK <- generateCBCParams AES256
> let sKey = PBES2 (PBES2Parameter kdfK encParamsK)
-- Scheme to reencrypt the certificate chain
> saltC <- generateSalt 8
> let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256
> encParamsC <- generateCBCParams AES128
> let sCert = PBES2 (PBES2Parameter kdfC encParamsC)
-- Write the content back to a new file
> let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred
> salt <- generateSalt 16
> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt 200000)
> writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12'
Variants toNamedCredential and fromNamedCredential are also available when
PKCS #12 elements need an alias (friendly name).
The library also supports integrity protection with PBMAC1 as defined in
RFC 9579. The following example shows
how to use PBKDF2 with SHA-256 HMAC and PRF:
> :set -XOverloadedStrings
-- Generate a private key
> :m Crypto.PubKey.RSA Data.X509
> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-- Put the key inside a bag
> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5
> let keyPair = keyPairFromPrivKey privKey
> let attrs = setFriendlyName "Some Key" []
> keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs
> contents = SafeContents [keyBag]
-- Encrypt the contents
> salt <- generateSalt 16
> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
> encParams <- generateCBCParams AES256
> let pbes = PBES2 (PBES2Parameter kdf encParams)
> Right pkcs12 = encrypted pbes "mypassword" contents
-- Save to PKCS #12 with PBMAC1 integrity protection (same password)
> salt' <- generateSalt 16
> let kdf' = PBKDF2 salt' 200000 (Just 32) PBKDF2_SHA256
> let authScheme = PBMAC1 $ PBMAC1Parameter kdf' (HMAC SHA256)
> let iParams = AuthSchemeIntegrity authScheme
> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
Right ()
Algorithms and security
For compatibility reasons cryptostore implements many outdated algorithms that
are still in use in data formats. Please check your security requirements. New
applications should favor PBKDF2 or Scrypt and AEAD ciphers.
Additionally, the package is designed exclusively for store and forward
scenarios, as most algorithms will not be perfectly safe for interactive use.
ECDSA signature generation uses the generic ECC implementation from cryptonite
and could leak the private key under timing attack. A padding oracle on
CBC-encrypted ciphertext allows to recover the plaintext.
Design
Main dependencies are:
- cryptonite implementation of
public-key systems, symmetric ciphers, KDFs, MAC, and one-way hash functions
- asn1-types and
asn1-encoding to encode
and decode ASN.1 content
- pem to read and write PEM files
- x509 contains the certificate and
private-key data types
Internally the ASN.1 parser used is a local implementation extending the code of
asn1-parse. This extension is
able to parse ASN1Repr, i.e. a stream of ASN.1 tags associated with the binary
decoding events the tags were originated from. Similarly generation of ASN.1
content does not use the ASN1S type but an extension which is able to encode a
stream where some parts have already been encoded. Retaining the original
BER/DER encoding is required when incorporating MACed or signed content.