diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,90 @@
 # 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
+
+## 0.3.0.1 - 2023-06-25
+
+* Add optional flag to use crypton instead of cryptonite
+
+## 0.3.0.0 - 2023-01-14
+
+* API change in PKCS5, PKCS8 and PKCS12 modules to handle better password-based
+  encryption derived from an empty password.  All encryption/decryption
+  functions now expect an opaque `ProtectionPassword` data type.  Conversion
+  functions `toProtectionPassword` and `fromProtectionPassword` are provided.
+  Additionnally in the PKCS12 module, the type `OptProtected` is replaced with
+  `OptAuthenticated` when dealing with password integrity.  Similarly at that
+  level, function `recover` is to be replaced with `recoverAuthenticated`.
+
+* Added support for KMAC (Keccak Message Authentication Code) in CMS
+  authenticated data, through constructors `KMAC_SHAKE128` and `KMAC_SHAKE256`.
+
+* CMS key agreement now supports derivation with HKDF along with X9.63.  Data
+  type `KeyAgreementParams` is modified to include a KDF instead of simply the
+  digest algorithm.  HKDF has assigned OIDs only for standard DH and cannot be
+  used with cofactor DH.
+
+* Added CMS utility functions to deal with the `signingTime` attribute.
+
+* Changed `withSignerCertificate` validation callback API to include the
+  `signingTime` value when available.
+
 ## 0.2.3.0 - 2022-11-05
 
 * Fix RC2 on big-endian architectures
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2022, 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://travis-ci.org/ocheron/cryptostore.png?branch=master)](https://travis-ci.org/ocheron/cryptostore)
-[![BSD](https://b.repl.ca/v1/license-BSD-blue.png)](https://en.wikipedia.org/wiki/BSD_licenses)
-[![Haskell](https://b.repl.ca/v1/language-haskell-lightgrey.png)](https://haskell.org/)
+[![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,17 +51,18 @@
 > :set -XOverloadedStrings
 > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5
 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-> salt <- generateSalt 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
-> encParams <- generateEncryptionParams (CBC AES256)
+> let keyPair = keyPairFromPrivKey privKey
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
+> encParams <- generateCBCParams AES256
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
-> writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey
+> writeEncryptedKeyFile "/path/to/newkey.pem" pbes "mypassword" keyPair
 Right ()
 ```
 
 Parameters used in this example are AES-256-CBC as cipher, PBKDF2 as
-key-derivation function, with an 8-byte salt, 2048 iterations and SHA-256 as
-pseudorandom function.
+key-derivation function, with a 16-byte salt, 200,000 iterations and SHA-256
+as pseudorandom function.
 
 ## Public Keys and Signed Objects
 
@@ -92,18 +94,33 @@
 API to read PKCS #12 files requires some password at each layer.  This API is
 available in module `Crypto.Store.PKCS12`.
 
-Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
+Reading a binary PKCS #12 file using a single password doing both integrity
+and privacy (usual case):
 
 ```haskell
 > :set -XOverloadedStrings
 > :m Crypto.Store.PKCS12
 > Right p12 <- readP12File "/path/to/file.p12"
-> let Right pkcs12 = recover "myintegrityassword" p12
+> let Right (password, pkcs12) = recoverAuthenticated "mypassword" p12
+> let Right contents = recover password (unPKCS12 pkcs12)
+> getAllSafeX509Certs contents
+[SignedExact {getSigned = ...}]
+> recover password (getAllSafeKeys contents)
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
+```
+
+Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
+
+```haskell
+> :set -XOverloadedStrings
+> :m Crypto.Store.PKCS12
+> Right p12 <- readP12File "/path/to/other.p12"
+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12)
 > getAllSafeX509Certs contents
 [SignedExact {getSigned = ...}]
 > recover "myprivacypassword" (getAllSafeKeys contents)
-Right [PrivKeyRSA ...]
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
 ```
 
 Generating a PKCS #12 file containing a private key:
@@ -117,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 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
-> encParams <- generateEncryptionParams (CBC AES256)
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
+> encParams <- generateCBCParams AES256
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
 >     Right pkcs12 = encrypted pbes "mypassword" contents
 
 -- Save to PKCS #12 with integrity protection (same password)
-> salt' <- generateSalt 8
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 2048)
-> writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12
+> salt' <- generateSalt 16
+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt' 200000)
+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
 Right ()
 ```
 
@@ -143,34 +161,68 @@
 > :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"
-> let Right pkcs12 = recover "myintegrityassword" p12
+> Right p12 <- readP12File "/path/to/other.p12"
+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)
 > cred
 (CertificateChain [...], PrivKeyRSA (...))
 
 -- Scheme to reencrypt the key
-> saltK <- generateSalt 8
-> let kdfK = PBKDF2 saltK 2048 Nothing PBKDF2_SHA256
-> encParamsK <- generateEncryptionParams (CBC AES256)
+> saltK <- generateSalt 16
+> let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256
+> encParamsK <- generateCBCParams AES256
 > let sKey = PBES2 (PBES2Parameter kdfK encParamsK)
 
 -- Scheme to reencrypt the certificate chain
 > saltC <- generateSalt 8
-> let kdfC = PBKDF2 saltC 1024 Nothing PBKDF2_SHA256
-> encParamsC <- generateEncryptionParams (CBC AES128)
+> let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256
+> encParamsC <- generateCBCParams AES128
 > let sCert = PBES2 (PBES2Parameter kdfC encParamsC)
 
 -- Write the content back to a new file
 > let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred
-> salt <- generateSalt 8
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 2048)
-> writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12'
+> salt <- generateSalt 16
+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt 200000)
+> writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12'
 ```
 
 Variants `toNamedCredential` and `fromNamedCredential` are also available when
 PKCS #12 elements need an alias (friendly name).
 
+The library also supports integrity protection with PBMAC1 as defined in
+[RFC 9579](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
@@ -208,15 +260,15 @@
 > 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,
 -- i.e. a KDF will derive the Key Encryption Key from a password
 -- that the recipient will need to know
-> salt <- generateSalt 8
-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256
-> keParams <- generateEncryptionParams (CBC AES128)
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
+> keParams <- generateCBCParams AES128
 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)
 
 -- Generate the enveloped structure for this single recipient.  Encrypted
@@ -256,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
 
@@ -266,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]
@@ -289,9 +341,67 @@
 > :m Crypto.Store.CMS Data.Default.Class
 > [SignedDataCI signedEncapData] <- readCMSFile "/path/to/signed.pem"
 > signedData <- fromAttached signedEncapData
-> let doValidation chain = null <$> validateNoFQHN store def noServiceID chain
+> 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.2.3.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,16 +9,21 @@
 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
+  manual: True
+  default: False
+
 library
   hs-source-dirs:      src
   exposed-modules:     Crypto.Store.CMS
@@ -47,22 +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
-                     , cryptonite >= 0.26
-                     , 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-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
 
@@ -85,15 +101,22 @@
                      , X509.Tests
   build-depends:       base >= 4.9 && < 5
                      , bytestring
-                     , asn1-types >= 0.3.1 && < 0.4
-                     , cryptonite >= 0.25
                      , memory
                      , tasty
                      , tasty-hunit
                      , tasty-quickcheck
+                     , 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
-                     , x509
-                     , cryptostore
   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
@@ -21,6 +21,11 @@
 -- * <https://tools.ietf.org/html/rfc8103 RFC 8103>: Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS)
 -- * <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(..)
@@ -60,6 +65,8 @@
     , KeyEncryptionParams(..)
     , KeyTransportParams(..)
     , KeyAgreementParams(..)
+    , KeyAgreementKDF(..)
+    , KeyEncapsulationMechanism(..)
     , RecipientInfo(..)
     , EnvelopedData(..)
     , ProducerOfRI
@@ -91,6 +98,10 @@
     , PasswordRecipientInfo(..)
     , forPasswordRecipient
     , withRecipientPassword
+     -- ** Key Encapsulation recipients
+    , KEMRecipientInfo(..)
+    , forKeyEncapRecipient
+    , withRecipientKeyEncap
     -- * Digested data
     , DigestProxy(..)
     , DigestAlgorithm(..)
@@ -104,8 +115,12 @@
     , ContentEncryptionParams
     , EncryptedContent
     , EncryptedData(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
     , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
+    , deriveEncryptionKey
     , getContentEncryptionAlg
     , encryptData
     , decryptData
@@ -120,11 +135,12 @@
     , AuthContentEncryptionAlg(..)
     , AuthContentEncryptionParams
     , AuthEnvelopedData(..)
-    , generateAuthEnc128Params
-    , generateAuthEnc256Params
+    , authEnc128Params
+    , authEnc256Params
     , generateChaChaPoly1305Params
     , generateCCMParams
     , generateGCMParams
+    , authDeriveEncryptionKey
     , authEnvelopData
     , openAuthEnvelopedData
     -- * Key derivation
@@ -132,6 +148,7 @@
     , generateSalt
     , KeyDerivationFunc(..)
     , PBKDF2_PRF(..)
+    , KeyDerivationFn(..)
     -- * Secret-key algorithms
     , HasKeySize(..)
     , generateKey
@@ -144,6 +161,10 @@
     , findAttribute
     , setAttribute
     , filterAttributes
+    -- * CMS standard attributes
+    , getSigningTimeAttr
+    , setSigningTimeAttr
+    , setSigningTimeAttrCurrent
     -- * Originator information
     , OriginatorInfo(..)
     , CertificateChoice(..)
@@ -158,7 +179,7 @@
 import Data.ASN1.Encoding
 import Data.ByteString (ByteString)
 import Data.Maybe (isJust)
-import Data.List (nub, unzip3)
+import Data.List (nub)
 
 import Crypto.Hash
 
@@ -248,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
@@ -298,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
@@ -377,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
@@ -405,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
@@ -419,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
@@ -63,11 +73,15 @@
     , transportEncrypt
     , transportDecrypt
     , KeyAgreementParams(..)
+    , KeyAgreementKDF(..)
     , ECDHPair
     , ecdhGenerate
     , ecdhPublic
     , ecdhEncrypt
     , ecdhDecrypt
+    , KeyEncapsulationMechanism(..)
+    , kemEncap
+    , kemDecap
     , MaskGenerationFunc(..)
     , mgf
     , SignatureValue
@@ -90,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
@@ -109,9 +123,11 @@
 import           Crypto.ECC (Curve_X25519, Curve_X448, ecdh)
 import           Crypto.Error
 import qualified Crypto.Hash as Hash
+import qualified Crypto.KDF.HKDF as HKDF
 import qualified Crypto.KDF.PBKDF2 as PBKDF2
 import qualified Crypto.KDF.Scrypt as Scrypt
 import qualified Crypto.MAC.HMAC as HMAC
+import qualified Crypto.MAC.KMAC as KMAC
 import qualified Crypto.MAC.Poly1305 as Poly1305
 import           Crypto.Number.Serialize
 import qualified Crypto.PubKey.Curve25519 as X25519
@@ -139,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
 
 
@@ -164,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)
@@ -173,7 +199,31 @@
     -- | SHAKE256 (variable size)
     SHAKE256 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE256 n)
 
-deriving instance Show (DigestProxy hashAlg)
+
+instance Show (DigestProxy hashAlg) where
+    showsPrec _ MD2          = showString "MD2"
+    showsPrec _ MD4          = showString "MD4"
+    showsPrec _ MD5          = showString "MD5"
+    showsPrec _ SHA1         = showString "SHA1"
+    showsPrec _ SHA224       = showString "SHA224"
+    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) =
+        showParen (d > 10) $ showString "SHAKE128 " . showNat 11 p
+    showsPrec d (SHAKE256 p) =
+        showParen (d > 10) $ showString "SHAKE256 " . showNat 11 p
+
+showNat :: KnownNat n => Int -> Proxy n -> ShowS
+showNat d n = showParen (d > 0) $
+    shows n . showString " :: Proxy " . shows (natVal n)
+
 deriving instance Eq (DigestProxy hashAlg)
 
 instance HasStrength (DigestProxy hashAlg) where
@@ -185,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
@@ -210,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
@@ -228,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
@@ -242,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
@@ -257,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]
@@ -277,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
@@ -297,12 +371,16 @@
     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 $
-        \(SomeNat p) -> DigestAlgorithm (SHAKE128 p)
+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE128 p)
     parseParameter Type_SHAKE256_Len = parseBitLen $
-        \(SomeNat p) -> DigestAlgorithm (SHAKE256 p)
+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE256 p)
 
 -- | Compute the digest of a message.
 digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString
@@ -318,13 +396,16 @@
 parseDigestParam :: Monoid e => DigestAlgorithm -> ParseASN1 e DigestAlgorithm
 parseDigestParam p = getNextMaybe nullOrNothing >> return p
 
-parseBitLen :: Monoid e => (SomeNat -> a) -> ParseASN1 e a
-parseBitLen build = do
+parseBitLen :: Monoid e => (SomeNat -> ParseASN1 e a) -> ParseASN1 e a
+parseBitLen fn = do
     IntVal n <- getNext
     case someNatVal n of
         Nothing -> throwParseError ("Invalid bit length: " ++ show n)
-        Just sn -> return (build sn)
+        Just sn -> fn sn
 
+p256 :: Proxy 256
+p256 = Proxy
+
 p512 :: Proxy 512
 p512 = Proxy
 
@@ -384,52 +465,129 @@
 -- | Message authentication code.  Equality is time constant.
 type MessageAuthenticationCode = AuthTag
 
+data MACType
+    = forall hashAlg . Hash.HashAlgorithm hashAlg
+        => TypeHMAC (DigestProxy hashAlg)
+    | TypeKMAC_SHAKE128
+    | TypeKMAC_SHAKE256
+
+deriving instance Show MACType
+
 -- | Message Authentication Code (MAC) Algorithm.
 data MACAlgorithm
     = forall hashAlg . Hash.HashAlgorithm hashAlg
         => HMAC (DigestProxy hashAlg)
+    | forall n . KnownNat n => KMAC_SHAKE128 (Proxy n) ByteString
+    | forall n . KnownNat n => KMAC_SHAKE256 (Proxy n) ByteString
 
-deriving instance Show MACAlgorithm
+instance Show MACAlgorithm where
+    showsPrec d (HMAC p) = showParen (d > 10) $
+        showString "HMAC " . showsPrec 11 p
+    showsPrec d (KMAC_SHAKE128 p s) = showParen (d > 10) $
+        showString "KMAC_SHAKE128 " . showNat 11 p .
+            showChar ' ' . showsPrec 11 s
+    showsPrec d (KMAC_SHAKE256 p s) = showParen (d > 10) $
+        showString "KMAC_SHAKE256 " . showNat 11 p .
+            showChar ' ' . showsPrec 11 s
 
 instance Eq MACAlgorithm where
-    HMAC a1 == HMAC a2 = DigestAlgorithm a1 == DigestAlgorithm a2
+    HMAC a1             == HMAC a2             =
+        DigestAlgorithm a1 == DigestAlgorithm a2
+    KMAC_SHAKE128 p1 s1 == KMAC_SHAKE128 p2 s2 =
+        natVal p1 == natVal p2 && s1 == s2
+    KMAC_SHAKE256 p1 s1 == KMAC_SHAKE256 p2 s2 =
+        natVal p1 == natVal p2 && s1 == s2
+    _                   == _                   = False
 
 instance HasStrength MACAlgorithm where
     getSecurityBits (HMAC a) = getSecurityBits (DigestAlgorithm a)
+    getSecurityBits (KMAC_SHAKE128 p _) = shakeSecurityBits 128 p
+    getSecurityBits (KMAC_SHAKE256 p _) = shakeSecurityBits 256 p
 
-instance Enumerable MACAlgorithm where
-    values = [ HMAC MD5
-             , HMAC SHA1
-             , HMAC SHA224
-             , HMAC SHA256
-             , HMAC SHA384
-             , HMAC SHA512
+instance Enumerable MACType where
+    values = [ TypeHMAC MD5
+             , TypeHMAC SHA1
+             , TypeHMAC SHA224
+             , TypeHMAC SHA256
+             , TypeHMAC SHA384
+             , TypeHMAC SHA512
+             , TypeHMAC SHA3_224
+             , TypeHMAC SHA3_256
+             , TypeHMAC SHA3_384
+             , TypeHMAC SHA3_512
+             , TypeKMAC_SHAKE128
+             , TypeKMAC_SHAKE256
              ]
 
-instance OIDable MACAlgorithm where
-    getObjectID (HMAC MD5)    = [1,3,6,1,5,5,8,1,1]
-    getObjectID (HMAC SHA1)   = [1,3,6,1,5,5,8,1,2]
-    getObjectID (HMAC SHA224) = [1,2,840,113549,2,8]
-    getObjectID (HMAC SHA256) = [1,2,840,113549,2,9]
-    getObjectID (HMAC SHA384) = [1,2,840,113549,2,10]
-    getObjectID (HMAC SHA512) = [1,2,840,113549,2,11]
+instance OIDable MACType where
+    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)
 
-instance OIDNameable MACAlgorithm where
+instance OIDNameable MACType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 instance AlgorithmId MACAlgorithm where
-    type AlgorithmType MACAlgorithm = MACAlgorithm
+    type AlgorithmType MACAlgorithm = MACType
     algorithmName _  = "mac algorithm"
-    algorithmType    = id
-    parameterASN1S _ = id
-    parseParameter p = getNextMaybe nullOrNothing >> return p
 
+    algorithmType (HMAC alg)          = TypeHMAC alg
+    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
+
+    parseParameter (TypeHMAC alg)    = getNextMaybe nullOrNothing >> return (HMAC alg)
+    parseParameter TypeKMAC_SHAKE128 = parseKMAC p256 KMAC_SHAKE128
+    parseParameter TypeKMAC_SHAKE256 = parseKMAC p512 KMAC_SHAKE256
+
+kmacASN1S :: (KnownNat n, ASN1Elem e)
+          => Integer -> proxy n -> ByteString -> ASN1Stream e
+kmacASN1S n p str
+    | B.null str && n == i = id
+    | otherwise = asn1Container Sequence (gIntVal i . gOctetString str)
+  where i = natVal p
+
+parseKMAC :: (Monoid e, KnownNat n')
+          => Proxy n'
+          -> (forall n . KnownNat n => Proxy n -> ByteString -> MACAlgorithm)
+          -> ParseASN1 e MACAlgorithm
+parseKMAC p' fn = do
+    b <- hasNext
+    if b then parseParams else return (fn p' B.empty)
+  where
+    parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->
+        fn p <$> parseOctetStringPrim
+
 instance HasKeySize MACAlgorithm where
     getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)
-      where digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy
+    getKeySizeSpecifier (KMAC_SHAKE128 p _) =
+        KeySizeFixed (digestSizeFromProxy (SHAKE128 p))
+    getKeySizeSpecifier (KMAC_SHAKE256 p _) =
+        KeySizeFixed (digestSizeFromProxy (SHAKE256 p))
 
+digestSizeFromProxy :: Hash.HashAlgorithm a => proxy a -> Int
+digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy
+
 -- | Invoke the MAC function.
 mac :: (ByteArrayAccess key, ByteArrayAccess message)
      => MACAlgorithm -> key -> message -> MessageAuthenticationCode
@@ -441,7 +599,19 @@
         => proxy a -> k -> m -> HMAC.HMAC a
     runHMAC _ = HMAC.hmac
 
+mac (KMAC_SHAKE128 p str) = \key -> AuthTag . B.convert . run128 p str key
+  where
+    run128 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)
+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE128 n)
+    run128 _ = KMAC.kmac
 
+mac (KMAC_SHAKE256 p str) = \key -> AuthTag . B.convert . run256 p str key
+  where
+    run256 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)
+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE256 n)
+    run256 _ = KMAC.kmac
+
+
 -- Content encryption
 
 -- | CMS content encryption cipher.
@@ -491,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"
@@ -498,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
@@ -522,6 +696,8 @@
              , CFB Camellia128
 
              , CTR Camellia128
+
+             , DeriveHKDF SHA256
              ]
 
 instance OIDable ContentEncryptionAlg where
@@ -548,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
@@ -556,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
@@ -568,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
@@ -578,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
@@ -586,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 =
@@ -605,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
@@ -613,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) =
@@ -641,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)
@@ -670,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)
@@ -687,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
@@ -753,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"
@@ -760,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
@@ -773,6 +985,8 @@
              , GCM AES128
              , GCM AES192
              , GCM AES256
+
+             , AuthDeriveHKDF SHA256
              ]
 
 instance OIDable AuthContentEncryptionAlg where
@@ -788,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
@@ -832,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
@@ -846,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
@@ -860,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
@@ -868,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
@@ -913,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
@@ -924,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
@@ -933,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
@@ -981,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
@@ -1011,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)
 
@@ -1019,21 +1259,25 @@
 -- 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
-        ParamsGCM cipher iv _   -> getAEAD cipher key AEAD_GCM iv >>= decrypt
+        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
 
-    decrypt :: AEAD a -> Either StoreError ba
-    decrypt aead = maybe badMac Right (aeadSimpleDecrypt aead aad bs expected)
+    decrypt :: Int -> AEAD a -> Either StoreError ba
+    decrypt len aead
+        | B.length (unAuthTag expected) /= len = badMac
+        | otherwise = maybe badMac Right (aeadSimpleDecrypt aead aad bs expected)
 
     ccpDecrypt :: ChaChaPoly1305.State -> Either StoreError ba
     ccpDecrypt state
@@ -1050,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
 
@@ -1122,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.
@@ -1154,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
@@ -1224,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)
@@ -1242,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
@@ -1347,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))
@@ -1368,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)
@@ -1415,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
 
 
@@ -1539,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
@@ -1554,46 +1888,70 @@
 
 -- Key agreement
 
-data KeyAgreementType = TypeStdDH DigestAlgorithm
-                      | TypeCofactorDH DigestAlgorithm
+-- | Key derivation function used for key agreement.
+data KeyAgreementKDF
+    = forall hashAlg . Hash.HashAlgorithm hashAlg
+      => KA_X963_KDF (DigestProxy hashAlg)
+      -- ^ ANSI-X9.63-KDF key derivation function
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+       => KA_HKDF (DigestProxy hashAlg)
+      -- ^ Extract-and-Expand HMAC-based key derivation function
+
+deriving instance Show KeyAgreementKDF
+
+instance Eq KeyAgreementKDF where
+    KA_X963_KDF a == KA_X963_KDF b = DigestAlgorithm a == DigestAlgorithm b
+    KA_HKDF a     == KA_HKDF b     = DigestAlgorithm a == DigestAlgorithm b
+    _             == _             = False
+
+data KeyAgreementType = TypeStdDH KeyAgreementKDF
+                      | TypeCofactorDH KeyAgreementKDF
                       deriving (Show,Eq)
 
 instance Enumerable KeyAgreementType where
-    values = [ TypeStdDH (DigestAlgorithm SHA1)
-             , TypeStdDH (DigestAlgorithm SHA224)
-             , TypeStdDH (DigestAlgorithm SHA256)
-             , TypeStdDH (DigestAlgorithm SHA384)
-             , TypeStdDH (DigestAlgorithm SHA512)
+    values = [ TypeStdDH (KA_X963_KDF SHA1)
+             , TypeStdDH (KA_X963_KDF SHA224)
+             , TypeStdDH (KA_X963_KDF SHA256)
+             , TypeStdDH (KA_X963_KDF SHA384)
+             , TypeStdDH (KA_X963_KDF SHA512)
 
-             , TypeCofactorDH (DigestAlgorithm SHA1)
-             , TypeCofactorDH (DigestAlgorithm SHA224)
-             , TypeCofactorDH (DigestAlgorithm SHA256)
-             , TypeCofactorDH (DigestAlgorithm SHA384)
-             , TypeCofactorDH (DigestAlgorithm SHA512)
+             , TypeCofactorDH (KA_X963_KDF SHA1)
+             , TypeCofactorDH (KA_X963_KDF SHA224)
+             , TypeCofactorDH (KA_X963_KDF SHA256)
+             , TypeCofactorDH (KA_X963_KDF SHA384)
+             , TypeCofactorDH (KA_X963_KDF SHA512)
+
+             , TypeStdDH (KA_HKDF SHA256)
+             , TypeStdDH (KA_HKDF SHA384)
+             , TypeStdDH (KA_HKDF SHA512)
              ]
 
 instance OIDable KeyAgreementType where
-    getObjectID (TypeStdDH (DigestAlgorithm SHA1))        = [1,3,133,16,840,63,0,2]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA224))      = [1,3,132,1,11,0]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA256))      = [1,3,132,1,11,1]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA384))      = [1,3,132,1,11,2]
-    getObjectID (TypeStdDH (DigestAlgorithm SHA512))      = [1,3,132,1,11,3]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA1))        = [1,3,133,16,840,63,0,2]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA224))      = [1,3,132,1,11,0]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA256))      = [1,3,132,1,11,1]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA384))      = [1,3,132,1,11,2]
+    getObjectID (TypeStdDH (KA_X963_KDF SHA512))      = [1,3,132,1,11,3]
 
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA1))   = [1,3,133,16,840,63,0,3]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA224)) = [1,3,132,1,14,0]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA256)) = [1,3,132,1,14,1]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA384)) = [1,3,132,1,14,2]
-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA512)) = [1,3,132,1,14,3]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA1))   = [1,3,133,16,840,63,0,3]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA224)) = [1,3,132,1,14,0]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA256)) = [1,3,132,1,14,1]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA384)) = [1,3,132,1,14,2]
+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA512)) = [1,3,132,1,14,3]
 
+    getObjectID (TypeStdDH (KA_HKDF SHA256))  = [1,2,840,113549,1,9,16,3,19]
+    getObjectID (TypeStdDH (KA_HKDF SHA384))  = [1,2,840,113549,1,9,16,3,20]
+    getObjectID (TypeStdDH (KA_HKDF SHA512))  = [1,2,840,113549,1,9,16,3,21]
+
     getObjectID ty = error ("Unsupported KeyAgreementType: " ++ show ty)
 
 instance OIDNameable KeyAgreementType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 -- | Key agreement algorithm with associated parameters.
-data KeyAgreementParams = StdDH DigestAlgorithm KeyEncryptionParams
+data KeyAgreementParams = StdDH KeyAgreementKDF KeyEncryptionParams
                           -- ^ 1-Pass D-H with Stardard ECDH
-                        | CofactorDH DigestAlgorithm KeyEncryptionParams
+                        | CofactorDH KeyAgreementKDF KeyEncryptionParams
                           -- ^ 1-Pass D-H with Cofactor ECDH
                         deriving (Show,Eq)
 
@@ -1611,21 +1969,31 @@
     parseParameter (TypeCofactorDH d) = CofactorDH d <$> parseAlgorithm Sequence
 
 ecdhKeyMaterial :: (ByteArrayAccess bin, ByteArray bout)
-                => DigestAlgorithm -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout
-ecdhKeyMaterial (DigestAlgorithm hashAlg) kep ukm zz
-    | r == 0    = B.concat (map chunk [1..d])
-    | otherwise = B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)
-  where
-    (d, r)   = outLen `divMod` Hash.hashDigestSize prx
+                => KeyAgreementKDF -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout
+ecdhKeyMaterial kdf kep ukm zz =
+    case kdf of
+        KA_HKDF hashAlg ->
+            HKDF.expand (extract hashAlg zz) otherInfo outLen
+        KA_X963_KDF hashAlg
+            | r == 0    -> B.concat (map chunk [1..d])
+            | otherwise -> B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)
+          where
+            (d, r)   = outLen `divMod` Hash.hashDigestSize prx
+            prx      = hashFromProxy hashAlg
 
-    prx      = hashFromProxy hashAlg
+            chunk     = B.convert . Hash.hashFinalize . hashCtx
+            hashCtx'  = Hash.hashUpdate (Hash.hashInitWith prx) zz
+            hashCtx i = Hash.hashUpdate (Hash.hashUpdate hashCtx' $ toWord32 i) otherInfo
+
+  where
     outLen   = getMaximumKeySize kep
     outBits  = 8 * outLen
     toWord32 = i2ospOf_ 4 . fromIntegral
 
-    chunk     = B.convert . Hash.hashFinalize . hashCtx
-    hashCtx'  = Hash.hashInitWith prx
-    hashCtx i = Hash.hashUpdate (Hash.hashUpdate (Hash.hashUpdate hashCtx' zz) (toWord32 i)) otherInfo
+    extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)
+            => DigestProxy a -> ikm -> HKDF.PRK a
+    extract _ = HKDF.extract (fromMaybe B.empty ukm)
+
     otherInfo =
         let ki  = algorithmASN1S Sequence kep
             eui = case ukm of
@@ -1701,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 ->
@@ -1713,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 ->
@@ -1755,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
@@ -1790,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
@@ -1802,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
@@ -1933,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
@@ -1960,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]
@@ -2007,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
@@ -2031,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
@@ -25,12 +25,20 @@
     , setContentTypeAttr
     , getMessageDigestAttr
     , setMessageDigestAttr
+    , getSigningTimeAttr
+    , setSigningTimeAttr
+    , setSigningTimeAttrCurrent
     ) where
 
+import Control.Monad.IO.Class
+
 import Data.ASN1.Types
 import Data.ByteString (ByteString)
+import Data.Hourglass
 import Data.Maybe (fromMaybe)
 
+import Time.System (dateCurrent)
+
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.CMS.Type
@@ -124,3 +132,36 @@
 -- | Add or replace the @messageDigest@ attribute in a list of attributes.
 setMessageDigestAttr :: ByteString -> [Attribute] -> [Attribute]
 setMessageDigestAttr d = setAttributeASN1S messageDigest (gOctetString d)
+
+
+-- Signing time
+
+signingTime :: OID
+signingTime = [1,2,840,113549,1,9,5]
+
+-- | Return the value of the @signingTime@ attribute.
+getSigningTimeAttr :: [Attribute] -> Maybe DateTime
+getSigningTimeAttr attrs = runParseAttribute signingTime attrs $ do
+    ASN1Time _ t offset <- getNext
+    let validOffset = maybe True (== TimezoneOffset 0) offset
+    if validOffset
+        then return t
+        else fail "getSigningTimeAttr: invalid timezone"
+
+-- | Add or replace the @signingTime@ attribute in a list of attributes.
+setSigningTimeAttr :: DateTime -> [Attribute] -> [Attribute]
+setSigningTimeAttr t =
+    let normalize val = val { dtTime = (dtTime val) { todNSec = 0 } }
+        offset = Just (TimezoneOffset 0)
+        ty | t >= timeConvert (Date 2050 January 1) = TimeGeneralized
+           | t <  timeConvert (Date 1950 January 1) = TimeGeneralized
+           | otherwise                              = TimeUTC
+     in setAttributeASN1S signingTime (gASN1Time ty (normalize t) offset)
+
+-- | Add or replace the @signingTime@ attribute in a list of attributes with the
+-- current time.  This is equivalent to calling 'setSigningTimeAttr' with the
+-- result of 'dateCurrent'.
+setSigningTimeAttrCurrent :: MonadIO m => [Attribute] -> m [Attribute]
+setSigningTimeAttrCurrent attrs = do
+    t <- liftIO dateCurrent
+    return (setSigningTimeAttr t attrs)
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/Encrypted.hs b/src/Crypto/Store/CMS/Encrypted.hs
--- a/src/Crypto/Store/CMS/Encrypted.hs
+++ b/src/Crypto/Store/CMS/Encrypted.hs
@@ -101,5 +101,3 @@
     parseEncryptedContent = parseWrapped <|> parsePrimitive
     parseWrapped  = onNextContainer (Container Context 0) parseOctetStrings
     parsePrimitive = do Other Context 0 bs <- getNext; return bs
-    parseOctetString = do OctetString bs <- getNext; return bs
-    parseOctetStrings = B.concat <$> getMany parseOctetString
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
@@ -81,6 +86,10 @@
 --
 -- Some key-derivation functions add restrictions to what characters
 -- are supported.
+--
+-- Beware: 'Data.String.fromString' truncates multi-byte characters.
+-- If the string may contain non-ASCII characters, prefer instead
+-- @'Crypto.Store.PKCS5.fromProtectionPassword' . 'Data.String.fromString'@.
 type Password = ByteString
 
 -- | Union type related to identification of the recipient.
@@ -91,15 +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) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                do { Other Context 0 bs <- getNext; return bs }
 
 getKTVersion :: RecipientIdentifier -> Integer
 getKTVersion (RecipientIASN _) = 0
@@ -164,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
 
@@ -173,8 +179,7 @@
     parse = parseIASN <|> parseSKI <|> parsePublic
       where parseIASN = OriginatorIASN <$> parse
             parseSKI  = OriginatorSKI  <$>
-                onNextContainer (Container Context 0) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                do { Other Context 0 bs <- getNext; return bs }
             parsePublic  = OriginatorPublic <$>
                 parseOriginatorPublicKey (Container Context 1)
 
@@ -186,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
@@ -218,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)
@@ -225,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
@@ -255,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
@@ -306,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
@@ -316,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
@@ -343,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
 
@@ -355,23 +437,30 @@
         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
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return KTRecipientInfo { ktRid = rid
                                    , ktKeyTransportParams = ktp
                                    , ktEncryptedKey = ek
@@ -392,9 +481,9 @@
 
         parseKEK = KEKRI <$> do
             IntVal 4 <- getNext
-            kid <- parse
+            kid <- parseKeyIdentifier Sequence
             kep <- parseAlgorithm Sequence
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return KEKRecipientInfo { kekId = kid
                                     , kekKeyEncryptionParams = kep
                                     , kekEncryptedKey = ek
@@ -404,23 +493,29 @@
             IntVal 0 <- getNext
             kdf <- parseAlgorithm (Container Context 0)
             kep <- parseAlgorithm Sequence
-            (OctetString ek) <- getNext
+            OctetString ek <- getNext
             return PasswordRecipientInfo { priKeyDerivationFunc = kdf
                                          , priKeyEncryptionParams = kep
                                          , 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
@@ -506,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
@@ -551,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
@@ -627,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)
@@ -105,12 +108,7 @@
 dataASN1S = gOctetString
 
 parseData :: Monoid e => ParseASN1 e ByteString
-parseData = do
-    next <- getNext
-    case next of
-        OctetString bs -> return bs
-        _              -> throwParseError "Data: parsed unexpected content"
-
+parseData = parseOctetString
 
 -- Encapsulation
 
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
@@ -31,6 +31,7 @@
 import           Data.ASN1.Types
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as B
+import           Data.Hourglass
 import           Data.List
 import           Data.Maybe
 import           Data.X509
@@ -47,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
@@ -90,7 +92,7 @@
         dig <- parseAlgorithm Sequence
         sAttrs <- parseAttributes (Container Context 0)
         alg <- parseAlgorithm Sequence
-        (OctetString sig) <- getNext
+        OctetString sig <- getNext
         uAttrs <- parseAttributes (Container Context 1)
         return SignerInfo { siSignerId = sid
                           , siDigestAlgorithm = dig
@@ -116,15 +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) parseBS
-            parseBS = do { OctetString bs <- getNext; return bs }
+                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,17 +167,19 @@
 -- the full message.
 certSigner :: MonadRandom m
            => SignatureAlg
-           -> PrivKey
+           -> KeyPair
            -> CertificateChain
            -> Maybe [Attribute]
            -> [Attribute]
            -> ProducerOfSI m
-certSigner alg priv (CertificateChain chain) sAttrsM uAttrs ct msg =
-    fmap build <$> generate
+certSigner _ _ (CertificateChain []) _ _ _ _ =
+    pure $ Left (InvalidInput "Empty certificate chain")
+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
-    cert = head chain
     obj  = signedObject (getSigned cert)
     isn  = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)
     pub  = certPubKey obj
@@ -192,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
@@ -226,16 +228,17 @@
 -- valid.  All transmitted certificates are implicitely trusted and all CRLs are
 -- ignored.
 withSignerKey :: Applicative f => ConsumerOfSI f
-withSignerKey = withSignerCertificate (\_ -> pure True)
+withSignerKey = withSignerCertificate (\_ _ -> pure True)
 
 -- | Verify that the signature is valid with one of the X.509 certificates
 -- contained in the signed data, and verify that the signer certificate is valid
 -- using the validation function supplied.  All CRLs are ignored.
 withSignerCertificate :: Applicative f
-                      => (CertificateChain -> f Bool) -> ConsumerOfSI f
+                      => (Maybe DateTime -> CertificateChain -> f Bool)
+                      -> ConsumerOfSI f
 withSignerCertificate validate ct msg SignerInfo{..} certs crls =
     case getCertificateChain of
-        Just chain -> validate chain
+        Just chain -> validate mSigningTime chain
         Nothing    -> pure False
   where
     getCertificateChain = do
@@ -245,6 +248,8 @@
         guard validSignature
         return $ CertificateChain (cert : others)
 
+    mSigningTime = getSigningTimeAttr siSignedAttrs
+
     x509Certificates = mapMaybe asX509 certs
 
     asX509 (CertificateCertificate c) = Just c
@@ -322,16 +327,10 @@
     onNextContainer Sequence $ do
         OID oid <- getNext
         withObjectID "content type" oid $ \ct ->
-            wrap ct <$> onNextContainerMaybe (Container Context 0) parseInner
+            wrap ct <$> onNextContainerMaybe (Container Context 0) parseOctetString
   where
     wrap ct Nothing  = (ct, Detached)
     wrap ct (Just c) = (ct, Attached c)
-
-    parseInner = parseContentSingle <|> parseContentChunks
-
-    parseContentSingle = do { OctetString bs <- getNext; return bs }
-    parseContentChunks = onNextContainer (Container Universal 4) $
-        B.concat <$> getMany parseContentSingle
 
 digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e
 digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list
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
@@ -24,9 +24,14 @@
     , Enumerable(..)
     , OIDNameableWrapper(..)
     , withObjectID
+    -- * Parsing octet strings
+    , parseOctetString
+    , parseOctetStringPrim
+    , parseOctetStrings
     -- * Parsing and encoding ASN.1 objects
     , ASN1Event
     , ASN1ObjectExact(..)
+    , derObjectExact
     , ProduceASN1Object(..)
     , encodeASN1Object
     , ParseASN1Object(..)
@@ -41,12 +46,15 @@
     , orElse
     ) where
 
+import Control.Applicative
+
 import           Data.ASN1.BinaryEncoding
 import           Data.ASN1.BinaryEncoding.Raw
 import           Data.ASN1.Encoding
 import           Data.ASN1.OID
 import           Data.ASN1.Types
 import           Data.ByteString (ByteString)
+import qualified Data.ByteString as B
 import           Data.List (find)
 import           Data.X509
 
@@ -171,6 +179,10 @@
     , 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
 
@@ -231,3 +243,22 @@
     case va of
         Nothing -> pb
         _       -> return va
+
+-- | Parse an octet string, in primitive or constructed encodings.
+parseOctetString :: Monoid e => ParseASN1 e ByteString
+parseOctetString = parseOctetStringPrim <|> parseConstructed
+  where
+    parseConstructed = onNextContainer (Container Universal 4) parseOctetStrings
+
+-- | Parse an octet string in primitive encoding.
+parseOctetStringPrim :: Monoid e => ParseASN1 e ByteString
+parseOctetStringPrim = do
+    next <- getNext
+    case next of
+        OctetString bs -> return bs
+        _ -> throwParseError "parseOctetStringPrim: parsed unexpected content"
+
+-- | Parse some octet strings, in primitive or constructed encodings, and
+-- concatenate the result.
+parseOctetStrings :: Monoid e => ParseASN1 e ByteString
+parseOctetStrings = B.concat <$> getMany parseOctetString
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,15 +8,17 @@
 -- 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 #-}
 {-# LANGUAGE RecordWildCards #-}
 {-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 module Crypto.Store.PKCS12
-    ( IntegrityParams
+    ( IntegrityParams(..)
     , readP12File
     , readP12FileFromMemory
     , writeP12File
@@ -58,19 +60,29 @@
     , toNamedCredential
     -- * Password-based protection
     , Password
+    , OptAuthenticated(..)
+    , recoverAuthenticated
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , OptProtected(..)
     , recover
     , 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
 
@@ -82,47 +94,101 @@
 import Crypto.Store.CMS.Algorithms
 import Crypto.Store.CMS.Attribute
 import Crypto.Store.CMS.Encrypted
+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
 
 
+-- Password-based integrity
+
+-- | Data type for objects that are possibly authenticated with a password.
+--
+-- Content is verified and retrieved by providing a 'Password' value.  When
+-- verification is successful, a value of type 'ProtectionPassword' is also
+-- returned and this value can be fed to an inner decryption layer that needs
+-- the same password (usual case for PKCS #12).
+data OptAuthenticated a = Unauthenticated a
+                          -- ^ Value is not authenticated
+                        | Authenticated (Password -> Either StoreError (ProtectionPassword, a))
+                          -- ^ Value is authenticated with a password
+
+instance Functor OptAuthenticated where
+    fmap f (Unauthenticated x) = Unauthenticated (f x)
+    fmap f (Authenticated g)   = Authenticated (fmap (fmap f) . g)
+
+-- | Try to recover an 'OptAuthenticated' content using the specified password.
+--
+-- When successful, the content is returned, as well as the password converted
+-- to type 'ProtectionPassword'.  This password value can then be fed to the
+-- inner decryption layer when both passwords are known to be same (usual case
+-- for PKCS #12).
+recoverAuthenticated :: Password -> OptAuthenticated a -> Either StoreError (ProtectionPassword, a)
+recoverAuthenticated pwd (Unauthenticated x) = Right (toProtectionPassword pwd, x)
+recoverAuthenticated pwd (Authenticated f)   = f pwd
+
+
 -- Decoding and parsing
 
 -- | Read a PKCS #12 file from disk.
-readP12File :: FilePath -> IO (Either StoreError (OptProtected PKCS12))
+readP12File :: FilePath -> IO (Either StoreError (OptAuthenticated PKCS12))
 readP12File path = readP12FileFromMemory <$> BS.readFile path
 
 -- | Read a PKCS #12 file from a bytearray in BER format.
-readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptProtected PKCS12)
+readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptAuthenticated PKCS12)
 readP12FileFromMemory ber = decode ber >>= integrity
   where
     integrity PFX{..} =
         case macData of
-            Nothing -> Unprotected <$> decode authSafeData
-            Just md -> return $ Protected (verify md authSafeData)
+            Nothing -> Unauthenticated <$> decode authSafeData
+            Just md -> return $ Authenticated (verify md authSafeData)
 
     verify MacData{..} content pwdUTF8 =
-        case digAlg of
-            DigestAlgorithm d ->
-                let fn key macAlg bs
-                        | not (securityAcceptable macAlg) =
-                            Left (InvalidParameter "Integrity MAC too weak")
-                        | macValue == mac macAlg key bs = decode bs
-                        | otherwise = Left BadContentMAC
-                 in pkcs12mac Left fn d macParams content 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 digAlg bs
+                            | not (securityAcceptable digAlg) =
+                                Left (InvalidParameter "Integrity MAC too weak")
+                            | 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
-             -> IntegrityParams -> Password
+             -> IntegrityParams -> ProtectionPassword
              -> PKCS12
              -> IO (Either StoreError ())
 writeP12File path intp pw aSafe =
@@ -131,21 +197,36 @@
         Right bs -> Right <$> BS.writeFile path bs
 
 -- | Write a PKCS #12 file to a bytearray in DER format.
-writeP12FileToMemory :: IntegrityParams -> Password
+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
@@ -189,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
     }
@@ -201,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
@@ -263,7 +353,7 @@
 unencrypted = PKCS12 . (:[]) . Unencrypted
 
 -- | Build a PKCS #12 encrypted with the specified scheme and password.
-encrypted :: EncryptionScheme -> Password -> SafeContents -> Either StoreError PKCS12
+encrypted :: EncryptionScheme -> ProtectionPassword -> SafeContents -> Either StoreError PKCS12
 encrypted alg pwd sc = PKCS12 . (:[]) . Encrypted <$> encrypt alg pwd bs
   where bs = encodeASN1Object sc
 
@@ -412,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
@@ -472,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 []           = []
@@ -489,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
@@ -502,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]
@@ -557,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))
@@ -586,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.
 --
@@ -603,12 +698,12 @@
 -- values so that the salt is not reused twice in the encryption process.
 fromCredential :: Maybe EncryptionScheme -- for certificates
                -> EncryptionScheme       -- for private key
-               -> Password
+               -> ProtectionPassword
                -> (X509.CertificateChain, X509.PrivKey)
                -> 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.
@@ -618,7 +713,7 @@
 fromNamedCredential :: String
                     -> Maybe EncryptionScheme -- for certificates
                     -> EncryptionScheme       -- for private key
-                    -> Password
+                    -> ProtectionPassword
                     -> (X509.CertificateChain, X509.PrivKey)
                     -> Either StoreError PKCS12
 fromNamedCredential name = fromCredential' (setFriendlyName name)
@@ -626,12 +721,13 @@
 fromCredential' :: ([Attribute] -> [Attribute])
                 -> Maybe EncryptionScheme -- for certificates
                 -> EncryptionScheme       -- for private key
-                -> Password
+                -> ProtectionPassword
                 -> (X509.CertificateChain, X509.PrivKey)
                 -> Either StoreError PKCS12
-fromCredential' trans algChain algKey pwd (X509.CertificateChain certs, key)
-    | null certs = Left (InvalidInput "Empty certificate chain")
-    | otherwise  = (<>) <$> pkcs12Chain <*> pkcs12Key
+fromCredential' _ _ _ _ (X509.CertificateChain [], _) =
+    Left (InvalidInput "Empty certificate chain")
+fromCredential' trans algChain algKey pwd (X509.CertificateChain certs@(leaf:_), key) =
+    (<>) <$> pkcs12Chain <*> pkcs12Key
   where
     pkcs12Key   = unencrypted <$> scKeyOrError
     pkcs12Chain =
@@ -643,12 +739,16 @@
     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 (head certs) X509.HashSHA1
+    X509.Fingerprint keyId = X509.getFingerprint leaf X509.HashSHA1
     attrs = trans (setLocalKeyId keyId [])
 
 -- Standard attributes
@@ -743,7 +843,7 @@
 parseOctetStringObject :: (Monoid e, ParseASN1Object [ASN1Event] obj)
                        => String -> ParseASN1 e obj
 parseOctetStringObject name = do
-    OctetString bs <- getNext
+    bs <- parseOctetString
     case decode bs of
         Left e  -> throwParseError (name ++ ": " ++ show e)
         Right c -> return c
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
@@ -11,7 +11,10 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE TypeFamilies #-}
 module Crypto.Store.PKCS5
-    ( Password
+    ( ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , EncryptedContent
     -- * High-level API
     , PKCS5(..)
@@ -21,6 +24,9 @@
     , EncryptionScheme(..)
     , PBEParameter(..)
     , PBES2Parameter(..)
+    -- * Message authentication schemes
+    , AuthenticationScheme(..)
+    , PBMAC1Parameter(..)
     -- * Key derivation
     , KeyDerivationFunc(..)
     , PBKDF2_PRF(..)
@@ -30,23 +36,27 @@
     , ContentEncryptionParams
     , ContentEncryptionAlg(..)
     , ContentEncryptionCipher(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
+    , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
     , getContentEncryptionAlg
     -- * Low-level API
     , pbEncrypt
     , pbDecrypt
+    , pbMac
     ) where
 
 import           Data.ASN1.Types
-import           Data.ByteArray (ByteArrayAccess)
 import           Data.ByteString (ByteString)
 import           Data.Maybe (fromMaybe)
 
 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.Enveloped
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 import Crypto.Store.PKCS5.PBES1
@@ -163,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).
@@ -192,20 +261,20 @@
     fromASN1 = runParseASN1State parse
 
 -- | Encrypt a bytestring with the specified encryption scheme and password.
-encrypt :: EncryptionScheme -> Password -> ByteString -> Either StoreError PKCS5
+encrypt :: EncryptionScheme -> ProtectionPassword -> ByteString -> Either StoreError PKCS5
 encrypt alg pwd bs = build <$> pbEncrypt alg bs pwd
   where
     build ed = ed `seq` PKCS5 { encryptionAlgorithm = alg, encryptedData = ed }
 
 -- | Decrypt the PKCS #5 content with the specified password.
-decrypt :: PKCS5 -> Password -> Either StoreError ByteString
+decrypt :: PKCS5 -> ProtectionPassword -> Either StoreError ByteString
 decrypt obj = pbDecrypt (encryptionAlgorithm obj) (encryptedData obj)
 
 
 -- Encryption Schemes
 
 -- | Encrypt a bytestring with the specified encryption scheme and password.
-pbEncrypt :: EncryptionScheme -> ByteString -> Password
+pbEncrypt :: EncryptionScheme -> ByteString -> ProtectionPassword
           -> Either StoreError EncryptedContent
 pbEncrypt (PBES2 p)                 = pbes2  contentEncrypt p
 pbEncrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentEncrypt MD5  DES p
@@ -219,7 +288,7 @@
 
 -- | Decrypt an encrypted bytestring with the specified encryption scheme and
 -- password.
-pbDecrypt :: EncryptionScheme -> EncryptedContent -> Password -> Either StoreError ByteString
+pbDecrypt :: EncryptionScheme -> EncryptedContent -> ProtectionPassword -> Either StoreError ByteString
 pbDecrypt (PBES2 p)                 = pbes2  contentDecrypt p
 pbDecrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentDecrypt MD5  DES p
 pbDecrypt (PBE_SHA1_DES_CBC p)      = pkcs5  Left contentDecrypt SHA1 DES p
@@ -230,9 +299,21 @@
 pbDecrypt (PBE_SHA1_RC2_128 p)      = pkcs12rc2 Left contentDecrypt SHA1 128 p
 pbDecrypt (PBE_SHA1_RC2_40 p)       = pkcs12rc2 Left contentDecrypt SHA1 40 p
 
-pbes2 :: ByteArrayAccess password
-      => (Key -> ContentEncryptionParams -> ByteString -> result)
-      -> PBES2Parameter -> ByteString -> password -> result
+pbes2 :: (Key -> ContentEncryptionParams -> ByteString -> result)
+      -> PBES2Parameter -> ByteString -> ProtectionPassword -> result
 pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs
-  where key = kdfDerive pbes2KDF len pwd :: Key
+  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/PKCS5/PBES1.hs b/src/Crypto/Store/PKCS5/PBES1.hs
--- a/src/Crypto/Store/PKCS5/PBES1.hs
+++ b/src/Crypto/Store/PKCS5/PBES1.hs
@@ -14,6 +14,11 @@
 module Crypto.Store.PKCS5.PBES1
     ( PBEParameter(..)
     , Key
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
+    , toProtectionPasswords
     , pkcs5
     , pkcs12
     , pkcs12rc2
@@ -38,6 +43,7 @@
 import           Data.ByteString (ByteString)
 import           Data.Maybe (fromMaybe)
 import           Data.Memory.PtrMethods
+import           Data.String (IsString(..))
 import           Data.Word
 
 import           Foreign.Ptr (plusPtr)
@@ -49,6 +55,64 @@
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 
+-- | A password stored as a sequence of UTF-8 bytes.
+--
+-- Some key-derivation functions add restrictions to what characters
+-- are supported.
+--
+-- The data type provides a special value 'emptyNotTerminated' that is used
+-- as alternate representation of empty passwords on some systems and that
+-- produces encryption results different than an empty bytearray.
+--
+-- Conversion to/from a regular sequence of bytes is possible with functions
+-- 'toProtectionPassword' and 'fromProtectionPassword'.
+--
+-- Beware: the 'fromString' implementation correctly handles multi-byte
+-- characters, so here is not equivalent to the 'ByteString' counterpart.
+data ProtectionPassword = NullPassword | PasswordUTF8 ByteString
+    deriving Eq
+
+instance Show ProtectionPassword where
+    showsPrec _ NullPassword     = showString "emptyNotTerminated"
+    showsPrec d (PasswordUTF8 b) = showParen (d > 10) $
+        showString "toProtectionPassword " . showsPrec 11 b
+
+instance IsString ProtectionPassword where
+    fromString = PasswordUTF8 . B.convert . S.toBytes S.UTF8 . fromString
+
+instance ByteArrayAccess ProtectionPassword where
+    length = applyPP 0 B.length
+    withByteArray = B.withByteArray . fromProtectionPassword
+
+applyPP :: a -> (ByteString -> a) -> ProtectionPassword -> a
+applyPP d _ NullPassword     = d
+applyPP _ f (PasswordUTF8 b) = f b
+
+-- | A value denoting an empty password, but having a special encoding when
+-- deriving a symmetric key on some systems, like the certificate export
+-- wizard on Windows.
+--
+-- This value is different from @'toProtectionPassword' ""@ and can be tried
+-- when decrypting content with a password known to be empty.
+emptyNotTerminated :: ProtectionPassword
+emptyNotTerminated = NullPassword
+
+-- | Extract the UTF-8 bytes in a password value.
+fromProtectionPassword :: ProtectionPassword -> ByteString
+fromProtectionPassword = applyPP B.empty id
+
+-- | Build a password value from a sequence of UTF-8 bytes.
+--
+-- When the password is empty, the special value 'emptyNotTerminated' may
+-- be tried as well.
+toProtectionPassword :: ByteString -> ProtectionPassword
+toProtectionPassword = PasswordUTF8
+
+toProtectionPasswords :: ByteString -> [ProtectionPassword]
+toProtectionPasswords bs
+    | B.null bs = [PasswordUTF8 B.empty, NullPassword]
+    | otherwise = [PasswordUTF8 bs]
+
 -- | Secret key.
 type Key = B.ScrubbedBytes
 
@@ -88,8 +152,9 @@
 rc4Combine key = Right . snd . RC4.combine (RC4.initialize key)
 
 -- | Conversion to UCS2 from UTF-8, ignoring non-BMP bits.
-toUCS2 :: (ByteArrayAccess butf8, ByteArray bucs2) => butf8 -> Maybe bucs2
-toUCS2 pwdUTF8
+toUCS2 :: ByteArray bucs2 => ProtectionPassword -> Maybe bucs2
+toUCS2 NullPassword = Just B.empty
+toUCS2 (PasswordUTF8 pwdUTF8)
     | B.null r  = Just pwdUCS2
     | otherwise = Nothing
   where
@@ -105,19 +170,19 @@
 
 -- | Apply PBKDF1 on the specified password and run an encryption or decryption
 -- function on some input using derived key and IV.
-pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)
+pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher)
       => (StoreError -> result)
       -> (Key -> ContentEncryptionParams -> ByteString -> result)
       -> DigestProxy hash
       -> ContentEncryptionCipher cipher
       -> PBEParameter
       -> ByteString
-      -> password
+      -> ProtectionPassword
       -> result
 pkcs5 failure encdec hashAlg cec pbeParam bs pwd
     | proxyBlockSize cec /= 8 = failure (InvalidParameter "Invalid cipher block size")
     | otherwise =
-        case pbkdf1 hashAlg pwd pbeParam 16 of
+        case pbkdf1 hashAlg (fromProtectionPassword pwd) pbeParam 16 of
             Left err -> failure err
             Right dk ->
                 let (key, iv) = B.splitAt 8 (dk :: Key)
@@ -145,14 +210,14 @@
 
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key and IV.
-pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)
+pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher)
        => (StoreError -> result)
        -> (Key -> ContentEncryptionParams -> ByteString -> result)
        -> DigestProxy hash
        -> ContentEncryptionCipher cipher
        -> PBEParameter
        -> ByteString
-       -> password
+       -> ProtectionPassword
        -> result
 pkcs12 failure encdec hashAlg cec pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -168,14 +233,14 @@
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key and IV.  This variant
 -- uses an RC2 cipher with the EKL specified (effective key length).
-pkcs12rc2 :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12rc2 :: Hash.HashAlgorithm hash
           => (StoreError -> result)
           -> (Key -> ContentEncryptionParams -> ByteString -> result)
           -> DigestProxy hash
           -> Int
           -> PBEParameter
           -> ByteString
-          -> password
+          -> ProtectionPassword
           -> result
 pkcs12rc2 failure encdec hashAlg len pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -191,14 +256,14 @@
 -- | Apply PKCS #12 derivation on the specified password and run an encryption
 -- or decryption function on some input using derived key.  This variant does
 -- not derive any IV and is required for RC4.
-pkcs12stream :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12stream :: Hash.HashAlgorithm hash
              => (StoreError -> result)
              -> (Key -> ByteString -> result)
              -> DigestProxy hash
              -> Int
              -> PBEParameter
              -> ByteString
-             -> password
+             -> ProtectionPassword
              -> result
 pkcs12stream failure encdec hashAlg keyLen pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
@@ -209,13 +274,13 @@
 
 -- | Apply PKCS #12 derivation on the specified password and run a MAC function
 -- on some input using derived key.
-pkcs12mac :: (Hash.HashAlgorithm hash, ByteArrayAccess password)
+pkcs12mac :: Hash.HashAlgorithm hash
           => (StoreError -> result)
           -> (Key -> MACAlgorithm -> ByteString -> result)
           -> DigestProxy hash
           -> PBEParameter
           -> ByteString
-          -> password
+          -> ProtectionPassword
           -> result
 pkcs12mac failure macFn hashAlg pbeParam bs pwdUTF8 =
     case toUCS2 pwdUTF8 of
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,17 +29,26 @@
     ( readKeyFile
     , readKeyFileFromMemory
     , pemToKey
+    , pemToKeyAccum
     , writeKeyFile
     , writeKeyFileToMemory
     , keyToPEM
     , writeEncryptedKeyFile
     , writeEncryptedKeyFileToMemory
     , encryptKeyToPEM
+    -- * Key pairs
+    , KeyPair
+    , keyPairFromPrivKey
+    , keyPairToPrivKey
+    , keyPairToPubKey
     -- * Serialization formats
     , PrivateKeyFormat(..)
     , FormattedKey(..)
     -- * Password-based protection
-    , Password
+    , ProtectionPassword
+    , emptyNotTerminated
+    , fromProtectionPassword
+    , toProtectionPassword
     , OptProtected(..)
     , recover
     , recoverA
@@ -51,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
@@ -63,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
@@ -78,7 +95,7 @@
 -- | Data type for objects that are possibly protected with a password.
 data OptProtected a = Unprotected a
                       -- ^ Value is unprotected
-                    | Protected (Password -> Either StoreError a)
+                    | Protected (ProtectionPassword -> Either StoreError a)
                       -- ^ Value is protected with a password
 
 instance Functor OptProtected where
@@ -86,7 +103,7 @@
     fmap f (Protected g)   = Protected (fmap f . g)
 
 -- | Try to recover an 'OptProtected' content using the specified password.
-recover :: Password -> OptProtected a -> Either StoreError a
+recover :: ProtectionPassword -> OptProtected a -> Either StoreError a
 recover _   (Unprotected x) = Right x
 recover pwd (Protected f)   = f pwd
 
@@ -98,12 +115,14 @@
 -- >
 -- > [encryptedKey] <- readKeyFile "privkey.pem"
 -- > let askForPassword = putStr "Please enter password: " >> B.getLine
--- > result <- recoverA askForPassword encryptedKey
+-- > result <- recoverA (toProtectionPassword <$> askForPassword) encryptedKey
 -- > case result of
 -- >     Left err  -> putStrLn $ "Unable to recover key: " ++ show err
 -- >     Right key -> print key
 recoverA :: Applicative f
-         => f Password -> OptProtected a -> f (Either StoreError a)
+         => f ProtectionPassword
+         -> OptProtected a
+         -> f (Either StoreError a)
 recoverA _   (Unprotected x) = pure (Right x)
 recoverA get (Protected f)   = fmap f get
 
@@ -111,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.
@@ -178,10 +206,10 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFile :: FilePath
-                      -> EncryptionScheme -> Password-> 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.
@@ -191,66 +219,67 @@
 --
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
-writeEncryptedKeyFileToMemory :: EncryptionScheme -> Password -> X509.PrivKey
-                              -> Either StoreError B.ByteString
-writeEncryptedKeyFileToMemory alg pwd privKey =
-    pemWriteBS <$> encryptKeyToPEM alg pwd privKey
+writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword
+                              -> 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 -> Password -> 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.
@@ -265,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)
@@ -281,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
 
@@ -298,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) =
@@ -391,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)) =
@@ -437,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)
 
@@ -477,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
@@ -497,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
@@ -626,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)
@@ -707,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
@@ -1,9 +1,9 @@
+{-# LANGUAGE OverloadedStrings #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module CMS.Instances
     ( arbitraryPassword
     , arbitraryAttributes
-    , arbitraryIntegrityDigest
     , arbitrarySigVer
     , arbitraryEnvDev
     ) where
@@ -11,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
@@ -21,6 +22,7 @@
 
 import Crypto.Store.CMS
 import Crypto.Store.Error
+import Crypto.Store.PKCS8
 
 import X509.Instances
 
@@ -125,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
@@ -137,24 +139,37 @@
         , 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 = (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest
+    arbitrary = oneof
+        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryDigest
+        , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx
+        , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx
+        ]
+      where arbitraryCtx = elements [ B.empty , "ctx" , "custom string" ]
 
 instance Arbitrary OAEPParams where
     arbitrary = do
@@ -197,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
@@ -233,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
@@ -270,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
@@ -291,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
@@ -344,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
@@ -377,20 +409,25 @@
 arbitraryAgreeParams :: Bool -> KeyEncryptionParams -> Gen KeyAgreementParams
 arbitraryAgreeParams allowCofactorDH alg
     | allowCofactorDH = oneof
-        [ flip StdDH alg <$> arbitraryDigest
-        , flip CofactorDH alg <$> arbitraryDigest
+        [ flip StdDH alg <$> elements stdKDFs
+        , flip CofactorDH alg <$> elements cofactorKDFs
         ]
-    | otherwise = flip StdDH alg <$> arbitraryDigest
+    | otherwise = flip StdDH alg <$> elements stdKDFs
   where
-    arbitraryDigest =
-        elements
-            [ DigestAlgorithm SHA1
-            , DigestAlgorithm SHA224
-            , DigestAlgorithm SHA256
-            , DigestAlgorithm SHA384
-            , DigestAlgorithm SHA512
-            ]
+    cofactorKDFs =
+        [ KA_X963_KDF SHA1
+        , KA_X963_KDF SHA224
+        , KA_X963_KDF SHA256
+        , KA_X963_KDF SHA384
+        , KA_X963_KDF SHA512
+        ]
 
+    stdKDFs = cofactorKDFs ++
+        [ KA_HKDF SHA256
+        , KA_HKDF SHA384
+        , KA_HKDF SHA512
+        ]
+
 arbitraryEnvDev :: ContentEncryptionKey
                 -> Gen ([ProducerOfRI Gen], ConsumerOfRI Gen)
 arbitraryEnvDev cek = sized $ \n -> do
@@ -400,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
@@ -430,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
@@ -463,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
@@ -230,29 +235,90 @@
 
 authEnvelopedDataTests :: TestTree
 authEnvelopedDataTests =
-    testCaseSteps "AuthEnvelopedData" $ \step -> do
-        cms <- readCMSFile path
-        assertEqual "unexpected parse count" count (length cms)
+    testGroup "AuthEnvelopedData"
+        [ testKT "KTRI" path3
+        , testKA "KARI" path4
+        , test "KEKRI" path1 withRecipientKey
+        ]
+  where test caseName path f = testCaseSteps caseName $ \step -> do
+            cms <- readCMSFile path
+            assertEqual "unexpected parse count" (length keys) (length cms)
 
-        forM_ (zip [0..] cms) $ \(index, ci) -> do
-            step ("testing vector " ++ show (index :: Int))
+            forM_ (zip [0..] cms) $ \(index, ci) -> do
+                let (name, key) = keys !! index
+
+                step ("testing " ++ name)
+                ae <- getAuthEnveloppedAttached ci
+                result <- openAuthEnvelopedData (f key) ae
+                assertRight result (verifyInnerMessage message)
+        testKT caseName path = testCaseSteps caseName $ \step -> do
+            let rsaPath = testFile "rsa-unencrypted-pkcs8.pem"
+            [Unprotected priv] <- readKeyFile rsaPath
+
+            cms <- readCMSFile path
+            assertEqual "unexpected parse count" (length modes * length keys) (length cms)
+
+            let pairs = [ (c, m) | c <- map fst keys, m <- modes ]
+            forM_ (zip pairs cms) $ \((c, m), ci) -> do
+                step ("testing " ++ c ++ " with " ++ m)
+                ae <- getAuthEnveloppedAttached ci
+                result <- openAuthEnvelopedData (withRecipientKeyTrans priv) ae
+                assertRight result (verifyInnerMessage message)
+        testKA caseName path = testCaseSteps caseName $ \step -> do
+            let ecdsaKeyPath  = testFile "ecdsa-p256-unencrypted-pkcs8.pem"
+                ecdsaCertPath = testFile "ecdsa-p256-self-signed-cert.pem"
+            [Unprotected priv] <- readKeyFile ecdsaKeyPath
+            [cert] <- readSignedObject ecdsaCertPath
+
+            cms <- readCMSFile path
+            assertEqual "unexpected parse count" (length mds * length keys) (length cms)
+
+            let pairs = [ (c, h) | c <- map fst keys, h <- mds ]
+            forM_ (zip pairs cms) $ \((c, h), ci) -> do
+                step ("testing " ++ c ++ " with " ++ h)
+                ae <- getAuthEnveloppedAttached ci
+                result <- openAuthEnvelopedData (withRecipientKeyAgree priv cert) ae
+                assertRight result (verifyInnerMessage message)
+        getAuthEnveloppedAttached ci = do
             assertBool "unexpected type" (hasType AuthEnvelopedDataType ci)
             let AuthEnvelopedDataCI aeEncap = ci
-            ae <- getAttached aeEncap
-            result <- openAuthEnvelopedData (withRecipientPassword pwd) ae
-            assertRight result (verifyInnerMessage msg)
+            getAttached aeEncap
+        path1 = testFile "cms-auth-enveloped-kekri-data.pem"
+        path3 = testFile "cms-auth-enveloped-ktri-data.pem"
+        path4 = testFile "cms-auth-enveloped-kari-data.pem"
+        keys  = [ ("AES128_GCM",           testKey 16)
+                , ("AES192_GCM",           testKey 24)
+                , ("AES256_GCM",           testKey 32)
+                ]
+        modes = [ "RSAES-PKCS1"
+                , "RSAES-OAEP"
+                ]
+        mds   = [ "SHA1"
+                , "SHA224"
+                , "SHA256"
+                , "SHA384"
+                , "SHA512"
+                ]
 
-            step ("testing encoded vector " ++ show index)
-            let [Just ci'] = pemToContentInfo [] (contentInfoToPEM ci)
-                AuthEnvelopedDataCI aeEncap' = ci'
-            ae' <- getAttached aeEncap'
-            result' <- openAuthEnvelopedData (withRecipientPassword pwd) ae'
-            assertRight result' (verifyInnerMessage msg)
-  where path  = testFile "cms-auth-enveloped-data-rfc6476.pem"
-        pwd   = fromString "password"
-        msg   = fromString "Some test data\NUL"
-        count = 2
+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 ->
@@ -324,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,10 +31,32 @@
 arbitraryAlias = resize 16 asciiChar
   where asciiChar = listOf $ choose ('\x20','\x7f')
 
-arbitraryIntegrityParams :: Gen IntegrityParams
-arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
+instance Arbitrary PBMAC1Parameter where
+    arbitrary = PBMAC1Parameter <$> arbitrary <*> arbitrary
 
-arbitraryPKCS12 :: Password -> Gen PKCS12
+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
     p <- one
     ps <- listOf one
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)
@@ -38,13 +39,12 @@
         let r = readP12FileFromMemory (pemContent pem)
 
         assertRight r $ \integrity ->
-            assertRight (recover pwd integrity) $ \privacy ->
-                assertRight (recover pwd $ unPKCS12 privacy) $ \scs -> do
+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
                     step ("Testing " ++ name)
-                    recover pwd (getAllSafeKeys scs) @?= Right [key]
+                    recover ppwd (getAllSafeKeys scs) @?= Right [key]
                     getAllSafeX509Certs scs @?= certs
   where
-    pwd :: Password
     pwd = fromString "dontchangeme"
 
     nameIntegrity n = "integrity with " ++ n
@@ -60,6 +60,8 @@
     integrityModes = [ "SHA-1"
                      , "SHA-256"
                      , "SHA-384"
+                     , "PBMAC1 with SHA-256"
+                     , "PBMAC1 with SHA-512"
                      ]
 
     privacyModes   = [ "aes-128-cbc"
@@ -67,33 +69,105 @@
                      , "PBE-SHA1-RC2-40"
                      ]
 
+testEmptyPassword :: TestTree
+testEmptyPassword = testCaseSteps "empty password" $ \step -> do
+    step "Reading PKCS #12 files"
+    pems <- readPEMs path
+    length pems @?= length infos
+
+    forM_ (zip infos pems) $ \((name, numKeys, numCerts), pem) -> do
+        let r = readP12FileFromMemory (pemContent pem)
+
+        assertRight r $ \integrity ->
+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
+                    step ("Testing " ++ name)
+                    assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->
+                        length keys @?= numKeys
+                    length (getAllSafeX509Certs scs) @?= numCerts
+  where
+    pwd = fromString ""
+
+    path  = testFile "pkcs12-empty-password.pem"
+    infos = [ ("Windows Certificate Export Wizard", 1, 2)
+            , ("OpenSSL", 1, 1)
+            , ("GnuTLS with --empty-password", 1, 1)
+            , ("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
-        pE <- arbitraryPassword
+        pE <- arbitrary
         c <- arbitraryPKCS12 pE
         let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c
-        return $ Right (Right c) === (recover (fromString "not-used") <$> r)
-    , testProperty "marshalling with authentication" $ do
-        params <- arbitraryIntegrityParams
-        c <- arbitraryPassword >>= arbitraryPKCS12
-        pI <- arbitraryPassword
+            unused = fromString "not-used"
+        return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)
+    , testProperty "marshalling with traditional authentication" $ do
+        params <- arbitraryTraditionalIntegrity
+        c <- arbitrary >>= arbitraryPKCS12
+        pI <- arbitrary
         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
-        return $ Right (Right (Right c)) === (fmap (recover pI) <$> r)
+            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 <- arbitraryPassword
-        chain <- arbitrary >>= arbitraryCertificateChain
+        pwd <- arbitrary
+        let pub = keyPairToPubKey (keyPairFromPrivKey privKey)
+        chain <- arbitraryCertificateChain pub
         chain' <- shuffleCertificateChain chain
         let cred = (chain, privKey)
             r = from pwd (chain', privKey) >>= recover pwd . to
@@ -104,5 +178,7 @@
     testGroup "PKCS12"
         [ 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
@@ -15,6 +15,11 @@
 
 import CMS.Instances
 
+instance Arbitrary ProtectionPassword where
+    arbitrary = oneof [ return emptyNotTerminated
+                      , toProtectionPassword <$> arbitraryPassword
+                      ]
+
 instance Arbitrary PBEParameter where
     arbitrary = do
         salt <- generateSalt 8
@@ -35,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
@@ -11,10 +12,12 @@
 import Test.Tasty.QuickCheck
 
 import Util
-import PKCS8.Instances
+import PKCS8.Instances ()
 
-keyTests :: String -> TestTree
-keyTests prefix =
+data KeyTestType = InnerOuter | OnlyOuter
+
+keyTests :: KeyTestType -> String -> TestTree
+keyTests InnerOuter prefix =
     testGroup "PrivateKey"
         [ testCase "read outer" $ do
               kOuter <- readKeyFile fOuter
@@ -43,6 +46,19 @@
   where
     fInner = testFile (prefix ++ "-unencrypted-trad.pem")
     fOuter = testFile (prefix ++ "-unencrypted-pkcs8.pem")
+keyTests OnlyOuter prefix =
+    testGroup "PrivateKey"
+        [ testCase "read" $ do
+              kOuter <- readKeyFile fOuter
+              length kOuter @?= 1
+        , testCase "write" $ do
+              bs <- B.readFile fOuter
+              let kOuter = readKeyFileFromMemory bs
+                  [Unprotected kO] = kOuter
+              writeKeyFileToMemory PKCS8Format [kO] @?= bs
+        ]
+  where
+    fOuter = testFile (prefix ++ "-unencrypted-pkcs8.pem")
 
 encryptedKeyTests :: String -> TestTree
 encryptedKeyTests prefix =
@@ -72,20 +88,26 @@
                            in all (\(Protected getKey) -> getKey pwd == Right key) kE
                 ]
 
-testType :: TestName -> String -> TestTree
-testType name prefix =
+testType :: TestName -> KeyTestType -> String -> TestTree
+testType name ty prefix =
     testGroup name
-        [ keyTests prefix
+        [ keyTests ty prefix
         , 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 ->
         let r = readKeyFileFromMemory $ writeKeyFileToMemory fmt l
         in map Right l === map (recover $ fromString "not-used") r
     , testProperty "marshalling with encryption" $ \es k -> do
-        p <- arbitraryPassword
+        p <- arbitrary
         let r = readKeyFileFromMemory <$> writeEncryptedKeyFileToMemory es p k
         return $ Right [Right k] === (map (recover p) <$> r)
     ]
@@ -93,13 +115,14 @@
 pkcs8Tests :: TestTree
 pkcs8Tests =
     testGroup "PKCS8"
-        [ testType "RSA"                        "rsa"
-        , testType "DSA"                        "dsa"
-        , testType "EC (named curve)"           "ecdsa-p256"
-        , testType "EC (explicit prime curve)"  "ecdsa-epc"
-        , testType "X25519"                     "x25519"
-        , testType "X448"                       "x448"
-        , testType "Ed25519"                    "ed25519"
-        , testType "Ed448"                      "ed448"
+        [ testType "RSA"                        InnerOuter  "rsa"
+        , testType "DSA"                        InnerOuter  "dsa"
+        , testType "EC (named curve)"           InnerOuter  "ecdsa-p256"
+        , testType "EC (explicit prime curve)"  InnerOuter  "ecdsa-epc"
+        , testType "X25519"                     OnlyOuter   "x25519"
+        , 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
@@ -35,7 +36,7 @@
               writeSignedObjectToMemory objs @?= bs
         , testCase "write public key" $ do
               bs <- B.readFile fKey
-              let key = head (readPubKeyFileFromMemory bs)
+              let (key : _) = readPubKeyFileFromMemory bs
               assertBool "first key differs" $
                   writePubKeyFileToMemory [key] `B.isPrefixOf` bs
         ]
@@ -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-auth-enveloped-data-rfc6476.pem b/tests/files/cms-auth-enveloped-data-rfc6476.pem
deleted file mode 100644
--- a/tests/files/cms-auth-enveloped-data-rfc6476.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN PKCS7-----
-MIHjBgsqhkiG9w0BCRABF6CB0zCB0AIBADFho18CAQCgGwYJKoZIhvcNAQUMMA4E
-CLfrI6dr0gUWAgITiDAjBgsqhkiG9w0BCRADCTAUBggqhkiG9w0DBwQIZpECRWtz
-u5kEGDCjerXY8odQ7EEEromZJvAurk/j81IrozBSBgkqhkiG9w0BBwEwMwYLKoZI
-hvcNAQkQAw8wJDAUBggqhkiG9w0DBwQI0tCBcU09nxEwDAYIKwYBBQUIAQIFAIAQ
-OsYGYUFdAH0RNc1p4VbKEAQUM2Xo8PMHBoYdqEcsbTodlCFAZH4=
------END PKCS7-----
------BEGIN PKCS7-----
-MIH9BgsqhkiG9w0BCRABF6CB7TCB6gIBADFyo3ACAQCgGwYJKoZIhvcNAQUMMA4E
-COe3h9+CHRLMAgITiDAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEAQIEEBHZXFIK
-Or8isjBw7/R9bvYEIBg5IifDwiwqpp8qsHckdarYWJzNu0yu0w3Cyx2DlGw3MFsG
-CSqGSIb3DQEHATA8BgsqhkiG9w0BCRADDzAtMB0GCWCGSAFlAwQBAgQQtyUCdoQ8
-WBulMOJAJ+7DBjAMBggrBgEFBQgBAgUAgBCYNg8MeWI2tS0tnhxihR4QBBSIpMGy
-ungbyvkUsOX80Y34AuKyng==
------END PKCS7-----
diff --git a/tests/files/cms-auth-enveloped-kari-data.pem b/tests/files/cms-auth-enveloped-kari-data.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/cms-auth-enveloped-kari-data.pem
@@ -0,0 +1,135 @@
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBwaGBvgIBA6BRoU8wCQYHKoZIzj0CAQNC
+AARLFBy9/eI4x8mR5wpZ774i/LeS8/FzuGegHdAo19bTdzUlv9kd69/0GzRF5ntH
+27ueue5YBRmFOKSRnLiU2mq3MBgGCSuBBRCGSD8AAjALBglghkgBZQMEAQUwTDBK
+MC4wITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4A
+BBjXV1HlxJT+Z8o6n5HC4RNRe7j4sP48HpIwgAYJKoZIhvcNAQcBMB4GCWCGSAFl
+AwQBBjARBAypQxH70WFEpg8UWToCARCggAQO6V6dHzqpE6FXmRa7arIAAAAABBCl
+bbL/+bic+x2r79kZpLsSAAAAAAAA
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBvqGBuwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AASJYlJVbYCr1BOjZpCH5hjLV/2X23Xc0Z5eTI8FBqUXlYHH7d1S/jEGJDfCcvIx
+TV9swOSRFK92I9kSVd9XCN1eMBUGBiuBBAELADALBglghkgBZQMEAQUwTDBKMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABBgE
+Xp5V6/AbK9bFcwjRJ3fm20YWgQ2nBowwgAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQB
+BjARBAyFVoCkD/eJA9YqjI8CARCggAQOpQwAlrBtnityfLYqU2sAAAAABBDLM+53
+Uk0Pn3Y7MZ+Mbn5eAAAAAAAA
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBvqGBuwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AASufl1PpqFUZDuTCXzKMaGerIO5843VUFSwBudjyvovjAHTcGwPJjvS+3EH4NEn
+7o/W7P09hwTSZ/rsBbBJPu+QMBUGBiuBBAELATALBglghkgBZQMEAQUwTDBKMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABBie
+ykbOXeqVMmbDWw3Ehn/QTjMlNs1uCL4wgAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQB
+BjARBAxEc1PrzckaDXBkWjgCARCggAQOuyUnmFAV/xahqSadwG8AAAAABBBi++VJ
+NnU5UVDQ6q1zMRqfAAAAAAAA
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBvqGBuwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AATX8MYoU25w7qYU/CFl+JZdFQ1ePZP2yL+FUJeV76Etegb+eeTl0dG70MIpVP3c
+uABRryMgV3jAniwgDnLDcLCrMBUGBiuBBAELAjALBglghkgBZQMEAQUwTDBKMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABBhe
+7wfppjJXy8UnxeR3KYGpjqhss/Vdb/4wgAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQB
+BjARBAyS8HZXcf5oDpq7XSYCARCggAQON5pXJ2YeE3u0Tu/JggsAAAAABBCDUZMb
+f9iObxa0nURyr2uNAAAAAAAA
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBvqGBuwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAQ50l98gMx9YTsLyif3Aj6ypa+8auXV415+hA7bgI5aXrY+20tvGbUheWAdCUM/
+DVLWcYhsWEwTEmL6Z5dMrQDCMBUGBiuBBAELAzALBglghkgBZQMEAQUwTDBKMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABBjI
+vXtw3gmmAn/yORO1a/NTTGqgmpOQq+swgAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQB
+BjARBAw/RTlPW3aoaAWpb4ACARCggAQO7kbgLubm2M+hoanfMdkAAAAABBAeQyvO
+tOT7PWNiqKaFTu9HAAAAAAAA
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByaGBxgIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAS/VBtw4zaTtkAb45PJ8cuAYshMXy/F1lKa3ZA4E6u3np+dfQ+kBT5VrxpegvFj
+Q/NdOxV4CTatDnNo8iH5h2d4MBgGCSuBBRCGSD8AAjALBglghkgBZQMEARkwVDBS
+MC4wITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4A
+BCB5X3DkEOElcCL2KEOiVJKcQpTqQWynlMyWeLe805y4YDCABgkqhkiG9w0BBwEw
+HgYJYIZIAWUDBAEaMBEEDPX7veh9yNKL511dCwIBEKCABA5/r2CzelEUDNFaJtk8
+jQAAAAAEEOWvi2b2dguSk94AjnrFBSAAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBxqGBwwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAQA3+RNupbspd7hUIQg7vXkrzB01ofr/7s7WuASl0OdzgJ3mBt3a5xtW7IqTE+B
+3Vw9J4mkAa9PLi3Iq9SEkZ5aMBUGBiuBBAELADALBglghkgBZQMEARkwVDBSMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCDl
+4XWpLmEPvdcqjhnf7xp8sDnDIVZ/eKXxzQ8YU7+avTCABgkqhkiG9w0BBwEwHgYJ
+YIZIAWUDBAEaMBEEDIKxljwkGp/uATZNuQIBEKCABA4JT8FfOkbtIUd9TPivOgAA
+AAAEEOTGxEy5JLrS0+nAg5HtSDEAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBxqGBwwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AARJpGAQMIpwZgHJpg2o1wmxGYoNcgtHEhedsOFLpQdou3G69y0eDgX7nB0tvRt7
+oYZEbgLPk7f4AECFVWSxnby9MBUGBiuBBAELATALBglghkgBZQMEARkwVDBSMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCD0
+HNfAnxgaNrlr1hC6M6O34Vyq1ZZ7MAInchQ6QjW8/TCABgkqhkiG9w0BBwEwHgYJ
+YIZIAWUDBAEaMBEEDDj5sfWnIjCmTf1y9wIBEKCABA5k60PO+CFkHN/evabZmwAA
+AAAEEJCwak43a0Ga4riBRKY9CNsAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBxqGBwwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAQj7fNzGeluKhvEL1t7QM9hnKiNItc4UEkHFzz3WJqepjx14l057XMAQoiyut9G
++W+Ddqx+ozVQOxu1RO5BHEY/MBUGBiuBBAELAjALBglghkgBZQMEARkwVDBSMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCBO
+XDmcoiudX6iCODyKyf+kj4ahTrVfH/kWYbocdvgrCzCABgkqhkiG9w0BBwEwHgYJ
+YIZIAWUDBAEaMBEEDL9ZV62lILi166Kr/wIBEKCABA7ayB6TLDk4NOQYpj6kvAAA
+AAAEEOiMHVSsVG7a7WcQp7DQmcgAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBxqGBwwIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAQnuS9ANZe6b7GWHi5qQQozrTubhuGToeFZAdQtiz95z5OrgWkmiX6ERkwaN1xB
+06M7crT9vuG0pX4GoboI4p4nMBUGBiuBBAELAzALBglghkgBZQMEARkwVDBSMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCA0
+PNagjdeo/KS1G+Y/lqmh3Rvz0T4rIVYChhNdvZWxbzCABgkqhkiG9w0BBwEwHgYJ
+YIZIAWUDBAEaMBEEDInQ2rexl6MGVX7tsgIBEKCABA4yHXaDnnVGt09Ctn67lgAA
+AAAEEGUG2rPiuRtGhTarSuS64uQAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGB0aGBzgIBA6BRoU8wCQYHKoZIzj0CAQNC
+AARnR8BsuxfZyGI8EufwUGDG+XuPF/1XzhhogH1DpHQy9XON4DUyLvlkWP+voIwE
+uMApKF5ESL1IL8T6UnzoN55AMBgGCSuBBRCGSD8AAjALBglghkgBZQMEAS0wXDBa
+MC4wITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4A
+BCgetPskb6M89OJ/Q6C9WwJYZrbT9Upvvktf8EHiRPWPri4BWOc8hcn8MIAGCSqG
+SIb3DQEHATAeBglghkgBZQMEAS4wEQQMxjZlfwz1CXn6KQBeAgEQoIAEDq6U8zcr
+EcjV5QDm7KA0AAAAAAQQruuKaBMv3S7igAIPPQp5jwAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBzqGBywIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAQEtpMiE4i41ln4lBfxorG/Mz7ENYFhI3FFjzvphl+/n58x3AhZHsKdJQfiBgwQ
+NbRjVoSg6gqA1NgwlJfMdC5yMBUGBiuBBAELADALBglghkgBZQMEAS0wXDBaMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCie
+fGtGheoPu9MlqzfxIkshaxv58mwcvnrO7Zjztbr/Y8iu+mQc6UVHMIAGCSqGSIb3
+DQEHATAeBglghkgBZQMEAS4wEQQMRnxKZZFpluuCvzMUAgEQoIAEDgbHkpLneCpj
+J3kFkjUHAAAAAAQQiRN5pWovrd3DmXkxUfScsgAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBzqGBywIBA6BRoU8wCQYHKoZIzj0CAQNC
+AARK9880f2VoFvmoN8xEHSn68EplcakaakOQgEqIJqYhQtk+jzWcONqFH13Tydf0
+twC6i18Ip33oM4qNO5baDw+/MBUGBiuBBAELATALBglghkgBZQMEAS0wXDBaMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCgU
+SlctsM4RmvAM189y5SF74X37M5v7amwj7Q2NxcwdqYq/T2Spvjh0MIAGCSqGSIb3
+DQEHATAeBglghkgBZQMEAS4wEQQMMaNatdENR6VvbGKnAgEQoIAEDmuLMPNqEAZV
+4G5072SbAAAAAAQQZil2/HiSIzjZEhj2xQjSCgAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBzqGBywIBA6BRoU8wCQYHKoZIzj0CAQNC
+AASbBeETneRZ79Q5cGBu17gZFZM++ZWzsMvcmx6Z1gYztIKYWtndW+INxVvrln4D
+Of8maq6/IfumffkvZpfqBnKPMBUGBiuBBAELAjALBglghkgBZQMEAS0wXDBaMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABCho
+nDmmA4NRBHpBGIGI0SwDPTw6CO1Adwjz6uhtcKd99duC7o50kQZgMIAGCSqGSIb3
+DQEHATAeBglghkgBZQMEAS4wEQQMJjonwKRKw6ThEDRlAgEQoIAEDs/bEIQQtKP9
+EZOmpMsFAAAAAAQQxCZqg6Ec63VcVAJgsVofaAAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGBzqGBywIBA6BRoU8wCQYHKoZIzj0CAQNC
+AAT0yC7d8EmBaHZecCKf8SOdDqm/o2CJteAk7ICcqbMjBhJc93ckpyE4o1Z/gB1Y
+1zwgerWFeaZXfObcRxh2CxZGMBUGBiuBBAELAzALBglghkgBZQMEAS0wXDBaMC4w
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6ZvHS4D4ABChH
+RQJ5OmptF0ap3vq7lknTscDMjwld7FhuDlxurwXb61f/Ubv8RsGAMIAGCSqGSIb3
+DQEHATAeBglghkgBZQMEAS4wEQQMEy1/SaOMEMJCYYB/AgEQoIAEDiDXAvTAGTTP
+4RN37jTOAAAAAAQQEJHRPVxSAJBdZ//YdeM1LQAAAAAAAA==
+-----END CMS-----
diff --git a/tests/files/cms-auth-enveloped-kekri-data.pem b/tests/files/cms-auth-enveloped-kekri-data.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/cms-auth-enveloped-kekri-data.pem
@@ -0,0 +1,18 @@
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADExoi8CAQQwAwQBMDALBglghkgBZQMEAQUE
+GKdm3FAAb+cJzRlmBNrIxZJR/lO7Hl9I9TCABgkqhkiG9w0BBwEwHgYJYIZIAWUD
+BAEGMBEEDBy96EqGGAX5GsRkvgIBEKCABA4/POyH35OvITLf81WHwQAAAAAEEKuM
+jJcreoQAUFYD+Y3HRCkAAAAAAAA=
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADE5ojcCAQQwAwQBMDALBglghkgBZQMEARkE
+IKEh8evpAq55aqYZa5EcwTXkGGFsx9Xljf5YWzLOVzPJMIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEARowEQQM2qmxxSb4nQshdsBZAgEQoIAEDj/eccFTdRRtFS59ge54
+AAAAAAQQlv+TpgF6XXZsjdVzg5uV7wAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADFBoj8CAQQwAwQBMDALBglghkgBZQMEAS0E
+KCp5RYkgCzU42HF7XXOaFQOEsk9bUFFxgV9NDlyVAo+2RrIPgVXXkMEwgAYJKoZI
+hvcNAQcBMB4GCWCGSAFlAwQBLjARBAx/18iNa9gajkGa3qYCARCggAQOZ0Hn+848
+qApi912KpsQAAAAABBBpz1K5ojGHJznVxTUAWfD9AAAAAAAA
+-----END CMS-----
diff --git a/tests/files/cms-auth-enveloped-ktri-data.pem b/tests/files/cms-auth-enveloped-ktri-data.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/cms-auth-enveloped-ktri-data.pem
@@ -0,0 +1,54 @@
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQEFAASB
+gA/maH0B5EK0DLU5rfbTGet4uBklSW9TkEfAskjNJ6C1KfePfy20oNfnsKMS5Mp1
+q/3Bz77LgH8+TWBZ5LKbgOT6AiMpWlUuUqzuchxmC9nx5P/ew0WJPQME5igMaV0z
+1rstr7dMZtIMa8aTQMzJ+LgguRNOYBuBLvB1w1vaxrc+MIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEAQYwEQQMlYZ0LFrSPPCSCobkAgEQoIAEDpNaDCywbuuvs15vS6S7
+AAAAAAQQGLodMaocucmqaveg0fT5xQAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQcwAASB
+gFrnvmHQe5ja0uvf6ni5ea0Jkss11cJ62lpyq41AYD52AwaoCC+FFdykgQBxgpO+
+1Byj0cUzzpxFoV354aZg2xUKu7RrN2ogUlei8TdEctfe5imOHPaD+W94xSipT46+
+VEXs+k7LsrJMtqBHmNvSZGIz/r6PpJMB0+Q5m1dqJQ8gMIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEAQYwEQQM44g5NAyvNtg1f7IhAgEQoIAEDvxH9L2wJAxh02F+yTgv
+AAAAAAQQa+rmrc/5NDP5/kB9FDvnkwAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQEFAASB
+gBiKzwAxQcgNfC5I/jsSWn559jU2f/ndZE4he3ojQua1HHrVVJUFVbwUC7bYs3Vf
+nHjo5AHi0i408asv4l2BgH3JWVkhnaUtHEajnipWkBEF2Bipb5u2i5NpeCMATdnX
+vUuUL6UAarWQttcWu+/J+73ZtV0ecrd8ybTIlcOx0QrtMIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEARowEQQM+TWtLNHBl3EU+JUdAgEQoIAEDtXaoIj5rEJLWljHXk0e
+AAAAAAQQjXRzifuEu8tEWisIQkPk6QAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQcwAASB
+gDBMbmVbY3ghH4Pc9N/kDCoYVb6Mp93oUOjLhVpx71Bqxv8xoWXzT4NV2Z3ssE/F
+k3RG1Y7LMYfv2I0QVZetuWJIehRBqouY/uW4RM7WWemuLT46M7Qszoi3vMnZ7Tuj
+8tbxQUeie4dBO0+MMluNYj4hc/gzaCxsy0nqISG6ERGFMIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEARowEQQM8zQfTdCgHG5KfkebAgEQoIAEDoJVmSUFhPA5dLsML5F9
+AAAAAAQQE3SXLSthb+rnxYrwohzIvQAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQEFAASB
+gIIv6EsKwGSUU0M4EHjI8mgYF1mm32nPIHf4xwYuKcgE9Klu8/Dob2k+Q5705aAc
+0mQvGiA8iaguiveGzvXegyuv/LVjl7bz1aTYkXwo22+5J3YstFJPrum+S1FbBdvI
+cKPzkUjaJDus1R0j36Zej2wYnTUJNSo4SE1lolcyAUj/MIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEAS4wEQQMtwUSL7+j+tyKBlp3AgEQoIAEDp3QpaI+/etoalZIMW+G
+AAAAAAQQ99WOo5xvNPX1CYu/2b9LoQAAAAAAAA==
+-----END CMS-----
+-----BEGIN CMS-----
+MIAGCyqGSIb3DQEJEAEXoIAwgAIBADGByDCBxQIBADAuMCExHzAdBgkqhkiG9w0B
+CQEWEHRlc3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzANBgkqhkiG9w0BAQcwAASB
+gC5UFYfqMeb1SoHKS3G2dr+ofYwU4+JzVrqCJnE6WKSQI3a50MN63TTpjuiOxnWU
+TCzo7QUIFCUms+NIFNR1FGmV58SycQqs/9gQcR0Xp9mnL0LkGte6pIIEnro+dHES
+aY+6eRViwFOZ6GO0qOODUgLHfYdotcqnuKEs8oV3IJb1MIAGCSqGSIb3DQEHATAe
+BglghkgBZQMEAS4wEQQMnJh28SAHjbT8F2U3AgEQoIAEDkcXXJq6tkKrY3tI5ecW
+AAAAAAQQK2+jlHbQwEjmeP0vQxBRZwAAAAAAAA==
+-----END CMS-----
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/ed25519-unencrypted-trad.pem b/tests/files/ed25519-unencrypted-trad.pem
deleted file mode 100644
--- a/tests/files/ed25519-unencrypted-trad.pem
+++ /dev/null
@@ -1,3 +0,0 @@
------BEGIN ED25519 PRIVATE KEY-----
-MC4CAQAwBQYDK2VwBCIEIOrKOPQUqxCUeZnZKJi5EmoEjxnDSSGp45U/t1evy/3v
------END ED25519 PRIVATE KEY-----
diff --git a/tests/files/ed448-unencrypted-trad.pem b/tests/files/ed448-unencrypted-trad.pem
deleted file mode 100644
--- a/tests/files/ed448-unencrypted-trad.pem
+++ /dev/null
@@ -1,4 +0,0 @@
------BEGIN ED448 PRIVATE KEY-----
-MEcCAQAwBQYDK2VxBDsEOceJ7Mc21YZq6/qusCHA5d3wARXuJxMKSRkvsyD4GEKq
-hlubDZL0FQiS3OgjbeuKN+63xa+OVVz9XA==
------END ED448 PRIVATE KEY-----
diff --git a/tests/files/pkcs12-empty-password.pem b/tests/files/pkcs12-empty-password.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/pkcs12-empty-password.pem
@@ -0,0 +1,191 @@
+The file from <https://bugzilla.mozilla.org/show_bug.cgi?id=265991>
+encoded in base64:
+
+    openssl base64 -in alice-nopassword.pfx
+
+-----BEGIN PKCS12-----
+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
+B9A=
+-----END PKCS12-----
+
+A file generated with OpenSSL and encoded in base64:
+
+    openssl pkcs12 -legacy -export -in tests/files/rsa-self-signed-cert.pem \
+      -inkey tests/files/rsa-unencrypted-trad.pem -password 'pass:' \
+        | openssl base64
+
+-----BEGIN PKCS12-----
+MIIGCQIBAzCCBc8GCSqGSIb3DQEHAaCCBcAEggW8MIIFuDCCArcGCSqGSIb3DQEH
+BqCCAqgwggKkAgEAMIICnQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+CTH
+s6/efloCAggAgIICcGL8NGL8zObJ3DcHTQyYuWq6U1qv8bpeZmb9TXvuwrfOz2qQ
++HUxpZ5wPooaGXhPOryPwv0uQ2KGmGmIcqi3lpf5LakeANoXbuP7SUE6N1G//Q8n
+tYb6ahhqDrlr1p6xafay4QiJkZjGMsM0w3XnFw5kqxbj3yNaARqEsr8UoPrx5RgF
+pHvmNeYqkiOeEt/YLLor2cMekSN5xw5X3neRR2hNEwvU1kZHAAFxZ2+Byeapbd2Q
+m0RepoS1Dto/Q7cw5z+rS+f7fGE7SMEIBl6FnRoQwrlHa16LVed5pMNIb2wY16S9
+Rf3hCwPQfHUaa9wgLIC9FLHdOlVsTY7WCrZ0R8pwahzSrgJE+Y3RYcpsHfDDvzW0
+mYj9tL+DrT/rbIq01JNydigj5sUrpoavblaCOGMGTWF1CJqHoeshnK5O3a3Ng+vq
+hhzLMoCDwLL31XQFWFoeIm1g5wiQULSYqSc6475CYG87zuHvLrYrQRk56GEXFVGd
+bNhGUIGyDouz5+D/jmmB89TwgppEN+EtIIRqX1pbNRboSP9hpqzsMadBwwvxfYzN
+60zMwvlg5X4sxv/W4XwczgY5dkf2JD6ZWjD0uEWcqMl/z+dTZULvqHMD/gMZVWHk
+Po3xKzSG2eVvLM2eOlRKq0vWGtHmKtV5cwWQ98Kx6C+rexlpSZbqDahVASazyYR/
+VmpprDoT1d7bPYj1qsQq0n9ww7UeI/hIg83X5+TZV4xvsEXNv9crqKx0Mjj2TsP9
+AjoNONNFGp1bZgYc38qQXh4jNbgi2Cr4H9l9YVVjrzIS6AiUep/3F6zDMaOVGsmD
+TrANXPy/2VA0dNP2CDCCAvkGCSqGSIb3DQEHAaCCAuoEggLmMIIC4jCCAt4GCyqG
+SIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAjInPcICW0iUwICCAAE
+ggKA99RQodJGF0d/80E2Q2hHB+RUXigpxFTYoHk+m5cwlPvZHhXCkrlMO2q9WHIh
+33KD2z2tJJ01hEB1P2LrD8TZDDMHNzRXIOAlXR5rhkMDia4uICPresE/j/RuVisJ
+LgUVG35V9R4H0CvGhoUa3AAg/JXob/GBTABPMSGg8bQDXLDMqD0sOJIYOU5g/ddq
+97HK7KHmfI5yigBvzDxM7SnbR/up7qvmwdMgs1gq339BJPBS2PvxFORXtnnW+pBG
+c68EpUQQVpfC+5UrZGf9zYEnsBR+ihE+8EYDHG/WJlLj++PXnMK7R1r1V5q024VD
+8B0FmlP5t19YGd8GcsnMhnFRfvugQ70GMODMNxejrXFxI/yXuN3OCHdkP/rCY36n
+9J9TMB5efcVJWcfU+UcWTQZbvDwlVTF8LOqZkteu16zZufn7Lo3IKYirT8Wq9tcD
+HrjkSiiUDMT3Q5sorYSum9s1n1jXbgGJRONmeTYpaXAEvi4HVbLKRcRL/0iv3YUM
+P+ekurzBomqlPqhU10DyXHg/6wb8+qHk1tAlJFY16hC+jFM87SkKr3CQsD1EdFLd
+eymlwzEVvpsAr8FFznoBo+fw0faZMg8g2IneCAXSqs4hqa9M2mmbDYRmx1CmU0Zt
+Bzwd7k2f9N/yLId7YmI40E9aCgybd2lirejm86ZSReDQ+hj4/03kzm3IjuWhMvce
+nprJZOXhrlQ/Wm9buAojkqfjWbvr3U81aqrKDCCM9gVx9NQx7DhPmqy4Fl6hLnEX
+HsHuoNyiN3/f/b6t98JCSBXWRxdqLGE/cgi2kqljokwsSb16SPe/KodF8lk6gkCn
+FbA90vTgoyWT/0ou+NpppAVCLzElMCMGCSqGSIb3DQEJFTEWBBTAbCJKoqJORdb4
+i56jGIji040OsDAxMCEwCQYFKw4DAhoFAAQUslUy7TCr35RrfHVPzfBYIA5cyMkE
+CBx9AR1csYyJAgIIAA==
+-----END PKCS12-----
+
+A file generated by GnuTLS with --empty-password:
+
+gnutls-certtool --to-p12 --p12-name=demo \
+    --load-privkey tests/files/rsa-unencrypted-trad.pem \
+    --load-certificate tests/files/rsa-self-signed-cert.pem \
+    --empty-password
+
+-----BEGIN PKCS12-----
+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
+PRSjE7CldboCAigA
+-----END PKCS12-----
+
+A file generated by GnuTLS with --null-password:
+
+gnutls-certtool --to-p12 --p12-name=demo \
+    --load-privkey tests/files/rsa-unencrypted-trad.pem \
+    --load-certificate tests/files/rsa-self-signed-cert.pem \
+    --null-password
+
+-----BEGIN PKCS12-----
+MIIGOAIBAzCCBgAGCSqGSIb3DQEHAaCCBfEEggXtMIIF6TCCAs8GCSqGSIb3DQEH
+BqCCAsAwggK8AgEAMIICtQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQMwDgQISK7f
+1bKjOwUCAhQ+gIICiADumPNzeqc45S7jTQ9drLhg5iv82TxbfyTtFV9J/lHSDxtu
+nmxfFycvRUDuo45GCHOqEkd/hHwM9btHKN9pphaTq+8jjDSKUWuMUGY1+j+sCcH8
+OAcyO5XQP7kD1rOCkC2vQpFkfC2FeXfOtMBecxUejy53rglj7x/6Jz2Br+7crfJf
+c/mcn0xEgA/XWMomEdSTwAj+IJwN7DuQNtJ81crIsQlC5KkdhJPZfRyuREns6dhs
+Eqdppdg3ig3IKdT+gnuseclQl4cFJHdr7Y7GrvoX5nVmsmlpEOzofdfDOaHwDs9l
+jLD9qYwSEBKRRdf3t3WUmuCUkjtI9Vr3+sn5lLqKrfHYzA8AOkTZclAQN2GHUu6Q
+U184yKGLMP+JSwRX57j/3wYIyLfyWJi4kwUxIV4OPwQXGcaTIHsgovQ5Ws5Hfc9L
+MXNdQqzMGcQrcRk/SUYH/aKp3qJaGXkLJ8jfj+VjVR53i/J/pNN1+LgaAQz8ZNfQ
+TtXJ3XynBBMzHeH+cnQQdWNHKSvqBR6ZrPgT/R6FyZBp/IX87jD7rUuv1mT+TPZo
+gO3xExSP3o79h2uOYjnoV70Msqr/3ZUm3ejAh1WRO8FiRnw0NT4sDIIi5CkLNQdq
+Vl2eQk3Sw1OUBZYWbYXK5W/K1uVNqqxyZQhrNNp59fBvo+ipnzWVd/E5XivTg/Qt
+nhaHPddWvM84fWiIfycMxndeAKCdLFpvbkDlUszfynN/tkPjikvoHOfVEjvXPHUl
+NC4bV+9S+nFVpkiOxBz/8vBcWsomlCuHlHzFNNaGLQ/6OwyJnRv9MRZXKyYHF7H9
+F9CQeo0QZ/4ONIzCGNClHu4rZ6eCx8erkl4MOkMrev6w+l5CxzCCAxIGCSqGSIb3
+DQEHAaCCAwMEggL/MIIC+zCCAvcGCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZI
+hvcNAQwBAzAOBAgfK2XV0A7JKgICFHMEggKAOqG9Hnp1gOgz5lzbJpRHtDV7gXI/
+ucFK0UgUsU7dWkdHl8sVn+FySZnrY+GfiWvi11UvWvwwObY1Fpj0N1gH/dpWZ4qw
+SJ4SgLAWoFEbBZBX7v8BiiEuMC8YFSHmswGshDC9BIuIgu9TvDMBuP31NGZEapDi
+wUto63uzK2N5YzFUeVrx1S/91vEKPZ2I76fwMO9EtffTT8yLrZ1o6fAyQctSMioK
+yUR7Tv580iFg7c+CxDylmkps+TJQZ29pToSPPcuQlQcJQXioe5Z6/uyyjNJL98fR
+R54+wScAQ7eEsjnjQz7gEk/F/6MW2zepi3PjyTyyeLoBCRtV5wxNv5qdLli2pK7Q
+0bmdogx6RfRPs6fXZ4gFQMWtkFwWF0evtz6CC6RAWZanK6BmfdG6BcfKTyZPcQXS
+cKw5ieN/ZUNREszFCDl70GRfwpZ3g+ZM0PQWqIEsjfp0DgmODX4aqZiSm8Jax22h
+OIZ6p3l9a/xi9SMiHH3LbHmF6sfJYe8J48TsL69wEhe2GcLSvFpsSCTiDLYLBCm4
+YZDh/rUO9JbneN01MTz9gpm4wqB0STIXOdqhxTJQ/fYR1/7nEq56S06MchsM2Sw0
+A1qedVemiJkFaoStTciM7ve9Gt8WmBw8c5mbKGUBFHwQQEfTfS/C1F8APzPGZ44J
+cJnh6cqtyeDxo87stz6/03vVP3hdO5Lis/4aAzmOjGdHIb50K+rJ1/RK3d9S91nt
+lcE9ze4U3vFm9ITLVpfDjx4//bLPlO+J1u55tGxZONyCPhneL/APY50dswrN7F5w
+tza40684HLfpjhQIrMjKvNH6BgVS+hZDXw67VsbI2ziNXN/z/pR4S79FODE+MBcG
+CSqGSIb3DQEJFDEKHggAZABlAG0AbzAjBgkqhkiG9w0BCRUxFgQU7D6eS5ULMlvb
+rOebaU/DleyLZ/QwLzAfMAcGBSsOAwIaBBQ7C7/I785K5+xEP3lXD+aj+iybjgQI
+nbWw5eZvpVsCAigA
+-----END PKCS12-----
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
diff --git a/tests/files/x25519-unencrypted-trad.pem b/tests/files/x25519-unencrypted-trad.pem
deleted file mode 100644
--- a/tests/files/x25519-unencrypted-trad.pem
+++ /dev/null
@@ -1,3 +0,0 @@
------BEGIN X25519 PRIVATE KEY-----
-MC4CAQAwBQYDK2VuBCIEIIhgx84XP3vLVdFwZWT88BG/gGRXl6YYUeo0lWX2E8RY
------END X25519 PRIVATE KEY-----
diff --git a/tests/files/x448-unencrypted-trad.pem b/tests/files/x448-unencrypted-trad.pem
deleted file mode 100644
--- a/tests/files/x448-unencrypted-trad.pem
+++ /dev/null
@@ -1,4 +0,0 @@
------BEGIN X448 PRIVATE KEY-----
-MEYCAQAwBQYDK2VvBDoEOOC8GlnbqcOSJ+ISHV8HJTD3WGdC1sQdZ+0Gkx7sUvL0
-Bm0KxY3cO9Rg9MQb3qkfVngRes0sb86Q
------END X448 PRIVATE KEY-----
