diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,59 @@
 # Revision history for cryptostore
 
+## 0.5.0.0 - 2026-02-08
+
+* Add support for key encapsulation to CMS, aka KEMRecipientInfo.  The only
+  mechanism implemented for now is RSA-KEM.
+
+* Decoding private keys in `Crypto.Store.PKCS8` is now more strict and makes
+  sure that the optional public key matches the private key when present.
+
+* Raise minimum bounds to crypton-x509-* >= 1.8.0 when flag `use_crypton`
+  is enabled, and reflect the new transitive dependencies
+
+* Fix encoding of key identifiers in CMS signer, originator and recipient info
+
+## 0.4.0.0 - 2025-10-12
+
+* Private keys are now represented as type `KeyPair` defined in module
+  `Crypto.Store.PKCS8` instead of `PrivKey`.  This ensures that the
+  corresponding public key is always available and that the library detects
+  and reports data mistakes involving a wrong public key.  A new error
+  `PublicPrivateKeyMismatch` is added for that purpose and returned by
+  functions `certSigner`, `withRecipientKeyAgree`, `fromCredential` and
+  `fromNamedCredential`.  Behavior of functions `toCredential` and
+  `toNamedCredential` is now more strict, they return a key and leaf
+  certificate that always match.  Functions to convert `KeyPair` to/from the
+  usual type `PrivKey` are also exposed from module `Crypto.Store.PKCS8`.
+
+* Added support of PBMAC1 for PKCS#12 integrity.  Type `IntegrityParams` used
+  in functions `writeP12File` and `writeP12FileToMemory` is modified.
+  A low-level function for PBMAC1 is also available in the PKCS5 module.
+
+* CMS now supports SHA-3 algorithms in HMAC, ECDSA, or as digest algorithm
+
+* Functions `pemToKey`, `pemToPubKey` and `pemToContentInfo` are modified to
+  return error details when a PEM object cannot be read.  The old API signature
+  that discarded error details is available with functions renamed
+  `pemToKeyAccum`, `pemToPubKeyAccum` and `pemToContentInfoAccum`.
+
+* Function `generateEncryptionParams` is removed and can be replaced with
+  functions `ecbParams`, `generateCBCParams`, `generateCFBParams` or
+  `generateCTRParams` according to the desired mode.
+
+* Functions `generateAuthEnc128Params` and `generateAuthEnc256Params` are
+  replaced with `authEnc128Params` and `authEnc256Params`.  The new API expects
+  a `ContentEncryptionParams` argument instead of `ContentEncryptionAlg`.
+
+* CMS encrypted data, enveloped data and authenticated-enveloped data now
+  support optional HKDF-SHA256 derivation of the content-encryption key.  When
+  generating encryption parameters, enable the key derivation feature with a
+  call to functions `deriveEncryptionKey` or `authDeriveEncryptionKey`.  The
+  feature prevents AEAD-to-CBC downgrade attack scenarios for `CCM` and `GCM`
+  modes but requires support from the receiving end.
+
+* Fixed encoding of some HMAC and PRF parameters
+
 ## 0.3.1.0 - 2024-05-05
 
 * Strict validation of GCM/CCM authentication tag length
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2024, Olivier Chéron
+Copyright (c) 2018-2026, Olivier Chéron
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 # cryptostore
 
-[![Build Status](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml)
 [![BSD](https://img.shields.io/badge/License-BSD-blue)](https://en.wikipedia.org/wiki/BSD_licenses)
 [![Haskell](https://img.shields.io/badge/Language-Haskell-lightgrey)](https://haskell.org/)
+[![Hackage](https://img.shields.io/hackage/v/cryptostore?label=Hackage&color=green)](https://hackage.haskell.org/package/cryptostore)
 
 This package allows to read and write cryptographic objects to/from ASN.1.
 
@@ -33,7 +33,7 @@
 > :m Crypto.Store.PKCS8
 > (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key
 > recover "mypassword" key
-Right (PrivKeyRSA ...)
+Right (keyPairFromPrivKey (PrivKeyRSA ...))
 ```
 
 Generating a private key and writing to disk, without encryption:
@@ -41,7 +41,8 @@
 ```haskell
 > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509
 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-> writeKeyFile PKCS8Format "/path/to/privkey.pem" [privKey]
+> let keyPair = keyPairFromPrivKey privKey
+> writeKeyFile PKCS8Format "/path/to/newkey.pem" [keyPair]
 ```
 
 Generating a private key and writing to disk, with password-based encryption:
@@ -50,11 +51,12 @@
 > :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 <- generateEncryptionParams (CBC AES256)
+> encParams <- generateCBCParams AES256
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
-> writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey
+> writeEncryptedKeyFile "/path/to/newkey.pem" pbes "mypassword" keyPair
 Right ()
 ```
 
@@ -104,7 +106,7 @@
 > getAllSafeX509Certs contents
 [SignedExact {getSigned = ...}]
 > recover password (getAllSafeKeys contents)
-Right [PrivKeyRSA ...]
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
 ```
 
 Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
@@ -112,13 +114,13 @@
 ```haskell
 > :set -XOverloadedStrings
 > :m Crypto.Store.PKCS12
-> Right p12 <- readP12File "/path/to/file.p12"
+> 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 [PrivKeyRSA ...]
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
 ```
 
 Generating a PKCS #12 file containing a private key:
@@ -132,21 +134,22 @@
 
 -- Put the key inside a bag
 > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS
+> let keyPair = keyPairFromPrivKey privKey
 > let attrs = setFriendlyName "Some Key" []
->     keyBag = Bag (KeyBag $ FormattedKey PKCS8Format privKey) attrs
+>     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 <- generateEncryptionParams (CBC AES256)
+> 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 = (DigestAlgorithm SHA256, PBEParameter salt' 200000)
-> writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12
+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt' 200000)
+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
 Right ()
 ```
 
@@ -158,7 +161,7 @@
 > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS
 
 -- Read PKCS #12 content as credential
-> Right p12 <- readP12File "/path/to/file.p12"
+> Right p12 <- readP12File "/path/to/other.p12"
 > let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)
 > cred
@@ -167,25 +170,59 @@
 -- Scheme to reencrypt the key
 > saltK <- generateSalt 16
 > let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256
-> encParamsK <- generateEncryptionParams (CBC AES256)
+> 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 <- generateEncryptionParams (CBC AES128)
+> 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 = (DigestAlgorithm SHA256, PBEParameter salt 200000)
+> 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](https://tools.ietf.org/html/rfc9579). The following example shows
+how to use PBKDF2 with SHA-256 HMAC and PRF:
+
+```haskell
+> :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 Crypto.Store.CMS
+> 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 ()
+```
+
 ## Cryptographic Message Syntax
 
 The API to read and write CMS content is available in `Crypto.Store.CMS`.  The
@@ -223,7 +260,7 @@
 > let info = DataCI "Hi, what will you need from the cryptostore?"
 
 -- Content encryption will use AES-128-CBC
-> ceParams <- generateEncryptionParams (CBC AES128)
+> ceParams <- generateCBCParams AES128
 > ceKey <- generateKey ceParams :: IO ContentEncryptionKey
 
 -- Encrypt the Content Encryption Key with a Password Recipient Info,
@@ -231,7 +268,7 @@
 -- that the recipient will need to know
 > salt <- generateSalt 16
 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
-> keParams <- generateEncryptionParams (CBC AES128)
+> keParams <- generateCBCParams AES128
 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)
 
 -- Generate the enveloped structure for this single recipient.  Encrypted
@@ -271,7 +308,7 @@
 
 -- Read signer certificate and private key
 > (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key
-> let Right priv = recover "mypassword" key
+> let Right pair = recover "mypassword" key
 > chain <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]
 > let cert = CertificateChain chain
 
@@ -281,7 +318,7 @@
 
 -- Generate the signed structure with a single signer.  Signed content is
 -- kept attached in the structure.
-> let signer = certSigner (RSAPSS params) priv cert (Just []) []
+> let signer = certSigner (RSAPSS params) pair cert (Just []) []
 > Right signedData <- signData [signer] info
 > let signedCI = toAttachedCI signedData
 > writeCMSFile "/path/to/signed.pem" [signedCI]
@@ -307,6 +344,64 @@
 > let doValidation _ chain = null <$> validateNoFQHN store def noServiceID chain
 > verifySignedData (withSignerCertificate doValidation) signedData
 Right (DataCI "Some trustworthy content")
+```
+
+### Authenticated-enveloped data
+
+The following examples generate a CMS structure auth-enveloping some data to a
+KEM recipient, then decrypt the data to recover the content.
+
+#### Generating authenticated-enveloped data
+
+```haskell
+> :set -XOverloadedStrings
+> :m Crypto.Store.CMS Data.X509 Crypto.Store.X509
+
+-- Input content info
+> let info = DataCI "Powered by Haskell"
+
+-- Read receipient certificate
+> [cert] <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]
+
+-- Content encryption will use AES-128-GCM, and we protect against manipulation
+-- of algorithm identifiers as defined in RFC 9709
+> aceParams' <- generateGCMParams AES128 16
+> let aceParams = authDeriveEncryptionKey aceParams'
+> aceKey <- generateKey aceParams :: IO ContentEncryptionKey
+
+-- Encrypt the Content Encryption Key with a KEM Recipient Info,
+-- i.e. a KDF will derive the Key Encryption Key from a shared secret produced
+-- by a Key Encapsulation Mechanism.  We are using RSA-KEM based on KDF3 with
+-- SHA-256 to produce a 16-byte shared secret.  Further derivation of the KEK
+-- uses HKDF with SHA-256.  The CEK is finally wrapped with AES-Wrap-128.
+> let kem = KeyEncapsulationRSA (KDF3 (DigestAlgorithm SHA256)) 16
+> let kdf = HKDF (DigestAlgorithm SHA256)
+> let kri = forKeyEncapRecipient cert kdf AES128_WRAP kem
+
+-- Generate the auth-enveloped structure for this single recipient.  Encrypted
+-- content is kept attached in the structure.
+> Right authEnvData <- authEnvelopData mempty aceKey aceParams [kri] [] [] info
+> let authEnvCI = toAttachedCI authEnvData
+> writeCMSFile "/path/to/authEnveloped.pem" [authEnvCI]
+```
+
+#### Opening the authenticated-enveloped data
+
+```haskell
+> :set -XOverloadedStrings
+> :m Crypto.Store.CMS Data.X509 Crypto.Store.X509 Crypto.Store.PKCS8
+
+-- Read receipient certificate and private key
+> (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key
+> let Right pair = recover "mypassword" key
+> [cert] <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]
+
+-- Then this recipient just has to read the file and recover enveloped
+-- content using the private key and certificate
+> [AuthEnvelopedDataCI authEnvEncapData] <- readCMSFile "/path/to/authEnveloped.pem"
+> authEnvData <- fromAttached authEnvEncapData
+> openAuthEnvelopedData (withRecipientKeyEncap pair cert) authEnvData
+Right (DataCI "Powered by Haskell")
 ```
 
 ## Algorithms and security
diff --git a/cryptostore.cabal b/cryptostore.cabal
--- a/cryptostore.cabal
+++ b/cryptostore.cabal
@@ -1,5 +1,5 @@
 name:                cryptostore
-version:             0.3.1.0
+version:             0.5.0.0
 synopsis:            Serialization of cryptographic data types
 description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS
                      (Cryptographic Message Syntax).
@@ -9,15 +9,15 @@
 maintainer:          olivier.cheron@gmail.com
 copyright:           Olivier Chéron
 category:            Cryptography, Codec
-homepage:            https://github.com/ocheron/cryptostore
-bug-reports:         https://github.com/ocheron/cryptostore/issues
+homepage:            https://codeberg.org/ocheron/cryptostore
+bug-reports:         https://codeberg.org/ocheron/cryptostore/issues
 build-type:          Simple
 extra-source-files:  README.md ChangeLog.md tests/files/*.pem
 cabal-version:       >=1.10
 
 source-repository head
   type:     git
-  location: https://github.com/ocheron/cryptostore
+  location: https://codeberg.org/ocheron/cryptostore
 
 flag use_crypton
   description: Use crypton instead of cryptonite
@@ -52,27 +52,33 @@
                      , Crypto.Store.CMS.Type
                      , Crypto.Store.CMS.Util
                      , Crypto.Store.Cipher.RC2.Primitive
+                     , Crypto.Store.Keys
                      , Crypto.Store.PEM
                      , Crypto.Store.PKCS5.PBES1
                      , Crypto.Store.PKCS8.EC
+                     , Crypto.Store.PubKey.RSA.KEM
                      , Crypto.Store.Util
   -- other-extensions:
   build-depends:       base >= 4.9 && < 5
                      , bytestring
                      , basement
                      , memory
-                     , pem >= 0.1 && < 0.3
-                     , asn1-types >= 0.3.1 && < 0.4
-                     , asn1-encoding >= 0.9 && < 0.10
-                     , hourglass >= 0.2
   if flag(use_crypton)
     build-depends:     crypton
-                     , crypton-x509
-                     , crypton-x509-validation
+                     , crypton-asn1-encoding >= 0.10.0 && < 0.11
+                     , crypton-asn1-types >= 0.4.1 && < 0.5
+                     , crypton-pem >= 0.2.4 && <0.4
+                     , crypton-x509 >= 1.8.0
+                     , crypton-x509-validation >= 1.8.0
+                     , time-hourglass
   else
     build-depends:     cryptonite >=0.26
                      , x509 >= 1.7.5
                      , x509-validation >= 1.5
+                     , pem >= 0.1 && < 0.3
+                     , asn1-types >= 0.3.1 && < 0.4
+                     , asn1-encoding >= 0.9.6 && < 0.10
+                     , hourglass >= 0.2.10
   default-language:    Haskell2010
   ghc-options:         -Wall
 
@@ -95,19 +101,22 @@
                      , X509.Tests
   build-depends:       base >= 4.9 && < 5
                      , bytestring
-                     , asn1-types >= 0.3.1 && < 0.4
                      , memory
                      , tasty
                      , tasty-hunit
                      , tasty-quickcheck
-                     , hourglass
-                     , pem
                      , cryptostore
   if flag(use_crypton)
     build-depends:     crypton
+                     , crypton-asn1-types >= 0.4.1 && < 0.5
+                     , crypton-pem >= 0.2.4 && <0.4
                      , crypton-x509
+                     , time-hourglass
   else
     build-depends:     cryptonite >=0.25
                      , x509
+                     , asn1-types >= 0.3.1 && < 0.4
+                     , hourglass
+                     , pem
   default-language:    Haskell2010
   ghc-options:         -Wall
diff --git a/src/Crypto/Store/ASN1/Generate.hs b/src/Crypto/Store/ASN1/Generate.hs
--- a/src/Crypto/Store/ASN1/Generate.hs
+++ b/src/Crypto/Store/ASN1/Generate.hs
@@ -107,7 +107,7 @@
 gIntVal :: ASN1Elem e => Integer -> ASN1Stream e
 gIntVal = gOne . IntVal
 
--- | Generate an 'OID' ASN.1 element.
+-- | Generate an t'OID' ASN.1 element.
 gOID :: ASN1Elem e => OID -> ASN1Stream e
 gOID = gOne . OID
 
diff --git a/src/Crypto/Store/ASN1/Parse.hs b/src/Crypto/Store/ASN1/Parse.hs
--- a/src/Crypto/Store/ASN1/Parse.hs
+++ b/src/Crypto/Store/ASN1/Parse.hs
@@ -9,7 +9,7 @@
 -- with the following additions:
 --
 -- * Parsed stream is annotated, i.e. parser input is @('ASN1', e)@ instead of
---   @'ASN1'@.  Main motivation is to allow to parse a sequence of 'ASN1Repr'
+--   @'ASN1'@.  Main motivation is to allow to parse a sequence of @ASN1Repr@
 --   and hold the exact binary content that has been parsed.  As consequence,
 --   no @getObject@ function is provided.  Function 'withAnnotations' runs
 --   a parser and returns all annotations consumed in a monoid concatenation.
@@ -40,7 +40,9 @@
     ) where
 
 import Data.ASN1.Types
-import Data.Monoid
+#if !(MIN_VERSION_base(4,11,0))
+import Data.Monoid ( (<>) )
+#endif
 import Control.Applicative
 import Control.Arrow (first)
 import Control.Monad (MonadPlus(..), liftM2)
diff --git a/src/Crypto/Store/CMS.hs b/src/Crypto/Store/CMS.hs
--- a/src/Crypto/Store/CMS.hs
+++ b/src/Crypto/Store/CMS.hs
@@ -22,6 +22,10 @@
 -- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc8702 RFC 8702>: Use of the SHAKE One-Way Hash Functions in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc9629 RFC 9629>: Using Key Encapsulation Mechanism (KEM) Algorithms in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc9688 RFC 9688>: Use of the SHA3 One-Way Hash Functions in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc9690 RFC 9690>: Use of the RSA-KEM Algorithm in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc9709 RFC 9709>: Encryption Key Derivation in the Cryptographic Message Syntax (CMS) Using HKDF with SHA-256
 {-# LANGUAGE RecordWildCards #-}
 module Crypto.Store.CMS
     ( ContentType(..)
@@ -62,6 +66,7 @@
     , KeyTransportParams(..)
     , KeyAgreementParams(..)
     , KeyAgreementKDF(..)
+    , KeyEncapsulationMechanism(..)
     , RecipientInfo(..)
     , EnvelopedData(..)
     , ProducerOfRI
@@ -93,6 +98,10 @@
     , PasswordRecipientInfo(..)
     , forPasswordRecipient
     , withRecipientPassword
+     -- ** Key Encapsulation recipients
+    , KEMRecipientInfo(..)
+    , forKeyEncapRecipient
+    , withRecipientKeyEncap
     -- * Digested data
     , DigestProxy(..)
     , DigestAlgorithm(..)
@@ -106,8 +115,12 @@
     , ContentEncryptionParams
     , EncryptedContent
     , EncryptedData(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
     , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
+    , deriveEncryptionKey
     , getContentEncryptionAlg
     , encryptData
     , decryptData
@@ -122,11 +135,12 @@
     , AuthContentEncryptionAlg(..)
     , AuthContentEncryptionParams
     , AuthEnvelopedData(..)
-    , generateAuthEnc128Params
-    , generateAuthEnc256Params
+    , authEnc128Params
+    , authEnc256Params
     , generateChaChaPoly1305Params
     , generateCCMParams
     , generateGCMParams
+    , authDeriveEncryptionKey
     , authEnvelopData
     , openAuthEnvelopedData
     -- * Key derivation
@@ -134,6 +148,7 @@
     , generateSalt
     , KeyDerivationFunc(..)
     , PBKDF2_PRF(..)
+    , KeyDerivationFn(..)
     -- * Secret-key algorithms
     , HasKeySize(..)
     , generateKey
@@ -254,7 +269,7 @@
             -> ContentInfo
             -> f (Either StoreError (EnvelopedData EncryptedContent))
 envelopData oinfo key params envFns attrs ci =
-    f <$> (sequence <$> traverse ($ key) envFns)
+    f . sequence <$> traverse ($ key) envFns
   where
     ebs = contentEncrypt key params (encapsulate ci)
     f ris = build <$> ebs <*> ris
@@ -304,7 +319,7 @@
                           -> ContentInfo
                           -> f (Either StoreError (AuthenticatedData EncapsulatedContent))
 generateAuthenticatedData oinfo key macAlg digAlg envFns aAttrs uAttrs ci =
-    f <$> (sequence <$> traverse ($ key) envFns)
+    f . sequence <$> traverse ($ key) envFns
   where
     msg = encapsulate ci
     ct  = getContentType ci
@@ -383,17 +398,17 @@
                 -> ContentInfo
                 -> f (Either StoreError (AuthEnvelopedData EncryptedContent))
 authEnvelopData oinfo key params envFns aAttrs uAttrs ci =
-    f <$> (sequence <$> traverse ($ key) envFns)
+    f . sequence <$> traverse ($ key) envFns
   where
-    raw = encodeASN1Object params
+    prm = derObjectExact params
     aad = encodeAuthAttrs aAttrs
-    ebs = authContentEncrypt key params raw aad (encapsulate ci)
+    ebs = authContentEncrypt key prm aad (encapsulate ci)
     f ris = build <$> ebs <*> ris
     build (authTag, bs) ris = AuthEnvelopedData
                        { aeOriginatorInfo = oinfo
                        , aeRecipientInfos = ris
                        , aeContentType = getContentType ci
-                       , aeContentEncryptionParams = ASN1ObjectExact params raw
+                       , aeContentEncryptionParams = prm
                        , aeEncryptedContent = bs
                        , aeAuthAttrs = aAttrs
                        , aeMAC = authTag
@@ -411,10 +426,8 @@
     return (r >>= decapsulate ct)
   where
     ct       = aeContentType
-    params   = exactObject aeContentEncryptionParams
-    raw      = exactObjectRaw aeContentEncryptionParams
     aad      = encodeAuthAttrs aeAuthAttrs
-    decr k   = authContentDecrypt k params raw aad aeEncryptedContent aeMAC
+    decr k   = authContentDecrypt k aeContentEncryptionParams aad aeEncryptedContent aeMAC
 
 
 -- SignedData
@@ -425,7 +438,7 @@
 signData :: Applicative f
          => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError (SignedData EncapsulatedContent))
 signData sigFns ci =
-    f <$> (sequence <$> traverse (\fn -> fn ct msg) sigFns)
+    f . sequence <$> traverse (\fn -> fn ct msg) sigFns
   where
     msg = encapsulate ci
     ct  = getContentType ci
diff --git a/src/Crypto/Store/CMS/Algorithms.hs b/src/Crypto/Store/CMS/Algorithms.hs
--- a/src/Crypto/Store/CMS/Algorithms.hs
+++ b/src/Crypto/Store/CMS/Algorithms.hs
@@ -8,6 +8,7 @@
 -- Cryptographic Message Syntax algorithms
 {-# LANGUAGE DataKinds #-}
 {-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -16,6 +17,7 @@
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
 {-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 module Crypto.Store.CMS.Algorithms
     ( DigestAlgorithm(..)
@@ -24,7 +26,7 @@
     , MessageAuthenticationCode
     , MACAlgorithm(..)
     , mac
-    , HasStrength
+    , HasStrength(..)
     , securityAcceptable
     , HasKeySize(..)
     , getMaximumKeySize
@@ -33,19 +35,24 @@
     , ContentEncryptionCipher(..)
     , ContentEncryptionAlg(..)
     , ContentEncryptionParams(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
     , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
+    , deriveEncryptionKey
     , getContentEncryptionAlg
     , proxyBlockSize
     , contentEncrypt
     , contentDecrypt
     , AuthContentEncryptionAlg(..)
     , AuthContentEncryptionParams
-    , generateAuthEnc128Params
-    , generateAuthEnc256Params
+    , authEnc128Params
+    , authEnc256Params
     , generateChaChaPoly1305Params
     , generateCCMParams
     , generateGCMParams
+    , authDeriveEncryptionKey
     , authContentEncrypt
     , authContentDecrypt
     , PBKDF2_PRF(..)
@@ -54,7 +61,10 @@
     , generateSalt
     , KeyDerivationFunc(..)
     , kdfKeyLength
+    , kdfKeyLengthModify
     , kdfDerive
+    , KeyDerivationFn(..)
+    , kdfApply
     , KeyEncryptionParams(..)
     , keyEncrypt
     , keyDecrypt
@@ -69,6 +79,9 @@
     , ecdhPublic
     , ecdhEncrypt
     , ecdhDecrypt
+    , KeyEncapsulationMechanism(..)
+    , kemEncap
+    , kemDecap
     , MaskGenerationFunc(..)
     , mgf
     , SignatureValue
@@ -91,7 +104,7 @@
 import           Data.ByteArray (ByteArray, ByteArrayAccess)
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
-import           Data.Maybe (fromMaybe)
+import           Data.Maybe (fromJust, fromMaybe)
 import           Data.Proxy
 import           Data.Word
 import qualified Data.X509 as X509
@@ -142,7 +155,9 @@
 import qualified Crypto.Store.KeyWrap.AES as AES_KW
 import qualified Crypto.Store.KeyWrap.TripleDES as TripleDES_KW
 import qualified Crypto.Store.KeyWrap.RC2 as RC2_KW
+import           Crypto.Store.Keys
 import           Crypto.Store.PKCS8.EC
+import qualified Crypto.Store.PubKey.RSA.KEM as RsaKem
 import           Crypto.Store.Util
 
 
@@ -167,6 +182,14 @@
     SHA384 :: DigestProxy Hash.SHA384
     -- | SHA-512
     SHA512 :: DigestProxy Hash.SHA512
+    -- | SHA3-224
+    SHA3_224 :: DigestProxy Hash.SHA3_224
+    -- | SHA3-256
+    SHA3_256 :: DigestProxy Hash.SHA3_256
+    -- | SHA3-384
+    SHA3_384 :: DigestProxy Hash.SHA3_384
+    -- | SHA3-512
+    SHA3_512 :: DigestProxy Hash.SHA3_512
     -- | SHAKE128 (256 bits)
     SHAKE128_256 :: DigestProxy (Hash.SHAKE128 256)
     -- | SHAKE256 (512 bits)
@@ -186,6 +209,10 @@
     showsPrec _ SHA256       = showString "SHA256"
     showsPrec _ SHA384       = showString "SHA384"
     showsPrec _ SHA512       = showString "SHA512"
+    showsPrec _ SHA3_224     = showString "SHA3_224"
+    showsPrec _ SHA3_256     = showString "SHA3_256"
+    showsPrec _ SHA3_384     = showString "SHA3_384"
+    showsPrec _ SHA3_512     = showString "SHA3_512"
     showsPrec _ SHAKE128_256 = showString "SHAKE128_256"
     showsPrec _ SHAKE256_512 = showString "SHAKE256_512"
     showsPrec d (SHAKE128 p) =
@@ -208,6 +235,10 @@
     getSecurityBits SHA256       = 128
     getSecurityBits SHA384       = 192
     getSecurityBits SHA512       = 256
+    getSecurityBits SHA3_224     = 112
+    getSecurityBits SHA3_256     = 128
+    getSecurityBits SHA3_384     = 192
+    getSecurityBits SHA3_512     = 256
     getSecurityBits SHAKE128_256 = 128
     getSecurityBits SHAKE256_512 = 256
     getSecurityBits (SHAKE128 a) = shakeSecurityBits 128 a
@@ -233,6 +264,10 @@
     DigestAlgorithm SHA256       == DigestAlgorithm SHA256       = True
     DigestAlgorithm SHA384       == DigestAlgorithm SHA384       = True
     DigestAlgorithm SHA512       == DigestAlgorithm SHA512       = True
+    DigestAlgorithm SHA3_224     == DigestAlgorithm SHA3_224     = True
+    DigestAlgorithm SHA3_256     == DigestAlgorithm SHA3_256     = True
+    DigestAlgorithm SHA3_384     == DigestAlgorithm SHA3_384     = True
+    DigestAlgorithm SHA3_512     == DigestAlgorithm SHA3_512     = True
     DigestAlgorithm SHAKE128_256 == DigestAlgorithm SHAKE128_256 = True
     DigestAlgorithm SHAKE256_512 == DigestAlgorithm SHAKE256_512 = True
     DigestAlgorithm (SHAKE128 a) == DigestAlgorithm (SHAKE128 b) = natVal a == natVal b
@@ -251,6 +286,10 @@
     | Type_SHA256
     | Type_SHA384
     | Type_SHA512
+    | Type_SHA3_224
+    | Type_SHA3_256
+    | Type_SHA3_384
+    | Type_SHA3_512
     | Type_SHAKE128_256
     | Type_SHAKE256_512
     | Type_SHAKE128_Len
@@ -265,6 +304,10 @@
              , Type_SHA256
              , Type_SHA384
              , Type_SHA512
+             , Type_SHA3_224
+             , Type_SHA3_256
+             , Type_SHA3_384
+             , Type_SHA3_512
              , Type_SHAKE128_256
              , Type_SHAKE256_512
              , Type_SHAKE128_Len
@@ -280,6 +323,10 @@
     getObjectID Type_SHA256       = [2,16,840,1,101,3,4,2,1]
     getObjectID Type_SHA384       = [2,16,840,1,101,3,4,2,2]
     getObjectID Type_SHA512       = [2,16,840,1,101,3,4,2,3]
+    getObjectID Type_SHA3_224     = [2,16,840,1,101,3,4,2,7]
+    getObjectID Type_SHA3_256     = [2,16,840,1,101,3,4,2,8]
+    getObjectID Type_SHA3_384     = [2,16,840,1,101,3,4,2,9]
+    getObjectID Type_SHA3_512     = [2,16,840,1,101,3,4,2,10]
     getObjectID Type_SHAKE128_256 = [2,16,840,1,101,3,4,2,11]
     getObjectID Type_SHAKE256_512 = [2,16,840,1,101,3,4,2,12]
     getObjectID Type_SHAKE128_Len = [2,16,840,1,101,3,4,2,17]
@@ -300,6 +347,10 @@
     algorithmType (DigestAlgorithm SHA256)       = Type_SHA256
     algorithmType (DigestAlgorithm SHA384)       = Type_SHA384
     algorithmType (DigestAlgorithm SHA512)       = Type_SHA512
+    algorithmType (DigestAlgorithm SHA3_224)     = Type_SHA3_224
+    algorithmType (DigestAlgorithm SHA3_256)     = Type_SHA3_256
+    algorithmType (DigestAlgorithm SHA3_384)     = Type_SHA3_384
+    algorithmType (DigestAlgorithm SHA3_512)     = Type_SHA3_512
     algorithmType (DigestAlgorithm SHAKE128_256) = Type_SHAKE128_256
     algorithmType (DigestAlgorithm SHAKE256_512) = Type_SHAKE256_512
     algorithmType (DigestAlgorithm (SHAKE128 _)) = Type_SHAKE128_Len
@@ -320,6 +371,10 @@
     parseParameter Type_SHA256       = parseDigestParam (DigestAlgorithm SHA256)
     parseParameter Type_SHA384       = parseDigestParam (DigestAlgorithm SHA384)
     parseParameter Type_SHA512       = parseDigestParam (DigestAlgorithm SHA512)
+    parseParameter Type_SHA3_224     = parseDigestParam (DigestAlgorithm SHA3_224)
+    parseParameter Type_SHA3_256     = parseDigestParam (DigestAlgorithm SHA3_256)
+    parseParameter Type_SHA3_384     = parseDigestParam (DigestAlgorithm SHA3_384)
+    parseParameter Type_SHA3_512     = parseDigestParam (DigestAlgorithm SHA3_512)
     parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)
     parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)
     parseParameter Type_SHAKE128_Len = parseBitLen $
@@ -456,19 +511,27 @@
              , TypeHMAC SHA256
              , TypeHMAC SHA384
              , TypeHMAC SHA512
+             , TypeHMAC SHA3_224
+             , TypeHMAC SHA3_256
+             , TypeHMAC SHA3_384
+             , TypeHMAC SHA3_512
              , TypeKMAC_SHAKE128
              , TypeKMAC_SHAKE256
              ]
 
 instance OIDable MACType where
-    getObjectID (TypeHMAC MD5)    = [1,3,6,1,5,5,8,1,1]
-    getObjectID (TypeHMAC SHA1)   = [1,3,6,1,5,5,8,1,2]
-    getObjectID (TypeHMAC SHA224) = [1,2,840,113549,2,8]
-    getObjectID (TypeHMAC SHA256) = [1,2,840,113549,2,9]
-    getObjectID (TypeHMAC SHA384) = [1,2,840,113549,2,10]
-    getObjectID (TypeHMAC SHA512) = [1,2,840,113549,2,11]
-    getObjectID TypeKMAC_SHAKE128 = [2,16,840,1,101,3,4,2,19]
-    getObjectID TypeKMAC_SHAKE256 = [2,16,840,1,101,3,4,2,20]
+    getObjectID (TypeHMAC MD5)      = [1,2,840,113549,2,6]
+    getObjectID (TypeHMAC SHA1)     = [1,2,840,113549,2,7]
+    getObjectID (TypeHMAC SHA224)   = [1,2,840,113549,2,8]
+    getObjectID (TypeHMAC SHA256)   = [1,2,840,113549,2,9]
+    getObjectID (TypeHMAC SHA384)   = [1,2,840,113549,2,10]
+    getObjectID (TypeHMAC SHA512)   = [1,2,840,113549,2,11]
+    getObjectID (TypeHMAC SHA3_224) = [2,16,840,1,101,3,4,2,13]
+    getObjectID (TypeHMAC SHA3_256) = [2,16,840,1,101,3,4,2,14]
+    getObjectID (TypeHMAC SHA3_384) = [2,16,840,1,101,3,4,2,15]
+    getObjectID (TypeHMAC SHA3_512) = [2,16,840,1,101,3,4,2,16]
+    getObjectID TypeKMAC_SHAKE128   = [2,16,840,1,101,3,4,2,19]
+    getObjectID TypeKMAC_SHAKE256   = [2,16,840,1,101,3,4,2,20]
 
     getObjectID ty = error ("Unsupported MACAlgorithm: " ++ show ty)
 
@@ -483,6 +546,12 @@
     algorithmType (KMAC_SHAKE128 _ _) = TypeKMAC_SHAKE128
     algorithmType (KMAC_SHAKE256 _ _) = TypeKMAC_SHAKE256
 
+    -- SHA-224, SHA-256, SHA-384, SHA-512 have NULL parameter,
+    -- other HMAC algorithms have no parameter
+    parameterASN1S (HMAC SHA224)         = gNull
+    parameterASN1S (HMAC SHA256)         = gNull
+    parameterASN1S (HMAC SHA384)         = gNull
+    parameterASN1S (HMAC SHA512)         = gNull
     parameterASN1S (HMAC _)              = id
     parameterASN1S (KMAC_SHAKE128 p str) = kmacASN1S 256 p str
     parameterASN1S (KMAC_SHAKE256 p str) = kmacASN1S 512 p str
@@ -507,7 +576,7 @@
     if b then parseParams else return (fn p' B.empty)
   where
     parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->
-        getNext >>= \(OctetString str) -> return (fn p str)
+        fn p <$> parseOctetStringPrim
 
 instance HasKeySize MACAlgorithm where
     getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)
@@ -592,6 +661,9 @@
       -- ^ Cipher Feedback
     | forall c . BlockCipher c => CTR (ContentEncryptionCipher c)
       -- ^ Counter
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => DeriveHKDF (DigestProxy hashAlg)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show ContentEncryptionAlg where
     show (ECB c) = shows c "_ECB"
@@ -599,6 +671,7 @@
     show CBC_RC2 = "RC2_CBC"
     show (CFB c) = shows c "_CFB"
     show (CTR c) = shows c "_CTR"
+    show (DeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d
 
 instance Enumerable ContentEncryptionAlg where
     values = [ CBC DES
@@ -623,6 +696,8 @@
              , CFB Camellia128
 
              , CTR Camellia128
+
+             , DeriveHKDF SHA256
              ]
 
 instance OIDable ContentEncryptionAlg where
@@ -649,6 +724,8 @@
 
     getObjectID (CTR Camellia128)  = [0,3,4401,5,3,1,9,9]
 
+    getObjectID (DeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]
+
     getObjectID ty = error ("Unsupported ContentEncryptionAlg: " ++ show ty)
 
 instance OIDNameable ContentEncryptionAlg where
@@ -657,7 +734,8 @@
 -- | Content encryption algorithm with associated parameters (i.e. the
 -- initialization vector).
 --
--- A value can be generated with 'generateEncryptionParams'.
+-- A value can be built with functions 'ecbParams', 'generateCBCParams',
+-- 'generateRC2EncryptionParams', 'generateCFBParams' and 'generateCTRParams'.
 data ContentEncryptionParams
     = forall c . BlockCipher c => ParamsECB (ContentEncryptionCipher c)
       -- ^ Electronic Codebook
@@ -669,6 +747,9 @@
       -- ^ Cipher Feedback
     | forall c . BlockCipher c => ParamsCTR (ContentEncryptionCipher c) (IV c)
       -- ^ Counter
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => ParamsDeriveHKDF (DigestProxy hashAlg) ContentEncryptionParams
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show ContentEncryptionParams where
     show = show . getContentEncryptionAlg
@@ -679,6 +760,8 @@
     ParamsCBCRC2 i1 iv1 == ParamsCBCRC2 i2 iv2 = i1 == i2 && iv1 `B.eq` iv2
     ParamsCFB c1 iv1    == ParamsCFB c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2
     ParamsCTR c1 iv1    == ParamsCTR c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2
+    ParamsDeriveHKDF d1 a1 == ParamsDeriveHKDF d2 a2 =
+        DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2
     _                   == _                   = False
 
 instance HasKeySize ContentEncryptionParams where
@@ -687,6 +770,7 @@
     getKeySizeSpecifier (ParamsCBCRC2 i _) = KeySizeFixed $ (i + 7) `div` 8
     getKeySizeSpecifier (ParamsCFB c _)    = getCipherKeySizeSpecifier c
     getKeySizeSpecifier (ParamsCTR c _)    = getCipherKeySizeSpecifier c
+    getKeySizeSpecifier (ParamsDeriveHKDF _ a) = getKeySizeSpecifier a
 
 instance ASN1Elem e => ProduceASN1Object e ContentEncryptionParams where
     asn1s param =
@@ -706,6 +790,7 @@
 ceParameterASN1S (ParamsCBCRC2 len iv) = rc2ParameterASN1S len iv
 ceParameterASN1S (ParamsCFB _ iv)      = gOctetString (B.convert iv)
 ceParameterASN1S (ParamsCTR _ iv)      = gOctetString (B.convert iv)
+ceParameterASN1S (ParamsDeriveHKDF _ a) = asn1s a
 
 parseCEParameter :: Monoid e
                  => ContentEncryptionAlg -> ParseASN1 e ContentEncryptionParams
@@ -714,6 +799,7 @@
 parseCEParameter CBC_RC2 = parseRC2Parameter
 parseCEParameter (CFB c) = ParamsCFB c <$> (getNext >>= getIV)
 parseCEParameter (CTR c) = ParamsCTR c <$> (getNext >>= getIV)
+parseCEParameter (DeriveHKDF d) = ParamsDeriveHKDF d <$> parse
 
 getIV :: BlockCipher cipher => ASN1 -> ParseASN1 e (IV cipher)
 getIV (OctetString ivBs) =
@@ -742,22 +828,41 @@
 getContentEncryptionAlg (ParamsCBCRC2 _ _) = CBC_RC2
 getContentEncryptionAlg (ParamsCFB c _)    = CFB c
 getContentEncryptionAlg (ParamsCTR c _)    = CTR c
+getContentEncryptionAlg (ParamsDeriveHKDF d _) = DeriveHKDF d
 
--- | Generate random parameters for the specified content encryption algorithm.
-generateEncryptionParams :: MonadRandom m
-                         => ContentEncryptionAlg -> m ContentEncryptionParams
-generateEncryptionParams (ECB c) = return (ParamsECB c)
-generateEncryptionParams (CBC c) = ParamsCBC c <$> ivGenerate undefined
-generateEncryptionParams CBC_RC2 = ParamsCBCRC2 128 <$> ivGenerate undefined
-generateEncryptionParams (CFB c) = ParamsCFB c <$> ivGenerate undefined
-generateEncryptionParams (CTR c) = ParamsCTR c <$> ivGenerate undefined
+-- | Get 'ECB' parameters for the specified cipher.
+ecbParams :: BlockCipher c
+          => ContentEncryptionCipher c -> ContentEncryptionParams
+ecbParams = ParamsECB
 
+-- | Generate random 'CBC' parameters for the specified cipher.
+generateCBCParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCBCParams c = ParamsCBC c <$> ivGenerate undefined
+
 -- | Generate random RC2 parameters with the specified effective key length (in
 -- bits).
 generateRC2EncryptionParams :: MonadRandom m
                             => Int -> m ContentEncryptionParams
 generateRC2EncryptionParams len = ParamsCBCRC2 len <$> ivGenerate undefined
 
+-- | Generate random 'CFB' parameters for the specified cipher.
+generateCFBParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCFBParams c = ParamsCFB c <$> ivGenerate undefined
+
+-- | Generate random 'CTR' parameters for the specified cipher.
+generateCTRParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCTRParams c = ParamsCTR c <$> ivGenerate undefined
+
+-- | Use derivation of the content-encryption key with HKDF-SHA256
+deriveEncryptionKey :: ContentEncryptionParams -> ContentEncryptionParams
+deriveEncryptionKey = ParamsDeriveHKDF SHA256
+
 -- | Encrypt a bytearray with the specified content encryption key and
 -- algorithm.
 contentEncrypt :: (ByteArray cek, ByteArray ba)
@@ -771,6 +876,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> force $ cbcEncrypt c iv $ padded c bs)
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> force $ cfbEncrypt c iv $ padded c bs)
         ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> force $ ctrCombine c iv $ padded c bs)
+        ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentEncrypt derived a bs
   where
     force x  = x `seq` Right x
     padded c = pad (PKCS7 $ blockSize c)
@@ -788,6 +894,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> unpadded c (cbcDecrypt c iv bs))
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> unpadded c (cfbDecrypt c iv bs))
         ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> unpadded c (ctrCombine c iv bs))
+        ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentDecrypt derived a bs
   where
     unpadded c decrypted =
         case unpad (PKCS7 $ blockSize c) decrypted of
@@ -854,6 +961,9 @@
       -- ^ Counter with CBC-MAC
     | forall c . BlockCipher c => GCM (ContentEncryptionCipher c)
       -- ^ Galois Counter Mode
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => AuthDeriveHKDF (DigestProxy hashAlg)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show AuthContentEncryptionAlg where
     show AUTH_ENC_128 = "AUTH_ENC_128"
@@ -861,6 +971,7 @@
     show CHACHA20_POLY1305 = "CHACHA20_POLY1305"
     show (CCM c)      = shows c "_CCM"
     show (GCM c)      = shows c "_GCM"
+    show (AuthDeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d
 
 instance Enumerable AuthContentEncryptionAlg where
     values = [ AUTH_ENC_128
@@ -874,6 +985,8 @@
              , GCM AES128
              , GCM AES192
              , GCM AES256
+
+             , AuthDeriveHKDF SHA256
              ]
 
 instance OIDable AuthContentEncryptionAlg where
@@ -889,6 +1002,8 @@
     getObjectID (GCM AES192)       = [2,16,840,1,101,3,4,1,26]
     getObjectID (GCM AES256)       = [2,16,840,1,101,3,4,1,46]
 
+    getObjectID (AuthDeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]
+
     getObjectID ty = error ("Unsupported AuthContentEncryptionAlg: " ++ show ty)
 
 instance OIDNameable AuthContentEncryptionAlg where
@@ -933,9 +1048,8 @@
 -- | Authenticated-content encryption algorithm with associated parameters
 -- (i.e. the nonce).
 --
--- A value can be generated with functions 'generateAuthEnc128Params',
--- 'generateAuthEnc256Params', 'generateChaChaPoly1305Params',
--- 'generateCCMParams' and 'generateGCMParams'.
+-- A value can be built with functions 'authEnc128Params', 'authEnc256Params',
+-- 'generateChaChaPoly1305Params', 'generateCCMParams' and 'generateGCMParams'.
 data AuthContentEncryptionParams
     = Params_AUTH_ENC_128 AuthEncParams
       -- ^ authEnc with 128-bit keying material
@@ -947,6 +1061,9 @@
       -- ^ Counter with CBC-MAC
     | forall c . BlockCipher c => ParamsGCM (ContentEncryptionCipher c) B.Bytes Int
       -- ^ Galois Counter Mode
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => ParamsAuthDeriveHKDF (DigestProxy hashAlg) (ASN1ObjectExact AuthContentEncryptionParams)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show AuthContentEncryptionParams where
     show = show . getAuthContentEncryptionAlg
@@ -961,6 +1078,10 @@
         cecI c1 == cecI c2 && iv1 == iv2 && (m1, l1) == (m2, l2)
     ParamsGCM c1 iv1 len1  == ParamsGCM c2 iv2 len2  =
         cecI c1 == cecI c2 && iv1 == iv2 && len1 == len2
+
+    ParamsAuthDeriveHKDF d1 a1 == ParamsAuthDeriveHKDF d2 a2 =
+        DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2
+
     _               == _               = False
 
 instance HasKeySize AuthContentEncryptionParams where
@@ -969,38 +1090,39 @@
     getKeySizeSpecifier (Params_CHACHA20_POLY1305 _) = KeySizeFixed 32
     getKeySizeSpecifier (ParamsCCM c _ _ _)     = getCipherKeySizeSpecifier c
     getKeySizeSpecifier (ParamsGCM c _ _)       = getCipherKeySizeSpecifier c
+    getKeySizeSpecifier (ParamsAuthDeriveHKDF _ a) = getKeySizeSpecifier (exactObject a)
 
-instance ASN1Elem e => ProduceASN1Object e AuthContentEncryptionParams where
+instance ProduceASN1Object ASN1P AuthContentEncryptionParams where
     asn1s param =
         asn1Container Sequence (oid . params)
       where
         oid    = gOID (getObjectID $ getAuthContentEncryptionAlg param)
         params = aceParameterASN1S param
 
-instance Monoid e => ParseASN1Object e AuthContentEncryptionParams where
+instance ParseASN1Object [ASN1Event] AuthContentEncryptionParams where
     parse = onNextContainer Sequence $ do
         OID oid <- getNext
         withObjectID "authenticated-content encryption algorithm" oid
             parseACEParameter
 
-aceParameterASN1S :: ASN1Elem e => AuthContentEncryptionParams -> ASN1Stream e
+aceParameterASN1S :: AuthContentEncryptionParams -> ASN1PS
 aceParameterASN1S (Params_AUTH_ENC_128 p) = asn1s p
 aceParameterASN1S (Params_AUTH_ENC_256 p) = asn1s p
 aceParameterASN1S (Params_CHACHA20_POLY1305 iv) = gOctetString (B.convert iv)
 aceParameterASN1S (ParamsCCM _ iv m _) =
-    asn1Container Sequence (nonce . icvlen)
+    asn1Container Sequence (if m == CCM_M12 then nonce else nonce . icvlen)
   where
     nonce  = gOctetString (B.convert iv)
     icvlen = gIntVal (fromIntegral $ getM m)
 aceParameterASN1S (ParamsGCM _ iv len) =
-    asn1Container Sequence (nonce . icvlen)
+    asn1Container Sequence (if len == 12 then nonce else nonce . icvlen)
   where
     nonce  = gOctetString (B.convert iv)
     icvlen = gIntVal (fromIntegral len)
+aceParameterASN1S (ParamsAuthDeriveHKDF _ a) = asn1s a
 
-parseACEParameter :: Monoid e
-                  => AuthContentEncryptionAlg
-                  -> ParseASN1 e AuthContentEncryptionParams
+parseACEParameter :: AuthContentEncryptionAlg
+                  -> ParseASN1 [ASN1Event] AuthContentEncryptionParams
 parseACEParameter AUTH_ENC_128 = Params_AUTH_ENC_128 <$> parse
 parseACEParameter AUTH_ENC_256 = Params_AUTH_ENC_256 <$> parse
 parseACEParameter CHACHA20_POLY1305 = do
@@ -1014,7 +1136,7 @@
     let ivlen = B.length iv
     when (ivlen < 7 || ivlen > 13) $
         throwParseError $ "Parsed invalid CCM nonce length: " ++ show ivlen
-    let Just l = fromL (15 - ivlen)
+    let l = fromJust $ fromL (15 - ivlen)
     m <- parseM
     return (ParamsCCM c (B.convert iv) m l)
 parseACEParameter (GCM c)      = onNextContainer Sequence $ do
@@ -1025,6 +1147,7 @@
     when (icvlen < 12 || icvlen > 16) $
         throwParseError $ "Parsed invalid GCM ICV length: " ++ show icvlen
     return (ParamsGCM c (B.convert iv) $ fromIntegral icvlen)
+parseACEParameter (AuthDeriveHKDF d) = ParamsAuthDeriveHKDF d <$> parse
 
 -- | Get the authenticated-content encryption algorithm.
 getAuthContentEncryptionAlg :: AuthContentEncryptionParams
@@ -1034,26 +1157,23 @@
 getAuthContentEncryptionAlg (Params_CHACHA20_POLY1305 _) = CHACHA20_POLY1305
 getAuthContentEncryptionAlg (ParamsCCM c _ _ _)     = CCM c
 getAuthContentEncryptionAlg (ParamsGCM c _ _)       = GCM c
+getAuthContentEncryptionAlg (ParamsAuthDeriveHKDF d _) = AuthDeriveHKDF d
 
--- | Generate random 'AUTH_ENC_128' parameters with the specified algorithms.
-generateAuthEnc128Params :: MonadRandom m
-                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm
-                         -> m AuthContentEncryptionParams
-generateAuthEnc128Params prfAlg cea macAlg = do
-    params <- generateEncryptionParams cea
-    return $ Params_AUTH_ENC_128 $
+-- | Get 'AUTH_ENC_128' parameters with the specified algorithms.
+authEnc128Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm
+                 -> AuthContentEncryptionParams
+authEnc128Params prfAlg params macAlg =
+    Params_AUTH_ENC_128 $
         AuthEncParams { prfAlgorithm = prfAlg
                       , encAlgorithm = params
                       , macAlgorithm = macAlg
                       }
 
--- | Generate random 'AUTH_ENC_256' parameters with the specified algorithms.
-generateAuthEnc256Params :: MonadRandom m
-                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm
-                         -> m AuthContentEncryptionParams
-generateAuthEnc256Params prfAlg cea macAlg = do
-    params <- generateEncryptionParams cea
-    return $ Params_AUTH_ENC_256 $
+-- | Get 'AUTH_ENC_256' parameters with the specified algorithms.
+authEnc256Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm
+                 -> AuthContentEncryptionParams
+authEnc256Params prfAlg params macAlg = do
+    Params_AUTH_ENC_256 $
         AuthEncParams { prfAlgorithm = prfAlg
                       , encAlgorithm = params
                       , macAlgorithm = macAlg
@@ -1082,19 +1202,37 @@
     iv <- nonceGenerate 12
     return (ParamsGCM c iv l)
 
+-- | Use derivation of the content-encryption key with HKDF-SHA256
+authDeriveEncryptionKey :: AuthContentEncryptionParams -> AuthContentEncryptionParams
+authDeriveEncryptionKey = ParamsAuthDeriveHKDF SHA256 . derObjectExact
+
+deriveHKDF :: (Hash.HashAlgorithm a, ProduceASN1Object ASN1P params, ByteArrayAccess ikm)
+           => DigestProxy a -> params -> ikm -> Either StoreError B.ScrubbedBytes
+deriveHKDF hashAlg params ikm =
+    kdfApply (HKDF (DigestAlgorithm hashAlg)) (salt :: B.Bytes) len info ikm
+  where
+    info = encodeASN1S (asn1s params)
+    len  = B.length ikm
+
+    -- "The Cryptographic Message Syntax"
+    salt = B.pack [84,104,101,32,67,114,121,112,116,111,103,114,97,112,104,105
+                  ,99,32,77,101,115,115,97,103,101,32,83,121,110,116,97,120]
+
 -- | Encrypt a bytearray with the specified authenticated-content encryption
 -- key and algorithm.
 authContentEncrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)
                    => cek
-                   -> AuthContentEncryptionParams -> ba
+                   -> ASN1ObjectExact AuthContentEncryptionParams
                    -> aad -> ba -> Either StoreError (AuthTag, ba)
-authContentEncrypt key params paramsRaw aad bs =
-    case params of
+authContentEncrypt key params aad bs =
+    case exactObject params of
         Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authEncrypt p
         Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authEncrypt p
         Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpEncrypt
         ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= encrypt (getM m)
         ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= encrypt len
+        ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->
+            authContentEncrypt derived a aad bs
   where
     msglen  = B.length bs
     force x = x `seq` Right x
@@ -1112,7 +1250,8 @@
     authEncrypt p@AuthEncParams{..} = do
         let (encKey, macKey) = authKeys key p
         encrypted <- contentEncrypt encKey encAlgorithm bs
-        let macMsg = paramsRaw `B.append` encrypted `B.append` B.convert aad
+        let paramsRaw = exactObjectRaw params
+            macMsg = B.convert paramsRaw `B.append` encrypted `B.append` B.convert aad
             found  = mac macAlgorithm macKey macMsg
         return (found, encrypted)
 
@@ -1120,15 +1259,17 @@
 -- and algorithm.
 authContentDecrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)
                    => cek
-                   -> AuthContentEncryptionParams -> ba
+                   -> ASN1ObjectExact AuthContentEncryptionParams
                    -> aad -> ba -> AuthTag -> Either StoreError ba
-authContentDecrypt key params paramsRaw aad bs expected =
-    case params of
+authContentDecrypt key params aad bs expected =
+    case exactObject params of
         Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authDecrypt p
         Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authDecrypt p
         Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpDecrypt
         ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= decrypt (getM m)
         ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= decrypt len
+        ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->
+            authContentDecrypt derived a aad bs expected
   where
     msglen  = B.length bs
     badMac  = Left BadContentMAC
@@ -1153,7 +1294,8 @@
         | otherwise         = badMac
       where
         (encKey, macKey) = authKeys key p
-        macMsg = paramsRaw `B.append` bs `B.append` B.convert aad
+        paramsRaw = exactObjectRaw params
+        macMsg = B.convert paramsRaw `B.append` bs `B.append` B.convert aad
         found  = mac macAlgorithm macKey macMsg
         acceptable = securityAcceptable macAlgorithm
 
@@ -1225,7 +1367,7 @@
     type AlgorithmType PBKDF2_PRF = PBKDF2_PRF
     algorithmName _  = "PBKDF2 PRF"
     algorithmType    = id
-    parameterASN1S _ = id
+    parameterASN1S _ = gNull
     parseParameter p = getNextMaybe nullOrNothing >> return p
 
 -- | Invoke the pseudorandom function.
@@ -1257,6 +1399,10 @@
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 -- | Key derivation algorithm and associated parameters.
+--
+-- Implementations are specialized to password inputs.  For other
+-- implementations specialized to inputs having good entropy, like the shared
+-- secrets produced by key-agreement schemes, see 'KeyDerivationFn'.
 data KeyDerivationFunc =
       -- | Key derivation with PBKDF2
       PBKDF2 { pbkdf2Salt           :: Salt       -- ^ Salt value
@@ -1327,6 +1473,12 @@
 kdfKeyLength PBKDF2{..} = pbkdf2KeyLength
 kdfKeyLength Scrypt{..} = scryptKeyLength
 
+-- | Modify the optional key length stored in the KDF parameters.
+kdfKeyLengthModify :: (Maybe Int -> Maybe Int)
+                   -> KeyDerivationFunc -> KeyDerivationFunc
+kdfKeyLengthModify f kdf@PBKDF2{..} = kdf{pbkdf2KeyLength = f pbkdf2KeyLength}
+kdfKeyLengthModify f kdf@Scrypt{..} = kdf{scryptKeyLength = f scryptKeyLength}
+
 -- | Run a key derivation function to produce a result of the specified length
 -- using the supplied password.
 kdfDerive :: (ByteArrayAccess password, ByteArray out)
@@ -1345,7 +1497,82 @@
 generateSalt :: MonadRandom m => Int -> m Salt
 generateSalt = getRandomBytes
 
+data TypeKeyDerivationFn
+    = TypeHKDF DigestAlgorithm
+    | TypeKDF3
+    deriving (Show,Eq)
 
+instance Enumerable TypeKeyDerivationFn where
+    values = [ TypeHKDF (DigestAlgorithm SHA256)
+             , TypeHKDF (DigestAlgorithm SHA384)
+             , TypeHKDF (DigestAlgorithm SHA512)
+             , TypeHKDF (DigestAlgorithm SHA3_224)
+             , TypeHKDF (DigestAlgorithm SHA3_256)
+             , TypeHKDF (DigestAlgorithm SHA3_384)
+             , TypeHKDF (DigestAlgorithm SHA3_512)
+             , TypeKDF3
+             ]
+
+instance OIDable TypeKeyDerivationFn where
+    getObjectID (TypeHKDF (DigestAlgorithm SHA256))   = [1,2,840,113549,1,9,16,3,28]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA384))   = [1,2,840,113549,1,9,16,3,29]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA512))   = [1,2,840,113549,1,9,16,3,30]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA3_224)) = [1,2,840,113549,1,9,16,3,32]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA3_256)) = [1,2,840,113549,1,9,16,3,33]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA3_384)) = [1,2,840,113549,1,9,16,3,34]
+    getObjectID (TypeHKDF (DigestAlgorithm SHA3_512)) = [1,2,840,113549,1,9,16,3,35]
+    getObjectID (TypeHKDF hashAlg) = error ("Unsupported HKDF hash: " ++ show hashAlg)
+    getObjectID TypeKDF3                              = [1,3,133,16,840,9,44,1,2]
+
+instance OIDNameable TypeKeyDerivationFn where
+    fromObjectID oid = unOIDNW <$> fromObjectID oid
+
+-- | Key derivation algorithm and associated parameters.
+--
+-- Implementations are specialized to inputs having good entropy, like the
+-- shared secrets produced by key-agreement schemes.  For other implementations
+-- specialized to password inputs, see 'KeyDerivationFunc'.
+data KeyDerivationFn
+      -- | Key derivation with HKDF
+    = HKDF DigestAlgorithm
+      -- | Key derivation with KDF3
+    | KDF3 DigestAlgorithm
+    deriving (Show,Eq)
+
+instance AlgorithmId KeyDerivationFn where
+    type AlgorithmType KeyDerivationFn = TypeKeyDerivationFn
+
+    algorithmName _ = "key derivation algorithm"
+    algorithmType (HKDF alg) = TypeHKDF alg
+    algorithmType (KDF3 _) = TypeKDF3
+
+    parameterASN1S (HKDF _) = id
+    parameterASN1S (KDF3 alg) = algorithmASN1S Sequence alg
+
+    parseParameter (TypeHKDF alg) = return (HKDF alg)
+    parseParameter TypeKDF3 = KDF3 <$> parseAlgorithm Sequence
+
+kdfApply :: (ByteArrayAccess salt, ByteArrayAccess ikm, ByteArray out)
+         => KeyDerivationFn -> salt -> Int -> ByteString -> ikm -> Either StoreError out
+kdfApply (HKDF (DigestAlgorithm p)) salt len info ikm
+    | len > maxLen = Left (InvalidParameter "HKDF OKM is too long")
+    | otherwise = Right $ HKDF.expand (extract p ikm) info len
+  where
+    maxLen = 255 * digestSizeFromProxy p
+
+    extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)
+            => DigestProxy a -> ikm -> HKDF.PRK a
+    extract _ = HKDF.extract salt
+
+kdfApply (KDF3 dig@(DigestAlgorithm p)) salt len info ikm
+    | not (securityAcceptable dig) =
+        Left (InvalidParameter "KDF3 digest too weak")
+    | B.null salt =
+        let RsaKem.KDF kdf = RsaKem.kdf3 (hashFromProxy p) len
+         in Right $ kdf info ikm
+    | otherwise = Left (InvalidInput "KDF3 salt must be empty")
+
+
 -- Key encryption
 
 data KeyEncryptionType = TypePWRIKEK
@@ -1450,6 +1677,7 @@
         ParamsCBCRC2 len iv -> let cc = getRC2Cipher len key in either (return . Left) (\c -> wrapEncrypt cbcEncrypt c iv bs) cc
         ParamsCFB cipher iv -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt cfbEncrypt c iv bs) cc
         ParamsCTR _ _       -> return $ Left (InvalidParameter "Unable to wrap key in CTR mode")
+        ParamsDeriveHKDF _ _ -> return $ Left (InvalidParameter "HKDF derivation is incompatible with key wrapping")
 keyEncrypt key AES128_WRAP      bs = return (getCipher AES128 key >>= (`AES_KW.wrap` bs))
 keyEncrypt key AES192_WRAP      bs = return (getCipher AES192 key >>= (`AES_KW.wrap` bs))
 keyEncrypt key AES256_WRAP      bs = return (getCipher AES256 key >>= (`AES_KW.wrap` bs))
@@ -1471,6 +1699,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> wrapDecrypt cbcDecrypt c iv bs)
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> wrapDecrypt cfbDecrypt c iv bs)
         ParamsCTR _ _       -> Left (InvalidParameter "Unable to unwrap key in CTR mode")
+        ParamsDeriveHKDF _ _ -> Left (InvalidParameter "HKDF derivation is incompatible with key unwrapping")
 keyDecrypt key AES128_WRAP      bs = getCipher AES128   key >>= (`AES_KW.unwrap` bs)
 keyDecrypt key AES192_WRAP      bs = getCipher AES192   key >>= (`AES_KW.unwrap` bs)
 keyDecrypt key AES256_WRAP      bs = getCipher AES256   key >>= (`AES_KW.unwrap` bs)
@@ -1518,19 +1747,21 @@
     fn formatted =
         let firstPass = encFn cipher iv formatted
             lastBlock = B.dropView firstPass (B.length firstPass - sz)
-            Just iv'  = makeIV lastBlock
+            iv'       = fromJust $ makeIV lastBlock  -- wrapped length >= sz
          in encFn cipher iv' firstPass
 
 wrapDecrypt :: (BlockCipher cipher, ByteArray ba)
             => (cipher -> IV cipher -> ba -> ba)
             -> cipher -> IV cipher -> ba -> Either StoreError ba
-wrapDecrypt decFn cipher iv input = keyUnwrap (decFn cipher iv firstPass)
+wrapDecrypt decFn cipher iv input
+    | B.length beg < sz = Left (InvalidInput "wrapDecrypt: input too short")
+    | otherwise = keyUnwrap (decFn cipher iv firstPass)
   where
     sz = blockSize cipher
     (beg, lb) = B.splitAt (B.length input - sz) input
     lastBlock = decFn cipher iv' lb
-    Just iv'  = makeIV (B.dropView beg (B.length beg - sz))
-    Just iv'' = makeIV lastBlock
+    iv'       = fromJust $ makeIV (B.dropView beg (B.length beg - sz))
+    iv''      = fromJust $ makeIV lastBlock
     firstPass = decFn cipher iv'' beg `B.append` lastBlock
 
 
@@ -1642,12 +1873,12 @@
 -- private key.
 transportDecrypt :: MonadRandom m
                  => KeyTransportParams
-                 -> X509.PrivKey
+                 -> KeyPair
                  -> ByteString
                  -> m (Either StoreError ByteString)
-transportDecrypt RSAES         (X509.PrivKeyRSA priv) bs =
+transportDecrypt RSAES         (KeyPairRSA priv _) bs =
     mapLeft RSAError <$> RSA.decryptSafer priv bs
-transportDecrypt (RSAESOAEP p) (X509.PrivKeyRSA priv) bs
+transportDecrypt (RSAESOAEP p) (KeyPairRSA priv _) bs
     | securityAcceptable p =
         withOAEPParams p $ \params ->
             mapLeft RSAError <$> RSAOAEP.decryptSafer params priv bs
@@ -1838,8 +2069,8 @@
 -- | Decrypt the specified content with an ECDH key pair and key-agreement
 -- algorithm.
 ecdhDecrypt :: ByteArray ba
-            => KeyAgreementParams -> Maybe ByteString -> X509.PrivKey -> ByteString -> ba -> Either StoreError ba
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =
+            => KeyAgreementParams -> Maybe ByteString -> KeyPair -> ByteString -> ba -> Either StoreError ba
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairEC priv _) pt bs =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
@@ -1850,16 +2081,16 @@
                         s = ECDH.getShared curve d pub
                         k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
                     keyDecrypt k kep bs
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX25519 priv) pt bs = do
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX25519 priv _) pt bs = do
     s <- fromCryptoFailable (X25519.publicKey pt >>= ecdh x25519 priv)
     let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
     keyDecrypt k kep bs
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX448 priv) pt bs = do
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX448 priv _) pt bs = do
     s <- fromCryptoFailable (X448.publicKey pt >>= ecdh x448 priv)
     let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
     keyDecrypt k kep bs
 ecdhDecrypt (StdDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType
-ecdhDecrypt (CofactorDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =
+ecdhDecrypt (CofactorDH dig kep) ukm (KeyPairEC priv _) pt bs =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
@@ -1892,8 +2123,8 @@
 ivGenerate :: (BlockCipher cipher, MonadRandom m) => cipher -> m (IV cipher)
 ivGenerate cipher = do
     bs <- getRandomBytes (blockSize cipher)
-    let Just iv = makeIV (bs :: ByteString)
-    return iv
+    let iv = makeIV (bs :: ByteString)
+    return $! fromJust iv
 
 nonceGenerate :: MonadRandom m => Int -> m B.Bytes
 nonceGenerate = getRandomBytes
@@ -1927,7 +2158,7 @@
 
 parseM :: Monoid e => ParseASN1 e CCM_M
 parseM = do
-    IntVal l <- getNext
+    l <- fromMaybe 12 <$> getNextMaybe intOrNothing
     case l of
         4  -> return CCM_M4
         6  -> return CCM_M6
@@ -1939,6 +2170,85 @@
         i -> throwParseError ("Parsed invalid CCM parameter M: " ++ show i)
 
 
+-- Key encapsulation
+
+data KeyEncapsulationType
+    = TypeKeyEncapsulationRSA
+    deriving (Show,Eq)
+
+instance Enumerable KeyEncapsulationType where
+    values = [TypeKeyEncapsulationRSA]
+
+instance OIDable KeyEncapsulationType where
+    getObjectID TypeKeyEncapsulationRSA = [1,0,18033,2,2,4]
+
+instance OIDNameable KeyEncapsulationType where
+    fromObjectID oid = unOIDNW <$> fromObjectID oid
+
+-- | Key encapsulation mechanism (KEM) with associated parameters.
+data KeyEncapsulationMechanism
+    = KeyEncapsulationRSA KeyDerivationFn Int
+      -- ^ Key encapsulation with RSA-KEM
+    deriving (Show,Eq)
+
+instance AlgorithmId KeyEncapsulationMechanism where
+    type AlgorithmType KeyEncapsulationMechanism = KeyEncapsulationType
+    algorithmName _ = "key encapsulation mechanism"
+
+    algorithmType (KeyEncapsulationRSA _ _) = TypeKeyEncapsulationRSA
+
+    parameterASN1S (KeyEncapsulationRSA kdf len)
+        | (kdf, len) == defaultRsaKemParams = id
+        | otherwise = asn1Container Sequence $
+            algorithmASN1S Sequence kdf . gIntVal (toInteger len)
+
+    parseParameter TypeKeyEncapsulationRSA =
+        fmap manageDef $ onNextContainerMaybe Sequence $ do
+            kdf <- parseAlgorithm Sequence
+            IntVal len <- getNext
+            when (len < 1) $
+                throwParseError "Illegal keyLength in RsaKemParameters"
+            return $ KeyEncapsulationRSA kdf (fromInteger len)
+      where
+        manageDef = fromMaybe (KeyEncapsulationRSA kdfDef lenDef)
+        (kdfDef, lenDef) = defaultRsaKemParams
+
+defaultRsaKemParams :: (KeyDerivationFn, Int)
+defaultRsaKemParams = (KDF3 $ DigestAlgorithm SHA256, 16)
+
+newtype RsaKemLen = RsaKemLen Int
+
+instance HasStrength RsaKemLen where
+    getSecurityBits (RsaKemLen len) = 8 * len
+
+kdfRsaKem :: (ByteArrayAccess bIn, ByteArray bOut)
+          => KeyDerivationFn -> Int -> RsaKem.KDF bIn (Either StoreError bOut)
+kdfRsaKem kdf len
+    | securityAcceptable (RsaKemLen len) = RsaKem.KDF $ kdfApply kdf noSalt len
+    | otherwise = RsaKem.KDF $ \_ _ ->
+        Left $ InvalidParameter "RSA-KEM shared-secret length too short"
+
+kemEncap :: (MonadRandom m, ByteArray ct, ByteArray ss)
+         => KeyEncapsulationMechanism -> X509.PubKey -> m (Either StoreError (ct, ss))
+kemEncap (KeyEncapsulationRSA kdf len) (X509.PubKeyRSA pub) = do
+    (ss, ct) <- RsaKem.encapsulate kdFn pub
+    return $ (ct, ) <$> ss
+  where kdFn = kdfRsaKem kdf len
+kemEncap _ _ = return (Left UnexpectedPublicKeyType)
+
+kemDecap :: (ByteArrayAccess ct, ByteArray ss)
+         => KeyEncapsulationMechanism -> KeyPair -> ct -> Either StoreError ss
+kemDecap (KeyEncapsulationRSA kdf len) (KeyPairRSA priv _) ct =
+    case RsaKem.decapsulate kdFn priv (B.convert ct :: B.Bytes) of
+        Just ss -> ss
+        Nothing -> Left $ InvalidParameter "Invalid RSA-KEM ciphertext"
+  where kdFn = kdfRsaKem kdf len
+kemDecap _ _ _ = Left UnexpectedPrivateKeyType
+
+noSalt :: ByteString
+noSalt = mempty
+
+
 -- Mask generation functions
 
 data MaskGenerationType = TypeMGF1
@@ -2070,6 +2380,10 @@
              , TypeECDSA (DigestAlgorithm SHA256)
              , TypeECDSA (DigestAlgorithm SHA384)
              , TypeECDSA (DigestAlgorithm SHA512)
+             , TypeECDSA (DigestAlgorithm SHA3_224)
+             , TypeECDSA (DigestAlgorithm SHA3_256)
+             , TypeECDSA (DigestAlgorithm SHA3_384)
+             , TypeECDSA (DigestAlgorithm SHA3_512)
 
              , TypeEd25519
              , TypeEd448
@@ -2097,6 +2411,10 @@
     getObjectID (TypeECDSA (DigestAlgorithm SHA256)) = [1,2,840,10045,4,3,2]
     getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3]
     getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_224)) = [2,16,840,1,101,3,4,3,9]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_256)) = [2,16,840,1,101,3,4,3,10]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_384)) = [2,16,840,1,101,3,4,3,11]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_512)) = [2,16,840,1,101,3,4,3,12]
 
     getObjectID TypeEd25519                          = [1,3,101,112]
     getObjectID TypeEd448                            = [1,3,101,113]
@@ -2144,23 +2462,22 @@
     parseParameter TypeEd25519      = return Ed25519
     parseParameter TypeEd448        = return Ed448
 
--- | Sign a message using the specified algorithm and private key.  The
--- corresponding public key is also required for some algorithms.
-signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> X509.PubKey -> ByteString -> m (Either StoreError SignatureValue)
-signatureGenerate RSAAnyHash _ _ _ =
+-- | Sign a message using the specified algorithm and private key.
+signatureGenerate :: MonadRandom m => SignatureAlg -> KeyPair -> ByteString -> m (Either StoreError SignatureValue)
+signatureGenerate RSAAnyHash _ _ =
     error "signatureGenerate: should call signatureResolveHash first"
-signatureGenerate (RSA alg)   (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =
+signatureGenerate (RSA alg)   (KeyPairRSA priv _) msg =
     let err = return . Left $ InvalidParameter ("Invalid hash algorithm for RSA: " ++ show alg)
      in withHashAlgorithmASN1 alg err $ \hashAlg ->
             mapLeft RSAError <$> RSA.signSafer (Just hashAlg) priv msg
-signatureGenerate (RSAPSS p)  (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =
+signatureGenerate (RSAPSS p)  (KeyPairRSA priv _) msg =
     withPSSParams p $ \params ->
         mapLeft RSAError <$> RSAPSS.signSafer params priv msg
-signatureGenerate (DSA alg)   (X509.PrivKeyDSA priv) (X509.PubKeyDSA _) msg =
+signatureGenerate (DSA alg)   (KeyPairDSA pair) msg =
     case alg of
         DigestAlgorithm t ->
-            Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg
-signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv)  (X509.PubKeyEC _)  msg =
+            Right . dsaFromSignature <$> DSA.sign (DSA.toPrivateKey pair) (hashFromProxy t) msg
+signatureGenerate (ECDSA alg) (KeyPairEC priv _)  msg =
     case alg of
         DigestAlgorithm t ->
             case ecdsaToPrivateKey priv of
@@ -2168,11 +2485,11 @@
                 Just p  ->
                     let h = hashFromProxy t
                      in Right . ecdsaFromSignature <$> ECDSA.sign p h msg
-signatureGenerate Ed25519 (X509.PrivKeyEd25519 priv) (X509.PubKeyEd25519 pub) msg =
+signatureGenerate Ed25519 (KeyPairEd25519 priv pub) msg =
     return . Right . B.convert $ Ed25519.sign priv pub msg
-signatureGenerate Ed448 (X509.PrivKeyEd448 priv) (X509.PubKeyEd448 pub) msg =
+signatureGenerate Ed448 (KeyPairEd448 priv pub) msg =
     return . Right . B.convert $ Ed448.sign priv pub msg
-signatureGenerate _ _ _ _ = return (Left UnexpectedPrivateKeyType)
+signatureGenerate _ _ _ = return (Left UnexpectedPrivateKeyType)
 
 -- | Verify a message signature using the specified algorithm and public key.
 signatureVerify :: SignatureAlg -> X509.PubKey -> ByteString -> SignatureValue -> Bool
diff --git a/src/Crypto/Store/CMS/Attribute.hs b/src/Crypto/Store/CMS/Attribute.hs
--- a/src/Crypto/Store/CMS/Attribute.hs
+++ b/src/Crypto/Store/CMS/Attribute.hs
@@ -37,7 +37,7 @@
 import Data.Hourglass
 import Data.Maybe (fromMaybe)
 
-import System.Hourglass (dateCurrent)
+import Time.System (dateCurrent)
 
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
diff --git a/src/Crypto/Store/CMS/Authenticated.hs b/src/Crypto/Store/CMS/Authenticated.hs
--- a/src/Crypto/Store/CMS/Authenticated.hs
+++ b/src/Crypto/Store/CMS/Authenticated.hs
@@ -10,7 +10,8 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
 module Crypto.Store.CMS.Authenticated
-    ( AuthenticatedData(..)
+    ( EncapsulatedContent
+    , AuthenticatedData(..)
     ) where
 
 import Control.Applicative
@@ -77,7 +78,7 @@
     parse =
         onNextContainer Sequence $ do
             IntVal v <- getNext
-            when (v `notElem` [0, 1, 3]) $
+            unless (v `elem` [0, 1, 3]) $
                 throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v)
             oi <- parseOriginatorInfo (Container Context 0) <|> return mempty
             ris <- onNextContainer Set parse
diff --git a/src/Crypto/Store/CMS/Enveloped.hs b/src/Crypto/Store/CMS/Enveloped.hs
--- a/src/Crypto/Store/CMS/Enveloped.hs
+++ b/src/Crypto/Store/CMS/Enveloped.hs
@@ -42,6 +42,10 @@
     , PasswordRecipientInfo(..)
     , forPasswordRecipient
     , withRecipientPassword
+    -- * Key Encapsulation recipients
+    , KEMRecipientInfo(..)
+    , forKeyEncapRecipient
+    , withRecipientKeyEncap
     ) where
 
 import Control.Applicative
@@ -67,6 +71,7 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 
 -- | Encrypted key.
 type EncryptedKey = ByteString
@@ -95,14 +100,13 @@
 
 instance ASN1Elem e => ProduceASN1Object e RecipientIdentifier where
     asn1s (RecipientIASN iasn) = asn1s iasn
-    asn1s (RecipientSKI  ski)  = asn1Container (Container Context 0)
-                                    (gOctetString ski)
+    asn1s (RecipientSKI  ski)  = gMany [Other Context 0 ski]
 
 instance Monoid e => ParseASN1Object e RecipientIdentifier where
     parse = parseIASN <|> parseSKI
       where parseIASN = RecipientIASN <$> parse
             parseSKI  = RecipientSKI  <$>
-                onNextContainer (Container Context 0) parseOctetStringPrim
+                do { Other Context 0 bs <- getNext; return bs }
 
 getKTVersion :: RecipientIdentifier -> Integer
 getKTVersion (RecipientIASN _) = 0
@@ -167,8 +171,7 @@
 
 instance ASN1Elem e => ProduceASN1Object e OriginatorIdentifierOrKey where
     asn1s (OriginatorIASN iasn)   = asn1s iasn
-    asn1s (OriginatorSKI  ski)    = asn1Container (Container Context 0)
-                                       (gOctetString ski)
+    asn1s (OriginatorSKI  ski)    = gMany [Other Context 0 ski]
     asn1s (OriginatorPublic pub)  =
         originatorPublicKeyASN1S (Container Context 1) pub
 
@@ -176,7 +179,7 @@
     parse = parseIASN <|> parseSKI <|> parsePublic
       where parseIASN = OriginatorIASN <$> parse
             parseSKI  = OriginatorSKI  <$>
-                onNextContainer (Container Context 0) parseOctetStringPrim
+                do { Other Context 0 bs <- getNext; return bs }
             parsePublic  = OriginatorPublic <$>
                 parseOriginatorPublicKey (Container Context 1)
 
@@ -188,14 +191,13 @@
 
 instance ASN1Elem e => ProduceASN1Object e KeyAgreeRecipientIdentifier where
     asn1s (KeyAgreeRecipientIASN iasn) = asn1s iasn
-    asn1s (KeyAgreeRecipientKI   ki)   = asn1Container (Container Context 0)
-                                            (asn1s ki)
+    asn1s (KeyAgreeRecipientKI   ki)   = keyIdentifierASN1S (Container Context 0) ki
 
 instance Monoid e => ParseASN1Object e KeyAgreeRecipientIdentifier where
     parse = parseIASN <|> parseKI
       where parseIASN = KeyAgreeRecipientIASN <$> parse
             parseKI   = KeyAgreeRecipientKI   <$>
-                onNextContainer (Container Context 0) parse
+                parseKeyIdentifier (Container Context 0)
 
 -- | Encrypted key for a recipient in a key-agreement RI.
 data RecipientEncryptedKey = RecipientEncryptedKey
@@ -220,6 +222,16 @@
                           -> Maybe EncryptedKey
 findRecipientEncryptedKey cert list = rekEncryptedKey <$> find fn list
   where
+    fn rek = matchRecipient cert $ case rekRid rek of
+        KeyAgreeRecipientIASN iasn -> RecipientIASN iasn
+        KeyAgreeRecipientKI   ki   -> RecipientSKI (keyIdentifier ki)
+
+matchRecipient :: SignedCertificate -> RecipientIdentifier -> Bool
+matchRecipient cert rid =
+    case rid of
+        RecipientIASN iasn -> matchIASN iasn
+        RecipientSKI  ski  -> matchSKI ski
+  where
     c = signedObject (getSigned cert)
     matchIASN iasn =
         (iasnIssuer iasn, iasnSerial iasn) == (certIssuerDN c, certSerial c)
@@ -227,11 +239,8 @@
         case extensionGet (certExtensions c) of
             Just (ExtSubjectKeyId idBs) -> idBs == ski
             Nothing                     -> False
-    fn rek = case rekRid rek of
-                 KeyAgreeRecipientIASN iasn -> matchIASN iasn
-                 KeyAgreeRecipientKI   ki   -> matchSKI (keyIdentifier ki)
 
--- | Additional information in a 'KeyIdentifier'.
+-- | Additional information in a t'KeyIdentifier'.
 data OtherKeyAttribute = OtherKeyAttribute
     { keyAttrId :: OID    -- ^ attribute identifier
     , keyAttr   :: [ASN1] -- ^ attribute value
@@ -257,23 +266,26 @@
     }
     deriving (Show,Eq)
 
-instance ASN1Elem e => ProduceASN1Object e KeyIdentifier where
-    asn1s KeyIdentifier{..} = asn1Container Sequence (keyId . date . other)
-      where
-        keyId = gOctetString keyIdentifier
-        date  = optASN1S keyDate $ \v -> gASN1Time TimeGeneralized v Nothing
-        other = optASN1S keyOther asn1s
+keyIdentifierASN1S :: ASN1Elem e
+                   => ASN1ConstructionType -> KeyIdentifier -> ASN1Stream e
+keyIdentifierASN1S ty KeyIdentifier{..} =
+    asn1Container ty (keyId . date . other)
+  where
+    keyId = gOctetString keyIdentifier
+    date  = optASN1S keyDate $ \v -> gASN1Time TimeGeneralized v Nothing
+    other = optASN1S keyOther asn1s
 
-instance Monoid e => ParseASN1Object e KeyIdentifier where
-    parse = onNextContainer Sequence $ do
-        OctetString keyId <- getNext
-        date <- getNextMaybe dateTimeOrNothing
-        b <- hasNext
-        other <- if b then Just <$> parse else return Nothing
-        return KeyIdentifier { keyIdentifier = keyId
-                             , keyDate = date
-                             , keyOther = other
-                             }
+parseKeyIdentifier :: Monoid e
+                   => ASN1ConstructionType -> ParseASN1 e KeyIdentifier
+parseKeyIdentifier ty = onNextContainer ty $ do
+    OctetString keyId <- getNext
+    date <- getNextMaybe dateTimeOrNothing
+    b <- hasNext
+    other <- if b then Just <$> parse else return Nothing
+    return KeyIdentifier { keyIdentifier = keyId
+                         , keyDate = date
+                         , keyOther = other
+                         }
 
 -- | Recipient using key transport.
 data KTRecipientInfo = KTRecipientInfo
@@ -308,7 +320,73 @@
     }
     deriving (Show,Eq)
 
--- | Information for a recipient of an 'EnvelopedData'.  An element contains
+-- | Recipient using key encapsulation.
+data KEMRecipientInfo = KEMRecipientInfo
+    { kemRid :: RecipientIdentifier                       -- ^ identifier of recipient
+    , kemEncapsulationParams :: KeyEncapsulationMechanism -- ^ key encapsulation mechanism
+    , kemCipherText :: ByteString                         -- ^ ciphertext for this recipient
+    , kemDerivationFn :: KeyDerivationFn                  -- ^ key derivation used
+    , kemKekLength :: Int                                 -- ^ size of key encryption key
+    , kemUkm :: Maybe UserKeyingMaterial                  -- ^ user keying material
+    , kemEncryptionParams :: KeyEncryptionParams          -- ^ key encryption algorithm
+    , kemEncryptedKey :: EncryptedKey                     -- ^ encrypted content-encryption key
+    }
+    deriving (Show,Eq)
+
+instance ASN1Elem e => ProduceASN1Object e KEMRecipientInfo where
+    asn1s KEMRecipientInfo{..} =
+        asn1Container Sequence (ver . rid . kem . ct . kdf . len . ukm . kep . ek)
+      where
+        ver = gIntVal 0
+        rid = asn1s kemRid
+        kem = algorithmASN1S Sequence kemEncapsulationParams
+        ct  = gOctetString kemCipherText
+        kdf = algorithmASN1S Sequence kemDerivationFn
+        len = gIntVal (toInteger kemKekLength)
+        ukm = optASN1S kemUkm $ asn1Container (Container Context 0) . gOctetString
+        kep = algorithmASN1S Sequence kemEncryptionParams
+        ek  = gOctetString kemEncryptedKey
+
+instance Monoid e => ParseASN1Object e KEMRecipientInfo where
+    parse = onNextContainer Sequence $ do
+        IntVal 0 <- getNext
+        rid <- parse
+        kem <- parseAlgorithm Sequence
+        OctetString ct <- getNext
+        kdf <- parseAlgorithm Sequence
+        IntVal len <- getNext
+        when (len < 1 || len > 65535) $
+            throwParseError ("KEMRecipientInfo: parsed invalid kekLength: " ++ show len)
+        ukm <- onNextContainerMaybe (Container Context 0) $
+                   do { OctetString bs <- getNext; return bs }
+        kep <- parseAlgorithm Sequence
+        OctetString ek <- getNext
+        return KEMRecipientInfo { kemRid = rid
+                                , kemEncapsulationParams = kem
+                                , kemCipherText = ct
+                                , kemDerivationFn = kdf
+                                , kemKekLength = fromInteger len
+                                , kemUkm = ukm
+                                , kemEncryptionParams = kep
+                                , kemEncryptedKey = ek
+                                }
+
+data CMSORIforKEMOtherInfo = CMSORIforKEMOtherInfo
+    { kemoiWrap :: KeyEncryptionParams
+    , kemoiKekLength :: Int
+    , kemoiUkm :: Maybe UserKeyingMaterial
+    }
+    deriving (Show,Eq)
+
+instance ASN1Elem e => ProduceASN1Object e CMSORIforKEMOtherInfo where
+    asn1s CMSORIforKEMOtherInfo{..} =
+        asn1Container Sequence (wrap . len . ukm)
+      where
+        wrap = algorithmASN1S Sequence kemoiWrap
+        len = gIntVal (toInteger kemoiKekLength)
+        ukm = optASN1S kemoiUkm $ asn1Container (Container Context 0) . gOctetString
+
+-- | Information for a recipient of an t'EnvelopedData'.  An element contains
 -- the content-encryption key in encrypted form.
 data RecipientInfo = KTRI KTRecipientInfo
                      -- ^ Recipient using key transport
@@ -318,6 +396,8 @@
                      -- ^ Recipient using key encryption
                    | PasswordRI PasswordRecipientInfo
                      -- ^ Recipient using password-based protection
+                   | KEMRI KEMRecipientInfo
+                     -- ^ Recipient using key encapsulation
     deriving (Show,Eq)
 
 instance ASN1Elem e => ProduceASN1Object e RecipientInfo where
@@ -345,7 +425,7 @@
         asn1Container (Container Context 2) (ver . kid . kep . ek)
       where
         ver = gIntVal 4
-        kid = asn1s kekId
+        kid = keyIdentifierASN1S Sequence kekId
         kep = algorithmASN1S Sequence kekKeyEncryptionParams
         ek  = gOctetString kekEncryptedKey
 
@@ -357,19 +437,26 @@
         kep = algorithmASN1S Sequence priKeyEncryptionParams
         ek  = gOctetString priEncryptedKey
 
+    asn1s (KEMRI ri) =
+        asn1Container (Container Context 4) (typ . val)
+      where
+        typ = gOID [1,2,840,113549,1,9,16,13,3]
+        val = asn1s ri
+
 instance Monoid e => ParseASN1Object e RecipientInfo where
     parse = do
         c <- onNextContainerMaybe Sequence parseKT
              `orElse` onNextContainerMaybe (Container Context 1) parseKA
              `orElse` onNextContainerMaybe (Container Context 2) parseKEK
              `orElse` onNextContainerMaybe (Container Context 3) parsePassword
+             `orElse` onNextContainerMaybe (Container Context 4) parseOther
         case c of
             Just val -> return val
             Nothing  -> throwParseError "RecipientInfo: unable to parse"
       where
         parseKT = KTRI <$> do
             IntVal v <- getNext
-            when (v `notElem` [0, 2]) $
+            unless (v `elem` [0, 2]) $
                 throwParseError ("RecipientInfo: parsed invalid KT version: " ++ show v)
             rid <- parse
             ktp <- parseAlgorithm Sequence
@@ -394,7 +481,7 @@
 
         parseKEK = KEKRI <$> do
             IntVal 4 <- getNext
-            kid <- parse
+            kid <- parseKeyIdentifier Sequence
             kep <- parseAlgorithm Sequence
             OctetString ek <- getNext
             return KEKRecipientInfo { kekId = kid
@@ -412,17 +499,23 @@
                                          , priEncryptedKey = ek
                                          }
 
+        parseOther = KEMRI <$> do
+            OID [1,2,840,113549,1,9,16,13,3] <- getNext
+            parse
+
 isVersion0 :: RecipientInfo -> Bool
 isVersion0 (KTRI x)       = getKTVersion (ktRid x) == 0
 isVersion0 (KARI _)       = False      -- because version is always 3
 isVersion0 (KEKRI _)      = False      -- because version is always 4
 isVersion0 (PasswordRI _) = True       -- because version is always 0
+isVersion0 (KEMRI _)      = True       -- because version is always 0
 
 isPwriOri :: RecipientInfo -> Bool
 isPwriOri (KTRI _)       = False
 isPwriOri (KARI _)       = False
 isPwriOri (KEKRI _)      = False
 isPwriOri (PasswordRI _) = True
+isPwriOri (KEMRI _)      = True
 
 -- | Enveloped content information.
 data EnvelopedData content = EnvelopedData
@@ -508,9 +601,9 @@
 --
 -- This function can be used as parameter to
 -- 'Crypto.Store.CMS.openEnvelopedData'.
-withRecipientKeyTrans :: MonadRandom m => PrivKey -> ConsumerOfRI m
-withRecipientKeyTrans privKey (KTRI KTRecipientInfo{..}) =
-    transportDecrypt ktKeyTransportParams privKey ktEncryptedKey
+withRecipientKeyTrans :: MonadRandom m => KeyPair -> ConsumerOfRI m
+withRecipientKeyTrans keyPair (KTRI KTRecipientInfo{..}) =
+    transportDecrypt ktKeyTransportParams keyPair ktEncryptedKey
 withRecipientKeyTrans _ _ = pure (Left RecipientTypeMismatch)
 
 -- | Generate a Key Agreement recipient from a certificate and
@@ -553,16 +646,18 @@
 --
 -- This function can be used as parameter to
 -- 'Crypto.Store.CMS.openEnvelopedData'.
-withRecipientKeyAgree :: MonadRandom m => PrivKey -> SignedCertificate -> ConsumerOfRI m
-withRecipientKeyAgree priv cert (KARI KARecipientInfo{..}) =
-    case kaOriginator of
-        OriginatorPublic (OriginatorPublicKeyEC _ ba) ->
-            case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of
-                Nothing -> pure (Left RecipientKeyNotFound)
-                Just ek ->
-                    let pub = bitArrayGetData ba
-                     in pure (ecdhDecrypt kaKeyAgreementParams kaUkm priv pub ek)
-        _ -> pure (Left UnsupportedOriginatorFormat)
+withRecipientKeyAgree :: MonadRandom m => KeyPair -> SignedCertificate -> ConsumerOfRI m
+withRecipientKeyAgree pair cert (KARI KARecipientInfo{..})
+    | keyPairMatchesCert pair cert =
+        case kaOriginator of
+            OriginatorPublic (OriginatorPublicKeyEC _ ba) ->
+                case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of
+                    Nothing -> pure (Left RecipientKeyNotFound)
+                    Just ek ->
+                        let pub = bitArrayGetData ba
+                         in pure (ecdhDecrypt kaKeyAgreementParams kaUkm pair pub ek)
+            _ -> pure (Left UnsupportedOriginatorFormat)
+    | otherwise = pure $ Left PublicPrivateKeyMismatch
 withRecipientKeyAgree _ _ _        = pure (Left RecipientTypeMismatch)
 
 -- | Generate a Key Encryption Key recipient from a key encryption key and
@@ -629,3 +724,84 @@
     len = fromMaybe (getMaximumKeySize priKeyEncryptionParams)
                     (kdfKeyLength priKeyDerivationFunc)
 withRecipientPassword _ _ = pure (Left RecipientTypeMismatch)
+
+-- | Generate a Key Encapsulation recipient from a certificate and
+-- desired algorithms.  The recipient info will contain the KEM ciphertext.
+--
+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.
+--
+-- To avoid decreasing the security strength, selected algorithms should all be
+-- equal or stronger than the content encryption key.
+forKeyEncapRecipient :: MonadRandom m
+                     => SignedCertificate
+                     -> KeyDerivationFn
+                     -> KeyEncryptionParams
+                     -> KeyEncapsulationMechanism
+                     -> ProducerOfRI m
+forKeyEncapRecipient cert kdf kep params inkey = do
+    ephemeral <- kemEncap params (certPubKey obj)
+    case ephemeral of
+        Right (ct, ss) ->
+            case kdfApply kdf noSalt len prm (ss :: EncryptedKey) of
+                Left err -> pure $ Left err
+                Right kek -> do
+                    ek <- keyEncrypt (kek :: EncryptedKey) kep inkey
+                    pure (KEMRI . build ct <$> ek)
+        Left err -> pure $ Left err
+  where
+    obj = signedObject (getSigned cert)
+    isn = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)
+    prm = encodeASN1S (asn1s info)
+
+    len  = getMaximumKeySize kep
+    info = CMSORIforKEMOtherInfo
+        { kemoiWrap = kep
+        , kemoiKekLength = len
+        , kemoiUkm = Nothing
+        }
+
+    build ct ek =
+        KEMRecipientInfo
+            { kemRid = RecipientIASN isn
+            , kemEncapsulationParams = params
+            , kemCipherText = ct
+            , kemDerivationFn = kdf
+            , kemKekLength = len
+            , kemUkm = Nothing
+            , kemEncryptionParams = kep
+            , kemEncryptedKey = ek
+            }
+
+-- | Use a Key Encapsulation recipient, knowing the recipient private key.
+-- The recipient certificate is also used to determine if a recipient info
+-- is applicable.
+--
+-- This function can be used as parameter to
+-- 'Crypto.Store.CMS.openEnvelopedData'.
+withRecipientKeyEncap :: MonadRandom m => KeyPair -> SignedCertificate -> ConsumerOfRI m
+withRecipientKeyEncap pair cert (KEMRI KEMRecipientInfo{..})
+    | not (keyPairMatchesCert pair cert) =
+        pure $ Left PublicPrivateKeyMismatch
+    | not (matchRecipient cert kemRid) =
+        pure $ Left NoRecipientInfoMatched
+    | otherwise =
+        case kemDecap kemEncapsulationParams pair kemCipherText of
+            Left err -> pure $ Left err
+            Right ss | len == kemKekLength -> pure $
+                case kdfApply kemDerivationFn noSalt kemKekLength prm (ss :: EncryptedKey) of
+                    Left err -> Left err
+                    Right kek -> keyDecrypt (kek :: EncryptedKey)
+                        kemEncryptionParams kemEncryptedKey
+            Right _ -> pure $ Left (InvalidInput "Wrong kekLength in KEMRecipientInfo")
+  where
+    len  = getMaximumKeySize kemEncryptionParams
+    prm  = encodeASN1S (asn1s info)
+    info = CMSORIforKEMOtherInfo
+        { kemoiWrap = kemEncryptionParams
+        , kemoiKekLength = kemKekLength
+        , kemoiUkm = kemUkm
+        }
+withRecipientKeyEncap _ _ _        = pure (Left RecipientTypeMismatch)
+
+noSalt :: ByteString
+noSalt = mempty
diff --git a/src/Crypto/Store/CMS/Info.hs b/src/Crypto/Store/CMS/Info.hs
--- a/src/Crypto/Store/CMS/Info.hs
+++ b/src/Crypto/Store/CMS/Info.hs
@@ -6,6 +6,7 @@
 -- Portability : unknown
 --
 -- CMS content information.
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -21,7 +22,9 @@
     , toDetachedCI
     ) where
 
+#if !(MIN_VERSION_base(4,13,0))
 import Control.Monad.Fail (MonadFail)
+#endif
 
 import Data.ASN1.Types
 import Data.ByteString (ByteString)
diff --git a/src/Crypto/Store/CMS/OriginatorInfo.hs b/src/Crypto/Store/CMS/OriginatorInfo.hs
--- a/src/Crypto/Store/CMS/OriginatorInfo.hs
+++ b/src/Crypto/Store/CMS/OriginatorInfo.hs
@@ -6,6 +6,7 @@
 -- Portability : unknown
 --
 --
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
@@ -24,7 +25,9 @@
 
 import Data.ASN1.Types
 import Data.Maybe (fromMaybe)
+#if !(MIN_VERSION_base(4,11,0))
 import Data.Semigroup
+#endif
 import Data.X509
 
 import Crypto.Store.ASN1.Generate
diff --git a/src/Crypto/Store/CMS/PEM.hs b/src/Crypto/Store/CMS/PEM.hs
--- a/src/Crypto/Store/CMS/PEM.hs
+++ b/src/Crypto/Store/CMS/PEM.hs
@@ -11,6 +11,7 @@
     , readCMSFileFromMemory
     , berToContentInfo
     , pemToContentInfo
+    , pemToContentInfoAccum
     , writeCMSFile
     , writeCMSFileToMemory
     , contentInfoToDER
@@ -18,7 +19,7 @@
     ) where
 
 import qualified Data.ByteString as B
-import           Data.Maybe (catMaybes)
+import           Data.Either (rights)
 
 import Crypto.Store.CMS.Info
 import Crypto.Store.CMS.Util
@@ -37,24 +38,24 @@
 readCMSFileFromMemory = either (const []) accumulate . pemParseBS
 
 accumulate :: [PEM] -> [ContentInfo]
-accumulate = catMaybes . foldr (flip pemToContentInfo) []
+accumulate = rights . map pemToContentInfo
 
 -- | Read a content info from a bytearray in BER format.
 berToContentInfo :: B.ByteString -> Either StoreError ContentInfo
 berToContentInfo = decodeASN1Object
 
--- | Read a content info from a 'PEM' element and add it to the accumulator
+-- | Read a content info from a t'PEM' element and add it to the accumulator
 -- list.
-pemToContentInfo :: [Maybe ContentInfo] -> PEM -> [Maybe ContentInfo]
-pemToContentInfo acc pem
-    | pemName pem `elem` names = decode (pemContent pem)
-    | otherwise                = Nothing : acc
-  where
-    names = [ "CMS", "PKCS7" ]
-    decode bs =
-        case berToContentInfo bs of
-            Left _ -> Nothing : acc
-            Right info -> Just info : acc
+pemToContentInfoAccum :: [Maybe ContentInfo] -> PEM -> [Maybe ContentInfo]
+pemToContentInfoAccum acc pem =
+    either (const Nothing) Just (pemToContentInfo pem) : acc
+
+-- | Read a content info from a t'PEM' element.
+pemToContentInfo :: PEM -> Either StoreError ContentInfo
+pemToContentInfo pem
+    | pemName pem `elem` names = berToContentInfo (pemContent pem)
+    | otherwise                = Left UnexpectedNameForPEM
+  where names = [ "CMS", "PKCS7" ]
 
 
 -- Writing to PEM format
diff --git a/src/Crypto/Store/CMS/Signed.hs b/src/Crypto/Store/CMS/Signed.hs
--- a/src/Crypto/Store/CMS/Signed.hs
+++ b/src/Crypto/Store/CMS/Signed.hs
@@ -48,6 +48,7 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 
 -- | Encapsulated content.
 type EncapsulatedContent = ByteString
@@ -117,14 +118,13 @@
 
 instance ASN1Elem e => ProduceASN1Object e SignerIdentifier where
     asn1s (SignerIASN iasn) = asn1s iasn
-    asn1s (SignerSKI  ski)  = asn1Container (Container Context 0)
-                                  (gOctetString ski)
+    asn1s (SignerSKI  ski)  = gMany [Other Context 0 ski]
 
 instance Monoid e => ParseASN1Object e SignerIdentifier where
     parse = parseIASN <|> parseSKI
       where parseIASN = SignerIASN <$> parse
             parseSKI  = SignerSKI  <$>
-                onNextContainer (Container Context 0) parseOctetStringPrim
+                do { Other Context 0 bs <- getNext; return bs }
 
 -- | Try to find a certificate with the specified identifier.
 findSigner :: SignerIdentifier
@@ -149,10 +149,10 @@
         (x : _, r) -> Just (x, r)
         ([]   , _)    -> Nothing
 
--- | Function able to produce a 'SignerInfo'.
+-- | Function able to produce a t'SignerInfo'.
 type ProducerOfSI m = ContentType -> ByteString -> m (Either StoreError (SignerInfo, [CertificateChoice], [RevocationInfoChoice]))
 
--- | Function able to consume a 'SignerInfo'.
+-- | Function able to consume a t'SignerInfo'.
 type ConsumerOfSI m = ContentType -> ByteString -> SignerInfo -> [CertificateChoice] -> [RevocationInfoChoice] -> m Bool
 
 -- | Create a signer info with the specified signature algorithm and
@@ -167,15 +167,16 @@
 -- the full message.
 certSigner :: MonadRandom m
            => SignatureAlg
-           -> PrivKey
+           -> KeyPair
            -> CertificateChain
            -> Maybe [Attribute]
            -> [Attribute]
            -> ProducerOfSI m
 certSigner _ _ (CertificateChain []) _ _ _ _ =
     pure $ Left (InvalidInput "Empty certificate chain")
-certSigner alg priv (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg =
-    fmap build <$> generate
+certSigner alg pair (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg
+    | keyPairMatchesKey pair pub = fmap build <$> generate
+    | otherwise = pure $ Left PublicPrivateKeyMismatch
   where
     md   = digest dig msg
     def  = DigestAlgorithm Crypto.Store.CMS.Algorithms.SHA256
@@ -193,7 +194,7 @@
                 let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs
                  in (l, encodeAuthAttrs l)
 
-    generate  = signatureGenerate alg' priv pub input
+    generate  = signatureGenerate alg' pair input
     build sig =
         let si = SignerInfo { siSignerId = SignerIASN isn
                             , siDigestAlgorithm = dig
diff --git a/src/Crypto/Store/CMS/Util.hs b/src/Crypto/Store/CMS/Util.hs
--- a/src/Crypto/Store/CMS/Util.hs
+++ b/src/Crypto/Store/CMS/Util.hs
@@ -31,6 +31,7 @@
     -- * Parsing and encoding ASN.1 objects
     , ASN1Event
     , ASN1ObjectExact(..)
+    , derObjectExact
     , ProduceASN1Object(..)
     , encodeASN1Object
     , ParseASN1Object(..)
@@ -177,6 +178,10 @@
     { exactObject    :: a           -- ^ The wrapped ASN.1 object
     , exactObjectRaw :: ByteString  -- ^ The raw representation of this object
     } deriving Show
+
+-- | Create an ASN.1 object with a raw data in DER format.
+derObjectExact :: ProduceASN1Object ASN1P a => a -> ASN1ObjectExact a
+derObjectExact obj = ASN1ObjectExact obj (encodeASN1Object obj)
 
 instance Eq a => Eq (ASN1ObjectExact a)
     where a == b = exactObject a == exactObject b
diff --git a/src/Crypto/Store/Error.hs b/src/Crypto/Store/Error.hs
--- a/src/Crypto/Store/Error.hs
+++ b/src/Crypto/Store/Error.hs
@@ -26,6 +26,8 @@
       -- ^ Error while decoding ASN.1 content
     | ParseFailure String
       -- ^ Error while parsing an ASN.1 object
+    | UnexpectedNameForPEM
+      -- ^ The name of the given PEM object is not supported
     | DecryptionFailed
       -- ^ Unable to decrypt, incorrect key or password?
     | BadContentMAC
@@ -42,6 +44,8 @@
       -- ^ Some condition is not met about input password
     | InvalidParameter String
       -- ^ Some condition is not met about algorithm parameters
+    | PublicPrivateKeyMismatch
+      -- ^ The public key or certificate does not match the private key
     | UnexpectedPublicKeyType
       -- ^ The algorithm expects another public key type
     | UnexpectedPrivateKeyType
diff --git a/src/Crypto/Store/KeyWrap/AES.hs b/src/Crypto/Store/KeyWrap/AES.hs
--- a/src/Crypto/Store/KeyWrap/AES.hs
+++ b/src/Crypto/Store/KeyWrap/AES.hs
@@ -10,6 +10,7 @@
 --
 -- Should be used with a cipher from module "Crypto.Cipher.AES".
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
 module Crypto.Store.KeyWrap.AES
     ( wrap
     , unwrap
@@ -20,7 +21,9 @@
 import           Data.Bits
 import           Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)
 import qualified Data.ByteArray as B
-import           Data.List
+#if !(MIN_VERSION_base(4,20,0))
+import           Data.List (foldl')
+#endif
 import           Data.Word
 
 import Crypto.Cipher.Types
diff --git a/src/Crypto/Store/KeyWrap/RC2.hs b/src/Crypto/Store/KeyWrap/RC2.hs
--- a/src/Crypto/Store/KeyWrap/RC2.hs
+++ b/src/Crypto/Store/KeyWrap/RC2.hs
@@ -16,6 +16,7 @@
 
 import           Data.ByteArray (ByteArray)
 import qualified Data.ByteArray as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Hash
@@ -62,7 +63,7 @@
             temp1      = cbcEncrypt cipher iv lcekpadicv
             temp2      = B.append (B.convert iv) temp1
             temp3      = reverseBytes temp2
-            Just iv'   = makeIV iv4adda22c79e82105
+            iv'        = fromJust $ makeIV iv4adda22c79e82105
          in cbcEncrypt cipher iv' temp3
 
 -- | Unwrap an encrypted RC2 key with the specified RC2 cipher.
@@ -75,14 +76,14 @@
     | otherwise          = invalid
   where
     inLen            = B.length wrapped
-    Just iv'         = makeIV iv4adda22c79e82105
+    iv'              = fromJust $ makeIV iv4adda22c79e82105
     temp3            = cbcDecrypt cipher iv' wrapped
     temp2            = reverseBytes temp3
     (ivBs, temp1)    = B.splitAt 8 temp2
-    Just iv          = makeIV ivBs
+    iv               = fromJust $ makeIV ivBs
     lcekpadicv       = cbcDecrypt cipher iv temp1
     (lcekpad, icv)   = B.splitAt (inLen - 16) lcekpadicv
-    Just (l, cekpad) = B.uncons lcekpad
+    (l, cekpad)      = fromJust $ B.uncons lcekpad
     len              = fromIntegral l
     padlen           = inLen - 16 - len - 1
     cek              = B.take len cekpad
diff --git a/src/Crypto/Store/KeyWrap/TripleDES.hs b/src/Crypto/Store/KeyWrap/TripleDES.hs
--- a/src/Crypto/Store/KeyWrap/TripleDES.hs
+++ b/src/Crypto/Store/KeyWrap/TripleDES.hs
@@ -15,6 +15,7 @@
 
 import           Data.ByteArray (ByteArray)
 import qualified Data.ByteArray as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Hash
@@ -40,7 +41,7 @@
         (InvalidInput "KeyWrap.TripleDES: invalid length for content encryption key")
   where
     inLen    = B.length cek
-    Just iv' = makeIV iv4adda22c79e82105
+    iv'      = fromJust $ makeIV iv4adda22c79e82105
     cekicv   = B.append cek (checksum cek)
     temp1    = cbcEncrypt cipher iv cekicv
     temp2    = B.append (B.convert iv) temp1
@@ -56,11 +57,11 @@
     | otherwise                    = invalid
   where
     inLen         = B.length wrapped
-    Just iv'      = makeIV iv4adda22c79e82105
+    iv'           = fromJust $ makeIV iv4adda22c79e82105
     temp3         = cbcDecrypt cipher iv' wrapped
     temp2         = reverseBytes temp3
     (ivBs, temp1) = B.splitAt 8 temp2
-    Just iv       = makeIV ivBs
+    iv            = fromJust $ makeIV ivBs
     cekicv        = cbcDecrypt cipher iv temp1
     (cek, icv)    = B.splitAt 24 cekicv
     invalid       = Left BadChecksum
diff --git a/src/Crypto/Store/Keys.hs b/src/Crypto/Store/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Store/Keys.hs
@@ -0,0 +1,122 @@
+-- |
+-- Module      : Crypto.Store.Keys
+-- License     : BSD-style
+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
+-- Stability   : experimental
+-- Portability : unknown
+--
+--
+{-# LANGUAGE RecordWildCards #-}
+module Crypto.Store.Keys
+    ( KeyPair(..), keyPairFromPrivKey, keyPairToPrivKey, keyPairToPubKey
+    , keyPairMatchesKey, keyPairMatchesCert
+    ) where
+
+import Data.Function (on)
+import Data.Maybe (fromMaybe)
+
+import qualified Data.X509 as X509
+import Data.X509.EC
+
+import qualified Crypto.PubKey.RSA.Types as RSA
+import qualified Crypto.PubKey.DSA as DSA
+import qualified Crypto.PubKey.Curve25519 as X25519
+import qualified Crypto.PubKey.Curve448 as X448
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
+
+import Crypto.Store.PKCS8.EC
+
+-- | Holds a private and public key together, with a guarantee that they both
+-- match.  Therefore no constructor is exposed.  Content may be accessed
+-- through functions 'keyPairToPrivKey' and 'keyPairToPubKey'.
+--
+-- Call function 'keyPairFromPrivKey' to build a @KeyPair@.
+data KeyPair =
+      KeyPairRSA RSA.PrivateKey RSA.PublicKey             -- ^ RSA key pair
+    | KeyPairDSA DSA.KeyPair                              -- ^ DSA key pair
+    | KeyPairEC X509.PrivKeyEC X509.PubKeyEC              -- ^ EC key pair
+    | KeyPairX25519 X25519.SecretKey X25519.PublicKey     -- ^ X25519 key pair
+    | KeyPairX448 X448.SecretKey X448.PublicKey           -- ^ X448 key pair
+    | KeyPairEd25519 Ed25519.SecretKey Ed25519.PublicKey  -- ^ Ed25519 key pair
+    | KeyPairEd448 Ed448.SecretKey Ed448.PublicKey        -- ^ Ed448 key pair
+
+instance Show KeyPair where
+    showsPrec d keyPair = showParen (d > 10) $
+        showString "keyPairFromPrivKey " . showsPrec 11 (keyPairToPrivKey keyPair)
+
+instance Eq KeyPair where
+    (==) = (==) `on` keyPairToPrivKey
+
+-- | Builds a key pair from an X.509 private key.
+keyPairFromPrivKey :: X509.PrivKey -> KeyPair
+keyPairFromPrivKey (X509.PrivKeyRSA priv) = KeyPairRSA priv (RSA.toPublicKey (RSA.KeyPair priv))
+keyPairFromPrivKey (X509.PrivKeyDSA priv) = KeyPairDSA (dsaPairFromPriv priv)
+keyPairFromPrivKey (X509.PrivKeyEC priv) = KeyPairEC priv (ecPubFromPriv priv)
+keyPairFromPrivKey (X509.PrivKeyX25519 priv) = KeyPairX25519 priv (X25519.toPublic priv)
+keyPairFromPrivKey (X509.PrivKeyX448 priv) = KeyPairX448 priv (X448.toPublic priv)
+keyPairFromPrivKey (X509.PrivKeyEd25519 priv) = KeyPairEd25519 priv (Ed25519.toPublic priv)
+keyPairFromPrivKey (X509.PrivKeyEd448 priv) = KeyPairEd448 priv (Ed448.toPublic priv)
+
+dsaPairFromPriv :: DSA.PrivateKey -> DSA.KeyPair
+dsaPairFromPriv k = DSA.KeyPair params y x
+  where y       = DSA.calculatePublic params x
+        params  = DSA.private_params k
+        x       = DSA.private_x k
+
+ecPubFromPriv :: X509.PrivKeyEC -> X509.PubKeyEC
+ecPubFromPriv priv = case priv of
+    X509.PrivKeyEC_Prime{..} -> X509.PubKeyEC_Prime
+        { pubkeyEC_pub = getSerializedPoint curve privkeyEC_priv
+        , pubkeyEC_a = privkeyEC_a
+        , pubkeyEC_b = privkeyEC_b
+        , pubkeyEC_prime = privkeyEC_prime
+        , pubkeyEC_generator = privkeyEC_generator
+        , pubkeyEC_order = privkeyEC_order
+        , pubkeyEC_cofactor = privkeyEC_cofactor
+        , pubkeyEC_seed = privkeyEC_seed
+        }
+    X509.PrivKeyEC_Named{..} -> X509.PubKeyEC_Named
+        { X509.pubkeyEC_name = privkeyEC_name
+        , X509.pubkeyEC_pub = getSerializedPoint curve privkeyEC_priv
+        }
+  where curve = fromMaybe (error "ecPubFromPriv: invalid EC parameters") (ecPrivKeyCurve priv)
+
+-- | Gets the X.509 private key in a key pair.
+keyPairToPrivKey :: KeyPair -> X509.PrivKey
+keyPairToPrivKey (KeyPairRSA priv _) = X509.PrivKeyRSA priv
+keyPairToPrivKey (KeyPairDSA pair) = X509.PrivKeyDSA (DSA.toPrivateKey pair)
+keyPairToPrivKey (KeyPairEC priv _) = X509.PrivKeyEC priv
+keyPairToPrivKey (KeyPairX25519 priv _) = X509.PrivKeyX25519 priv
+keyPairToPrivKey (KeyPairX448 priv _) = X509.PrivKeyX448 priv
+keyPairToPrivKey (KeyPairEd25519 priv _) = X509.PrivKeyEd25519 priv
+keyPairToPrivKey (KeyPairEd448 priv _) = X509.PrivKeyEd448 priv
+
+-- | Gets the X.509 public key in a key pair.
+keyPairToPubKey :: KeyPair -> X509.PubKey
+keyPairToPubKey (KeyPairRSA _ pub) = X509.PubKeyRSA pub
+keyPairToPubKey (KeyPairDSA pair) = X509.PubKeyDSA (DSA.toPublicKey pair)
+keyPairToPubKey (KeyPairEC _ pub) = X509.PubKeyEC pub
+keyPairToPubKey (KeyPairX25519 _ pub) = X509.PubKeyX25519 pub
+keyPairToPubKey (KeyPairX448 _ pub) = X509.PubKeyX448 pub
+keyPairToPubKey (KeyPairEd25519 _ pub) = X509.PubKeyEd25519 pub
+keyPairToPubKey (KeyPairEd448 _ pub) = X509.PubKeyEd448 pub
+
+-- | Returns 'True' when the given X.509 public key is consistent with a key
+-- pair, which means that the public key can be derived from the private key in
+-- the key pair.
+keyPairMatchesKey :: KeyPair -> X509.PubKey -> Bool
+keyPairMatchesKey (KeyPairRSA _ pub) (X509.PubKeyRSA other) = pub == other
+keyPairMatchesKey (KeyPairDSA pair) (X509.PubKeyDSA other) = DSA.toPublicKey pair == other
+keyPairMatchesKey (KeyPairEC _ pub) (X509.PubKeyEC other) = pub == other
+keyPairMatchesKey (KeyPairX25519 _ pub) (X509.PubKeyX25519 other) = pub == other
+keyPairMatchesKey (KeyPairX448 _ pub) (X509.PubKeyX448 other) = pub == other
+keyPairMatchesKey (KeyPairEd25519 _ pub) (X509.PubKeyEd25519 other) = pub == other
+keyPairMatchesKey (KeyPairEd448 _ pub) (X509.PubKeyEd448 other) = pub == other
+keyPairMatchesKey _ _ = False
+
+keyPairMatchesCert :: KeyPair -> X509.SignedCertificate -> Bool
+keyPairMatchesCert keyPair cert =
+    let obj = X509.signedObject (X509.getSigned cert)
+        pub = X509.certPubKey obj
+     in keyPairMatchesKey keyPair pub
diff --git a/src/Crypto/Store/PEM.hs b/src/Crypto/Store/PEM.hs
--- a/src/Crypto/Store/PEM.hs
+++ b/src/Crypto/Store/PEM.hs
@@ -11,6 +11,7 @@
     , writePEMs
     , pemsWriteBS
     , pemsWriteLBS
+    , mkPEM
     , module Data.PEM
     ) where
 
@@ -19,6 +20,9 @@
 import qualified Data.ByteString.Lazy as L
 
 -- | Read a PEM file from disk.
+--
+-- The function uses lazy IO so will retain an open file descriptor until the
+-- list spine is fully evaluated.
 readPEMs :: FilePath -> IO [PEM]
 readPEMs filepath = either error id . pemParseLBS <$> L.readFile filepath
 
@@ -33,3 +37,7 @@
 -- | Write a PEM file to disk.
 writePEMs :: FilePath -> [PEM] -> IO ()
 writePEMs filepath = L.writeFile filepath . pemsWriteLBS
+
+-- | Make a PEM without headers.
+mkPEM :: String -> B.ByteString -> PEM
+mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
diff --git a/src/Crypto/Store/PKCS12.hs b/src/Crypto/Store/PKCS12.hs
--- a/src/Crypto/Store/PKCS12.hs
+++ b/src/Crypto/Store/PKCS12.hs
@@ -8,6 +8,7 @@
 -- Personal Information Exchange Syntax, aka PKCS #12.
 --
 -- Only password integrity mode and password privacy modes are supported.
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -17,7 +18,7 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 module Crypto.Store.PKCS12
-    ( IntegrityParams
+    ( IntegrityParams(..)
     , readP12File
     , readP12FileFromMemory
     , writeP12File
@@ -70,14 +71,18 @@
     , recoverA
     ) where
 
+import Control.Applicative
 import Control.Monad
 
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import qualified Data.ByteString as BS
 import           Data.List (partition)
-import           Data.Maybe (fromMaybe, mapMaybe)
+import           Data.Maybe (isJust, fromMaybe, mapMaybe)
+#if !(MIN_VERSION_base(4,11,0))
 import           Data.Semigroup
+#endif
+import           Data.String (fromString)
 import qualified Data.X509 as X509
 import qualified Data.X509.Validation as X509
 
@@ -92,6 +97,7 @@
 import Crypto.Store.CMS.Enveloped
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 import Crypto.Store.PKCS5
 import Crypto.Store.PKCS5.PBES1
 import Crypto.Store.PKCS8
@@ -141,25 +147,44 @@
             Just md -> return $ Authenticated (verify md authSafeData)
 
     verify MacData{..} content pwdUTF8 =
-        case digAlg of
-            DigestAlgorithm d -> loop (toProtectionPasswords pwdUTF8)
+        case macAlg of
+            MacTraditional (DigestAlgorithm d) -> loop (toProtectionPasswords pwdUTF8)
               where
                 -- iterate over all possible representations of a password
                 -- until a successful match is found
                 loop []           = Left BadContentMAC
                 loop (pwd:others) =
-                    let fn key macAlg bs
-                            | not (securityAcceptable macAlg) =
+                    let fn key digAlg bs
+                            | not (securityAcceptable digAlg) =
                                 Left (InvalidParameter "Integrity MAC too weak")
-                            | macValue == mac macAlg key bs = (pwd,) <$> decode bs
+                            | macValue == mac digAlg key bs = (pwd,) <$> decode bs
                             | otherwise = loop others
                      in pkcs12mac Left fn d macParams content pwd
 
+            -- for RFC 9579 the primary representation is always used, this is
+            -- fine because we assume the encryption layer also uses that
+            -- representation
+            MacAuthScheme authScheme
+                | not (hasExplicitKeyLength authScheme) ->
+                    Left (InvalidParameter "KDF key length must be explicit")
+                | not (securityAcceptable authScheme) ->
+                    Left (InvalidParameter "Integrity MAC too weak")
+                | macValue == pbMac authScheme content pwd -> (pwd,) <$> decode content
+                | otherwise -> Left BadContentMAC
+              where pwd = toProtectionPassword pwdUTF8
 
+    hasExplicitKeyLength (PBMAC1 p) = isJust $ kdfKeyLength (pbmac1KDF p)
+
+
 -- Generating and encoding
 
 -- | Parameters used for password integrity mode.
-type IntegrityParams = (DigestAlgorithm, PBEParameter)
+data IntegrityParams
+    = TraditionalIntegrity DigestAlgorithm PBEParameter
+      -- ^ Traditional PKCS #12 integrity, with a digest algoritm
+    | AuthSchemeIntegrity AuthenticationScheme
+      -- ^ Integrity with an authentication scheme such as PBMAC1
+    deriving (Show,Eq)
 
 -- | Write a PKCS #12 file to disk.
 writeP12File :: FilePath
@@ -175,18 +200,33 @@
 writeP12FileToMemory :: IntegrityParams -> ProtectionPassword
                      -> PKCS12
                      -> Either StoreError BS.ByteString
-writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =
+writeP12FileToMemory intp pwdUTF8 aSafe =
     encode <$> protect
   where
     content   = encodeASN1Object aSafe
     encode md = encodeASN1Object PFX { authSafeData = content, macData = Just md }
 
-    protect = pkcs12mac Left fn hashAlg pbeParam content pwdUTF8
-    fn key macAlg bs = Right MacData { digAlg    = alg
-                                     , macValue  = mac macAlg key bs
-                                     , macParams = pbeParam
-                                     }
+    protect = case intp of
+        TraditionalIntegrity alg@(DigestAlgorithm hashAlg) pbeParam ->
+            let fn key macAlg bs = Right MacData { macAlg    = MacTraditional alg
+                                                 , macValue  = mac macAlg key bs
+                                                 , macParams = pbeParam
+                                                 }
+             in pkcs12mac Left fn hashAlg pbeParam content pwdUTF8
+        AuthSchemeIntegrity authScheme
+            | pwdUTF8 == emptyNotTerminated ->
+                Left (InvalidPassword "Authentication scheme requires terminated password")
+            | otherwise ->
+                Right MacData { macAlg    = MacAuthScheme (transform authScheme)
+                              , macValue  = pbMac authScheme content pwdUTF8
+                              , macParams = unused
+                              }
+    unused = PBEParameter (fromString "NOT USED") 1
 
+    transform (PBMAC1 p) = PBMAC1 (ensureExplicitKeyLength p)
+    ensureExplicitKeyLength p = p { pbmac1KDF = kdfKeyLengthModify fn (pbmac1KDF p) }
+      where fn = Just . fromMaybe (getMaximumKeySize $ pbmac1AScheme p)
+
 -- | Write a PKCS #12 file without integrity protection to disk.
 writeUnprotectedP12File :: FilePath -> PKCS12 -> IO ()
 writeUnprotectedP12File path = BS.writeFile path . writeUnprotectedP12FileToMemory
@@ -230,8 +270,12 @@
         m <- if b then Just <$> parse else pure Nothing
         return PFX { authSafeData = d, macData = m }
 
+data MacAlg = MacTraditional DigestAlgorithm
+            | MacAuthScheme AuthenticationScheme
+            deriving (Show,Eq)
+
 data MacData = MacData
-    { digAlg :: DigestAlgorithm
+    { macAlg :: MacAlg
     , macValue :: MessageAuthenticationCode
     , macParams :: PBEParameter
     }
@@ -242,29 +286,34 @@
         asn1Container Sequence (m . s . i)
       where
         m = asn1Container Sequence (a . v)
-        a = algorithmASN1S Sequence digAlg
+        a = gMacAlg macAlg
         v = gOctetString (B.convert macValue)
         s = gOctetString (pbeSalt macParams)
         i = gIntVal (fromIntegral $ pbeIterationCount macParams)
 
+        gMacAlg (MacTraditional alg) = algorithmASN1S Sequence alg
+        gMacAlg (MacAuthScheme authScheme) = algorithmASN1S Sequence authScheme
+
 instance Monoid e => ParseASN1Object e MacData where
     parse = onNextContainer Sequence $ do
         (a, v) <- onNextContainer Sequence $ do
-            a <- parseAlgorithm Sequence
+            a <- parseTraditional <|> parseAuthScheme
             OctetString v <- getNext
             return (a, v)
         OctetString s <- getNext
         b <- hasNext
         IntVal i <- if b then getNext else pure (IntVal 1)
-        return MacData { digAlg = a
+        return MacData { macAlg = a
                        , macValue = AuthTag (B.convert v)
                        , macParams = PBEParameter s (fromIntegral i)
                        }
-
+      where
+        parseTraditional = MacTraditional <$> parseAlgorithm Sequence
+        parseAuthScheme = MacAuthScheme <$> parseAlgorithm Sequence
 
 -- AuthenticatedSafe
 
--- | PKCS #12 privacy wrapper, adding optional encryption to 'SafeContents'.
+-- | PKCS #12 privacy wrapper, adding optional encryption to t'SafeContents'.
 -- ASN.1 equivalent is @AuthenticatedSafe@.
 --
 -- The semigroup interface allows to combine multiple pieces encrypted
@@ -453,7 +502,7 @@
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 -- | Main bag payload in PKCS #12 contents.
-data SafeInfo = KeyBag (FormattedKey X509.PrivKey) -- ^ unencrypted private key
+data SafeInfo = KeyBag (FormattedKey KeyPair)      -- ^ unencrypted private key
               | PKCS8ShroudedKeyBag PKCS5          -- ^ encrypted private key
               | CertBag (Bag CertInfo)             -- ^ certificate
               | CRLBag (Bag CRLInfo)               -- ^ CRL
@@ -513,7 +562,7 @@
 filterByLocalKeyId :: BS.ByteString -> SafeContents -> SafeContents
 filterByLocalKeyId d = filterBags ((== Just d) . getLocalKeyId)
 
-getSafeKeysId :: SafeContents -> [OptProtected (Id X509.PrivKey)]
+getSafeKeysId :: SafeContents -> [OptProtected (Id KeyPair)]
 getSafeKeysId (SafeContents scs) = loop scs
   where
     loop []           = []
@@ -530,10 +579,10 @@
         return (mkId k bag)
 
 -- | Return all private keys contained in the safe contents.
-getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]
+getSafeKeys :: SafeContents -> [OptProtected KeyPair]
 getSafeKeys = map (fmap unId) . getSafeKeysId
 
-getAllSafeKeysId :: [SafeContents] -> OptProtected [Id X509.PrivKey]
+getAllSafeKeysId :: [SafeContents] -> OptProtected [Id KeyPair]
 getAllSafeKeysId = applySamePassword . concatMap getSafeKeysId
 
 -- | Return all private keys contained in the safe content list.  All shrouded
@@ -543,7 +592,7 @@
 -- least is encrypted.  This does not mean all keys were actually protected in
 -- the input.  If detailed view is required then function 'getSafeKeys' is
 -- available.
-getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey]
+getAllSafeKeys :: [SafeContents] -> OptProtected [KeyPair]
 getAllSafeKeys = applySamePassword . concatMap getSafeKeys
 
 getSafeX509CertsId :: SafeContents -> [Id X509.SignedCertificate]
@@ -598,21 +647,25 @@
                 -- and follow the issuers to get all certificates in the chain
                 let filtered = map (filterByLocalKeyId d) l
                 leaf <- single (getAllSafeX509Certs filtered)
-                pure (buildCertificateChain leaf certs, k)
+                guard (keyPairMatchesCert k leaf)
+                pure (buildCertificateChain leaf certs, keyPairToPrivKey k)
             Nothing ->
                 case idName iKey of
                     Just name -> do
                         -- same but using friendly name of private key
                         let filtered = map (filterByFriendlyName name) l
                         leaf <- single (getAllSafeX509Certs filtered)
-                        pure (buildCertificateChain leaf certs, k)
+                        guard (keyPairMatchesCert k leaf)
+                        pure (buildCertificateChain leaf certs, keyPairToPrivKey k)
                     Nothing   -> do
-                        -- no identifier available, so we simply return all
-                        -- certificates with input order
-                        guard (not $ null certs)
-                        pure (X509.CertificateChain certs, k)
+                        -- no identifier available, so we have to search all
+                        -- certificates for the one consistent with the private
+                        -- key
+                        leaf <- single (filterWithPrivKey k certs)
+                        pure (buildCertificateChain leaf certs, keyPairToPrivKey k)
+    filterWithPrivKey = filter . keyPairMatchesCert
 
--- | Extract the private key and certificate chain from a 'PKCS12' value.  A
+-- | Extract the private key and certificate chain from a t'PKCS12' value.  A
 -- credential is returned when the structure contains exactly one private key
 -- and at least one X.509 certificate.
 toCredential :: PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))
@@ -627,16 +680,17 @@
     fn keys  = do
         k <- single keys
         leaf <- single (getAllSafeX509Certs filtered)
-        pure (buildCertificateChain leaf certs, k)
+        guard (keyPairMatchesCert k leaf)
+        pure (buildCertificateChain leaf certs, keyPairToPrivKey k)
 
 -- | Extract a private key and certificate chain with the specified friendly
--- name from a 'PKCS12' value.  A credential is returned when the structure
+-- name from a t'PKCS12' value.  A credential is returned when the structure
 -- contains exactly one private key and one X.509 certificate with the name.
 toNamedCredential :: String -> PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))
 toNamedCredential name p12 = unSamePassword $
     SamePassword (unPKCS12 p12) >>= getInnerCredentialNamed name
 
--- | Build a 'PKCS12' value containing a private key and certificate chain.
+-- | Build a t'PKCS12' value containing a private key and certificate chain.
 -- Distinct encryption is applied for both.  Encrypting the certificate chain is
 -- optional.
 --
@@ -649,7 +703,7 @@
                -> Either StoreError PKCS12
 fromCredential = fromCredential' id
 
--- | Build a 'PKCS12' value containing a private key and certificate chain
+-- | Build a t'PKCS12' value containing a private key and certificate chain
 -- identified with the specified friendly name.  Distinct encryption is applied
 -- for private key and certificates.  Encrypting the certificate chain is
 -- optional.
@@ -685,10 +739,14 @@
     certAttrs     = attrs : repeat []
     toCertBag a c = Bag (CertBag (Bag (CertX509 c) [])) a
 
-    scKeyOrError = wrap <$> encrypt algKey pwd encodedKey
+    scKeyOrError
+        | keyPairMatchesCert pair leaf =
+            wrap <$> encrypt algKey pwd encodedKey
+        | otherwise = Left PublicPrivateKeyMismatch
 
     wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) attrs]
-    encodedKey    = encodeASN1Object (FormattedKey PKCS8Format key)
+    encodedKey    = encodeASN1Object (FormattedKey PKCS8Format pair)
+    pair          = keyPairFromPrivKey key
 
     X509.Fingerprint keyId = X509.getFingerprint leaf X509.HashSHA1
     attrs = trans (setLocalKeyId keyId [])
diff --git a/src/Crypto/Store/PKCS5.hs b/src/Crypto/Store/PKCS5.hs
--- a/src/Crypto/Store/PKCS5.hs
+++ b/src/Crypto/Store/PKCS5.hs
@@ -24,6 +24,9 @@
     , EncryptionScheme(..)
     , PBEParameter(..)
     , PBES2Parameter(..)
+    -- * Message authentication schemes
+    , AuthenticationScheme(..)
+    , PBMAC1Parameter(..)
     -- * Key derivation
     , KeyDerivationFunc(..)
     , PBKDF2_PRF(..)
@@ -33,11 +36,16 @@
     , ContentEncryptionParams
     , ContentEncryptionAlg(..)
     , ContentEncryptionCipher(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
+    , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
     , getContentEncryptionAlg
     -- * Low-level API
     , pbEncrypt
     , pbDecrypt
+    , pbMac
     ) where
 
 import           Data.ASN1.Types
@@ -47,6 +55,7 @@
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.CMS.Algorithms
+import Crypto.Store.CMS.Authenticated
 import Crypto.Store.CMS.Encrypted
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
@@ -164,7 +173,66 @@
 instance Monoid e => ParseASN1Object e EncryptionScheme where
     parse = parseAlgorithm Sequence
 
+data AuthenticationSchemeType = Type_PBMAC1
 
+instance Enumerable AuthenticationSchemeType where
+    values = [ Type_PBMAC1
+             ]
+
+instance OIDable AuthenticationSchemeType where
+    getObjectID Type_PBMAC1 = [1,2,840,113549,1,5,14]
+
+instance OIDNameable AuthenticationSchemeType where
+    fromObjectID oid = unOIDNW <$> fromObjectID oid
+
+-- | Password-Based Message Authentication Scheme (PBMAC).
+newtype AuthenticationScheme = PBMAC1 PBMAC1Parameter -- ^ PBMAC1
+                             deriving (Show,Eq)
+
+instance HasStrength AuthenticationScheme where
+    getSecurityBits (PBMAC1 p) = getSecurityBits (pbmac1AScheme p)
+
+-- | PBMAC1 parameters.
+data PBMAC1Parameter = PBMAC1Parameter
+    { pbmac1KDF     :: KeyDerivationFunc  -- ^ Key derivation function
+    , pbmac1AScheme :: MACAlgorithm       -- ^ Underlying message authentication scheme
+    }
+    deriving (Show,Eq)
+
+instance ASN1Elem e => ProduceASN1Object e PBMAC1Parameter where
+    asn1s PBMAC1Parameter{..} =
+        let kdFunc  = algorithmASN1S Sequence pbmac1KDF
+            aScheme = algorithmASN1S Sequence pbmac1AScheme
+         in asn1Container Sequence (kdFunc . aScheme)
+
+instance Monoid e => ParseASN1Object e PBMAC1Parameter where
+    parse = onNextContainer Sequence $ do
+        kdFunc  <- parseAlgorithm Sequence
+        aScheme <- parseAlgorithm Sequence
+        case kdfKeyLength kdFunc of
+            Nothing -> return ()
+            Just sz
+                | validateKeySize aScheme sz -> return ()
+                | otherwise -> throwParseError "PBMAC1Parameter: parsed key length incompatible with message authentication scheme"
+        return PBMAC1Parameter { pbmac1KDF = kdFunc, pbmac1AScheme = aScheme }
+
+instance AlgorithmId AuthenticationScheme where
+    type AlgorithmType AuthenticationScheme = AuthenticationSchemeType
+    algorithmName _  = "message authentication scheme"
+
+    algorithmType (PBMAC1 _)                 = Type_PBMAC1
+
+    parameterASN1S (PBMAC1 p)                 = asn1s p
+
+    parseParameter Type_PBMAC1                 = PBMAC1 <$> parse
+
+instance ASN1Elem e => ProduceASN1Object e AuthenticationScheme where
+    asn1s = algorithmASN1S Sequence
+
+instance Monoid e => ParseASN1Object e AuthenticationScheme where
+    parse = parseAlgorithm Sequence
+
+
 -- High-level API
 
 -- | Content encrypted with a Password-Based Encryption Scheme (PBES).
@@ -236,3 +304,16 @@
 pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs
   where key = kdfDerive pbes2KDF len (fromProtectionPassword pwd) :: Key
         len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)
+
+
+-- Message Authentication Schemes
+
+-- | Authenticate a message with the specified authentication scheme and
+-- password.
+pbMac :: AuthenticationScheme -> EncapsulatedContent -> ProtectionPassword -> MessageAuthenticationCode
+pbMac (PBMAC1 p) = pbmac1 p
+
+pbmac1 :: PBMAC1Parameter -> ByteString -> ProtectionPassword -> MessageAuthenticationCode
+pbmac1 PBMAC1Parameter{..} bs pwd = mac pbmac1AScheme key bs
+  where key = kdfDerive pbmac1KDF len (fromProtectionPassword pwd) :: Key
+        len = fromMaybe (getMaximumKeySize pbmac1AScheme) (kdfKeyLength pbmac1KDF)
diff --git a/src/Crypto/Store/PKCS8.hs b/src/Crypto/Store/PKCS8.hs
--- a/src/Crypto/Store/PKCS8.hs
+++ b/src/Crypto/Store/PKCS8.hs
@@ -9,7 +9,11 @@
 --
 -- Presents an API similar to "Data.X509.Memory" and "Data.X509.File" but
 -- allows to write private keys and provides support for password-based
--- encryption.
+-- encryption.  Private keys are now stored along with the corresponding
+-- public key in a type 'KeyPair'.  Components of type 'X509.PrivKey' and
+--  'X509.PubKey' can be obtained through functions 'keyPairToPrivKey' and
+-- 'keyPairToPubKey'.  Function 'keyPairFromPrivKey' can be called to build a
+-- 'KeyPair'.
 --
 -- Functions to read a private key return an object wrapped in the
 -- 'OptProtected' data type.
@@ -25,12 +29,18 @@
     ( readKeyFile
     , readKeyFileFromMemory
     , pemToKey
+    , pemToKeyAccum
     , writeKeyFile
     , writeKeyFileToMemory
     , keyToPEM
     , writeEncryptedKeyFile
     , writeEncryptedKeyFileToMemory
     , encryptKeyToPEM
+    -- * Key pairs
+    , KeyPair
+    , keyPairFromPrivKey
+    , keyPairToPrivKey
+    , keyPairToPubKey
     -- * Serialization formats
     , PrivateKeyFormat(..)
     , FormattedKey(..)
@@ -54,7 +64,10 @@
 import Data.ASN1.BinaryEncoding
 import Data.ASN1.BitArray
 import Data.ASN1.Encoding
+import Data.ASN1.Prim
+import Data.Bifunctor (first)
 import Data.ByteArray (ByteArrayAccess, convert)
+import Data.Either (rights)
 import Data.Maybe
 import qualified Data.X509 as X509
 import qualified Data.ByteString as B
@@ -66,13 +79,14 @@
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
 import qualified Crypto.PubKey.Ed25519 as Ed25519
 import qualified Crypto.PubKey.Ed448 as Ed448
-import qualified Crypto.PubKey.RSA as RSA
+import qualified Crypto.PubKey.RSA.Types as RSA
 
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.CMS.Attribute
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 import Crypto.Store.PEM
 import Crypto.Store.PKCS5
 import Crypto.Store.PKCS8.EC
@@ -116,63 +130,72 @@
 -- Reading from PEM format
 
 -- | Read private keys from a PEM file.
-readKeyFile :: FilePath -> IO [OptProtected X509.PrivKey]
+readKeyFile :: FilePath -> IO [OptProtected KeyPair]
 readKeyFile path = accumulate <$> readPEMs path
 
 -- | Read private keys from a bytearray in PEM format.
-readKeyFileFromMemory :: B.ByteString -> [OptProtected X509.PrivKey]
+readKeyFileFromMemory :: B.ByteString -> [OptProtected KeyPair]
 readKeyFileFromMemory = either (const []) accumulate . pemParseBS
 
-accumulate :: [PEM] -> [OptProtected X509.PrivKey]
-accumulate = catMaybes . foldr (flip pemToKey) []
+accumulate :: [PEM] -> [OptProtected KeyPair]
+accumulate = rights . map pemToKey
 
--- | Read a private key from a 'PEM' element and add it to the accumulator list.
-pemToKey :: [Maybe (OptProtected X509.PrivKey)] -> PEM -> [Maybe (OptProtected X509.PrivKey)]
-pemToKey acc pem =
-    case decodeASN1' BER (pemContent pem) of
-        Left _     -> acc
-        Right asn1 -> run (getParser $ pemName pem) asn1 : acc
+-- | Read a private key from a t'PEM' element and add it to the accumulator
+-- list.
+--
+-- This API is modelled after the original @pemToKey@ in "Data.X509.Memory".
+pemToKeyAccum :: [Maybe (OptProtected KeyPair)] -> PEM -> [Maybe (OptProtected KeyPair)]
+pemToKeyAccum acc pem =
+    case pemToKey pem of
+        Left (DecodingError _) -> acc
+        Left _                 -> Nothing : acc
+        Right key              -> Just key : acc
 
-  where
-    run p = either (const Nothing) Just . runParseASN1 p
+-- | Read a private key from a t'PEM' element.
+pemToKey :: PEM -> Either StoreError (OptProtected KeyPair)
+pemToKey pem = do
+    asn1 <- mapLeft DecodingError $ decodeASN1' BER (pemContent pem)
+    parser <- getParser (pemName pem)
+    mapLeft ParseFailure $ runParseASN1 parser asn1
 
+  where
     allTypes  = unFormat <$> parse
-    rsa       = X509.PrivKeyRSA . unFormat <$> parse
-    dsa       = X509.PrivKeyDSA . DSA.toPrivateKey . unFormat <$> parse
-    ecdsa     = X509.PrivKeyEC . unFormat <$> parse
-    x25519    = X509.PrivKeyX25519 <$> parseModern
-    x448      = X509.PrivKeyX448 <$> parseModern
-    ed25519   = X509.PrivKeyEd25519 <$> parseModern
-    ed448     = X509.PrivKeyEd448 <$> parseModern
+    rsa       = parseFormattedKeyPair (keyPairFromPrivKey . X509.PrivKeyRSA)
+    dsa       = parseFormattedKeyPair KeyPairDSA
+    ecdsa     = parseFormattedKeyPair (keyPairFromPrivKey . X509.PrivKeyEC)
+    x25519    = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyX25519)
+    x448      = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyX448)
+    ed25519   = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyEd25519)
+    ed448     = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyEd448)
     encrypted = inner . decrypt <$> parse
 
-    getParser "PRIVATE KEY"           = Unprotected <$> allTypes
-    getParser "RSA PRIVATE KEY"       = Unprotected <$> rsa
-    getParser "DSA PRIVATE KEY"       = Unprotected <$> dsa
-    getParser "EC PRIVATE KEY"        = Unprotected <$> ecdsa
-    getParser "X25519 PRIVATE KEY"    = Unprotected <$> x25519
-    getParser "X448 PRIVATE KEY"      = Unprotected <$> x448
-    getParser "ED25519 PRIVATE KEY"   = Unprotected <$> ed25519
-    getParser "ED448 PRIVATE KEY"     = Unprotected <$> ed448
-    getParser "ENCRYPTED PRIVATE KEY" = Protected   <$> encrypted
-    getParser _                       = empty
+    getParser "PRIVATE KEY"           = return (Unprotected <$> allTypes)
+    getParser "RSA PRIVATE KEY"       = return (Unprotected <$> rsa)
+    getParser "DSA PRIVATE KEY"       = return (Unprotected <$> dsa)
+    getParser "EC PRIVATE KEY"        = return (Unprotected <$> ecdsa)
+    getParser "X25519 PRIVATE KEY"    = return (Unprotected <$> x25519)
+    getParser "X448 PRIVATE KEY"      = return (Unprotected <$> x448)
+    getParser "ED25519 PRIVATE KEY"   = return (Unprotected <$> ed25519)
+    getParser "ED448 PRIVATE KEY"     = return (Unprotected <$> ed448)
+    getParser "ENCRYPTED PRIVATE KEY" = return (Protected   <$> encrypted)
+    getParser _                       = Left UnexpectedNameForPEM
 
     inner decfn pwd = do
         decrypted <- decfn pwd
         asn1 <- mapLeft DecodingError $ decodeASN1' BER decrypted
-        case run allTypes asn1 of
-            Nothing -> Left (ParseFailure "No key parsed after decryption")
-            Just k  -> return k
+        case runParseASN1 allTypes asn1 of
+            Left _   -> Left (ParseFailure "No key parsed after decryption")
+            Right k  -> return k
 
 
 -- Writing to PEM format
 
 -- | Write unencrypted private keys to a PEM file.
-writeKeyFile :: PrivateKeyFormat -> FilePath -> [X509.PrivKey] -> IO ()
+writeKeyFile :: PrivateKeyFormat -> FilePath -> [KeyPair] -> IO ()
 writeKeyFile fmt path = writePEMs path . map (keyToPEM fmt)
 
 -- | Write unencrypted private keys to a bytearray in PEM format.
-writeKeyFileToMemory :: PrivateKeyFormat -> [X509.PrivKey] -> B.ByteString
+writeKeyFileToMemory :: PrivateKeyFormat -> [KeyPair] -> B.ByteString
 writeKeyFileToMemory fmt = pemsWriteBS . map (keyToPEM fmt)
 
 -- | Write a PKCS #8 encrypted private key to a PEM file.
@@ -183,10 +206,10 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFile :: FilePath
-                      -> EncryptionScheme -> ProtectionPassword -> X509.PrivKey
+                      -> EncryptionScheme -> ProtectionPassword -> KeyPair
                       -> IO (Either StoreError ())
-writeEncryptedKeyFile path alg pwd privKey =
-    let pem = encryptKeyToPEM alg pwd privKey
+writeEncryptedKeyFile path alg pwd keyPair =
+    let pem = encryptKeyToPEM alg pwd keyPair
      in either (return . Left) (fmap Right . writePEMs path . (:[])) pem
 
 -- | Write a PKCS #8 encrypted private key to a bytearray in PEM format.
@@ -197,65 +220,66 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword
-                              -> X509.PrivKey -> Either StoreError B.ByteString
-writeEncryptedKeyFileToMemory alg pwd privKey =
-    pemWriteBS <$> encryptKeyToPEM alg pwd privKey
+                              -> KeyPair -> Either StoreError B.ByteString
+writeEncryptedKeyFileToMemory alg pwd keyPair =
+    pemWriteBS <$> encryptKeyToPEM alg pwd keyPair
 
 -- | Generate an unencrypted PEM for a private key.
-keyToPEM :: PrivateKeyFormat -> X509.PrivKey -> PEM
+keyToPEM :: PrivateKeyFormat -> KeyPair -> PEM
 keyToPEM TraditionalFormat = keyToTraditionalPEM
 keyToPEM PKCS8Format       = keyToModernPEM
 
-keyToTraditionalPEM :: X509.PrivKey -> PEM
-keyToTraditionalPEM privKey =
+keyToTraditionalPEM :: KeyPair -> PEM
+keyToTraditionalPEM keyPair =
     mkPEM (typeTag ++ " PRIVATE KEY") (encodeASN1S asn1)
-  where (typeTag, asn1) = traditionalPrivKeyASN1S privKey
+  where (typeTag, asn1) = traditionalPrivKeyASN1S keyPair
 
-traditionalPrivKeyASN1S :: ASN1Elem e => X509.PrivKey -> (String, ASN1Stream e)
-traditionalPrivKeyASN1S privKey =
-    case privKey of
-        X509.PrivKeyRSA k -> ("RSA", traditional k)
-        X509.PrivKeyDSA k -> ("DSA", traditional (dsaPrivToPair k))
-        X509.PrivKeyEC  k -> ("EC",  traditional k)
-        X509.PrivKeyX25519  k -> ("X25519",  tradModern k)
-        X509.PrivKeyX448    k -> ("X448",    tradModern k)
-        X509.PrivKeyEd25519 k -> ("ED25519", tradModern k)
-        X509.PrivKeyEd448   k -> ("ED448",   tradModern k)
+traditionalPrivKeyASN1S :: ASN1Elem e => KeyPair -> (String, ASN1Stream e)
+traditionalPrivKeyASN1S keyPair =
+    case keyPair of
+        KeyPairRSA k _ -> ("RSA", traditional k)
+        KeyPairDSA p   -> ("DSA", traditional p)
+        KeyPairEC  k _ -> ("EC",  traditional k)
+        KeyPairX25519  k _ -> ("X25519",  tradModern k)
+        KeyPairX448    k _ -> ("X448",    tradModern k)
+        KeyPairEd25519 k _ -> ("ED25519", tradModern k)
+        KeyPairEd448   k _ -> ("ED448",   tradModern k)
   where
+    traditional :: ProduceASN1Object e (Traditional a) => a -> ASN1Stream e
     traditional a = asn1s (Traditional a)
-    tradModern a  = asn1s (Modern [] a)
 
-keyToModernPEM :: X509.PrivKey -> PEM
-keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)
-  where asn1 = modernPrivKeyASN1S [] privKey
+    tradModern :: ProduceASN1Object e (Modern a) => a -> ASN1Stream e
+    tradModern = modernASN1S (const $ keyPairToPubKey keyPair)
 
-modernPrivKeyASN1S :: ASN1Elem e => [Attribute] -> X509.PrivKey -> ASN1Stream e
-modernPrivKeyASN1S attrs privKey =
-    case privKey of
-        X509.PrivKeyRSA k -> modern k
-        X509.PrivKeyDSA k -> modern (dsaPrivToPair k)
-        X509.PrivKeyEC  k -> modern k
-        X509.PrivKeyX25519  k -> modern k
-        X509.PrivKeyX448    k -> modern k
-        X509.PrivKeyEd25519 k -> modern k
-        X509.PrivKeyEd448   k -> modern k
+keyToModernPEM :: KeyPair -> PEM
+keyToModernPEM keyPair = mkPEM "PRIVATE KEY" (encodeASN1S asn1)
+  where asn1 = modernASN1S keyPairToPubKey keyPair
+
+modernPrivKeyASN1S :: ASN1Elem e
+                   => [Attribute] -> Maybe GenericPubKey -> KeyPair -> ASN1Stream e
+modernPrivKeyASN1S attrs mPub keyPair =
+    case keyPair of
+        KeyPairRSA k _ -> modern k
+        KeyPairDSA p   -> modern p
+        KeyPairEC  k _ -> modern k
+        KeyPairX25519  k _ -> modern k
+        KeyPairX448    k _ -> modern k
+        KeyPairEd25519 k _ -> modern k
+        KeyPairEd448   k _ -> modern k
   where
-    modern a = asn1s (Modern attrs a)
+    modern a = asn1s (Modern attrs mPub a)
 
 -- | Generate a PKCS #8 encrypted PEM for a private key.
 --
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
-encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> X509.PrivKey
+encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> KeyPair
                 -> Either StoreError PEM
-encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs
-  where bs = pemContent (keyToModernPEM privKey)
+encryptKeyToPEM alg pwd keyPair = toPEM <$> encrypt alg pwd bs
+  where bs = pemContent (keyToModernPEM keyPair)
         toPEM pkcs8 = mkPEM "ENCRYPTED PRIVATE KEY" (encodeASN1Object pkcs8)
 
-mkPEM :: String -> B.ByteString -> PEM
-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
 
-
 -- Private key formats: traditional (SSLeay compatible) and modern (PKCS #8)
 
 -- | Private-key serialization format.
@@ -270,15 +294,65 @@
 parseTraditional :: ParseASN1Object e (Traditional a) => ParseASN1 e a
 parseTraditional = unTraditional <$> parse
 
-data Modern a = Modern [Attribute] a
+data Modern a = Modern [Attribute] (Maybe GenericPubKey) a
 
 instance Functor Modern where
-    fmap f (Modern attrs a) = Modern attrs (f a)
+    fmap f (Modern attrs mPub a) = Modern attrs mPub (f a)
 
-parseModern :: ParseASN1Object e (Modern a) => ParseASN1 e a
-parseModern = unModern <$> parse
-  where unModern (Modern _ a) = a
+modernASN1S :: ProduceASN1Object e (Modern a)
+            => (a -> X509.PubKey) -> a -> ASN1Stream e
+modernASN1S toPubKey a = asn1s (Modern [] mPub a)
+  where
+    noPubKey = True  -- we never generate with publicKey but the code exists
+    mPub | noPubKey  = Nothing
+         | otherwise = case getGenericPubKey toPubKey a of
+             Right (gpk, []) -> Just gpk
+             _ -> Nothing
 
+parseModernKeyPair :: ParseASN1Object e (Modern a)
+                   => (a -> KeyPair) -> ParseASN1 e KeyPair
+parseModernKeyPair = parseModern keyPairToPubKey
+
+parseModern :: ParseASN1Object e (Modern b)
+            => (a -> X509.PubKey) -> (b -> a) -> ParseASN1 e a
+parseModern toPubKey mapFn = do
+    Modern _ mPub b <- parse
+    verifyPubKey toPubKey (mapFn b) mPub
+
+verifyPubKey :: (a -> X509.PubKey) -> a -> Maybe GenericPubKey -> ParseASN1 e a
+verifyPubKey _ a Nothing = return a
+verifyPubKey toPubKey a (Just gpk)
+    | Right (gpk, []) == derived = return a
+    | otherwise = throwParseError "PKCS8 public key does not match private key"
+  where derived = getGenericPubKey toPubKey a
+
+getGenericPubKey :: (a -> X509.PubKey) -> a -> Either String (GenericPubKey, [ASN1])
+getGenericPubKey toPubKey a = runParseASN1State parsePubKeyGen asn1
+  where asn1 = toASN1 (toPubKey a) []
+
+parsePubKeyGen :: Monoid e => ParseASN1 e GenericPubKey
+parsePubKeyGen = onNextContainer Sequence $ do
+    void $ getNextContainer Sequence  -- ignore algorithm,
+    BitString bits <- getNext         -- we want the bit string only
+    return (GenericPubKey $ bitArrayGetData bits)
+
+newtype GenericPubKey = GenericPubKey B.ByteString
+    deriving (Show,Eq)
+
+instance ASN1Elem e => ProduceASN1Object e (Maybe GenericPubKey) where
+    asn1s Nothing = id
+    asn1s (Just (GenericPubKey bits)) = gMany [Other Context 1 bs]
+      where bs = putBitString (toBitArray bits 0)
+
+instance Monoid e => ParseASN1Object e (Maybe GenericPubKey) where
+    parse = Just . GenericPubKey <$> parseTaggedBitString <|> return Nothing
+      where
+        parseTaggedBitString = do
+            Other _ 1 bs <- getNext
+            case getBitString bs of
+                Right (BitString bits) -> return (bitArrayGetData bits)
+                r -> throwParseError ("GenericPubKey: invalid bit string: " ++ show r)
+
 -- | A key associated with format.  Allows to implement 'ASN1Object' instances.
 data FormattedKey a = FormattedKey PrivateKeyFormat a
     deriving (Show,Eq)
@@ -286,16 +360,35 @@
 instance Functor FormattedKey where
     fmap f (FormattedKey fmt a) = FormattedKey fmt (f a)
 
-instance (ProduceASN1Object e (Traditional a), ProduceASN1Object e (Modern a)) => ProduceASN1Object e (FormattedKey a) where
-    asn1s (FormattedKey TraditionalFormat k) = asn1s (Traditional k)
-    asn1s (FormattedKey PKCS8Format k)       = asn1s (Modern [] k)
+instance ASN1Elem e => ProduceASN1Object e (FormattedKey KeyPair) where
+    asn1s = formattedASN1S keyPairToPubKey
 
-instance (Monoid e, ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a)) => ParseASN1Object e (FormattedKey a) where
-    parse = (modern <$> parseModern) <|> (traditional <$> parseTraditional)
-      where
-        traditional = FormattedKey TraditionalFormat
-        modern      = FormattedKey PKCS8Format
+instance Monoid e => ParseASN1Object e (FormattedKey KeyPair) where
+    parse = parseFormatted keyPairToPubKey
 
+formattedASN1S :: (ProduceASN1Object e (Traditional a), ProduceASN1Object e (Modern a))
+               => (a -> X509.PubKey) -> FormattedKey a -> ASN1Stream e
+formattedASN1S _ (FormattedKey TraditionalFormat k) = asn1s (Traditional k)
+formattedASN1S toPubKey (FormattedKey PKCS8Format k) = modernASN1S toPubKey k
+
+parseFormattedKeyPair :: ParseASN1Object e (Modern a)
+                      => (a -> KeyPair) -> ParseASN1 e KeyPair
+parseFormattedKeyPair mapFn =
+    unFormat <$> parseFormattedInternal keyPairToPubKey mapFn
+
+parseFormatted :: (ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a))
+               => (a -> X509.PubKey) -> ParseASN1 e (FormattedKey a)
+parseFormatted toPubKey = parseFormattedInternal toPubKey id
+
+parseFormattedInternal :: (ParseASN1Object e (Traditional a), ParseASN1Object e (Modern b))
+                       => (a -> X509.PubKey) -> (b -> a) -> ParseASN1 e (FormattedKey a)
+parseFormattedInternal toPubKey mapFn =
+    (modern <$> parseModern toPubKey mapFn) <|>
+    (traditional <$> parseTraditional)
+  where
+    traditional = FormattedKey TraditionalFormat
+    modern      = FormattedKey PKCS8Format
+
 unFormat :: FormattedKey a -> a
 unFormat (FormattedKey _ a) = a
 
@@ -303,56 +396,46 @@
 -- Private Keys
 
 instance ASN1Object (FormattedKey X509.PrivKey) where
+    toASN1 = toASN1 . fmap keyPairFromPrivKey
+    fromASN1 = fmap (first (fmap keyPairToPrivKey)) <$> fromASN1
+
+instance ASN1Object (FormattedKey KeyPair) where
     toASN1   = asn1s
     fromASN1 = runParseASN1State parse
 
-instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKey) where
-    asn1s (Traditional privKey) = snd $ traditionalPrivKeyASN1S privKey
+instance ASN1Elem e => ProduceASN1Object e (Traditional KeyPair) where
+    asn1s (Traditional keyPair) = snd $ traditionalPrivKeyASN1S keyPair
 
-instance Monoid e => ParseASN1Object e (Traditional X509.PrivKey) where
+instance Monoid e => ParseASN1Object e (Traditional KeyPair) where
     parse = rsa <|> dsa <|> ecdsa
       where
-        rsa   = Traditional . X509.PrivKeyRSA . unTraditional <$> parse
-        dsa   = Traditional . X509.PrivKeyDSA . DSA.toPrivateKey . unTraditional <$> parse
-        ecdsa = Traditional . X509.PrivKeyEC . unTraditional <$> parse
+        rsa   = Traditional . keyPairFromPrivKey . X509.PrivKeyRSA . unTraditional <$> parse
+        dsa   = Traditional . keyPairFromPrivKey . X509.PrivKeyDSA . DSA.toPrivateKey . unTraditional <$> parse
+        ecdsa = Traditional . keyPairFromPrivKey . X509.PrivKeyEC . unTraditional <$> parse
 
-instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKey) where
-    asn1s (Modern attrs privKey) = modernPrivKeyASN1S attrs privKey
+instance ASN1Elem e => ProduceASN1Object e (Modern KeyPair) where
+    asn1s (Modern attrs mPub keyPair) = modernPrivKeyASN1S attrs mPub keyPair
 
-instance Monoid e => ParseASN1Object e (Modern X509.PrivKey) where
+instance Monoid e => ParseASN1Object e (Modern KeyPair) where
     parse = rsa <|> dsa <|> ecdsa <|> x25519 <|> x448 <|> ed25519 <|> ed448
       where
-        rsa   = fmap X509.PrivKeyRSA  <$> parse
-        dsa   = fmap (X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse
-        ecdsa = fmap X509.PrivKeyEC <$> parse
-        x25519  = fmap X509.PrivKeyX25519 <$> parse
-        x448    = fmap X509.PrivKeyX448 <$> parse
-        ed25519 = fmap X509.PrivKeyEd25519 <$> parse
-        ed448   = fmap X509.PrivKeyEd448 <$> parse
-
-skipVersion :: Monoid e => ParseASN1 e ()
-skipVersion = do
-    IntVal v <- getNext
-    when (v /= 0 && v /= 1) $
-        throwParseError ("PKCS8: parsed invalid version: " ++ show v)
-
-skipPublicKey :: Monoid e => ParseASN1 e ()
-skipPublicKey = void (fmap Just parseTaggedPrimitive <|> return Nothing)
-  where parseTaggedPrimitive = do { Other _ 1 bs <- getNext; return bs }
-
-parseAttrKeys :: Monoid e => ParseASN1 e ([Attribute], B.ByteString)
-parseAttrKeys = do
-    OctetString bs <- getNext
-    attrs <- parseAttributes (Container Context 0)
-    skipPublicKey
-    return (attrs, bs)
+        rsa   = fmap (keyPairFromPrivKey . X509.PrivKeyRSA) <$> parse
+        dsa   = fmap (keyPairFromPrivKey . X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse
+        ecdsa = fmap (keyPairFromPrivKey . X509.PrivKeyEC) <$> parse
+        x25519  = fmap (keyPairFromPrivKey . X509.PrivKeyX25519) <$> parse
+        x448    = fmap (keyPairFromPrivKey . X509.PrivKeyX448) <$> parse
+        ed25519 = fmap (keyPairFromPrivKey . X509.PrivKeyEd25519) <$> parse
+        ed448   = fmap (keyPairFromPrivKey . X509.PrivKeyEd448) <$> parse
 
 
 -- RSA
 
+toPubKeyRSA :: RSA.PrivateKey -> X509.PubKey
+toPubKeyRSA = X509.PubKeyRSA . RSA.toPublicKey . RSA.KeyPair
+
 instance ASN1Object (FormattedKey RSA.PrivateKey) where
-    toASN1   = asn1s
-    fromASN1 = runParseASN1State parse
+    toASN1   = formattedASN1S toPubKeyRSA
+    fromASN1 = runParseASN1State (parseFormatted toPubKeyRSA)
 
 instance ASN1Elem e => ProduceASN1Object e (Traditional RSA.PrivateKey) where
     asn1s (Traditional privKey) =
@@ -396,34 +479,37 @@
         return (Traditional privKey)
 
 instance ASN1Elem e => ProduceASN1Object e (Modern RSA.PrivateKey) where
-    asn1s (Modern attrs privKey) =
+    asn1s (Modern attrs mPub privKey) =
         asn1Container Sequence (v . alg . bs . att)
       where
-        v     = gIntVal 0
+        v     = versionASN1S mPub
         alg   = asn1Container Sequence (oid . gNull)
         oid   = gOID [1,2,840,113549,1,1,1]
         bs    = gOctetString (encodeASN1Object $ Traditional privKey)
-        att   = attributesASN1S (Container Context 0) attrs
+        att   = attrKeysASN1S attrs mPub
 
 instance Monoid e => ParseASN1Object e (Modern RSA.PrivateKey) where
     parse = onNextContainer Sequence $ do
-        skipVersion
+        v2 <- parseVersion
         Null <- onNextContainer Sequence $ do
                     OID [1,2,840,113549,1,1,1] <- getNext
                     getNext
-        (attrs, bs) <- parseAttrKeys
+        (attrs, bs, mPub) <- parseAttrKeys v2
         let inner = decodeASN1' BER bs
             strError = Left .  ("PKCS8: error decoding inner RSA: " ++) . show
         case either strError (runParseASN1 parseTraditional) inner of
              Left err -> throwParseError ("PKCS8: error parsing inner RSA: " ++ err)
-             Right privKey -> return (Modern attrs privKey)
+             Right privKey -> return (Modern attrs mPub privKey)
 
 
 -- DSA
 
+toPubKeyDSA :: DSA.KeyPair -> X509.PubKey
+toPubKeyDSA = X509.PubKeyDSA . DSA.toPublicKey
+
 instance ASN1Object (FormattedKey DSA.KeyPair) where
-    toASN1   = asn1s
-    fromASN1 = runParseASN1State parse
+    toASN1   = formattedASN1S toPubKeyDSA
+    fromASN1 = runParseASN1State (parseFormatted toPubKeyDSA)
 
 instance ASN1Elem e => ProduceASN1Object e (Traditional DSA.KeyPair) where
     asn1s (Traditional (DSA.KeyPair params pub priv)) =
@@ -442,27 +528,27 @@
         return (Traditional $ DSA.KeyPair params pub priv)
 
 instance ASN1Elem e => ProduceASN1Object e (Modern DSA.KeyPair) where
-    asn1s (Modern attrs (DSA.KeyPair params _ priv)) =
+    asn1s (Modern attrs mPub (DSA.KeyPair params _ priv)) =
         asn1Container Sequence (v . alg . bs . att)
       where
-        v     = gIntVal 0
+        v     = versionASN1S mPub
         alg   = asn1Container Sequence (oid . pr)
         oid   = gOID [1,2,840,10040,4,1]
         pr    = asn1Container Sequence (pqgASN1S params)
         bs    = gOctetString (encodeASN1S $ gIntVal priv)
-        att   = attributesASN1S (Container Context 0) attrs
+        att   = attrKeysASN1S attrs mPub
 
 instance Monoid e => ParseASN1Object e (Modern DSA.KeyPair) where
     parse = onNextContainer Sequence $ do
-        skipVersion
+        v2 <- parseVersion
         params <- onNextContainer Sequence $ do
                       OID [1,2,840,10040,4,1] <- getNext
                       onNextContainer Sequence parsePQG
-        (attrs, bs) <- parseAttrKeys
+        (attrs, bs, mPub) <- parseAttrKeys v2
         case decodeASN1' BER bs of
             Right [IntVal priv] ->
                 let pub = DSA.calculatePublic params priv
-                 in return (Modern attrs $ DSA.KeyPair params pub priv)
+                 in return (Modern attrs mPub $ DSA.KeyPair params pub priv)
             Right _ -> throwParseError "PKCS8: invalid format when parsing inner DSA"
             Left  e -> throwParseError ("PKCS8: error parsing inner DSA: " ++ show e)
 
@@ -482,18 +568,15 @@
                       , DSA.params_g = g
                       }
 
-dsaPrivToPair :: DSA.PrivateKey -> DSA.KeyPair
-dsaPrivToPair k = DSA.KeyPair params pub x
-  where pub     = DSA.calculatePublic params x
-        params  = DSA.private_params k
-        x       = DSA.private_x k
 
-
 -- ECDSA
 
+toPubKeyEC :: X509.PrivKeyEC -> X509.PubKey
+toPubKeyEC = keyPairToPubKey . keyPairFromPrivKey . X509.PrivKeyEC
+
 instance ASN1Object (FormattedKey X509.PrivKeyEC) where
-    toASN1   = asn1s
-    fromASN1 = runParseASN1State parse
+    toASN1   = formattedASN1S toPubKeyEC
+    fromASN1 = runParseASN1State (parseFormatted toPubKeyEC)
 
 instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKeyEC) where
     asn1s = innerEcdsaASN1S True . unTraditional
@@ -502,27 +585,27 @@
     parse = Traditional <$> parseInnerEcdsa Nothing
 
 instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKeyEC) where
-    asn1s (Modern attrs privKey) = asn1Container Sequence (v . f . bs . att)
+    asn1s (Modern attrs mPub privKey) = asn1Container Sequence (v . f . bs . att)
       where
-        v     = gIntVal 0
+        v     = versionASN1S mPub
         f     = asn1Container Sequence (oid . curveFnASN1S privKey)
         oid   = gOID [1,2,840,10045,2,1]
         bs    = gOctetString (encodeASN1S inner)
         inner = innerEcdsaASN1S False privKey
-        att   = attributesASN1S (Container Context 0) attrs
+        att   = attrKeysASN1S attrs mPub
 
 instance Monoid e => ParseASN1Object e (Modern X509.PrivKeyEC) where
     parse = onNextContainer Sequence $ do
-        skipVersion
+        v2 <- parseVersion
         f <- onNextContainer Sequence $ do
             OID [1,2,840,10045,2,1] <- getNext
             parseCurveFn
-        (attrs, bs) <- parseAttrKeys
+        (attrs, bs, mPub) <- parseAttrKeys v2
         let inner = decodeASN1' BER bs
             strError = Left .  ("PKCS8: error decoding inner EC: " ++) . show
         case either strError (runParseASN1 $ parseInnerEcdsa $ Just f) inner of
             Left err -> throwParseError ("PKCS8: error parsing inner EC: " ++ err)
-            Right privKey -> return (Modern attrs privKey)
+            Right privKey -> return (Modern attrs mPub privKey)
 
 innerEcdsaASN1S :: ASN1Elem e => Bool -> X509.PrivKeyEC -> ASN1Stream e
 innerEcdsaASN1S addC k
@@ -631,69 +714,55 @@
 -- X25519, X448, Ed25519, Ed448
 
 instance ASN1Elem e => ProduceASN1Object e (Modern X25519.SecretKey) where
-    asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)
-      where
-        v     = gIntVal 0
-        alg   = asn1Container Sequence (gOID [1,3,101,110])
-        bs    = innerEddsaASN1S privKey
-        att   = attributesASN1S (Container Context 0) attrs
+    asn1s = produceModernEddsa [1,3,101,110]
 
 instance Monoid e => ParseASN1Object e (Modern X25519.SecretKey) where
-    parse = onNextContainer Sequence $ do
-        skipVersion
-        onNextContainer Sequence $ do { OID [1,3,101,110] <- getNext; return () }
-        (attrs, bs) <- parseAttrKeys
-        Modern attrs <$> parseInnerEddsa "X25519" X25519.secretKey bs
+    parse = parseModernEddsa "X25519" [1,3,101,110] X25519.secretKey
 
 instance ASN1Elem e => ProduceASN1Object e (Modern X448.SecretKey) where
-    asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)
-      where
-        v     = gIntVal 0
-        alg   = asn1Container Sequence (gOID [1,3,101,111])
-        bs    = innerEddsaASN1S privKey
-        att   = attributesASN1S (Container Context 0) attrs
+    asn1s = produceModernEddsa [1,3,101,111]
 
 instance Monoid e => ParseASN1Object e (Modern X448.SecretKey) where
-    parse = onNextContainer Sequence $ do
-        skipVersion
-        onNextContainer Sequence $ do { OID [1,3,101,111] <- getNext; return () }
-        (attrs, bs) <- parseAttrKeys
-        Modern attrs <$> parseInnerEddsa "X448" X448.secretKey bs
+    parse = parseModernEddsa "X448" [1,3,101,111] X448.secretKey
 
 instance ASN1Elem e => ProduceASN1Object e (Modern Ed25519.SecretKey) where
-    asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)
-      where
-        v     = gIntVal 0
-        alg   = asn1Container Sequence (gOID [1,3,101,112])
-        bs    = innerEddsaASN1S privKey
-        att   = attributesASN1S (Container Context 0) attrs
+    asn1s = produceModernEddsa [1,3,101,112]
 
 instance Monoid e => ParseASN1Object e (Modern Ed25519.SecretKey) where
-    parse = onNextContainer Sequence $ do
-        skipVersion
-        onNextContainer Sequence $ do { OID [1,3,101,112] <- getNext; return () }
-        (attrs, bs) <- parseAttrKeys
-        Modern attrs <$> parseInnerEddsa "Ed25519" Ed25519.secretKey bs
+    parse = parseModernEddsa "Ed25519" [1,3,101,112] Ed25519.secretKey
 
 instance ASN1Elem e => ProduceASN1Object e (Modern Ed448.SecretKey) where
-    asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)
-      where
-        v     = gIntVal 0
-        alg   = asn1Container Sequence (gOID [1,3,101,113])
-        bs    = innerEddsaASN1S privKey
-        att   = attributesASN1S (Container Context 0) attrs
+    asn1s = produceModernEddsa [1,3,101,113]
 
 instance Monoid e => ParseASN1Object e (Modern Ed448.SecretKey) where
-    parse = onNextContainer Sequence $ do
-        skipVersion
-        onNextContainer Sequence $ do { OID [1,3,101,113] <- getNext; return () }
-        (attrs, bs) <- parseAttrKeys
-        Modern attrs <$> parseInnerEddsa "Ed448" Ed448.secretKey bs
+    parse = parseModernEddsa "Ed448" [1,3,101,113] Ed448.secretKey
 
+-- * Producer helpers
+
+produceModernEddsa :: (ASN1Elem e, ByteArrayAccess key) => OID -> Modern key -> ASN1Stream e
+produceModernEddsa oid (Modern attrs mPub privKey) = asn1Container Sequence (v . alg . bs . att)
+  where
+    v     = versionASN1S mPub
+    alg   = asn1Container Sequence (gOID oid)
+    bs    = innerEddsaASN1S privKey
+    att   = attrKeysASN1S attrs mPub
+
 innerEddsaASN1S :: (ASN1Elem e, ByteArrayAccess key) => key -> ASN1Stream e
 innerEddsaASN1S key = gOctetString (encodeASN1S inner)
   where inner = gOctetString (convert key)
 
+-- * Parser helpers
+
+parseModernEddsa :: Monoid e => String -> OID -> (B.ByteString -> CryptoFailable a) -> ParseASN1 e (Modern a)
+parseModernEddsa name expectedOid buildKey = onNextContainer Sequence $ do
+  v2 <- parseVersion
+  onNextContainer Sequence $ do
+    OID oid <- getNext
+    when (oid /= expectedOid) $
+      throwParseError ("PKCS8: while parsing " ++ name ++ " expected OID " ++ show expectedOid ++ " while got " ++ show oid)
+  (attrs, bs, mPub) <- parseAttrKeys v2
+  Modern attrs mPub <$> parseInnerEddsa name buildKey bs
+
 parseInnerEddsa :: Monoid e
                 => String
                 -> (B.ByteString -> CryptoFailable key)
@@ -712,3 +781,28 @@
             CryptoPassed privKey -> return privKey
             CryptoFailed _       ->
                 throwParseError ("PKCS8: parsed invalid " ++ name ++ " secret key")
+
+versionASN1S :: ASN1Elem e => Maybe GenericPubKey -> ASN1Stream e
+versionASN1S mPub = gIntVal (if isJust mPub then 1 else 0)
+
+parseVersion :: Monoid e => ParseASN1 e Bool
+parseVersion = do
+    IntVal v <- getNext
+    when (v /= 0 && v /= 1) $
+        throwParseError ("PKCS8: parsed invalid version: " ++ show v)
+    return (v /= 0)
+
+attrKeysASN1S :: ASN1Elem e => [Attribute] -> Maybe GenericPubKey -> ASN1Stream e
+attrKeysASN1S attrs mPub = att . asn1s mPub
+  where att = attributesASN1S (Container Context 0) attrs
+
+parseAttrKeys :: Monoid e
+              => Bool
+              -> ParseASN1 e ([Attribute], B.ByteString, Maybe GenericPubKey)
+parseAttrKeys v2 = do
+    OctetString bs <- getNext
+    attrs <- parseAttributes (Container Context 0)
+    mPub <- parse
+    when (isJust mPub && not v2) $
+        throwParseError "PKCS8: public key allowed only for version 2"
+    return (attrs, bs, mPub)
diff --git a/src/Crypto/Store/PubKey/RSA/KEM.hs b/src/Crypto/Store/PubKey/RSA/KEM.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Store/PubKey/RSA/KEM.hs
@@ -0,0 +1,93 @@
+-- |
+-- Module      : Crypto.Store.PubKey.RSA.KEM
+-- License     : BSD-style
+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
+-- Stability   : experimental
+-- Portability : unknown
+--
+-- RSA as a Key-Encapsulation Mechanism (KEM).
+module Crypto.Store.PubKey.RSA.KEM
+    ( KDF(..), kdf3
+    -- * Operations
+    , encapsulate, encapsulateWith, decapsulate
+    ) where
+
+import           Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)
+import qualified Data.ByteArray as B
+import           Data.ByteString (ByteString, empty)
+
+import           Crypto.Hash
+import           Crypto.Number.Generate
+import           Crypto.Number.Serialize (os2ip, i2ospOf_)
+import qualified Crypto.PubKey.RSA.Prim as RSA
+import qualified Crypto.PubKey.RSA.Types as RSA
+import           Crypto.Random
+
+-- | Key derivation used by RSA-KEM.
+newtype KDF bIn bOut = KDF (ByteString -> bIn -> bOut)
+
+-- | KDF3 from ANSI X9.44-2007 (R2017)
+kdf3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)
+     => a -> Int -> KDF bIn bOut
+kdf3 hashAlg outLen = KDF (doKDF3 hashAlg outLen)
+
+doKDF3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)
+       => a -> Int -> ByteString -> bIn -> bOut
+doKDF3 hashAlg outLen otherInfo input
+    | r == 0    = B.concat $ map doChunk [ 1 .. k ]
+    | otherwise = B.take outLen $ B.concat $ map doChunk [ 1 .. k + 1 ]
+  where
+    (k, r) = outLen `divMod` blk
+    blk    = hashDigestSize hashAlg
+    doChunk i =
+        let ctx0 = hashInitWith hashAlg
+            ctx1 = hashUpdate ctx0 (i2ospOf_ 4 (toInteger i) :: Bytes)
+            ctx2 = hashUpdate ctx1 input
+            ctx3 = hashUpdate ctx2 otherInfo
+         in hashFinalize ctx3
+
+-- | Generate a shared secret key and an associated ciphertext using randomness.
+encapsulate :: (MonadRandom m, ByteArray ciphertext)
+            => KDF ciphertext sharedSecret
+            -> RSA.PublicKey
+            -> m (sharedSecret, ciphertext)
+encapsulate kdf pub = encap kdf pub <$> generateMax (RSA.public_n pub)
+
+-- | Generate a shared secret key and an associated ciphertext using a
+-- specified random input.  This input must be an integer in range [0, n) and
+-- not repeated with other encapsulations.  For testing purposes.
+encapsulateWith :: ByteArray ciphertext
+                => KDF ciphertext sharedSecret
+                -> RSA.PublicKey
+                -> Integer
+                -> Maybe (sharedSecret, ciphertext)
+encapsulateWith kdf pub z
+    | z < 0 || z >= RSA.public_n pub = Nothing
+    | otherwise = Just $ encap kdf pub z
+
+encap :: ByteArray ciphertext
+      => KDF ciphertext sharedSecret
+      -> RSA.PublicKey
+      -> Integer
+      -> (sharedSecret, ciphertext)
+encap (KDF kdf) pub z = (ss, ct)
+  where
+    zz  = i2ospOf_ (RSA.public_size pub) z
+    ct  = RSA.ep pub zz
+    ss  = kdf empty zz
+
+-- | Return the shared secret for a given ciphertext.
+decapsulate :: ByteArray ciphertext
+            => KDF ciphertext sharedSecret
+            -> RSA.PrivateKey
+            -> ciphertext
+            -> Maybe sharedSecret
+decapsulate (KDF kdf) priv ct
+    | B.length ct < RSA.public_size pub = Nothing
+    | c >= RSA.public_n pub = Nothing
+    | otherwise = Just ss
+  where
+    pub = RSA.private_pub priv
+    c   = os2ip ct
+    zz  = RSA.dp Nothing priv ct
+    ss  = kdf empty zz
diff --git a/src/Crypto/Store/Util.hs b/src/Crypto/Store/Util.hs
--- a/src/Crypto/Store/Util.hs
+++ b/src/Crypto/Store/Util.hs
@@ -20,7 +20,9 @@
 import           Data.Bits
 import           Data.ByteArray (ByteArray, ByteArrayAccess)
 import qualified Data.ByteArray as B
-import           Data.List
+#if !(MIN_VERSION_base(4,20,0))
+import           Data.List (foldl')
+#endif
 import           Data.Memory.Endian
 import           Data.Word
 
diff --git a/src/Crypto/Store/X509.hs b/src/Crypto/Store/X509.hs
--- a/src/Crypto/Store/X509.hs
+++ b/src/Crypto/Store/X509.hs
@@ -20,6 +20,7 @@
     , readPubKeyFile
     , readPubKeyFileFromMemory
     , pemToPubKey
+    , pemToPubKeyAccum
     , writePubKeyFile
     , writePubKeyFileToMemory
     , pubKeyToPEM
@@ -36,7 +37,7 @@
 import Data.ASN1.Types
 import Data.ASN1.BinaryEncoding
 import Data.ASN1.Encoding
-import Data.Maybe
+import Data.Either (rights)
 import Data.Proxy
 import qualified Data.X509 as X509
 import qualified Data.ByteString as B
@@ -46,7 +47,9 @@
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.CMS.Util
+import Crypto.Store.Error
 import Crypto.Store.PEM
+import Crypto.Store.Util
 
 
 -- | Class of signed objects convertible to PEM.
@@ -78,24 +81,32 @@
 readPubKeyFileFromMemory = either (const []) accumulate . pemParseBS
 
 accumulate :: [PEM] -> [X509.PubKey]
-accumulate = catMaybes . foldr (flip pemToPubKey) []
+accumulate = rights . map pemToPubKey
 
--- | Read a public key from a 'PEM' element and add it to the accumulator list.
-pemToPubKey :: [Maybe X509.PubKey] -> PEM -> [Maybe X509.PubKey]
-pemToPubKey acc pem =
-    case decodeASN1' BER (pemContent pem) of
-        Left _     -> acc
-        Right asn1 -> run (getParser $ pemName pem) asn1 : acc
+-- | Read a public key from a t'PEM' element and add it to the accumulator list.
+--
+-- This API is modelled after function @pemToKey@ in "Data.X509.Memory".
+pemToPubKeyAccum :: [Maybe X509.PubKey] -> PEM -> [Maybe X509.PubKey]
+pemToPubKeyAccum acc pem =
+    case pemToPubKey pem of
+        Left (DecodingError _) -> acc
+        Left _                 -> Nothing : acc
+        Right pubKey           -> Just pubKey : acc
 
-  where
-    run p asn1 =
-        case p asn1 of
-            Right (pubKey, []) -> Just pubKey
-            _                  -> Nothing
+-- | Read a public key from a t'PEM' element.
+pemToPubKey :: PEM -> Either StoreError X509.PubKey
+pemToPubKey pem = do
+    asn1 <- mapLeft DecodingError $ decodeASN1' BER (pemContent pem)
+    parser <- getParser (pemName pem)
+    (pubKey, unparsed) <- mapLeft ParseFailure $ parser asn1
+    case unparsed of
+        [] -> return pubKey
+        er -> Left $ ParseFailure ("pemToPubKey: remaining state " ++ show er)
 
-    getParser "PUBLIC KEY"           = fromASN1
-    getParser "RSA PUBLIC KEY"       = runParseASN1State rsapkParser
-    getParser _                      = const (Left undefined)
+  where
+    getParser "PUBLIC KEY"           = return fromASN1
+    getParser "RSA PUBLIC KEY"       = return (runParseASN1State rsapkParser)
+    getParser _                      = Left UnexpectedNameForPEM
 
     rsapkParser = (\(RSAPublicKey pub) -> X509.PubKeyRSA pub) <$> parse
 
@@ -149,9 +160,6 @@
 signedToPEM :: forall a. SignedObject a => X509.SignedExact a -> PEM
 signedToPEM obj = mkPEM (signedObjectName prx) (X509.encodeSignedObject obj)
   where prx = Proxy :: Proxy a
-
-mkPEM :: String -> B.ByteString -> PEM
-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
 
 
 -- RSA public keys
diff --git a/tests/CMS/Instances.hs b/tests/CMS/Instances.hs
--- a/tests/CMS/Instances.hs
+++ b/tests/CMS/Instances.hs
@@ -4,7 +4,6 @@
 module CMS.Instances
     ( arbitraryPassword
     , arbitraryAttributes
-    , arbitraryIntegrityDigest
     , arbitrarySigVer
     , arbitraryEnvDev
     ) where
@@ -12,6 +11,7 @@
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
+import           Data.Maybe (fromJust)
 import           Data.X509
 
 import GHC.TypeLits
@@ -22,6 +22,7 @@
 
 import Crypto.Store.CMS
 import Crypto.Store.Error
+import Crypto.Store.PKCS8
 
 import X509.Instances
 
@@ -126,7 +127,7 @@
 
 arbitraryNat :: Gen SomeNat
 arbitraryNat = unwrap <$> arbitrary
-  where unwrap (Positive i) = let Just n = someNatVal (127 + i) in n
+  where unwrap (Positive i) = let n = someNatVal (127 + i) in fromJust n
 
 instance Arbitrary DigestAlgorithm where
     arbitrary = oneof
@@ -138,25 +139,33 @@
         , pure $ DigestAlgorithm SHA256
         , pure $ DigestAlgorithm SHA384
         , pure $ DigestAlgorithm SHA512
+        , pure $ DigestAlgorithm SHA3_224
+        , pure $ DigestAlgorithm SHA3_256
+        , pure $ DigestAlgorithm SHA3_384
+        , pure $ DigestAlgorithm SHA3_512
         , pure $ DigestAlgorithm SHAKE128_256
         , pure $ DigestAlgorithm SHAKE256_512
         , (\(SomeNat p) -> DigestAlgorithm (SHAKE128 p)) <$> arbitraryNat
         , (\(SomeNat p) -> DigestAlgorithm (SHAKE256 p)) <$> arbitraryNat
         ]
 
-arbitraryIntegrityDigest :: Gen DigestAlgorithm
-arbitraryIntegrityDigest = elements
+arbitraryDigest :: Gen DigestAlgorithm
+arbitraryDigest = elements
     [ DigestAlgorithm MD5
     , DigestAlgorithm SHA1
     , DigestAlgorithm SHA224
     , DigestAlgorithm SHA256
     , DigestAlgorithm SHA384
     , DigestAlgorithm SHA512
+    , DigestAlgorithm SHA3_224
+    , DigestAlgorithm SHA3_256
+    , DigestAlgorithm SHA3_384
+    , DigestAlgorithm SHA3_512
     ]
 
 instance Arbitrary MACAlgorithm where
     arbitrary = oneof
-        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest
+        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryDigest
         , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx
         , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx
         ]
@@ -203,33 +212,30 @@
         , pure $ ECDSA (DigestAlgorithm SHA256)
         , pure $ ECDSA (DigestAlgorithm SHA384)
         , pure $ ECDSA (DigestAlgorithm SHA512)
+        , pure $ ECDSA (DigestAlgorithm SHA3_224)
+        , pure $ ECDSA (DigestAlgorithm SHA3_256)
+        , pure $ ECDSA (DigestAlgorithm SHA3_384)
+        , pure $ ECDSA (DigestAlgorithm SHA3_512)
 
         , pure Ed25519
         , pure Ed448
         ]
 
-arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)
-arbitraryKeyPair RSAAnyHash = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (RSA _) = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (RSAPSS _) = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (DSA _) = do
-    (pub, priv) <- arbitraryDSA
-    return (PubKeyDSA pub, PrivKeyDSA priv)
-arbitraryKeyPair (ECDSA _) = do
-    (pub, priv) <- arbitraryNamedEC
-    return (PubKeyEC pub, PrivKeyEC priv)
-arbitraryKeyPair Ed25519 = do
-    (pub, priv) <- arbitraryEd25519
-    return (PubKeyEd25519 pub, PrivKeyEd25519 priv)
-arbitraryKeyPair Ed448 = do
-    (pub, priv) <- arbitraryEd448
-    return (PubKeyEd448 pub, PrivKeyEd448 priv)
+arbitraryKeyPair :: SignatureAlg -> Gen KeyPair
+arbitraryKeyPair RSAAnyHash =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (RSA _) =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (RSAPSS _) =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (DSA _) =
+    keyPairFromPrivKey . PrivKeyDSA <$> arbitraryPrivDSA
+arbitraryKeyPair (ECDSA _) =
+    keyPairFromPrivKey . PrivKeyEC . snd <$> arbitraryNamedEC
+arbitraryKeyPair Ed25519 =
+    keyPairFromPrivKey . PrivKeyEd25519 . snd <$> arbitraryEd25519
+arbitraryKeyPair Ed448 =
+    keyPairFromPrivKey . PrivKeyEd448 . snd <$> arbitraryEd448
 
 arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen)
 arbitrarySigVer alg = sized $ \n -> do
@@ -239,11 +245,12 @@
     return (sigFns, verFn)
   where
     onePair = do
-        (pub, priv) <- arbitraryKeyPair alg
+        pair <- arbitraryKeyPair alg
+        let pub = keyPairToPubKey pair
         chain <- arbitraryCertificateChain pub
         sAttrs <- oneof [ pure Nothing, Just <$> arbitraryAttributes ]
         uAttrs <- arbitraryAttributes
-        return (certSigner alg priv chain sAttrs uAttrs, withPublicKey pub)
+        return (certSigner alg pair chain sAttrs uAttrs, withPublicKey pub)
 
 instance Arbitrary PBKDF2_PRF where
     arbitrary = elements
@@ -276,13 +283,19 @@
         , CFB Camellia128
 
         , CTR Camellia128
+
+        , DeriveHKDF SHA256
         ]
 
 instance Arbitrary ContentEncryptionParams where
     arbitrary = arbitrary >>= gen
       where
+        gen (ECB c) = return (ecbParams c)
+        gen (CBC c) = generateCBCParams c
         gen CBC_RC2 = choose (24, 512) >>= generateRC2EncryptionParams
-        gen alg     = generateEncryptionParams alg
+        gen (CFB c) = generateCFBParams c
+        gen (CTR c) = generateCTRParams c
+        gen (DeriveHKDF _) = deriveEncryptionKey <$> arbitrary
 
 instance Arbitrary AuthContentEncryptionAlg where
     arbitrary = elements
@@ -297,22 +310,23 @@
         , GCM AES128
         , GCM AES192
         , GCM AES256
+
+        , AuthDeriveHKDF SHA256
         ]
 
 instance Arbitrary AuthContentEncryptionParams where
     arbitrary = do
         alg <- arbitrary
         case alg of
-            AUTH_ENC_128 -> arb3 generateAuthEnc128Params
-            AUTH_ENC_256 -> arb3 generateAuthEnc256Params
+            AUTH_ENC_128 -> arb3 authEnc128Params
+            AUTH_ENC_256 -> arb3 authEnc256Params
             CHACHA20_POLY1305 -> generateChaChaPoly1305Params
             CCM c -> do m <- arbitraryM
                         l <- arbitraryL
                         generateCCMParams c m l
             GCM c -> choose (12,16) >>= generateGCMParams c
-      where arb3 fn = do
-                a <- arbitrary; b <- arbitrary; c <- arbitrary
-                fn a b c
+            AuthDeriveHKDF _ -> authDeriveEncryptionKey <$> arbitrary
+      where arb3 fn = fn <$> arbitrary <*> arbitrary <*> arbitrary
 
 arbitraryM :: Gen CCM_M
 arbitraryM = elements
@@ -350,6 +364,18 @@
                           , scryptKeyLength = Nothing
                           }
 
+instance Arbitrary KeyDerivationFn where
+    arbitrary = elements (map HKDF digests ++ map KDF3 digests)
+      where
+        digests = [ DigestAlgorithm SHA256
+                  , DigestAlgorithm SHA384
+                  , DigestAlgorithm SHA512
+                  , DigestAlgorithm SHA3_224
+                  , DigestAlgorithm SHA3_256
+                  , DigestAlgorithm SHA3_384
+                  , DigestAlgorithm SHA3_512
+                  ]
+
 instance Arbitrary KeyTransportParams where
     arbitrary = oneof
         [ pure RSAES
@@ -411,25 +437,26 @@
     return (envFns, devFn)
   where
     len     = B.length cek
-    onePair = oneof [ arbitraryKT, arbitraryKA, arbitraryKEK, arbitraryPW ]
+    onePair = oneof [ arbitraryKT, arbitraryKA, arbitraryKEK, arbitraryPW, arbitraryKEM ]
 
     arbitraryKT = do
         (pub, priv) <- arbitraryLargeRSA
+        let pair = keyPairFromPrivKey (PrivKeyRSA priv)
         cert <- arbitrarySignedCertificate (PubKeyRSA pub)
         ktp  <- arbitrary
         let envFn = forKeyTransRecipient cert ktp
-            devFn = withRecipientKeyTrans (PrivKeyRSA priv)
+            devFn = withRecipientKeyTrans pair
         return (envFn, devFn)
 
     arbitraryKA = do
-        (cert, priv) <- arbitraryDHParams
+        (cert, pair) <- arbitraryDHParams
         let allowCofactorDH =
-                case priv of
+                case keyPairToPrivKey pair of
                     PrivKeyEC _ -> True
                     _           -> False
         kap <- arbitraryAlg >>= arbitraryAgreeParams allowCofactorDH
         let envFn = forKeyAgreeRecipient cert kap
-            devFn = withRecipientKeyAgree priv cert
+            devFn = withRecipientKeyAgree pair cert
         return (envFn, devFn)
 
     arbitraryKEK = do
@@ -441,10 +468,18 @@
     arbitraryPW  = do
         pwd <- arbitraryPassword
         kdf <- arbitrary
-        cea <- arbitrary `suchThat` notModeCTR
+        cea <- arbitrary `suchThat` compatiblePW
         let es = PWRIKEK cea
         return (forPasswordRecipient pwd kdf es, withRecipientPassword pwd)
 
+    arbitraryKEM  = do
+        (cert, pair, params) <- arbitraryKEMParams
+        kdf <- arbitrary
+        kep <- arbitraryAlg
+        let envFn = forKeyEncapRecipient cert kdf kep params
+            devFn = withRecipientKeyEncap pair cert
+        return (envFn, devFn)
+
     arbitraryAlg
         | len == 24      = oneof [ return AES128_WRAP
                                  , return AES192_WRAP
@@ -474,27 +509,44 @@
                               , arbitraryCredX448
                               ]
 
+    arbitraryKEMParams = arbitraryCredRSA >>= rsaKemToKEM
+
+    rsaKemToKEM (cert, pair) = do
+        kdf <- arbitrary
+        keyLen <- choose (8,32)
+        return (cert, pair, KeyEncapsulationRSA kdf keyLen)
+
     arbitraryCredNamedEC = do
         (pub, priv) <- arbitraryNamedEC
+        let pair = keyPairFromPrivKey (PrivKeyEC priv)
         cert <- arbitrarySignedCertificate (PubKeyEC pub)
-        return (cert, PrivKeyEC priv)
+        return (cert, pair)
 
     arbitraryCredX25519 = do
         (pub, priv) <- arbitraryX25519
+        let pair = keyPairFromPrivKey (PrivKeyX25519 priv)
         cert <- arbitrarySignedCertificate (PubKeyX25519 pub)
-        return (cert, PrivKeyX25519 priv)
+        return (cert, pair)
 
     arbitraryCredX448 = do
         (pub, priv) <- arbitraryX448
+        let pair = keyPairFromPrivKey (PrivKeyX448 priv)
         cert <- arbitrarySignedCertificate (PubKeyX448 pub)
-        return (cert, PrivKeyX448 priv)
+        return (cert, pair)
 
-    -- key wrapping in PWRIKEK is incompatible with CTR mode so we must never
-    -- generate this combination
-    notModeCTR params =
+    arbitraryCredRSA = do
+        (pub, priv) <- arbitraryRSA
+        let pair = keyPairFromPrivKey (PrivKeyRSA priv)
+        cert <- arbitrarySignedCertificate (PubKeyRSA pub)
+        return (cert, pair)
+
+    -- key wrapping in PWRIKEK is incompatible with CTR mode or HKDF key
+    -- derivation, so we must never generate these combinations
+    compatiblePW params =
         case getContentEncryptionAlg params of
-            CTR _ -> False
-            _     -> True
+            CTR _        -> False
+            DeriveHKDF _ -> False
+            _            -> True
 
 instance Arbitrary OriginatorInfo where
     arbitrary = OriginatorInfo <$> arbitrary <*> arbitrary
diff --git a/tests/CMS/Tests.hs b/tests/CMS/Tests.hs
--- a/tests/CMS/Tests.hs
+++ b/tests/CMS/Tests.hs
@@ -1,4 +1,5 @@
 -- | CMS tests.
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 module CMS.Tests (cmsTests) where
 
 import Control.Monad
@@ -180,12 +181,16 @@
                 ci' = toAttachedCI dd'
             ci @?= ci'
   where path  = testFile "cms-digested-data.pem"
-        algs  = [ ("MD5",    DigestAlgorithm MD5)
-                , ("SHA1",   DigestAlgorithm SHA1)
-                , ("SHA224", DigestAlgorithm SHA224)
-                , ("SHA256", DigestAlgorithm SHA256)
-                , ("SHA384", DigestAlgorithm SHA384)
-                , ("SHA512", DigestAlgorithm SHA512)
+        algs  = [ ("MD5",      DigestAlgorithm MD5)
+                , ("SHA1",     DigestAlgorithm SHA1)
+                , ("SHA224",   DigestAlgorithm SHA224)
+                , ("SHA256",   DigestAlgorithm SHA256)
+                , ("SHA384",   DigestAlgorithm SHA384)
+                , ("SHA512",   DigestAlgorithm SHA512)
+                , ("SHA3_224", DigestAlgorithm SHA3_224)
+                , ("SHA3_256", DigestAlgorithm SHA3_256)
+                , ("SHA3_384", DigestAlgorithm SHA3_384)
+                , ("SHA3_512", DigestAlgorithm SHA3_512)
                 ]
 
 encryptedDataTests :: TestTree
@@ -295,6 +300,25 @@
                 , "SHA512"
                 ]
 
+rfc9690Tests :: TestTree
+rfc9690Tests = testCase "rfc9690" $ do
+    keys <- readKeyFile path
+    length keys @?= 1
+    certs <- readSignedObject path
+    length certs @?= 1
+    cms <- readCMSFile path
+    length cms @?= 1
+
+    let Right pair = recover (fromString "not-used") key
+        [EnvelopedDataCI envelopedEncapData] = cms
+        [cert] = certs
+        [key] = keys
+
+    envelopedData <- fromAttached envelopedEncapData
+    result <- openEnvelopedData (withRecipientKeyEncap pair cert) envelopedData
+    result @?= Right (DataCI $ fromString "Hello, world!")
+  where path = testFile "rfc9690.pem"
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling" $ \l ->
@@ -366,5 +390,6 @@
         , digestedDataTests
         , encryptedDataTests
         , authEnvelopedDataTests
+        , rfc9690Tests
         , propertyTests
         ]
diff --git a/tests/KeyWrap/RC2.hs b/tests/KeyWrap/RC2.hs
--- a/tests/KeyWrap/RC2.hs
+++ b/tests/KeyWrap/RC2.hs
@@ -6,6 +6,7 @@
 
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Error
@@ -68,12 +69,11 @@
     initCipher ekl k = throwCryptoError (rc2WithEffectiveKeyLength ekl k)
 
     wrapUnwrapProperty :: TestKey RC2 -> TestIV RC2 -> Message -> Gen Property
-    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) = do
+    wrapUnwrapProperty (Key key) iv (Message msg) = do
         ekl <- choose (1, 1024)
         let ctx = initCipher ekl key
-        wrapped <- wrap ctx iv msg
+        wrapped <- wrap ctx (unIV iv) msg
         return $ (wrapped >>= unwrap ctx) === Right msg
-      where Just iv = makeIV ivBs
 
     makeTest :: Integer -> Vector -> TestTree
     makeTest i Vector{..} =
@@ -82,7 +82,7 @@
             , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)
             ]
       where ctx = initCipher vecEKL vecKey
-            Just iv = makeIV vecIV
+            iv = fromJust $ makeIV vecIV
             doWrap = wrap' Left withRandomPad
             withRandomPad f len
                 | B.length vecPad /= len = Left (InvalidInput "unexpected length")
diff --git a/tests/KeyWrap/TripleDES.hs b/tests/KeyWrap/TripleDES.hs
--- a/tests/KeyWrap/TripleDES.hs
+++ b/tests/KeyWrap/TripleDES.hs
@@ -5,6 +5,7 @@
 module KeyWrap.TripleDES (tripledeskwTests) where
 
 import Data.ByteString (ByteString, pack)
+import Data.Maybe (fromJust)
 
 import Crypto.Cipher.TripleDES
 import Crypto.Cipher.Types
@@ -62,10 +63,9 @@
     initCipher k = throwCryptoError (cipherInit k)
 
     wrapUnwrapProperty :: TestKey cipher -> TestIV cipher -> Message -> Property
-    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) =
-        (wrap ctx iv msg >>= unwrap ctx) === Right msg
+    wrapUnwrapProperty (Key key) iv (Message msg) =
+        (wrap ctx (unIV iv) msg >>= unwrap ctx) === Right msg
       where ctx = initCipher key
-            Just iv = makeIV ivBs
 
     makeTest :: Integer -> Vector -> TestTree
     makeTest i Vector{..} =
@@ -74,7 +74,7 @@
             , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)
             ]
       where ctx = initCipher vecKey
-            Just iv = makeIV vecIV
+            iv = fromJust $ makeIV vecIV
 
 tripledeskwTests :: TestTree
 tripledeskwTests = testGroup "KeyWrap.TripleDES"
diff --git a/tests/PKCS12/Instances.hs b/tests/PKCS12/Instances.hs
--- a/tests/PKCS12/Instances.hs
+++ b/tests/PKCS12/Instances.hs
@@ -1,18 +1,23 @@
+{-# LANGUAGE CPP #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module PKCS12.Instances
     ( arbitraryPassword
     , arbitraryAlias
-    , arbitraryIntegrityParams
+    , arbitraryTraditionalIntegrity
+    , arbitraryAuthSchemeIntegrity
     , arbitraryPKCS12
     ) where
 
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
+#if !(MIN_VERSION_base(4,11,0))
 import           Data.Semigroup
+#endif
 
 import Test.Tasty.QuickCheck
 
+import Crypto.Store.CMS
 import Crypto.Store.PKCS12
 import Crypto.Store.PKCS5
 
@@ -26,8 +31,30 @@
 arbitraryAlias = resize 16 asciiChar
   where asciiChar = listOf $ choose ('\x20','\x7f')
 
-arbitraryIntegrityParams :: Gen IntegrityParams
-arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
+instance Arbitrary PBMAC1Parameter where
+    arbitrary = PBMAC1Parameter <$> arbitrary <*> arbitrary
+
+arbitraryAuthScheme :: Gen AuthenticationScheme
+arbitraryAuthScheme = PBMAC1 <$> arbitrary
+
+arbitraryIntegrityDigest :: Gen DigestAlgorithm
+arbitraryIntegrityDigest = elements
+    [ DigestAlgorithm MD2
+    , DigestAlgorithm MD4
+    , DigestAlgorithm MD5
+    , DigestAlgorithm SHA1
+    , DigestAlgorithm SHA224
+    , DigestAlgorithm SHA256
+    , DigestAlgorithm SHA384
+    , DigestAlgorithm SHA512
+    ]
+
+arbitraryTraditionalIntegrity :: Gen IntegrityParams
+arbitraryTraditionalIntegrity =
+    TraditionalIntegrity <$> arbitraryIntegrityDigest <*> arbitrary
+
+arbitraryAuthSchemeIntegrity :: Gen IntegrityParams
+arbitraryAuthSchemeIntegrity = AuthSchemeIntegrity <$> arbitraryAuthScheme
 
 arbitraryPKCS12 :: ProtectionPassword -> Gen PKCS12
 arbitraryPKCS12 pwd = do
diff --git a/tests/PKCS12/Tests.hs b/tests/PKCS12/Tests.hs
--- a/tests/PKCS12/Tests.hs
+++ b/tests/PKCS12/Tests.hs
@@ -6,6 +6,7 @@
 import Data.PEM (pemContent)
 import Data.String (fromString)
 
+import Crypto.Store.Error
 import Crypto.Store.PKCS12
 import Crypto.Store.PKCS8
 import Crypto.Store.X509 (readSignedObject)
@@ -59,6 +60,8 @@
     integrityModes = [ "SHA-1"
                      , "SHA-256"
                      , "SHA-384"
+                     , "PBMAC1 with SHA-256"
+                     , "PBMAC1 with SHA-512"
                      ]
 
     privacyModes   = [ "aes-128-cbc"
@@ -92,6 +95,40 @@
             , ("GnuTLS with --null-password", 1, 1)
             ]
 
+testRfc9579 :: TestTree
+testRfc9579 = testCaseSteps "rfc9579" $ \step -> do
+    step "Reading PKCS #12 files"
+    pems <- readPEMs path
+    length pems @?= length infos
+
+    forM_ (zip infos pems) $ \((name, integrityError), pem) -> do
+        let r = readP12FileFromMemory (pemContent pem)
+
+        assertRight r $ \integrity -> case integrityError of
+            Nothing ->
+                assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                    assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
+                        step ("Testing " ++ name)
+                        assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->
+                            length keys @?= 1
+                        length (getAllSafeX509Certs scs) @?= 1
+            Just expectedErr ->
+                assertLeft (recoverAuthenticated pwd integrity) $ \err -> do
+                    err @?= expectedErr
+                    step ("Testing " ++ name)
+  where
+    pwd = fromString "1234"
+
+    path  = testFile "pkcs12-rfc9579.pem"
+    infos = [ ("SHA-256 HMAC and PRF", Nothing)
+            , ("SHA-256 HMAC and SHA-512 PRF", Nothing)
+            , ("SHA-512 HMAC and PRF", Nothing)
+            , ("Incorrect Iteration Count", Just BadContentMAC)
+            , ("Incorrect Salt", Just BadContentMAC)
+            , ("Missing Key Length",
+                Just $ InvalidParameter "KDF key length must be explicit")
+            ]
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling" $ do
@@ -100,27 +137,37 @@
         let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c
             unused = fromString "not-used"
         return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)
-    , testProperty "marshalling with authentication" $ do
-        params <- arbitraryIntegrityParams
+    , testProperty "marshalling with traditional authentication" $ do
+        params <- arbitraryTraditionalIntegrity
         c <- arbitrary >>= arbitraryPKCS12
         pI <- arbitrary
         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
             p = fromProtectionPassword pI
         return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)
+    , testProperty "marshalling with authentication scheme" $ do
+        params <- arbitraryAuthSchemeIntegrity
+        c <- arbitrary >>= arbitraryPKCS12
+        pI <- arbitrary `suchThat` (/= emptyNotTerminated)
+        let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
+            p = fromProtectionPassword pI
+        return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)
     , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $
         \pChain pKey privKey ->
+            supportedAsEncodedPubKey privKey ==>
             testCredConv privKey toCredential (fromCredential pChain pKey)
     , localOption (QuickCheckTests 20) $ testProperty "converting named credentials" $
-        \pChain pKey privKey -> do
-            name <- arbitraryAlias
-            testCredConv privKey
-                (toNamedCredential name)
-                (fromNamedCredential name pChain pKey)
+        \pChain pKey privKey ->
+            supportedAsEncodedPubKey privKey ==> do
+                name <- arbitraryAlias
+                testCredConv privKey
+                    (toNamedCredential name)
+                    (fromNamedCredential name pChain pKey)
     ]
   where
     testCredConv privKey to from = do
         pwd <- arbitrary
-        chain <- arbitrary >>= arbitraryCertificateChain
+        let pub = keyPairToPubKey (keyPairFromPrivKey privKey)
+        chain <- arbitraryCertificateChain pub
         chain' <- shuffleCertificateChain chain
         let cred = (chain, privKey)
             r = from pwd (chain', privKey) >>= recover pwd . to
@@ -132,5 +179,6 @@
         [ testType "RSA"                        "rsa"
         , testType "Ed25519"                    "ed25519"
         , testEmptyPassword
+        , testRfc9579
         , propertyTests
         ]
diff --git a/tests/PKCS8/Instances.hs b/tests/PKCS8/Instances.hs
--- a/tests/PKCS8/Instances.hs
+++ b/tests/PKCS8/Instances.hs
@@ -40,14 +40,18 @@
                       , PBE_SHA1_RC2_40 <$> arbitrary
                       ]
 
+instance Arbitrary KeyPair where
+    arbitrary = keyPairFromPrivKey <$> arbitrary
+
 instance Arbitrary PrivateKeyFormat where
     arbitrary = elements [ TraditionalFormat, PKCS8Format ]
 
-instance Arbitrary (FormattedKey PrivKey) where
+instance Arbitrary (FormattedKey KeyPair) where
     arbitrary = do
-        key <- arbitrary
+        pair <- arbitrary
+        let key = keyPairToPrivKey pair
         fmt <- if pkcs8only key then return PKCS8Format else arbitrary
-        return (FormattedKey fmt key)
+        return (FormattedKey fmt pair)
 
 pkcs8only :: PrivKey -> Bool
 pkcs8only (PrivKeyX25519  _)   = True
diff --git a/tests/PKCS8/Tests.hs b/tests/PKCS8/Tests.hs
--- a/tests/PKCS8/Tests.hs
+++ b/tests/PKCS8/Tests.hs
@@ -1,4 +1,5 @@
 -- | PKCS #8 tests.
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 module PKCS8.Tests (pkcs8Tests) where
 
 import qualified Data.ByteString as B
@@ -94,6 +95,12 @@
         , encryptedKeyTests prefix
         ]
 
+rfc8410Tests :: TestTree
+rfc8410Tests = testCase "rfc8410" $ do
+    keys <- readKeyFile path
+    length keys @?= 2
+  where path = testFile "rfc8410.pem"
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling" $ \fmt l ->
@@ -116,5 +123,6 @@
         , testType "X448"                       OnlyOuter   "x448"
         , testType "Ed25519"                    OnlyOuter   "ed25519"
         , testType "Ed448"                      OnlyOuter   "ed448"
+        , rfc8410Tests
         , propertyTests
         ]
diff --git a/tests/Util.hs b/tests/Util.hs
--- a/tests/Util.hs
+++ b/tests/Util.hs
@@ -2,18 +2,19 @@
 -- | Test utilities.
 module Util
     ( assertJust
+    , assertLeft
     , assertRight
     , getAttached
     , getDetached
     , testFile
     , TestKey(..)
-    , TestIV(..)
+    , TestIV, unIV
     ) where
 
 import Control.Monad (when)
 
 import Data.ByteString (ByteString, pack)
-import Data.Maybe (isNothing)
+import Data.Maybe (fromJust, isNothing)
 
 import Crypto.Cipher.Types
 import Crypto.Store.CMS
@@ -25,6 +26,11 @@
 assertJust (Just a) f = f a
 assertJust Nothing  _ = assertFailure "expecting Just but got Nothing"
 
+assertLeft :: Show b => Either a b -> (a -> Assertion) -> Assertion
+assertLeft (Left a)  f = f a
+assertLeft (Right val) _ =
+    assertFailure ("expecting Left but got: Right " ++ show val)
+
 assertRight :: Show a => Either a b -> (b -> Assertion) -> Assertion
 assertRight (Right b)  f = f b
 assertRight (Left val) _ =
@@ -35,14 +41,14 @@
     let m = fromAttached e
     when (isNothing m) $
         assertFailure "expecting attached but got detached content"
-    let Just r = m in return r
+    return $! fromJust m
 
 getDetached :: Encapsulates struct => a -> struct (Encap a) -> IO (struct a)
 getDetached c e = do
     let m = fromDetached c e
     when (isNothing m) $
         assertFailure "expecting detached but got attached content"
-    let Just r = m in return r
+    return $! fromJust m
 
 testFile :: String -> FilePath
 testFile name = "tests/files/" ++ name
@@ -58,6 +64,9 @@
       where cipher = undefined :: cipher
 
 newtype TestIV cipher = IV ByteString deriving (Show, Eq)
+
+unIV :: BlockCipher cipher => TestIV cipher -> IV cipher
+unIV (IV ivBS) = fromJust $ makeIV ivBS
 
 instance BlockCipher cipher => Arbitrary (TestIV cipher) where
     arbitrary = IV . pack <$> vector (blockSize cipher)
diff --git a/tests/X509/Instances.hs b/tests/X509/Instances.hs
--- a/tests/X509/Instances.hs
+++ b/tests/X509/Instances.hs
@@ -4,7 +4,7 @@
     ( arbitraryOID
     , arbitraryRSA
     , arbitraryLargeRSA
-    , arbitraryDSA
+    , arbitraryPrivDSA
     , arbitraryNamedEC
     , arbitraryX25519
     , arbitraryX448
@@ -13,6 +13,7 @@
     , arbitrarySignedCertificate
     , arbitraryCertificateChain
     , shuffleCertificateChain
+    , supportedAsEncodedPubKey
     ) where
 
 import           Control.Monad (zipWithM)
@@ -58,7 +59,7 @@
 
 instance Arbitrary PubKey where
     arbitrary = oneof [ PubKeyRSA . fst <$> arbitraryRSA
-                      , PubKeyDSA . fst <$> arbitraryDSA
+                      , PubKeyDSA <$> arbitraryPubDSA
                       , PubKeyEC . fst  <$> arbitraryNamedEC
                       --, PubKeyEC . fst  <$> arbitraryExplicitPrimeCurve
                       , PubKeyX25519 . fst <$> arbitraryX25519
@@ -69,7 +70,7 @@
 
 instance Arbitrary PrivKey where
     arbitrary = oneof [ PrivKeyRSA . snd <$> arbitraryRSA
-                      , PrivKeyDSA . snd <$> arbitraryDSA
+                      , PrivKeyDSA <$> arbitraryPrivDSA
                       , PrivKeyEC . snd  <$> arbitraryNamedEC
                       , PrivKeyEC . snd  <$> arbitraryExplicitPrimeCurve
                       , PrivKeyX25519 . snd <$> arbitraryX25519
@@ -78,6 +79,12 @@
                       , PrivKeyEd448 . snd <$> arbitraryEd448
                       ]
 
+-- package 'x509' has not implemented the serialization of PrivKeyEC_Prime
+-- so we cannot use it in a certificate
+supportedAsEncodedPubKey :: PrivKey -> Bool
+supportedAsEncodedPubKey (PrivKeyEC PrivKeyEC_Prime{}) = False
+supportedAsEncodedPubKey _ = True
+
 arbitraryRSA :: Gen (RSA.PublicKey, RSA.PrivateKey)
 arbitraryRSA = do
     n <- elements [ 768, 1024 ]     -- enough bits to sign with SHA-512
@@ -90,13 +97,11 @@
     e <- elements [ 3, 0x10001 ]
     RSA.generate (n `div` 8) e
 
-arbitraryDSA :: Gen (DSA.PublicKey, DSA.PrivateKey)
+arbitraryDSA :: Gen DSA.KeyPair
 arbitraryDSA = do
     x <- DSA.generatePrivate params
     let y = DSA.calculatePublic params x
-        priv = DSA.PrivateKey { DSA.private_params = params, DSA.private_x = x }
-        pub = DSA.PublicKey { DSA.public_params = params, DSA.public_y = y }
-    return (pub, priv)
+    return (DSA.KeyPair params y x)
   where
     -- DSA parameters were generated using 'openssl dsaparam -C 2048'
     params = DSA.Params
@@ -104,6 +109,12 @@
         , DSA.params_g = 0x10E51AEA37880C5E52DD477ED599D55050C47012D038B9E4B3199C9DE9A5B873B1ABC8B954F26AFEA6C028BCE1783CFE19A88C64E4ED6BFD638802A78457A5C25ABEA98BE9C6EF18A95504C324315EABE7C1EA50E754591E3EFD3D33D4AE47F82F8978ABC871C135133767ACC60683F065430C749C43893D73596B12D5835A78778D0140B2F63B32A5658308DD5BA6BBC49CF6692929FA6A966419404F9A2C216860E3F339EDDB49AD32C294BDB4C9C6BB0D1CC7B691C65968C3A0A5106291CD3810147C8A16B4BFE22968AD9D3890733F4AA9ACD8687A5B981653A4B1824004639956E8C1EDAF31A8224191E8ABD645D2901F5B164B4B93F98039A6EAEC6088
         , DSA.params_q = 0xE1FDFADD32F46B5035EEB3DB81F9974FBCA69BE2223E62FCA8C77989B2AACDF7
         }
+
+arbitraryPrivDSA :: Gen DSA.PrivateKey
+arbitraryPrivDSA = DSA.toPrivateKey <$> arbitraryDSA
+
+arbitraryPubDSA :: Gen DSA.PublicKey
+arbitraryPubDSA = DSA.toPublicKey <$> arbitraryDSA
 
 arbitraryNamedEC :: Gen (PubKeyEC, PrivKeyEC)
 arbitraryNamedEC = do
diff --git a/tests/X509/Tests.hs b/tests/X509/Tests.hs
--- a/tests/X509/Tests.hs
+++ b/tests/X509/Tests.hs
@@ -1,4 +1,5 @@
 -- | X.509 tests.
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 module X509.Tests (x509Tests) where
 
 import qualified Data.ByteString as B
@@ -43,6 +44,12 @@
     fCert = testFile (prefix ++ "-self-signed-cert.pem")
     fKey  = testFile (prefix ++ "-public.pem")
 
+rfc8410Tests :: TestTree
+rfc8410Tests = testCase "rfc8410" $ do
+    keys <- readPubKeyFile path
+    length keys @?= 1
+  where path = testFile "rfc8410.pem"
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling public keys" $ \keys ->
@@ -70,5 +77,6 @@
         , keyTests "X448"                       "x448"       1
         , keyTests "Ed25519"                    "ed25519"    1
         , keyTests "Ed448"                      "ed448"      1
+        , rfc8410Tests
         , propertyTests
         ]
diff --git a/tests/files/cms-digested-data.pem b/tests/files/cms-digested-data.pem
--- a/tests/files/cms-digested-data.pem
+++ b/tests/files/cms-digested-data.pem
@@ -25,3 +25,22 @@
 EAQOaGVsbG8sIHdvcmxkDQoEQNmRYGENk6X3fR9Sk9k++QSKUPYBvF+nGKHL4FXN
 QVoNz4kKv9BI4MPnsV77ikuw7kWGR3OFLWO8waCyFK+EEu0=
 -----END CMS-----
+-----BEGIN CMS-----
+MFwGCSqGSIb3DQEHBaBPME0CAQAwCwYJYIZIAWUDBAIHMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQcrHRNG/sKxDCiVbjFsYvAvrXdZNaPFFkUnKb7eg==
+-----END CMS-----
+-----BEGIN CMS-----
+MGAGCSqGSIb3DQEHBaBTMFECAQAwCwYJYIZIAWUDBAIIMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQgead3JnYtNJOYL50QfPgWkqUtpmuiU9T7itIfOmfa
+3l4=
+-----END CMS-----
+-----BEGIN CMS-----
+MHAGCSqGSIb3DQEHBaBjMGECAQAwCwYJYIZIAWUDBAIJMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQwPvv+XafJtPR2UXRqu2t6l3BuJeoOghvrAfPbbRQ2
+cr5IHYmzioNIZ1HlrP6RG0R3
+-----END CMS-----
+-----BEGIN CMS-----
+MIGABgkqhkiG9w0BBwWgczBxAgEAMAsGCWCGSAFlAwQCCjAdBgkqhkiG9w0BBwGg
+EAQOaGVsbG8sIHdvcmxkDQoEQHjsLFT6QOVe+FI6fBZhWEIHpXzDX9L4/ARGWEXp
+rKVTw6DfjRFICt66Mxxqn4WbRCa/FJXAnVY1PduwTN1Fiqw=
+-----END CMS-----
diff --git a/tests/files/ed25519-pkcs12.pem b/tests/files/ed25519-pkcs12.pem
--- a/tests/files/ed25519-pkcs12.pem
+++ b/tests/files/ed25519-pkcs12.pem
@@ -86,6 +86,64 @@
 AA==
 -----END PKCS12-----
 -----BEGIN PKCS12-----
+MIIE6QIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH
+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG
+SIb3DQEFDDAkBBADZyEJTf+SYDt1cuTOQqVaAgIIADAMBggqhkiG9w0CCQUAMB0G
+CWCGSAFlAwQBKgQQxjg/dHyDo8ty5b1MLNsCs4CCAkB15cjQKudM1ywzel7q2pYU
+tOU6nBPPd5oblD8fiH+TeOuNb9WXLlPB1Ra5ywqYrbqbPNpFNriigV20orksM1Dk
+/BCTXfxgaC4RO1Y3zNANiMCw8rafSpm5fX/tU12tc8kei0D1TcmaR3+i135Nl3xZ
+zFXmm0lQ/rPHHYwW8uzAQOT9QVUmkie7P54aCRj99PCpVcv0W5sxqKYmmyfJerqZ
+DtY6Q9ZMbe7jiu4bUxKZ06ggfl/UYwmIkT2xjN/EpWm0EO+hVIgIMq09kEkFQyiH
+LP37rxOrOQ1dzopztALm3+8o2UiWTm53CwDJx8+0Qvb6eldLNpgUDZcrvjyRUlzF
+gaQGq6OmkRxWme1A0xC/vc+fppW75YnFbKdMzDfmbxDwzjYprAiFlk+4bPmSZHuP
+/mKx+jYFXubOV/4G4CrO9toHtztJVRm2O48WY8JqFKSCvlW2/PqMry0v4pxHRrKc
+N1hWyhIxAYUf8+A4an5XEvuE3rwmFzHU3SlxvpWECVO+qzteY+/pMXEwdJiqSiH3
+ICsGaGcnWE+UOSsVkp3hje6MIkGYMec7dakG785Zwwp+zQgZ9vXKa4rmvFg79tnL
+VJbehXobf5tBbg8c525Pw4siYNaD6bk2VJbpew2F3OrwslmlhlxRPZEt/oGY+dgK
+haRPhAAfVxnFav+fApm2IkR9nbEqomthrCoD5VoIWEXS3IVrvu9RAlqBOpKPp+kZ
+cU472Uy+4MKJ5kME2f1b6AyuHzT/KfmItKTB4qdSCHgwggF6BgkqhkiG9w0BBwGg
+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w
+UjAxBgkqhkiG9w0BBQwwJAQQA137X5gsqDKTJ08FgEK6mAICCAAwDAYIKoZIhvcN
+AgkFADAdBglghkgBZQMEASoEEPkEeCkd5FOqDNhqcFGwngwEQIJ4pEDx6DBFChYf
+p+/XCMom7KTB9JwjxJPaYeroUVBEjz1MpOntyJetOjJzruEXGMB/cR4ARJNnS25+
+PnNJN5oxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G
+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg
+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAyADUANgAgAC0AcABiAG0AYQBjADEAXwBw
+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADIANQA2MH0wbTBJBgkqhkiG9w0BBQ4w
+PDAsBgkqhkiG9w0BBQwwHwQIH6958Xey3xgCAggAAgEgMAwGCCqGSIb3DQIJBQAw
+DAYIKoZIhvcNAgkFAAQgGHJF7GEkuv9ZXtkHlH9yDHwFRjTYsH0peoVdnVVTScME
+CB+vefF3st8YAgIIAA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIFCwIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH
+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG
+SIb3DQEFDDAkBBBBRdQTNqGIKpGPW0+w4J0FAgIIADAMBggqhkiG9w0CCQUAMB0G
+CWCGSAFlAwQBKgQQ0KQc53YHlFcirdn9bulXVoCCAkCGp/FB1JeyqO5yQqM3wRrd
+CBLA+0Cg8u64CoagWGmjfYsGWiobeaUOnFc62GORJNH/vozKfU70aYRbGw0iO7La
+VKzl+VPt9m5e2jC9QUVhtE/kH0jpx0fuYK68DWMklDHrLpSFHfNbZAwFzOcqojaG
+oknhWAVYDrIkjFGcWWBDbHHeSQA+kEisjrnUIL7cUavu4OQVxveXNQ0DOFlnfOc6
+HOZ2+9w4MvjbJUKo9cuqVPzISyLvp4jp2LGOdTOM2OCRWlZh8SelmKwfSJTKhGhx
+xPMOeN+Pg7MeVJvUWOdGEmQdGGti2cBX5ysORBcjgsh1cS+tVYCPwxP1RZVIAfac
+KitW8q4Q3E/RncomQs8R0lLi26kadPGj0OUoAhly7PQxe4HG0ZCHCdI2pld8YkJo
+mZAU5hOG5LI/q6Le5fXnPU1XHYwU0z9CQsN3NmYoufD1yZZtA1z7fpNbyxqcOu0P
+OXFE7UUNM0oF5HJpwZYM4asReGbXMnYB4DQvW35FhVp7rFD5Z0mteCF4GK/sgbv/
+HhtwtNVbHp35DIUceVIJrV9qYHN3y7YVjm5RNFzfCGL8T8CuNfB3pIHvIuvqSjyd
+5z/tm3/Kx/wMxqJqQIArXb531w7cac03JhH7OPAdTwneaV6VEPl1pEuIZeb129ap
+jn1Rp4E62YVsqgEuky3BGrRZNuwuP34X5c6O9kwVN2WNk2FltZ+Vwq8czLblxVmZ
+uQjhs6hh9GsT4tPwgX6Br+FRuTRAWG/4cQeVAU7G68EwggF6BgkqhkiG9w0BBwGg
+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w
+UjAxBgkqhkiG9w0BBQwwJAQQrcodfjPmwrpE2L8ETV+uvAICCAAwDAYIKoZIhvcN
+AgkFADAdBglghkgBZQMEASoEEAPPKSb0fhAIfNfyxOVQ2F0EQCRw+/jJY01yo0f3
+fUbcZVUpYQtvxMlpNoTV0hdi0DwC4AO5dkBmZdvCIBgo/c/BY8jL0NNsUxFb8VyX
+Xj0V7FMxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G
+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg
+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQA1ADEAMgAgAC0AcABiAG0AYQBjADEAXwBw
+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADUAMQAyMIGeMIGNMEkGCSqGSIb3DQEF
+DjA8MCwGCSqGSIb3DQEFDDAfBAhSQf9Bis7WlAICCAACAUAwDAYIKoZIhvcNAgsF
+ADAMBggqhkiG9w0CCwUABEDmbcmLz5XXSQPkGAd0VfCypwFm71EP4K+IlxTVAXAC
+VOChMbMfdeiCAsepDo9nWIOsg+r3KvQVtM98TyIBU4tsBAhSQf9Bis7WlAICCAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
 MIIDXwIBAzCCAyUGCSqGSIb3DQEHAaCCAxYEggMSMIIDDjCCAhMGCSqGSIb3DQEH
 AaCCAgQEggIAMIIB/DCCAfgGCyqGSIb3DQEMCgEDoIIBczCCAW8GCiqGSIb3DQEJ
 FgGgggFfBIIBWzCCAVcwggEJoAMCAQICFEInN18eR8QZFfy9Yb39ycd56f8PMAUG
diff --git a/tests/files/pkcs12-rfc9579.pem b/tests/files/pkcs12-rfc9579.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/pkcs12-rfc9579.pem
@@ -0,0 +1,354 @@
+-----BEGIN PKCS12-----
+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
+3QQITk9UIFVTRUQCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+6QQITk9UIFVTRUQCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+3QQITk9UIFVTRUQCAggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+3QQIb0c8OLAuMXMCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+b0c8OLAuMXMCAggA
+-----END PKCS12-----
diff --git a/tests/files/rfc8410.pem b/tests/files/rfc8410.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/rfc8410.pem
@@ -0,0 +1,19 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=
+-----END PUBLIC KEY-----
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+-----END PRIVATE KEY-----
+-----BEGIN PRIVATE KEY-----
+MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
+Z9w7lshQhqowtrbLDFw4rXAxZuE=
+-----END PRIVATE KEY-----
+
+same private key with a modified public key:
+
+-----BEGIN PRIVATE KEY-----
+MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC
+oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB
+Z9w7lshQhqowtrbLDFw4sXAxZuE=
+-----END PRIVATE KEY-----
diff --git a/tests/files/rfc9690.pem b/tests/files/rfc9690.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/rfc9690.pem
@@ -0,0 +1,84 @@
+-----BEGIN CMS-----
+MIICXAYJKoZIhvcNAQcDoIICTTCCAkkCAQMxggIEpIICAAYLKoZIhvcNAQkQDQMw
+ggHvAgEAgBSe62fJuVp01E0vFjlmgOgBtcuknDAJBgcogYxxAgIEBIIBgMBx/Cc6
++Oe9sVLga/czEDYQdBVKQ6vPPJPBNJnSBlNEPu2e9dPAaF5Kp2poVIFbuXaR/5+N
+rBXup9dPRSvzUKZGFj1oKI6XjL96cwie5ScS+aT0ngas57vIWrFNTjNsl8VyiiZU
+E4x7JuiDXGsKn77SZJXE6t90Wikzvig/aoixZpX8BmZoc8+202cY7zN2zvwQDDlB
+88SUlEB4MlgHpVkYa5XMq/NxTPr3n4O9MFN/3ZrtWkzcvYvQSG+u1z6dSGswh9bI
+BlRrbiZxV1yYRh5EH2VUK9ld4m0PU6ZOeEjXMdlgjQU+jTRVRmAthiNv/jcEyYrV
+kUTzCJ5ebVJ7VJe6EDx51i6A0CNUELBvcafZvRw4AA+RDWMS6i8go1V1Na0Bswk/
+tffuUHCA0Pd9SMnDs3lva33TeGCF+4lRI/BMofHBviLHR6jfrOMjcPsNVweD4n27
+fnT8qU7jlnb949ipVT2HgiRzbjfhkdq5U8fiKMB61coxIkIcFN69ByqatjAbBgor
+gQUQhkgJLAECMA0GCWCGSAFlAwQCAQUAAgEQMAsGCWCGSAFlAwQBBQQYKHguXT15
+SnYWuGP7z8cZt48S3gjPKG4JMDwGCSqGSIb3DQEHATAdBglghkgBZQMEAQIEEEgM
+yv66vvrO263eyviId4GAEMbKZdt73Xaw834vq2Jktm0=
+-----END CMS-----
+-----BEGIN RSA PRIVATE KEY-----
+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
+-----END RSA PRIVATE KEY-----
+
+adding also a recipient certificate generated with:
+
+    openssl req -new -subj /emailAddress=test@example.com \
+      -key tests/files/rfc9690.pem \
+    | openssl x509 -req \
+      -CA tests/files/rsa-self-signed-cert.pem \
+      -CAkey tests/files/rsa-unencrypted-pkcs8.pem \
+      -force_pubkey tests/files/rfc9690.pem \
+      -set_serial 1
+
+-----BEGIN CERTIFICATE-----
+MIIC/jCCAmegAwIBAgIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkB
+FhB0ZXN0QGV4YW1wbGUuY29tMB4XDTI1MTAyNjA1MzM1NloXDTI1MTEyNTA1MzM1
+NlowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCAaIwDQYJKoZI
+hvcNAQEBBQADggGPADCCAYoCggGBAN6HFteHMZ3DyeO35xIwWQMnwtpamqS9xE+I
+71egu4Bnla0UMVjw8JfnBWAQUdno949/9HnA5rvl7lYtvrOSRynns2vorX7obM98
+on4HhKXivE/krrB4Z9tPzQS4WCm4d5IgHAoEOenaQ6w7w3C6qr0t44P1Tvjj52W6
+H6Bzjnr7fEP66GoSXtT5SMkOLnVgfM2XsyeJiy32zgPWFWmF8dgBx2SXr6N55kZ5
+YCV6OphNNBP+qpIYwxykjHZdlhupjvUwg2XPamN3ACQ1OiK9L0h+6lJfP7zdd8uF
+DhZJ/ITOJJmemyam2beTW4x7WOLkNFTFt0chEs4aztNpq6d/WrizZx9xF3V2ldbZ
+O+R96gu8yWnhljCckHzhN2HxBkSp/H8bm44u9eg/FHnAMQMxchS+ptSYjsleWnAI
+jqcTdeKkWGXR51zjPQ6hiBqaHmNVLg+DKNOaLDqjlKQSRwPX0sIUmRu1TX/r4DVk
+yzWEmCyNOCojHbpZ71OnbPkD8/iirQIDAQABo0IwQDAdBgNVHQ4EFgQUnutnybla
+dNRNLxY5ZoDoAbXLpJwwHwYDVR0jBBgwFoAU/JTTK/+8Q86nWQwPMu+1PpT6Bg4w
+DQYJKoZIhvcNAQELBQADgYEAOdp9bzCo2D2IQIrYk1mLwHkJ8IQLd1+3O968DPem
+dofgQDm7PwG/+qzmXdNFkU6cJmz+7E70DEsciw50mkzuH5zsKkTe8IbCfJ4oAN5p
+M5p495h6r1a678GJJ45ornqe3D0v1mmraCJpV0hK9lO03ph+4nfrof31eW5AVXyN
+/Ho=
+-----END CERTIFICATE-----
diff --git a/tests/files/rsa-pkcs12.pem b/tests/files/rsa-pkcs12.pem
--- a/tests/files/rsa-pkcs12.pem
+++ b/tests/files/rsa-pkcs12.pem
@@ -148,6 +148,95 @@
 VSE7eqvIyhBRAshh1EtyB3sLY/MknrZqUrz3NqATBAiMSKkq/xXbXQICCAA=
 -----END PKCS12-----
 -----BEGIN PKCS12-----
+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
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+E1VoEw6fFI8UgYExHAQIb6vXMUPEaigCAggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
 MIIGWwIBAzCCBiEGCSqGSIb3DQEHAaCCBhIEggYOMIIGCjCCAsQGCSqGSIb3DQEH
 AaCCArUEggKxMIICrTCCAqkGCyqGSIb3DQEMCgEDoIICLDCCAigGCiqGSIb3DQEJ
 FgGgggIYBIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF
