diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,46 @@
 # Revision history for cryptostore
 
+## 0.4.0.0 - 2025-10-12
+
+* Private keys are now represented as type `KeyPair` defined in module
+  `Crypto.Store.PKCS8` instead of `PrivKey`.  This ensures that the
+  corresponding public key is always available and that the library detects
+  and reports data mistakes involving a wrong public key.  A new error
+  `PublicPrivateKeyMismatch` is added for that purpose and returned by
+  functions `certSigner`, `withRecipientKeyAgree`, `fromCredential` and
+  `fromNamedCredential`.  Behavior of functions `toCredential` and
+  `toNamedCredential` is now more strict, they return a key and leaf
+  certificate that always match.  Functions to convert `KeyPair` to/from the
+  usual type `PrivKey` are also exposed from module `Crypto.Store.PKCS8`.
+
+* Added support of PBMAC1 for PKCS#12 integrity.  Type `IntegrityParams` used
+  in functions `writeP12File` and `writeP12FileToMemory` is modified.
+  A low-level function for PBMAC1 is also available in the PKCS5 module.
+
+* CMS now supports SHA-3 algorithms in HMAC, ECDSA, or as digest algorithm
+
+* Functions `pemToKey`, `pemToPubKey` and `pemToContentInfo` are modified to
+  return error details when a PEM object cannot be read.  The old API signature
+  that discarded error details is available with functions renamed
+  `pemToKeyAccum`, `pemToPubKeyAccum` and `pemToContentInfoAccum`.
+
+* Function `generateEncryptionParams` is removed and can be replaced with
+  functions `ecbParams`, `generateCBCParams`, `generateCFBParams` or
+  `generateCTRParams` according to the desired mode.
+
+* Functions `generateAuthEnc128Params` and `generateAuthEnc256Params` are
+  replaced with `authEnc128Params` and `authEnc256Params`.  The new API expects
+  a `ContentEncryptionParams` argument instead of `ContentEncryptionAlg`.
+
+* CMS encrypted data, enveloped data and authenticated-enveloped data now
+  support optional HKDF-SHA256 derivation of the content-encryption key.  When
+  generating encryption parameters, enable the key derivation feature with a
+  call to functions `deriveEncryptionKey` or `authDeriveEncryptionKey`.  The
+  feature prevents AEAD-to-CBC downgrade attack scenarios for `CCM` and `GCM`
+  modes but requires support from the receiving end.
+
+* Fixed encoding of some HMAC and PRF parameters
+
 ## 0.3.1.0 - 2024-05-05
 
 * Strict validation of GCM/CCM authentication tag length
diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2024, Olivier Chéron
+Copyright (c) 2018-2025, Olivier Chéron
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,5 @@
 # cryptostore
 
-[![Build Status](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml)
 [![BSD](https://img.shields.io/badge/License-BSD-blue)](https://en.wikipedia.org/wiki/BSD_licenses)
 [![Haskell](https://img.shields.io/badge/Language-Haskell-lightgrey)](https://haskell.org/)
 
@@ -33,7 +32,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 +40,8 @@
 ```haskell
 > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509
 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
-> writeKeyFile PKCS8Format "/path/to/privkey.pem" [privKey]
+> let keyPair = keyPairFromPrivKey privKey
+> writeKeyFile PKCS8Format "/path/to/newkey.pem" [keyPair]
 ```
 
 Generating a private key and writing to disk, with password-based encryption:
@@ -50,11 +50,12 @@
 > :set -XOverloadedStrings
 > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5
 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
+> let keyPair = keyPairFromPrivKey privKey
 > salt <- generateSalt 16
 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
-> encParams <- generateEncryptionParams (CBC AES256)
+> encParams <- generateCBCParams AES256
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
-> writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey
+> writeEncryptedKeyFile "/path/to/newkey.pem" pbes "mypassword" keyPair
 Right ()
 ```
 
@@ -104,7 +105,7 @@
 > getAllSafeX509Certs contents
 [SignedExact {getSigned = ...}]
 > recover password (getAllSafeKeys contents)
-Right [PrivKeyRSA ...]
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
 ```
 
 Reading a binary PKCS #12 file using distinct integrity and privacy passwords:
@@ -112,13 +113,13 @@
 ```haskell
 > :set -XOverloadedStrings
 > :m Crypto.Store.PKCS12
-> Right p12 <- readP12File "/path/to/file.p12"
+> Right p12 <- readP12File "/path/to/other.p12"
 > let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12)
 > getAllSafeX509Certs contents
 [SignedExact {getSigned = ...}]
 > recover "myprivacypassword" (getAllSafeKeys contents)
-Right [PrivKeyRSA ...]
+Right [keyPairFromPrivKey (PrivKeyRSA ...)]
 ```
 
 Generating a PKCS #12 file containing a private key:
@@ -132,21 +133,22 @@
 
 -- Put the key inside a bag
 > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS
+> let keyPair = keyPairFromPrivKey privKey
 > let attrs = setFriendlyName "Some Key" []
->     keyBag = Bag (KeyBag $ FormattedKey PKCS8Format privKey) attrs
+>     keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs
 >     contents = SafeContents [keyBag]
 
 -- Encrypt the contents
 > salt <- generateSalt 16
 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
-> encParams <- generateEncryptionParams (CBC AES256)
+> encParams <- generateCBCParams AES256
 > let pbes = PBES2 (PBES2Parameter kdf encParams)
 >     Right pkcs12 = encrypted pbes "mypassword" contents
 
 -- Save to PKCS #12 with integrity protection (same password)
 > salt' <- generateSalt 16
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 200000)
-> writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12
+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt' 200000)
+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
 Right ()
 ```
 
@@ -158,7 +160,7 @@
 > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS
 
 -- Read PKCS #12 content as credential
-> Right p12 <- readP12File "/path/to/file.p12"
+> Right p12 <- readP12File "/path/to/other.p12"
 > let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12
 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)
 > cred
@@ -167,25 +169,59 @@
 -- Scheme to reencrypt the key
 > saltK <- generateSalt 16
 > let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256
-> encParamsK <- generateEncryptionParams (CBC AES256)
+> encParamsK <- generateCBCParams AES256
 > let sKey = PBES2 (PBES2Parameter kdfK encParamsK)
 
 -- Scheme to reencrypt the certificate chain
 > saltC <- generateSalt 8
 > let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256
-> encParamsC <- generateEncryptionParams (CBC AES128)
+> encParamsC <- generateCBCParams AES128
 > let sCert = PBES2 (PBES2Parameter kdfC encParamsC)
 
 -- Write the content back to a new file
 > let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred
 > salt <- generateSalt 16
-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 200000)
+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt 200000)
 > writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12'
 ```
 
 Variants `toNamedCredential` and `fromNamedCredential` are also available when
 PKCS #12 elements need an alias (friendly name).
 
+The library also supports integrity protection with PBMAC1 as defined in
+[RFC 9579](https://tools.ietf.org/html/rfc9579). The following example shows
+how to use PBKDF2 with SHA-256 HMAC and PRF:
+
+```haskell
+> :set -XOverloadedStrings
+
+-- Generate a private key
+> :m Crypto.PubKey.RSA Data.X509
+> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001
+
+-- Put the key inside a bag
+> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS
+> let keyPair = keyPairFromPrivKey privKey
+> let attrs = setFriendlyName "Some Key" []
+>     keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs
+>     contents = SafeContents [keyBag]
+
+-- Encrypt the contents
+> salt <- generateSalt 16
+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
+> encParams <- generateCBCParams AES256
+> let pbes = PBES2 (PBES2Parameter kdf encParams)
+>     Right pkcs12 = encrypted pbes "mypassword" contents
+
+-- Save to PKCS #12 with PBMAC1 integrity protection (same password)
+> salt' <- generateSalt 16
+> let kdf' = PBKDF2 salt' 200000 (Just 32) PBKDF2_SHA256
+> let authScheme = PBMAC1 $ PBMAC1Parameter kdf' (HMAC SHA256)
+> let iParams = AuthSchemeIntegrity authScheme
+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12
+Right ()
+```
+
 ## Cryptographic Message Syntax
 
 The API to read and write CMS content is available in `Crypto.Store.CMS`.  The
@@ -223,7 +259,7 @@
 > let info = DataCI "Hi, what will you need from the cryptostore?"
 
 -- Content encryption will use AES-128-CBC
-> ceParams <- generateEncryptionParams (CBC AES128)
+> ceParams <- generateCBCParams AES128
 > ceKey <- generateKey ceParams :: IO ContentEncryptionKey
 
 -- Encrypt the Content Encryption Key with a Password Recipient Info,
@@ -231,7 +267,7 @@
 -- that the recipient will need to know
 > salt <- generateSalt 16
 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256
-> keParams <- generateEncryptionParams (CBC AES128)
+> keParams <- generateCBCParams AES128
 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)
 
 -- Generate the enveloped structure for this single recipient.  Encrypted
@@ -271,7 +307,7 @@
 
 -- Read signer certificate and private key
 > (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key
-> let Right priv = recover "mypassword" key
+> let Right pair = recover "mypassword" key
 > chain <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]
 > let cert = CertificateChain chain
 
@@ -281,7 +317,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]
diff --git a/cryptostore.cabal b/cryptostore.cabal
--- a/cryptostore.cabal
+++ b/cryptostore.cabal
@@ -1,5 +1,5 @@
 name:                cryptostore
-version:             0.3.1.0
+version:             0.4.0.0
 synopsis:            Serialization of cryptographic data types
 description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS
                      (Cryptographic Message Syntax).
@@ -9,15 +9,15 @@
 maintainer:          olivier.cheron@gmail.com
 copyright:           Olivier Chéron
 category:            Cryptography, Codec
-homepage:            https://github.com/ocheron/cryptostore
-bug-reports:         https://github.com/ocheron/cryptostore/issues
+homepage:            https://codeberg.org/ocheron/cryptostore
+bug-reports:         https://codeberg.org/ocheron/cryptostore/issues
 build-type:          Simple
 extra-source-files:  README.md ChangeLog.md tests/files/*.pem
 cabal-version:       >=1.10
 
 source-repository head
   type:     git
-  location: https://github.com/ocheron/cryptostore
+  location: https://codeberg.org/ocheron/cryptostore
 
 flag use_crypton
   description: Use crypton instead of cryptonite
@@ -52,6 +52,7 @@
                      , 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
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
@@ -40,7 +40,9 @@
     ) where
 
 import Data.ASN1.Types
-import Data.Monoid
+#if !(MIN_VERSION_base(4,11,0))
+import Data.Monoid ( (<>) )
+#endif
 import Control.Applicative
 import Control.Arrow (first)
 import Control.Monad (MonadPlus(..), liftM2)
diff --git a/src/Crypto/Store/CMS.hs b/src/Crypto/Store/CMS.hs
--- a/src/Crypto/Store/CMS.hs
+++ b/src/Crypto/Store/CMS.hs
@@ -22,6 +22,8 @@
 -- * <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/rfc9688 RFC 9688>: Use of the SHA3 One-Way Hash Functions 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(..)
@@ -106,8 +108,12 @@
     , ContentEncryptionParams
     , EncryptedContent
     , EncryptedData(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
     , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
+    , deriveEncryptionKey
     , getContentEncryptionAlg
     , encryptData
     , decryptData
@@ -122,11 +128,12 @@
     , AuthContentEncryptionAlg(..)
     , AuthContentEncryptionParams
     , AuthEnvelopedData(..)
-    , generateAuthEnc128Params
-    , generateAuthEnc256Params
+    , authEnc128Params
+    , authEnc256Params
     , generateChaChaPoly1305Params
     , generateCCMParams
     , generateGCMParams
+    , authDeriveEncryptionKey
     , authEnvelopData
     , openAuthEnvelopedData
     -- * Key derivation
@@ -385,15 +392,15 @@
 authEnvelopData oinfo key params envFns aAttrs uAttrs ci =
     f <$> (sequence <$> traverse ($ key) envFns)
   where
-    raw = encodeASN1Object params
+    prm = derObjectExact params
     aad = encodeAuthAttrs aAttrs
-    ebs = authContentEncrypt key params raw aad (encapsulate ci)
+    ebs = authContentEncrypt key prm aad (encapsulate ci)
     f ris = build <$> ebs <*> ris
     build (authTag, bs) ris = AuthEnvelopedData
                        { aeOriginatorInfo = oinfo
                        , aeRecipientInfos = ris
                        , aeContentType = getContentType ci
-                       , aeContentEncryptionParams = ASN1ObjectExact params raw
+                       , aeContentEncryptionParams = prm
                        , aeEncryptedContent = bs
                        , aeAuthAttrs = aAttrs
                        , aeMAC = authTag
@@ -411,10 +418,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
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 #-}
@@ -24,7 +25,7 @@
     , MessageAuthenticationCode
     , MACAlgorithm(..)
     , mac
-    , HasStrength
+    , HasStrength(..)
     , securityAcceptable
     , HasKeySize(..)
     , getMaximumKeySize
@@ -33,19 +34,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,6 +60,7 @@
     , generateSalt
     , KeyDerivationFunc(..)
     , kdfKeyLength
+    , kdfKeyLengthModify
     , kdfDerive
     , KeyEncryptionParams(..)
     , keyEncrypt
@@ -91,7 +98,7 @@
 import           Data.ByteArray (ByteArray, ByteArrayAccess)
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
-import           Data.Maybe (fromMaybe)
+import           Data.Maybe (fromJust, fromMaybe)
 import           Data.Proxy
 import           Data.Word
 import qualified Data.X509 as X509
@@ -142,6 +149,7 @@
 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           Crypto.Store.Util
 
@@ -167,6 +175,14 @@
     SHA384 :: DigestProxy Hash.SHA384
     -- | SHA-512
     SHA512 :: DigestProxy Hash.SHA512
+    -- | SHA3-224
+    SHA3_224 :: DigestProxy Hash.SHA3_224
+    -- | SHA3-256
+    SHA3_256 :: DigestProxy Hash.SHA3_256
+    -- | SHA3-384
+    SHA3_384 :: DigestProxy Hash.SHA3_384
+    -- | SHA3-512
+    SHA3_512 :: DigestProxy Hash.SHA3_512
     -- | SHAKE128 (256 bits)
     SHAKE128_256 :: DigestProxy (Hash.SHAKE128 256)
     -- | SHAKE256 (512 bits)
@@ -186,6 +202,10 @@
     showsPrec _ SHA256       = showString "SHA256"
     showsPrec _ SHA384       = showString "SHA384"
     showsPrec _ SHA512       = showString "SHA512"
+    showsPrec _ SHA3_224     = showString "SHA3_224"
+    showsPrec _ SHA3_256     = showString "SHA3_256"
+    showsPrec _ SHA3_384     = showString "SHA3_384"
+    showsPrec _ SHA3_512     = showString "SHA3_512"
     showsPrec _ SHAKE128_256 = showString "SHAKE128_256"
     showsPrec _ SHAKE256_512 = showString "SHAKE256_512"
     showsPrec d (SHAKE128 p) =
@@ -208,6 +228,10 @@
     getSecurityBits SHA256       = 128
     getSecurityBits SHA384       = 192
     getSecurityBits SHA512       = 256
+    getSecurityBits SHA3_224     = 112
+    getSecurityBits SHA3_256     = 128
+    getSecurityBits SHA3_384     = 192
+    getSecurityBits SHA3_512     = 256
     getSecurityBits SHAKE128_256 = 128
     getSecurityBits SHAKE256_512 = 256
     getSecurityBits (SHAKE128 a) = shakeSecurityBits 128 a
@@ -233,6 +257,10 @@
     DigestAlgorithm SHA256       == DigestAlgorithm SHA256       = True
     DigestAlgorithm SHA384       == DigestAlgorithm SHA384       = True
     DigestAlgorithm SHA512       == DigestAlgorithm SHA512       = True
+    DigestAlgorithm SHA3_224     == DigestAlgorithm SHA3_224     = True
+    DigestAlgorithm SHA3_256     == DigestAlgorithm SHA3_256     = True
+    DigestAlgorithm SHA3_384     == DigestAlgorithm SHA3_384     = True
+    DigestAlgorithm SHA3_512     == DigestAlgorithm SHA3_512     = True
     DigestAlgorithm SHAKE128_256 == DigestAlgorithm SHAKE128_256 = True
     DigestAlgorithm SHAKE256_512 == DigestAlgorithm SHAKE256_512 = True
     DigestAlgorithm (SHAKE128 a) == DigestAlgorithm (SHAKE128 b) = natVal a == natVal b
@@ -251,6 +279,10 @@
     | Type_SHA256
     | Type_SHA384
     | Type_SHA512
+    | Type_SHA3_224
+    | Type_SHA3_256
+    | Type_SHA3_384
+    | Type_SHA3_512
     | Type_SHAKE128_256
     | Type_SHAKE256_512
     | Type_SHAKE128_Len
@@ -265,6 +297,10 @@
              , Type_SHA256
              , Type_SHA384
              , Type_SHA512
+             , Type_SHA3_224
+             , Type_SHA3_256
+             , Type_SHA3_384
+             , Type_SHA3_512
              , Type_SHAKE128_256
              , Type_SHAKE256_512
              , Type_SHAKE128_Len
@@ -280,6 +316,10 @@
     getObjectID Type_SHA256       = [2,16,840,1,101,3,4,2,1]
     getObjectID Type_SHA384       = [2,16,840,1,101,3,4,2,2]
     getObjectID Type_SHA512       = [2,16,840,1,101,3,4,2,3]
+    getObjectID Type_SHA3_224     = [2,16,840,1,101,3,4,2,7]
+    getObjectID Type_SHA3_256     = [2,16,840,1,101,3,4,2,8]
+    getObjectID Type_SHA3_384     = [2,16,840,1,101,3,4,2,9]
+    getObjectID Type_SHA3_512     = [2,16,840,1,101,3,4,2,10]
     getObjectID Type_SHAKE128_256 = [2,16,840,1,101,3,4,2,11]
     getObjectID Type_SHAKE256_512 = [2,16,840,1,101,3,4,2,12]
     getObjectID Type_SHAKE128_Len = [2,16,840,1,101,3,4,2,17]
@@ -300,6 +340,10 @@
     algorithmType (DigestAlgorithm SHA256)       = Type_SHA256
     algorithmType (DigestAlgorithm SHA384)       = Type_SHA384
     algorithmType (DigestAlgorithm SHA512)       = Type_SHA512
+    algorithmType (DigestAlgorithm SHA3_224)     = Type_SHA3_224
+    algorithmType (DigestAlgorithm SHA3_256)     = Type_SHA3_256
+    algorithmType (DigestAlgorithm SHA3_384)     = Type_SHA3_384
+    algorithmType (DigestAlgorithm SHA3_512)     = Type_SHA3_512
     algorithmType (DigestAlgorithm SHAKE128_256) = Type_SHAKE128_256
     algorithmType (DigestAlgorithm SHAKE256_512) = Type_SHAKE256_512
     algorithmType (DigestAlgorithm (SHAKE128 _)) = Type_SHAKE128_Len
@@ -320,6 +364,10 @@
     parseParameter Type_SHA256       = parseDigestParam (DigestAlgorithm SHA256)
     parseParameter Type_SHA384       = parseDigestParam (DigestAlgorithm SHA384)
     parseParameter Type_SHA512       = parseDigestParam (DigestAlgorithm SHA512)
+    parseParameter Type_SHA3_224     = parseDigestParam (DigestAlgorithm SHA3_224)
+    parseParameter Type_SHA3_256     = parseDigestParam (DigestAlgorithm SHA3_256)
+    parseParameter Type_SHA3_384     = parseDigestParam (DigestAlgorithm SHA3_384)
+    parseParameter Type_SHA3_512     = parseDigestParam (DigestAlgorithm SHA3_512)
     parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)
     parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)
     parseParameter Type_SHAKE128_Len = parseBitLen $
@@ -456,19 +504,27 @@
              , TypeHMAC SHA256
              , TypeHMAC SHA384
              , TypeHMAC SHA512
+             , TypeHMAC SHA3_224
+             , TypeHMAC SHA3_256
+             , TypeHMAC SHA3_384
+             , TypeHMAC SHA3_512
              , TypeKMAC_SHAKE128
              , TypeKMAC_SHAKE256
              ]
 
 instance OIDable MACType where
-    getObjectID (TypeHMAC MD5)    = [1,3,6,1,5,5,8,1,1]
-    getObjectID (TypeHMAC SHA1)   = [1,3,6,1,5,5,8,1,2]
-    getObjectID (TypeHMAC SHA224) = [1,2,840,113549,2,8]
-    getObjectID (TypeHMAC SHA256) = [1,2,840,113549,2,9]
-    getObjectID (TypeHMAC SHA384) = [1,2,840,113549,2,10]
-    getObjectID (TypeHMAC SHA512) = [1,2,840,113549,2,11]
-    getObjectID TypeKMAC_SHAKE128 = [2,16,840,1,101,3,4,2,19]
-    getObjectID TypeKMAC_SHAKE256 = [2,16,840,1,101,3,4,2,20]
+    getObjectID (TypeHMAC MD5)      = [1,2,840,113549,2,6]
+    getObjectID (TypeHMAC SHA1)     = [1,2,840,113549,2,7]
+    getObjectID (TypeHMAC SHA224)   = [1,2,840,113549,2,8]
+    getObjectID (TypeHMAC SHA256)   = [1,2,840,113549,2,9]
+    getObjectID (TypeHMAC SHA384)   = [1,2,840,113549,2,10]
+    getObjectID (TypeHMAC SHA512)   = [1,2,840,113549,2,11]
+    getObjectID (TypeHMAC SHA3_224) = [2,16,840,1,101,3,4,2,13]
+    getObjectID (TypeHMAC SHA3_256) = [2,16,840,1,101,3,4,2,14]
+    getObjectID (TypeHMAC SHA3_384) = [2,16,840,1,101,3,4,2,15]
+    getObjectID (TypeHMAC SHA3_512) = [2,16,840,1,101,3,4,2,16]
+    getObjectID TypeKMAC_SHAKE128   = [2,16,840,1,101,3,4,2,19]
+    getObjectID TypeKMAC_SHAKE256   = [2,16,840,1,101,3,4,2,20]
 
     getObjectID ty = error ("Unsupported MACAlgorithm: " ++ show ty)
 
@@ -483,6 +539,12 @@
     algorithmType (KMAC_SHAKE128 _ _) = TypeKMAC_SHAKE128
     algorithmType (KMAC_SHAKE256 _ _) = TypeKMAC_SHAKE256
 
+    -- SHA-224, SHA-256, SHA-384, SHA-512 have NULL parameter,
+    -- other HMAC algorithms have no parameter
+    parameterASN1S (HMAC SHA224)         = gNull
+    parameterASN1S (HMAC SHA256)         = gNull
+    parameterASN1S (HMAC SHA384)         = gNull
+    parameterASN1S (HMAC SHA512)         = gNull
     parameterASN1S (HMAC _)              = id
     parameterASN1S (KMAC_SHAKE128 p str) = kmacASN1S 256 p str
     parameterASN1S (KMAC_SHAKE256 p str) = kmacASN1S 512 p str
@@ -507,7 +569,7 @@
     if b then parseParams else return (fn p' B.empty)
   where
     parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->
-        getNext >>= \(OctetString str) -> return (fn p str)
+        fn p <$> parseOctetStringPrim
 
 instance HasKeySize MACAlgorithm where
     getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)
@@ -592,6 +654,9 @@
       -- ^ Cipher Feedback
     | forall c . BlockCipher c => CTR (ContentEncryptionCipher c)
       -- ^ Counter
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => DeriveHKDF (DigestProxy hashAlg)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show ContentEncryptionAlg where
     show (ECB c) = shows c "_ECB"
@@ -599,6 +664,7 @@
     show CBC_RC2 = "RC2_CBC"
     show (CFB c) = shows c "_CFB"
     show (CTR c) = shows c "_CTR"
+    show (DeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d
 
 instance Enumerable ContentEncryptionAlg where
     values = [ CBC DES
@@ -623,6 +689,8 @@
              , CFB Camellia128
 
              , CTR Camellia128
+
+             , DeriveHKDF SHA256
              ]
 
 instance OIDable ContentEncryptionAlg where
@@ -649,6 +717,8 @@
 
     getObjectID (CTR Camellia128)  = [0,3,4401,5,3,1,9,9]
 
+    getObjectID (DeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]
+
     getObjectID ty = error ("Unsupported ContentEncryptionAlg: " ++ show ty)
 
 instance OIDNameable ContentEncryptionAlg where
@@ -657,7 +727,8 @@
 -- | Content encryption algorithm with associated parameters (i.e. the
 -- initialization vector).
 --
--- A value can be generated with 'generateEncryptionParams'.
+-- A value can be built with functions 'ecbParams', 'generateCBCParams',
+-- 'generateRC2EncryptionParams', 'generateCFBParams' and 'generateCTRParams'.
 data ContentEncryptionParams
     = forall c . BlockCipher c => ParamsECB (ContentEncryptionCipher c)
       -- ^ Electronic Codebook
@@ -669,6 +740,9 @@
       -- ^ Cipher Feedback
     | forall c . BlockCipher c => ParamsCTR (ContentEncryptionCipher c) (IV c)
       -- ^ Counter
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => ParamsDeriveHKDF (DigestProxy hashAlg) ContentEncryptionParams
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show ContentEncryptionParams where
     show = show . getContentEncryptionAlg
@@ -679,6 +753,8 @@
     ParamsCBCRC2 i1 iv1 == ParamsCBCRC2 i2 iv2 = i1 == i2 && iv1 `B.eq` iv2
     ParamsCFB c1 iv1    == ParamsCFB c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2
     ParamsCTR c1 iv1    == ParamsCTR c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2
+    ParamsDeriveHKDF d1 a1 == ParamsDeriveHKDF d2 a2 =
+        DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2
     _                   == _                   = False
 
 instance HasKeySize ContentEncryptionParams where
@@ -687,6 +763,7 @@
     getKeySizeSpecifier (ParamsCBCRC2 i _) = KeySizeFixed $ (i + 7) `div` 8
     getKeySizeSpecifier (ParamsCFB c _)    = getCipherKeySizeSpecifier c
     getKeySizeSpecifier (ParamsCTR c _)    = getCipherKeySizeSpecifier c
+    getKeySizeSpecifier (ParamsDeriveHKDF _ a) = getKeySizeSpecifier a
 
 instance ASN1Elem e => ProduceASN1Object e ContentEncryptionParams where
     asn1s param =
@@ -706,6 +783,7 @@
 ceParameterASN1S (ParamsCBCRC2 len iv) = rc2ParameterASN1S len iv
 ceParameterASN1S (ParamsCFB _ iv)      = gOctetString (B.convert iv)
 ceParameterASN1S (ParamsCTR _ iv)      = gOctetString (B.convert iv)
+ceParameterASN1S (ParamsDeriveHKDF _ a) = asn1s a
 
 parseCEParameter :: Monoid e
                  => ContentEncryptionAlg -> ParseASN1 e ContentEncryptionParams
@@ -714,6 +792,7 @@
 parseCEParameter CBC_RC2 = parseRC2Parameter
 parseCEParameter (CFB c) = ParamsCFB c <$> (getNext >>= getIV)
 parseCEParameter (CTR c) = ParamsCTR c <$> (getNext >>= getIV)
+parseCEParameter (DeriveHKDF d) = ParamsDeriveHKDF d <$> parse
 
 getIV :: BlockCipher cipher => ASN1 -> ParseASN1 e (IV cipher)
 getIV (OctetString ivBs) =
@@ -742,22 +821,41 @@
 getContentEncryptionAlg (ParamsCBCRC2 _ _) = CBC_RC2
 getContentEncryptionAlg (ParamsCFB c _)    = CFB c
 getContentEncryptionAlg (ParamsCTR c _)    = CTR c
+getContentEncryptionAlg (ParamsDeriveHKDF d _) = DeriveHKDF d
 
--- | Generate random parameters for the specified content encryption algorithm.
-generateEncryptionParams :: MonadRandom m
-                         => ContentEncryptionAlg -> m ContentEncryptionParams
-generateEncryptionParams (ECB c) = return (ParamsECB c)
-generateEncryptionParams (CBC c) = ParamsCBC c <$> ivGenerate undefined
-generateEncryptionParams CBC_RC2 = ParamsCBCRC2 128 <$> ivGenerate undefined
-generateEncryptionParams (CFB c) = ParamsCFB c <$> ivGenerate undefined
-generateEncryptionParams (CTR c) = ParamsCTR c <$> ivGenerate undefined
+-- | Get 'ECB' parameters for the specified cipher.
+ecbParams :: BlockCipher c
+          => ContentEncryptionCipher c -> ContentEncryptionParams
+ecbParams = ParamsECB
 
+-- | Generate random 'CBC' parameters for the specified cipher.
+generateCBCParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCBCParams c = ParamsCBC c <$> ivGenerate undefined
+
 -- | Generate random RC2 parameters with the specified effective key length (in
 -- bits).
 generateRC2EncryptionParams :: MonadRandom m
                             => Int -> m ContentEncryptionParams
 generateRC2EncryptionParams len = ParamsCBCRC2 len <$> ivGenerate undefined
 
+-- | Generate random 'CFB' parameters for the specified cipher.
+generateCFBParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCFBParams c = ParamsCFB c <$> ivGenerate undefined
+
+-- | Generate random 'CTR' parameters for the specified cipher.
+generateCTRParams :: (MonadRandom m, BlockCipher c)
+                  => ContentEncryptionCipher c
+                  -> m ContentEncryptionParams
+generateCTRParams c = ParamsCTR c <$> ivGenerate undefined
+
+-- | Use derivation of the content-encryption key with HKDF-SHA256
+deriveEncryptionKey :: ContentEncryptionParams -> ContentEncryptionParams
+deriveEncryptionKey = ParamsDeriveHKDF SHA256
+
 -- | Encrypt a bytearray with the specified content encryption key and
 -- algorithm.
 contentEncrypt :: (ByteArray cek, ByteArray ba)
@@ -771,6 +869,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> force $ cbcEncrypt c iv $ padded c bs)
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> force $ cfbEncrypt c iv $ padded c bs)
         ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> force $ ctrCombine c iv $ padded c bs)
+        ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentEncrypt derived a bs
   where
     force x  = x `seq` Right x
     padded c = pad (PKCS7 $ blockSize c)
@@ -788,6 +887,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> unpadded c (cbcDecrypt c iv bs))
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> unpadded c (cfbDecrypt c iv bs))
         ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> unpadded c (ctrCombine c iv bs))
+        ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentDecrypt derived a bs
   where
     unpadded c decrypted =
         case unpad (PKCS7 $ blockSize c) decrypted of
@@ -854,6 +954,9 @@
       -- ^ Counter with CBC-MAC
     | forall c . BlockCipher c => GCM (ContentEncryptionCipher c)
       -- ^ Galois Counter Mode
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => AuthDeriveHKDF (DigestProxy hashAlg)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show AuthContentEncryptionAlg where
     show AUTH_ENC_128 = "AUTH_ENC_128"
@@ -861,6 +964,7 @@
     show CHACHA20_POLY1305 = "CHACHA20_POLY1305"
     show (CCM c)      = shows c "_CCM"
     show (GCM c)      = shows c "_GCM"
+    show (AuthDeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d
 
 instance Enumerable AuthContentEncryptionAlg where
     values = [ AUTH_ENC_128
@@ -874,6 +978,8 @@
              , GCM AES128
              , GCM AES192
              , GCM AES256
+
+             , AuthDeriveHKDF SHA256
              ]
 
 instance OIDable AuthContentEncryptionAlg where
@@ -889,6 +995,8 @@
     getObjectID (GCM AES192)       = [2,16,840,1,101,3,4,1,26]
     getObjectID (GCM AES256)       = [2,16,840,1,101,3,4,1,46]
 
+    getObjectID (AuthDeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]
+
     getObjectID ty = error ("Unsupported AuthContentEncryptionAlg: " ++ show ty)
 
 instance OIDNameable AuthContentEncryptionAlg where
@@ -933,9 +1041,8 @@
 -- | Authenticated-content encryption algorithm with associated parameters
 -- (i.e. the nonce).
 --
--- A value can be generated with functions 'generateAuthEnc128Params',
--- 'generateAuthEnc256Params', 'generateChaChaPoly1305Params',
--- 'generateCCMParams' and 'generateGCMParams'.
+-- A value can be built with functions 'authEnc128Params', 'authEnc256Params',
+-- 'generateChaChaPoly1305Params', 'generateCCMParams' and 'generateGCMParams'.
 data AuthContentEncryptionParams
     = Params_AUTH_ENC_128 AuthEncParams
       -- ^ authEnc with 128-bit keying material
@@ -947,6 +1054,9 @@
       -- ^ Counter with CBC-MAC
     | forall c . BlockCipher c => ParamsGCM (ContentEncryptionCipher c) B.Bytes Int
       -- ^ Galois Counter Mode
+    | forall hashAlg . Hash.HashAlgorithm hashAlg
+        => ParamsAuthDeriveHKDF (DigestProxy hashAlg) (ASN1ObjectExact AuthContentEncryptionParams)
+      -- ^ Derivation of content-encryption key with HKDF
 
 instance Show AuthContentEncryptionParams where
     show = show . getAuthContentEncryptionAlg
@@ -961,6 +1071,10 @@
         cecI c1 == cecI c2 && iv1 == iv2 && (m1, l1) == (m2, l2)
     ParamsGCM c1 iv1 len1  == ParamsGCM c2 iv2 len2  =
         cecI c1 == cecI c2 && iv1 == iv2 && len1 == len2
+
+    ParamsAuthDeriveHKDF d1 a1 == ParamsAuthDeriveHKDF d2 a2 =
+        DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2
+
     _               == _               = False
 
 instance HasKeySize AuthContentEncryptionParams where
@@ -969,38 +1083,39 @@
     getKeySizeSpecifier (Params_CHACHA20_POLY1305 _) = KeySizeFixed 32
     getKeySizeSpecifier (ParamsCCM c _ _ _)     = getCipherKeySizeSpecifier c
     getKeySizeSpecifier (ParamsGCM c _ _)       = getCipherKeySizeSpecifier c
+    getKeySizeSpecifier (ParamsAuthDeriveHKDF _ a) = getKeySizeSpecifier (exactObject a)
 
-instance ASN1Elem e => ProduceASN1Object e AuthContentEncryptionParams where
+instance ProduceASN1Object ASN1P AuthContentEncryptionParams where
     asn1s param =
         asn1Container Sequence (oid . params)
       where
         oid    = gOID (getObjectID $ getAuthContentEncryptionAlg param)
         params = aceParameterASN1S param
 
-instance Monoid e => ParseASN1Object e AuthContentEncryptionParams where
+instance ParseASN1Object [ASN1Event] AuthContentEncryptionParams where
     parse = onNextContainer Sequence $ do
         OID oid <- getNext
         withObjectID "authenticated-content encryption algorithm" oid
             parseACEParameter
 
-aceParameterASN1S :: ASN1Elem e => AuthContentEncryptionParams -> ASN1Stream e
+aceParameterASN1S :: AuthContentEncryptionParams -> ASN1PS
 aceParameterASN1S (Params_AUTH_ENC_128 p) = asn1s p
 aceParameterASN1S (Params_AUTH_ENC_256 p) = asn1s p
 aceParameterASN1S (Params_CHACHA20_POLY1305 iv) = gOctetString (B.convert iv)
 aceParameterASN1S (ParamsCCM _ iv m _) =
-    asn1Container Sequence (nonce . icvlen)
+    asn1Container Sequence (if m == CCM_M12 then nonce else nonce . icvlen)
   where
     nonce  = gOctetString (B.convert iv)
     icvlen = gIntVal (fromIntegral $ getM m)
 aceParameterASN1S (ParamsGCM _ iv len) =
-    asn1Container Sequence (nonce . icvlen)
+    asn1Container Sequence (if len == 12 then nonce else nonce . icvlen)
   where
     nonce  = gOctetString (B.convert iv)
     icvlen = gIntVal (fromIntegral len)
+aceParameterASN1S (ParamsAuthDeriveHKDF _ a) = asn1s a
 
-parseACEParameter :: Monoid e
-                  => AuthContentEncryptionAlg
-                  -> ParseASN1 e AuthContentEncryptionParams
+parseACEParameter :: AuthContentEncryptionAlg
+                  -> ParseASN1 [ASN1Event] AuthContentEncryptionParams
 parseACEParameter AUTH_ENC_128 = Params_AUTH_ENC_128 <$> parse
 parseACEParameter AUTH_ENC_256 = Params_AUTH_ENC_256 <$> parse
 parseACEParameter CHACHA20_POLY1305 = do
@@ -1014,7 +1129,7 @@
     let ivlen = B.length iv
     when (ivlen < 7 || ivlen > 13) $
         throwParseError $ "Parsed invalid CCM nonce length: " ++ show ivlen
-    let Just l = fromL (15 - ivlen)
+    let l = fromJust $ fromL (15 - ivlen)
     m <- parseM
     return (ParamsCCM c (B.convert iv) m l)
 parseACEParameter (GCM c)      = onNextContainer Sequence $ do
@@ -1025,6 +1140,7 @@
     when (icvlen < 12 || icvlen > 16) $
         throwParseError $ "Parsed invalid GCM ICV length: " ++ show icvlen
     return (ParamsGCM c (B.convert iv) $ fromIntegral icvlen)
+parseACEParameter (AuthDeriveHKDF d) = ParamsAuthDeriveHKDF d <$> parse
 
 -- | Get the authenticated-content encryption algorithm.
 getAuthContentEncryptionAlg :: AuthContentEncryptionParams
@@ -1034,26 +1150,23 @@
 getAuthContentEncryptionAlg (Params_CHACHA20_POLY1305 _) = CHACHA20_POLY1305
 getAuthContentEncryptionAlg (ParamsCCM c _ _ _)     = CCM c
 getAuthContentEncryptionAlg (ParamsGCM c _ _)       = GCM c
+getAuthContentEncryptionAlg (ParamsAuthDeriveHKDF d _) = AuthDeriveHKDF d
 
--- | Generate random 'AUTH_ENC_128' parameters with the specified algorithms.
-generateAuthEnc128Params :: MonadRandom m
-                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm
-                         -> m AuthContentEncryptionParams
-generateAuthEnc128Params prfAlg cea macAlg = do
-    params <- generateEncryptionParams cea
-    return $ Params_AUTH_ENC_128 $
+-- | Get 'AUTH_ENC_128' parameters with the specified algorithms.
+authEnc128Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm
+                 -> AuthContentEncryptionParams
+authEnc128Params prfAlg params macAlg =
+    Params_AUTH_ENC_128 $
         AuthEncParams { prfAlgorithm = prfAlg
                       , encAlgorithm = params
                       , macAlgorithm = macAlg
                       }
 
--- | Generate random 'AUTH_ENC_256' parameters with the specified algorithms.
-generateAuthEnc256Params :: MonadRandom m
-                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm
-                         -> m AuthContentEncryptionParams
-generateAuthEnc256Params prfAlg cea macAlg = do
-    params <- generateEncryptionParams cea
-    return $ Params_AUTH_ENC_256 $
+-- | Get 'AUTH_ENC_256' parameters with the specified algorithms.
+authEnc256Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm
+                 -> AuthContentEncryptionParams
+authEnc256Params prfAlg params macAlg = do
+    Params_AUTH_ENC_256 $
         AuthEncParams { prfAlgorithm = prfAlg
                       , encAlgorithm = params
                       , macAlgorithm = macAlg
@@ -1082,19 +1195,43 @@
     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
+    | len > maxLen = Left (InvalidParameter "HKDF IKM is too long")
+    | otherwise = Right $ HKDF.expand (extract hashAlg ikm) info len
+  where
+    maxLen = 255 * digestSizeFromProxy hashAlg
+    info = encodeASN1S (asn1s params)
+    len = B.length ikm
+
+    extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)
+            => DigestProxy a -> ikm -> HKDF.PRK a
+    extract _ = HKDF.extract (salt :: B.Bytes)
+
+    -- "The Cryptographic Message Syntax"
+    salt = B.pack [84,104,101,32,67,114,121,112,116,111,103,114,97,112,104,105
+                  ,99,32,77,101,115,115,97,103,101,32,83,121,110,116,97,120]
+
 -- | Encrypt a bytearray with the specified authenticated-content encryption
 -- key and algorithm.
 authContentEncrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)
                    => cek
-                   -> AuthContentEncryptionParams -> ba
+                   -> ASN1ObjectExact AuthContentEncryptionParams
                    -> aad -> ba -> Either StoreError (AuthTag, ba)
-authContentEncrypt key params paramsRaw aad bs =
-    case params of
+authContentEncrypt key params aad bs =
+    case exactObject params of
         Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authEncrypt p
         Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authEncrypt p
         Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpEncrypt
         ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= encrypt (getM m)
         ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= encrypt len
+        ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->
+            authContentEncrypt derived a aad bs
   where
     msglen  = B.length bs
     force x = x `seq` Right x
@@ -1112,7 +1249,8 @@
     authEncrypt p@AuthEncParams{..} = do
         let (encKey, macKey) = authKeys key p
         encrypted <- contentEncrypt encKey encAlgorithm bs
-        let macMsg = paramsRaw `B.append` encrypted `B.append` B.convert aad
+        let paramsRaw = exactObjectRaw params
+            macMsg = B.convert paramsRaw `B.append` encrypted `B.append` B.convert aad
             found  = mac macAlgorithm macKey macMsg
         return (found, encrypted)
 
@@ -1120,15 +1258,17 @@
 -- and algorithm.
 authContentDecrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)
                    => cek
-                   -> AuthContentEncryptionParams -> ba
+                   -> ASN1ObjectExact AuthContentEncryptionParams
                    -> aad -> ba -> AuthTag -> Either StoreError ba
-authContentDecrypt key params paramsRaw aad bs expected =
-    case params of
+authContentDecrypt key params aad bs expected =
+    case exactObject params of
         Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authDecrypt p
         Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authDecrypt p
         Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpDecrypt
         ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= decrypt (getM m)
         ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= decrypt len
+        ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->
+            authContentDecrypt derived a aad bs expected
   where
     msglen  = B.length bs
     badMac  = Left BadContentMAC
@@ -1153,7 +1293,8 @@
         | otherwise         = badMac
       where
         (encKey, macKey) = authKeys key p
-        macMsg = paramsRaw `B.append` bs `B.append` B.convert aad
+        paramsRaw = exactObjectRaw params
+        macMsg = B.convert paramsRaw `B.append` bs `B.append` B.convert aad
         found  = mac macAlgorithm macKey macMsg
         acceptable = securityAcceptable macAlgorithm
 
@@ -1225,7 +1366,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.
@@ -1327,6 +1468,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)
@@ -1450,6 +1597,7 @@
         ParamsCBCRC2 len iv -> let cc = getRC2Cipher len key in either (return . Left) (\c -> wrapEncrypt cbcEncrypt c iv bs) cc
         ParamsCFB cipher iv -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt cfbEncrypt c iv bs) cc
         ParamsCTR _ _       -> return $ Left (InvalidParameter "Unable to wrap key in CTR mode")
+        ParamsDeriveHKDF _ _ -> return $ Left (InvalidParameter "HKDF derivation is incompatible with key wrapping")
 keyEncrypt key AES128_WRAP      bs = return (getCipher AES128 key >>= (`AES_KW.wrap` bs))
 keyEncrypt key AES192_WRAP      bs = return (getCipher AES192 key >>= (`AES_KW.wrap` bs))
 keyEncrypt key AES256_WRAP      bs = return (getCipher AES256 key >>= (`AES_KW.wrap` bs))
@@ -1471,6 +1619,7 @@
         ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> wrapDecrypt cbcDecrypt c iv bs)
         ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> wrapDecrypt cfbDecrypt c iv bs)
         ParamsCTR _ _       -> Left (InvalidParameter "Unable to unwrap key in CTR mode")
+        ParamsDeriveHKDF _ _ -> Left (InvalidParameter "HKDF derivation is incompatible with key unwrapping")
 keyDecrypt key AES128_WRAP      bs = getCipher AES128   key >>= (`AES_KW.unwrap` bs)
 keyDecrypt key AES192_WRAP      bs = getCipher AES192   key >>= (`AES_KW.unwrap` bs)
 keyDecrypt key AES256_WRAP      bs = getCipher AES256   key >>= (`AES_KW.unwrap` bs)
@@ -1518,19 +1667,21 @@
     fn formatted =
         let firstPass = encFn cipher iv formatted
             lastBlock = B.dropView firstPass (B.length firstPass - sz)
-            Just iv'  = makeIV lastBlock
+            iv'       = fromJust $ makeIV lastBlock  -- wrapped length >= sz
          in encFn cipher iv' firstPass
 
 wrapDecrypt :: (BlockCipher cipher, ByteArray ba)
             => (cipher -> IV cipher -> ba -> ba)
             -> cipher -> IV cipher -> ba -> Either StoreError ba
-wrapDecrypt decFn cipher iv input = keyUnwrap (decFn cipher iv firstPass)
+wrapDecrypt decFn cipher iv input
+    | B.length beg < sz = Left (InvalidInput "wrapDecrypt: input too short")
+    | otherwise = keyUnwrap (decFn cipher iv firstPass)
   where
     sz = blockSize cipher
     (beg, lb) = B.splitAt (B.length input - sz) input
     lastBlock = decFn cipher iv' lb
-    Just iv'  = makeIV (B.dropView beg (B.length beg - sz))
-    Just iv'' = makeIV lastBlock
+    iv'       = fromJust $ makeIV (B.dropView beg (B.length beg - sz))
+    iv''      = fromJust $ makeIV lastBlock
     firstPass = decFn cipher iv'' beg `B.append` lastBlock
 
 
@@ -1642,12 +1793,12 @@
 -- private key.
 transportDecrypt :: MonadRandom m
                  => KeyTransportParams
-                 -> X509.PrivKey
+                 -> KeyPair
                  -> ByteString
                  -> m (Either StoreError ByteString)
-transportDecrypt RSAES         (X509.PrivKeyRSA priv) bs =
+transportDecrypt RSAES         (KeyPairRSA priv _) bs =
     mapLeft RSAError <$> RSA.decryptSafer priv bs
-transportDecrypt (RSAESOAEP p) (X509.PrivKeyRSA priv) bs
+transportDecrypt (RSAESOAEP p) (KeyPairRSA priv _) bs
     | securityAcceptable p =
         withOAEPParams p $ \params ->
             mapLeft RSAError <$> RSAOAEP.decryptSafer params priv bs
@@ -1838,8 +1989,8 @@
 -- | Decrypt the specified content with an ECDH key pair and key-agreement
 -- algorithm.
 ecdhDecrypt :: ByteArray ba
-            => KeyAgreementParams -> Maybe ByteString -> X509.PrivKey -> ByteString -> ba -> Either StoreError ba
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =
+            => KeyAgreementParams -> Maybe ByteString -> KeyPair -> ByteString -> ba -> Either StoreError ba
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairEC priv _) pt bs =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
@@ -1850,16 +2001,16 @@
                         s = ECDH.getShared curve d pub
                         k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
                     keyDecrypt k kep bs
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX25519 priv) pt bs = do
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX25519 priv _) pt bs = do
     s <- fromCryptoFailable (X25519.publicKey pt >>= ecdh x25519 priv)
     let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
     keyDecrypt k kep bs
-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX448 priv) pt bs = do
+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX448 priv _) pt bs = do
     s <- fromCryptoFailable (X448.publicKey pt >>= ecdh x448 priv)
     let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
     keyDecrypt k kep bs
 ecdhDecrypt (StdDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType
-ecdhDecrypt (CofactorDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =
+ecdhDecrypt (CofactorDH dig kep) ukm (KeyPairEC priv _) pt bs =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
@@ -1892,8 +2043,8 @@
 ivGenerate :: (BlockCipher cipher, MonadRandom m) => cipher -> m (IV cipher)
 ivGenerate cipher = do
     bs <- getRandomBytes (blockSize cipher)
-    let Just iv = makeIV (bs :: ByteString)
-    return iv
+    let iv = makeIV (bs :: ByteString)
+    return $! fromJust iv
 
 nonceGenerate :: MonadRandom m => Int -> m B.Bytes
 nonceGenerate = getRandomBytes
@@ -1927,7 +2078,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
@@ -2070,6 +2221,10 @@
              , TypeECDSA (DigestAlgorithm SHA256)
              , TypeECDSA (DigestAlgorithm SHA384)
              , TypeECDSA (DigestAlgorithm SHA512)
+             , TypeECDSA (DigestAlgorithm SHA3_224)
+             , TypeECDSA (DigestAlgorithm SHA3_256)
+             , TypeECDSA (DigestAlgorithm SHA3_384)
+             , TypeECDSA (DigestAlgorithm SHA3_512)
 
              , TypeEd25519
              , TypeEd448
@@ -2097,6 +2252,10 @@
     getObjectID (TypeECDSA (DigestAlgorithm SHA256)) = [1,2,840,10045,4,3,2]
     getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3]
     getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_224)) = [2,16,840,1,101,3,4,3,9]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_256)) = [2,16,840,1,101,3,4,3,10]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_384)) = [2,16,840,1,101,3,4,3,11]
+    getObjectID (TypeECDSA (DigestAlgorithm SHA3_512)) = [2,16,840,1,101,3,4,3,12]
 
     getObjectID TypeEd25519                          = [1,3,101,112]
     getObjectID TypeEd448                            = [1,3,101,113]
@@ -2144,23 +2303,22 @@
     parseParameter TypeEd25519      = return Ed25519
     parseParameter TypeEd448        = return Ed448
 
--- | Sign a message using the specified algorithm and private key.  The
--- corresponding public key is also required for some algorithms.
-signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> X509.PubKey -> ByteString -> m (Either StoreError SignatureValue)
-signatureGenerate RSAAnyHash _ _ _ =
+-- | Sign a message using the specified algorithm and private key.
+signatureGenerate :: MonadRandom m => SignatureAlg -> KeyPair -> ByteString -> m (Either StoreError SignatureValue)
+signatureGenerate RSAAnyHash _ _ =
     error "signatureGenerate: should call signatureResolveHash first"
-signatureGenerate (RSA alg)   (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =
+signatureGenerate (RSA alg)   (KeyPairRSA priv _) msg =
     let err = return . Left $ InvalidParameter ("Invalid hash algorithm for RSA: " ++ show alg)
      in withHashAlgorithmASN1 alg err $ \hashAlg ->
             mapLeft RSAError <$> RSA.signSafer (Just hashAlg) priv msg
-signatureGenerate (RSAPSS p)  (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =
+signatureGenerate (RSAPSS p)  (KeyPairRSA priv _) msg =
     withPSSParams p $ \params ->
         mapLeft RSAError <$> RSAPSS.signSafer params priv msg
-signatureGenerate (DSA alg)   (X509.PrivKeyDSA priv) (X509.PubKeyDSA _) msg =
+signatureGenerate (DSA alg)   (KeyPairDSA pair) msg =
     case alg of
         DigestAlgorithm t ->
-            Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg
-signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv)  (X509.PubKeyEC _)  msg =
+            Right . dsaFromSignature <$> DSA.sign (DSA.toPrivateKey pair) (hashFromProxy t) msg
+signatureGenerate (ECDSA alg) (KeyPairEC priv _)  msg =
     case alg of
         DigestAlgorithm t ->
             case ecdsaToPrivateKey priv of
@@ -2168,11 +2326,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/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
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
@@ -67,6 +67,7 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 
 -- | Encrypted key.
 type EncryptedKey = ByteString
@@ -508,9 +509,9 @@
 --
 -- This function can be used as parameter to
 -- 'Crypto.Store.CMS.openEnvelopedData'.
-withRecipientKeyTrans :: MonadRandom m => PrivKey -> ConsumerOfRI m
-withRecipientKeyTrans privKey (KTRI KTRecipientInfo{..}) =
-    transportDecrypt ktKeyTransportParams privKey ktEncryptedKey
+withRecipientKeyTrans :: MonadRandom m => KeyPair -> ConsumerOfRI m
+withRecipientKeyTrans keyPair (KTRI KTRecipientInfo{..}) =
+    transportDecrypt ktKeyTransportParams keyPair ktEncryptedKey
 withRecipientKeyTrans _ _ = pure (Left RecipientTypeMismatch)
 
 -- | Generate a Key Agreement recipient from a certificate and
@@ -553,16 +554,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
diff --git a/src/Crypto/Store/CMS/Info.hs b/src/Crypto/Store/CMS/Info.hs
--- a/src/Crypto/Store/CMS/Info.hs
+++ b/src/Crypto/Store/CMS/Info.hs
@@ -6,6 +6,7 @@
 -- Portability : unknown
 --
 -- CMS content information.
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -21,7 +22,9 @@
     , toDetachedCI
     ) where
 
+#if !(MIN_VERSION_base(4,13,0))
 import Control.Monad.Fail (MonadFail)
+#endif
 
 import Data.ASN1.Types
 import Data.ByteString (ByteString)
diff --git a/src/Crypto/Store/CMS/OriginatorInfo.hs b/src/Crypto/Store/CMS/OriginatorInfo.hs
--- a/src/Crypto/Store/CMS/OriginatorInfo.hs
+++ b/src/Crypto/Store/CMS/OriginatorInfo.hs
@@ -6,6 +6,7 @@
 -- Portability : unknown
 --
 --
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
@@ -24,7 +25,9 @@
 
 import Data.ASN1.Types
 import Data.Maybe (fromMaybe)
+#if !(MIN_VERSION_base(4,11,0))
 import Data.Semigroup
+#endif
 import Data.X509
 
 import Crypto.Store.ASN1.Generate
diff --git a/src/Crypto/Store/CMS/PEM.hs b/src/Crypto/Store/CMS/PEM.hs
--- a/src/Crypto/Store/CMS/PEM.hs
+++ b/src/Crypto/Store/CMS/PEM.hs
@@ -11,6 +11,7 @@
     , readCMSFileFromMemory
     , berToContentInfo
     , pemToContentInfo
+    , pemToContentInfoAccum
     , writeCMSFile
     , writeCMSFileToMemory
     , contentInfoToDER
@@ -18,7 +19,7 @@
     ) where
 
 import qualified Data.ByteString as B
-import           Data.Maybe (catMaybes)
+import           Data.Either (rights)
 
 import Crypto.Store.CMS.Info
 import Crypto.Store.CMS.Util
@@ -37,7 +38,7 @@
 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
@@ -45,16 +46,16 @@
 
 -- | Read a content info from a '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 'PEM' element.
+pemToContentInfo :: PEM -> Either StoreError ContentInfo
+pemToContentInfo pem
+    | pemName pem `elem` names = berToContentInfo (pemContent pem)
+    | otherwise                = Left UnexpectedNameForPEM
+  where names = [ "CMS", "PKCS7" ]
 
 
 -- Writing to PEM format
diff --git a/src/Crypto/Store/CMS/Signed.hs b/src/Crypto/Store/CMS/Signed.hs
--- a/src/Crypto/Store/CMS/Signed.hs
+++ b/src/Crypto/Store/CMS/Signed.hs
@@ -48,6 +48,7 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 
 -- | Encapsulated content.
 type EncapsulatedContent = ByteString
@@ -167,15 +168,16 @@
 -- the full message.
 certSigner :: MonadRandom m
            => SignatureAlg
-           -> PrivKey
+           -> KeyPair
            -> CertificateChain
            -> Maybe [Attribute]
            -> [Attribute]
            -> ProducerOfSI m
 certSigner _ _ (CertificateChain []) _ _ _ _ =
     pure $ Left (InvalidInput "Empty certificate chain")
-certSigner alg priv (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg =
-    fmap build <$> generate
+certSigner alg pair (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg
+    | keyPairMatchesKey pair pub = fmap build <$> generate
+    | otherwise = pure $ Left PublicPrivateKeyMismatch
   where
     md   = digest dig msg
     def  = DigestAlgorithm Crypto.Store.CMS.Algorithms.SHA256
@@ -193,7 +195,7 @@
                 let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs
                  in (l, encodeAuthAttrs l)
 
-    generate  = signatureGenerate alg' priv pub input
+    generate  = signatureGenerate alg' pair input
     build sig =
         let si = SignerInfo { siSignerId = SignerIASN isn
                             , siDigestAlgorithm = dig
diff --git a/src/Crypto/Store/CMS/Util.hs b/src/Crypto/Store/CMS/Util.hs
--- a/src/Crypto/Store/CMS/Util.hs
+++ b/src/Crypto/Store/CMS/Util.hs
@@ -31,6 +31,7 @@
     -- * Parsing and encoding ASN.1 objects
     , ASN1Event
     , ASN1ObjectExact(..)
+    , derObjectExact
     , ProduceASN1Object(..)
     , encodeASN1Object
     , ParseASN1Object(..)
@@ -177,6 +178,10 @@
     { exactObject    :: a           -- ^ The wrapped ASN.1 object
     , exactObjectRaw :: ByteString  -- ^ The raw representation of this object
     } deriving Show
+
+-- | Create an ASN.1 object with a raw data in DER format.
+derObjectExact :: ProduceASN1Object ASN1P a => a -> ASN1ObjectExact a
+derObjectExact obj = ASN1ObjectExact obj (encodeASN1Object obj)
 
 instance Eq a => Eq (ASN1ObjectExact a)
     where a == b = exactObject a == exactObject b
diff --git a/src/Crypto/Store/Error.hs b/src/Crypto/Store/Error.hs
--- a/src/Crypto/Store/Error.hs
+++ b/src/Crypto/Store/Error.hs
@@ -26,6 +26,8 @@
       -- ^ Error while decoding ASN.1 content
     | ParseFailure String
       -- ^ Error while parsing an ASN.1 object
+    | UnexpectedNameForPEM
+      -- ^ The name of the given PEM object is not supported
     | DecryptionFailed
       -- ^ Unable to decrypt, incorrect key or password?
     | BadContentMAC
@@ -42,6 +44,8 @@
       -- ^ Some condition is not met about input password
     | InvalidParameter String
       -- ^ Some condition is not met about algorithm parameters
+    | PublicPrivateKeyMismatch
+      -- ^ The public key or certificate does not match the private key
     | UnexpectedPublicKeyType
       -- ^ The algorithm expects another public key type
     | UnexpectedPrivateKeyType
diff --git a/src/Crypto/Store/KeyWrap/AES.hs b/src/Crypto/Store/KeyWrap/AES.hs
--- a/src/Crypto/Store/KeyWrap/AES.hs
+++ b/src/Crypto/Store/KeyWrap/AES.hs
@@ -10,6 +10,7 @@
 --
 -- Should be used with a cipher from module "Crypto.Cipher.AES".
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
 module Crypto.Store.KeyWrap.AES
     ( wrap
     , unwrap
@@ -20,7 +21,9 @@
 import           Data.Bits
 import           Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)
 import qualified Data.ByteArray as B
-import           Data.List
+#if !(MIN_VERSION_base(4,20,0))
+import           Data.List (foldl')
+#endif
 import           Data.Word
 
 import Crypto.Cipher.Types
diff --git a/src/Crypto/Store/KeyWrap/RC2.hs b/src/Crypto/Store/KeyWrap/RC2.hs
--- a/src/Crypto/Store/KeyWrap/RC2.hs
+++ b/src/Crypto/Store/KeyWrap/RC2.hs
@@ -16,6 +16,7 @@
 
 import           Data.ByteArray (ByteArray)
 import qualified Data.ByteArray as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Hash
@@ -62,7 +63,7 @@
             temp1      = cbcEncrypt cipher iv lcekpadicv
             temp2      = B.append (B.convert iv) temp1
             temp3      = reverseBytes temp2
-            Just iv'   = makeIV iv4adda22c79e82105
+            iv'        = fromJust $ makeIV iv4adda22c79e82105
          in cbcEncrypt cipher iv' temp3
 
 -- | Unwrap an encrypted RC2 key with the specified RC2 cipher.
@@ -75,14 +76,14 @@
     | otherwise          = invalid
   where
     inLen            = B.length wrapped
-    Just iv'         = makeIV iv4adda22c79e82105
+    iv'              = fromJust $ makeIV iv4adda22c79e82105
     temp3            = cbcDecrypt cipher iv' wrapped
     temp2            = reverseBytes temp3
     (ivBs, temp1)    = B.splitAt 8 temp2
-    Just iv          = makeIV ivBs
+    iv               = fromJust $ makeIV ivBs
     lcekpadicv       = cbcDecrypt cipher iv temp1
     (lcekpad, icv)   = B.splitAt (inLen - 16) lcekpadicv
-    Just (l, cekpad) = B.uncons lcekpad
+    (l, cekpad)      = fromJust $ B.uncons lcekpad
     len              = fromIntegral l
     padlen           = inLen - 16 - len - 1
     cek              = B.take len cekpad
diff --git a/src/Crypto/Store/KeyWrap/TripleDES.hs b/src/Crypto/Store/KeyWrap/TripleDES.hs
--- a/src/Crypto/Store/KeyWrap/TripleDES.hs
+++ b/src/Crypto/Store/KeyWrap/TripleDES.hs
@@ -15,6 +15,7 @@
 
 import           Data.ByteArray (ByteArray)
 import qualified Data.ByteArray as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Hash
@@ -40,7 +41,7 @@
         (InvalidInput "KeyWrap.TripleDES: invalid length for content encryption key")
   where
     inLen    = B.length cek
-    Just iv' = makeIV iv4adda22c79e82105
+    iv'      = fromJust $ makeIV iv4adda22c79e82105
     cekicv   = B.append cek (checksum cek)
     temp1    = cbcEncrypt cipher iv cekicv
     temp2    = B.append (B.convert iv) temp1
@@ -56,11 +57,11 @@
     | otherwise                    = invalid
   where
     inLen         = B.length wrapped
-    Just iv'      = makeIV iv4adda22c79e82105
+    iv'           = fromJust $ makeIV iv4adda22c79e82105
     temp3         = cbcDecrypt cipher iv' wrapped
     temp2         = reverseBytes temp3
     (ivBs, temp1) = B.splitAt 8 temp2
-    Just iv       = makeIV ivBs
+    iv            = fromJust $ makeIV ivBs
     cekicv        = cbcDecrypt cipher iv temp1
     (cek, icv)    = B.splitAt 24 cekicv
     invalid       = Left BadChecksum
diff --git a/src/Crypto/Store/Keys.hs b/src/Crypto/Store/Keys.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Store/Keys.hs
@@ -0,0 +1,122 @@
+-- |
+-- Module      : Crypto.Store.Keys
+-- License     : BSD-style
+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
+-- Stability   : experimental
+-- Portability : unknown
+--
+--
+{-# LANGUAGE RecordWildCards #-}
+module Crypto.Store.Keys
+    ( KeyPair(..), keyPairFromPrivKey, keyPairToPrivKey, keyPairToPubKey
+    , keyPairMatchesKey, keyPairMatchesCert
+    ) where
+
+import Data.Function (on)
+import Data.Maybe (fromMaybe)
+
+import qualified Data.X509 as X509
+import Data.X509.EC
+
+import qualified Crypto.PubKey.RSA.Types as RSA
+import qualified Crypto.PubKey.DSA as DSA
+import qualified Crypto.PubKey.Curve25519 as X25519
+import qualified Crypto.PubKey.Curve448 as X448
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
+
+import Crypto.Store.PKCS8.EC
+
+-- | Holds a private and public key together, with guaranty 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
 
@@ -33,3 +34,7 @@
 -- | Write a PEM file to disk.
 writePEMs :: FilePath -> [PEM] -> IO ()
 writePEMs filepath = L.writeFile filepath . pemsWriteLBS
+
+-- | Make a PEM without headers.
+mkPEM :: String -> B.ByteString -> PEM
+mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
diff --git a/src/Crypto/Store/PKCS12.hs b/src/Crypto/Store/PKCS12.hs
--- a/src/Crypto/Store/PKCS12.hs
+++ b/src/Crypto/Store/PKCS12.hs
@@ -8,6 +8,7 @@
 -- Personal Information Exchange Syntax, aka PKCS #12.
 --
 -- Only password integrity mode and password privacy modes are supported.
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
@@ -17,7 +18,7 @@
 {-# LANGUAGE TypeFamilies #-}
 {-# LANGUAGE UndecidableInstances #-}
 module Crypto.Store.PKCS12
-    ( IntegrityParams
+    ( IntegrityParams(..)
     , readP12File
     , readP12FileFromMemory
     , writeP12File
@@ -70,14 +71,18 @@
     , recoverA
     ) where
 
+import Control.Applicative
 import Control.Monad
 
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import qualified Data.ByteString as BS
 import           Data.List (partition)
-import           Data.Maybe (fromMaybe, mapMaybe)
+import           Data.Maybe (isJust, fromMaybe, mapMaybe)
+#if !(MIN_VERSION_base(4,11,0))
 import           Data.Semigroup
+#endif
+import           Data.String (fromString)
 import qualified Data.X509 as X509
 import qualified Data.X509.Validation as X509
 
@@ -92,6 +97,7 @@
 import Crypto.Store.CMS.Enveloped
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 import Crypto.Store.PKCS5
 import Crypto.Store.PKCS5.PBES1
 import Crypto.Store.PKCS8
@@ -141,25 +147,44 @@
             Just md -> return $ Authenticated (verify md authSafeData)
 
     verify MacData{..} content pwdUTF8 =
-        case digAlg of
-            DigestAlgorithm d -> loop (toProtectionPasswords pwdUTF8)
+        case macAlg of
+            MacTraditional (DigestAlgorithm d) -> loop (toProtectionPasswords pwdUTF8)
               where
                 -- iterate over all possible representations of a password
                 -- until a successful match is found
                 loop []           = Left BadContentMAC
                 loop (pwd:others) =
-                    let fn key macAlg bs
-                            | not (securityAcceptable macAlg) =
+                    let fn key digAlg bs
+                            | not (securityAcceptable digAlg) =
                                 Left (InvalidParameter "Integrity MAC too weak")
-                            | macValue == mac macAlg key bs = (pwd,) <$> decode bs
+                            | macValue == mac digAlg key bs = (pwd,) <$> decode bs
                             | otherwise = loop others
                      in pkcs12mac Left fn d macParams content pwd
 
+            -- for RFC 9579 the primary representation is always used, this is
+            -- fine because we assume the encryption layer also uses that
+            -- representation
+            MacAuthScheme authScheme
+                | not (hasExplicitKeyLength authScheme) ->
+                    Left (InvalidParameter "KDF key length must be explicit")
+                | not (securityAcceptable authScheme) ->
+                    Left (InvalidParameter "Integrity MAC too weak")
+                | macValue == pbMac authScheme content pwd -> (pwd,) <$> decode content
+                | otherwise -> Left BadContentMAC
+              where pwd = toProtectionPassword pwdUTF8
 
+    hasExplicitKeyLength (PBMAC1 p) = isJust $ kdfKeyLength (pbmac1KDF p)
+
+
 -- Generating and encoding
 
 -- | Parameters used for password integrity mode.
-type IntegrityParams = (DigestAlgorithm, PBEParameter)
+data IntegrityParams
+    = TraditionalIntegrity DigestAlgorithm PBEParameter
+      -- ^ Traditional PKCS #12 integrity, with a digest algoritm
+    | AuthSchemeIntegrity AuthenticationScheme
+      -- ^ Integrity with an authentication scheme such as PBMAC1
+    deriving (Show,Eq)
 
 -- | Write a PKCS #12 file to disk.
 writeP12File :: FilePath
@@ -175,18 +200,33 @@
 writeP12FileToMemory :: IntegrityParams -> ProtectionPassword
                      -> PKCS12
                      -> Either StoreError BS.ByteString
-writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =
+writeP12FileToMemory intp pwdUTF8 aSafe =
     encode <$> protect
   where
     content   = encodeASN1Object aSafe
     encode md = encodeASN1Object PFX { authSafeData = content, macData = Just md }
 
-    protect = pkcs12mac Left fn hashAlg pbeParam content pwdUTF8
-    fn key macAlg bs = Right MacData { digAlg    = alg
-                                     , macValue  = mac macAlg key bs
-                                     , macParams = pbeParam
-                                     }
+    protect = case intp of
+        TraditionalIntegrity alg@(DigestAlgorithm hashAlg) pbeParam ->
+            let fn key macAlg bs = Right MacData { macAlg    = MacTraditional alg
+                                                 , macValue  = mac macAlg key bs
+                                                 , macParams = pbeParam
+                                                 }
+             in pkcs12mac Left fn hashAlg pbeParam content pwdUTF8
+        AuthSchemeIntegrity authScheme
+            | pwdUTF8 == emptyNotTerminated ->
+                Left (InvalidPassword "Authentication scheme requires terminated password")
+            | otherwise ->
+                Right MacData { macAlg    = MacAuthScheme (transform authScheme)
+                              , macValue  = pbMac authScheme content pwdUTF8
+                              , macParams = unused
+                              }
+    unused = PBEParameter (fromString "NOT USED") 1
 
+    transform (PBMAC1 p) = PBMAC1 (ensureExplicitKeyLength p)
+    ensureExplicitKeyLength p = p { pbmac1KDF = kdfKeyLengthModify fn (pbmac1KDF p) }
+      where fn = Just . fromMaybe (getMaximumKeySize $ pbmac1AScheme p)
+
 -- | Write a PKCS #12 file without integrity protection to disk.
 writeUnprotectedP12File :: FilePath -> PKCS12 -> IO ()
 writeUnprotectedP12File path = BS.writeFile path . writeUnprotectedP12FileToMemory
@@ -230,8 +270,12 @@
         m <- if b then Just <$> parse else pure Nothing
         return PFX { authSafeData = d, macData = m }
 
+data MacAlg = MacTraditional DigestAlgorithm
+            | MacAuthScheme AuthenticationScheme
+            deriving (Show,Eq)
+
 data MacData = MacData
-    { digAlg :: DigestAlgorithm
+    { macAlg :: MacAlg
     , macValue :: MessageAuthenticationCode
     , macParams :: PBEParameter
     }
@@ -242,25 +286,30 @@
         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
 
@@ -453,7 +502,7 @@
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 -- | Main bag payload in PKCS #12 contents.
-data SafeInfo = KeyBag (FormattedKey X509.PrivKey) -- ^ unencrypted private key
+data SafeInfo = KeyBag (FormattedKey KeyPair)      -- ^ unencrypted private key
               | PKCS8ShroudedKeyBag PKCS5          -- ^ encrypted private key
               | CertBag (Bag CertInfo)             -- ^ certificate
               | CRLBag (Bag CRLInfo)               -- ^ CRL
@@ -513,7 +562,7 @@
 filterByLocalKeyId :: BS.ByteString -> SafeContents -> SafeContents
 filterByLocalKeyId d = filterBags ((== Just d) . getLocalKeyId)
 
-getSafeKeysId :: SafeContents -> [OptProtected (Id X509.PrivKey)]
+getSafeKeysId :: SafeContents -> [OptProtected (Id KeyPair)]
 getSafeKeysId (SafeContents scs) = loop scs
   where
     loop []           = []
@@ -530,10 +579,10 @@
         return (mkId k bag)
 
 -- | Return all private keys contained in the safe contents.
-getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]
+getSafeKeys :: SafeContents -> [OptProtected KeyPair]
 getSafeKeys = map (fmap unId) . getSafeKeysId
 
-getAllSafeKeysId :: [SafeContents] -> OptProtected [Id X509.PrivKey]
+getAllSafeKeysId :: [SafeContents] -> OptProtected [Id KeyPair]
 getAllSafeKeysId = applySamePassword . concatMap getSafeKeysId
 
 -- | Return all private keys contained in the safe content list.  All shrouded
@@ -543,7 +592,7 @@
 -- least is encrypted.  This does not mean all keys were actually protected in
 -- the input.  If detailed view is required then function 'getSafeKeys' is
 -- available.
-getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey]
+getAllSafeKeys :: [SafeContents] -> OptProtected [KeyPair]
 getAllSafeKeys = applySamePassword . concatMap getSafeKeys
 
 getSafeX509CertsId :: SafeContents -> [Id X509.SignedCertificate]
@@ -598,19 +647,23 @@
                 -- 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
 -- credential is returned when the structure contains exactly one private key
@@ -627,7 +680,8 @@
     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
@@ -685,10 +739,14 @@
     certAttrs     = attrs : repeat []
     toCertBag a c = Bag (CertBag (Bag (CertX509 c) [])) a
 
-    scKeyOrError = wrap <$> encrypt algKey pwd encodedKey
+    scKeyOrError
+        | keyPairMatchesCert pair leaf =
+            wrap <$> encrypt algKey pwd encodedKey
+        | otherwise = Left PublicPrivateKeyMismatch
 
     wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) attrs]
-    encodedKey    = encodeASN1Object (FormattedKey PKCS8Format key)
+    encodedKey    = encodeASN1Object (FormattedKey PKCS8Format pair)
+    pair          = keyPairFromPrivKey key
 
     X509.Fingerprint keyId = X509.getFingerprint leaf X509.HashSHA1
     attrs = trans (setLocalKeyId keyId [])
diff --git a/src/Crypto/Store/PKCS5.hs b/src/Crypto/Store/PKCS5.hs
--- a/src/Crypto/Store/PKCS5.hs
+++ b/src/Crypto/Store/PKCS5.hs
@@ -24,6 +24,9 @@
     , EncryptionScheme(..)
     , PBEParameter(..)
     , PBES2Parameter(..)
+    -- * Message authentication schemes
+    , AuthenticationScheme(..)
+    , PBMAC1Parameter(..)
     -- * Key derivation
     , KeyDerivationFunc(..)
     , PBKDF2_PRF(..)
@@ -33,11 +36,16 @@
     , ContentEncryptionParams
     , ContentEncryptionAlg(..)
     , ContentEncryptionCipher(..)
-    , generateEncryptionParams
+    , ecbParams
+    , generateCBCParams
+    , generateRC2EncryptionParams
+    , generateCFBParams
+    , generateCTRParams
     , getContentEncryptionAlg
     -- * Low-level API
     , pbEncrypt
     , pbDecrypt
+    , pbMac
     ) where
 
 import           Data.ASN1.Types
@@ -47,6 +55,7 @@
 import Crypto.Store.ASN1.Parse
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.CMS.Algorithms
+import Crypto.Store.CMS.Authenticated
 import Crypto.Store.CMS.Encrypted
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
@@ -164,7 +173,66 @@
 instance Monoid e => ParseASN1Object e EncryptionScheme where
     parse = parseAlgorithm Sequence
 
+data AuthenticationSchemeType = Type_PBMAC1
 
+instance Enumerable AuthenticationSchemeType where
+    values = [ Type_PBMAC1
+             ]
+
+instance OIDable AuthenticationSchemeType where
+    getObjectID Type_PBMAC1 = [1,2,840,113549,1,5,14]
+
+instance OIDNameable AuthenticationSchemeType where
+    fromObjectID oid = unOIDNW <$> fromObjectID oid
+
+-- | Password-Based Message Authentication Scheme (PBMAC).
+newtype AuthenticationScheme = PBMAC1 PBMAC1Parameter -- ^ PBMAC1
+                             deriving (Show,Eq)
+
+instance HasStrength AuthenticationScheme where
+    getSecurityBits (PBMAC1 p) = getSecurityBits (pbmac1AScheme p)
+
+-- | PBMAC1 parameters.
+data PBMAC1Parameter = PBMAC1Parameter
+    { pbmac1KDF     :: KeyDerivationFunc  -- ^ Key derivation function
+    , pbmac1AScheme :: MACAlgorithm       -- ^ Underlying message authentication scheme
+    }
+    deriving (Show,Eq)
+
+instance ASN1Elem e => ProduceASN1Object e PBMAC1Parameter where
+    asn1s PBMAC1Parameter{..} =
+        let kdFunc  = algorithmASN1S Sequence pbmac1KDF
+            aScheme = algorithmASN1S Sequence pbmac1AScheme
+         in asn1Container Sequence (kdFunc . aScheme)
+
+instance Monoid e => ParseASN1Object e PBMAC1Parameter where
+    parse = onNextContainer Sequence $ do
+        kdFunc  <- parseAlgorithm Sequence
+        aScheme <- parseAlgorithm Sequence
+        case kdfKeyLength kdFunc of
+            Nothing -> return ()
+            Just sz
+                | validateKeySize aScheme sz -> return ()
+                | otherwise -> throwParseError "PBMAC1Parameter: parsed key length incompatible with message authentication scheme"
+        return PBMAC1Parameter { pbmac1KDF = kdFunc, pbmac1AScheme = aScheme }
+
+instance AlgorithmId AuthenticationScheme where
+    type AlgorithmType AuthenticationScheme = AuthenticationSchemeType
+    algorithmName _  = "message authentication scheme"
+
+    algorithmType (PBMAC1 _)                 = Type_PBMAC1
+
+    parameterASN1S (PBMAC1 p)                 = asn1s p
+
+    parseParameter Type_PBMAC1                 = PBMAC1 <$> parse
+
+instance ASN1Elem e => ProduceASN1Object e AuthenticationScheme where
+    asn1s = algorithmASN1S Sequence
+
+instance Monoid e => ParseASN1Object e AuthenticationScheme where
+    parse = parseAlgorithm Sequence
+
+
 -- High-level API
 
 -- | Content encrypted with a Password-Based Encryption Scheme (PBES).
@@ -236,3 +304,16 @@
 pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs
   where key = kdfDerive pbes2KDF len (fromProtectionPassword pwd) :: Key
         len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)
+
+
+-- Message Authentication Schemes
+
+-- | Authenticate a message with the specified authentication scheme and
+-- password.
+pbMac :: AuthenticationScheme -> EncapsulatedContent -> ProtectionPassword -> MessageAuthenticationCode
+pbMac (PBMAC1 p) = pbmac1 p
+
+pbmac1 :: PBMAC1Parameter -> ByteString -> ProtectionPassword -> MessageAuthenticationCode
+pbmac1 PBMAC1Parameter{..} bs pwd = mac pbmac1AScheme key bs
+  where key = kdfDerive pbmac1KDF len (fromProtectionPassword pwd) :: Key
+        len = fromMaybe (getMaximumKeySize pbmac1AScheme) (kdfKeyLength pbmac1KDF)
diff --git a/src/Crypto/Store/PKCS8.hs b/src/Crypto/Store/PKCS8.hs
--- a/src/Crypto/Store/PKCS8.hs
+++ b/src/Crypto/Store/PKCS8.hs
@@ -9,7 +9,10 @@
 --
 -- 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 actually stored along with the corresponding
+-- public key in a type 'KeyPair'.  'X509.PrivKey' and 'X509.PubKey' components
+-- can be obtained by calling functions 'keyPairToPrivKey' and
+-- 'keyPairToPubKey'.  Call function 'keyPairFromPrivKey' to build a 'KeyPair'.
 --
 -- Functions to read a private key return an object wrapped in the
 -- 'OptProtected' data type.
@@ -25,12 +28,18 @@
     ( readKeyFile
     , readKeyFileFromMemory
     , pemToKey
+    , pemToKeyAccum
     , writeKeyFile
     , writeKeyFileToMemory
     , keyToPEM
     , writeEncryptedKeyFile
     , writeEncryptedKeyFileToMemory
     , encryptKeyToPEM
+    -- * Key pairs
+    , KeyPair
+    , keyPairFromPrivKey
+    , keyPairToPrivKey
+    , keyPairToPubKey
     -- * Serialization formats
     , PrivateKeyFormat(..)
     , FormattedKey(..)
@@ -54,7 +63,9 @@
 import Data.ASN1.BinaryEncoding
 import Data.ASN1.BitArray
 import Data.ASN1.Encoding
+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
@@ -73,6 +84,7 @@
 import Crypto.Store.CMS.Attribute
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Keys
 import Crypto.Store.PEM
 import Crypto.Store.PKCS5
 import Crypto.Store.PKCS8.EC
@@ -116,63 +128,71 @@
 -- 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
+--
+-- 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 '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       = keyPairFromPrivKey . X509.PrivKeyRSA . unFormat <$> parse
+    dsa       = KeyPairDSA . unFormat <$> parse
+    ecdsa     = keyPairFromPrivKey . X509.PrivKeyEC . unFormat <$> parse
+    x25519    = keyPairFromPrivKey . X509.PrivKeyX25519 <$> parseModern
+    x448      = keyPairFromPrivKey . X509.PrivKeyX448 <$> parseModern
+    ed25519   = keyPairFromPrivKey . X509.PrivKeyEd25519 <$> parseModern
+    ed448     = keyPairFromPrivKey . X509.PrivKeyEd448 <$> parseModern
     encrypted = inner . decrypt <$> parse
 
-    getParser "PRIVATE KEY"           = Unprotected <$> allTypes
-    getParser "RSA PRIVATE KEY"       = Unprotected <$> rsa
-    getParser "DSA PRIVATE KEY"       = Unprotected <$> dsa
-    getParser "EC PRIVATE KEY"        = Unprotected <$> ecdsa
-    getParser "X25519 PRIVATE KEY"    = Unprotected <$> x25519
-    getParser "X448 PRIVATE KEY"      = Unprotected <$> x448
-    getParser "ED25519 PRIVATE KEY"   = Unprotected <$> ed25519
-    getParser "ED448 PRIVATE KEY"     = Unprotected <$> ed448
-    getParser "ENCRYPTED PRIVATE KEY" = Protected   <$> encrypted
-    getParser _                       = empty
+    getParser "PRIVATE KEY"           = return (Unprotected <$> allTypes)
+    getParser "RSA PRIVATE KEY"       = return (Unprotected <$> rsa)
+    getParser "DSA PRIVATE KEY"       = return (Unprotected <$> dsa)
+    getParser "EC PRIVATE KEY"        = return (Unprotected <$> ecdsa)
+    getParser "X25519 PRIVATE KEY"    = return (Unprotected <$> x25519)
+    getParser "X448 PRIVATE KEY"      = return (Unprotected <$> x448)
+    getParser "ED25519 PRIVATE KEY"   = return (Unprotected <$> ed25519)
+    getParser "ED448 PRIVATE KEY"     = return (Unprotected <$> ed448)
+    getParser "ENCRYPTED PRIVATE KEY" = return (Protected   <$> encrypted)
+    getParser _                       = Left UnexpectedNameForPEM
 
     inner decfn pwd = do
         decrypted <- decfn pwd
         asn1 <- mapLeft DecodingError $ decodeASN1' BER decrypted
-        case run allTypes asn1 of
-            Nothing -> Left (ParseFailure "No key parsed after decryption")
-            Just k  -> return k
+        case runParseASN1 allTypes asn1 of
+            Left _   -> Left (ParseFailure "No key parsed after decryption")
+            Right k  -> return k
 
 
 -- Writing to PEM format
 
 -- | Write unencrypted private keys to a PEM file.
-writeKeyFile :: PrivateKeyFormat -> FilePath -> [X509.PrivKey] -> IO ()
+writeKeyFile :: PrivateKeyFormat -> FilePath -> [KeyPair] -> IO ()
 writeKeyFile fmt path = writePEMs path . map (keyToPEM fmt)
 
 -- | Write unencrypted private keys to a bytearray in PEM format.
-writeKeyFileToMemory :: PrivateKeyFormat -> [X509.PrivKey] -> B.ByteString
+writeKeyFileToMemory :: PrivateKeyFormat -> [KeyPair] -> B.ByteString
 writeKeyFileToMemory fmt = pemsWriteBS . map (keyToPEM fmt)
 
 -- | Write a PKCS #8 encrypted private key to a PEM file.
@@ -183,10 +203,10 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFile :: FilePath
-                      -> EncryptionScheme -> ProtectionPassword -> X509.PrivKey
+                      -> EncryptionScheme -> ProtectionPassword -> KeyPair
                       -> IO (Either StoreError ())
-writeEncryptedKeyFile path alg pwd privKey =
-    let pem = encryptKeyToPEM alg pwd privKey
+writeEncryptedKeyFile path alg pwd keyPair =
+    let pem = encryptKeyToPEM alg pwd keyPair
      in either (return . Left) (fmap Right . writePEMs path . (:[])) pem
 
 -- | Write a PKCS #8 encrypted private key to a bytearray in PEM format.
@@ -197,48 +217,48 @@
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
 writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword
-                              -> X509.PrivKey -> Either StoreError B.ByteString
-writeEncryptedKeyFileToMemory alg pwd privKey =
-    pemWriteBS <$> encryptKeyToPEM alg pwd privKey
+                              -> KeyPair -> Either StoreError B.ByteString
+writeEncryptedKeyFileToMemory alg pwd keyPair =
+    pemWriteBS <$> encryptKeyToPEM alg pwd keyPair
 
 -- | Generate an unencrypted PEM for a private key.
-keyToPEM :: PrivateKeyFormat -> X509.PrivKey -> PEM
+keyToPEM :: PrivateKeyFormat -> KeyPair -> PEM
 keyToPEM TraditionalFormat = keyToTraditionalPEM
 keyToPEM PKCS8Format       = keyToModernPEM
 
-keyToTraditionalPEM :: X509.PrivKey -> PEM
-keyToTraditionalPEM privKey =
+keyToTraditionalPEM :: KeyPair -> PEM
+keyToTraditionalPEM keyPair =
     mkPEM (typeTag ++ " PRIVATE KEY") (encodeASN1S asn1)
-  where (typeTag, asn1) = traditionalPrivKeyASN1S privKey
+  where (typeTag, asn1) = traditionalPrivKeyASN1S keyPair
 
-traditionalPrivKeyASN1S :: ASN1Elem e => X509.PrivKey -> (String, ASN1Stream e)
-traditionalPrivKeyASN1S privKey =
-    case privKey of
-        X509.PrivKeyRSA k -> ("RSA", traditional k)
-        X509.PrivKeyDSA k -> ("DSA", traditional (dsaPrivToPair k))
-        X509.PrivKeyEC  k -> ("EC",  traditional k)
-        X509.PrivKeyX25519  k -> ("X25519",  tradModern k)
-        X509.PrivKeyX448    k -> ("X448",    tradModern k)
-        X509.PrivKeyEd25519 k -> ("ED25519", tradModern k)
-        X509.PrivKeyEd448   k -> ("ED448",   tradModern k)
+traditionalPrivKeyASN1S :: ASN1Elem e => KeyPair -> (String, ASN1Stream e)
+traditionalPrivKeyASN1S keyPair =
+    case keyPair of
+        KeyPairRSA k _ -> ("RSA", traditional k)
+        KeyPairDSA p   -> ("DSA", traditional p)
+        KeyPairEC  k _ -> ("EC",  traditional k)
+        KeyPairX25519  k _ -> ("X25519",  tradModern k)
+        KeyPairX448    k _ -> ("X448",    tradModern k)
+        KeyPairEd25519 k _ -> ("ED25519", tradModern k)
+        KeyPairEd448   k _ -> ("ED448",   tradModern k)
   where
     traditional a = asn1s (Traditional a)
     tradModern a  = asn1s (Modern [] a)
 
-keyToModernPEM :: X509.PrivKey -> PEM
-keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)
-  where asn1 = modernPrivKeyASN1S [] privKey
+keyToModernPEM :: KeyPair -> PEM
+keyToModernPEM keyPair = mkPEM "PRIVATE KEY" (encodeASN1S asn1)
+  where asn1 = modernPrivKeyASN1S [] 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
+modernPrivKeyASN1S :: ASN1Elem e => [Attribute] -> KeyPair -> ASN1Stream e
+modernPrivKeyASN1S attrs 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)
 
@@ -246,16 +266,13 @@
 --
 -- Fresh 'EncryptionScheme' parameters should be generated for each key to
 -- encrypt.
-encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> X509.PrivKey
+encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> KeyPair
                 -> Either StoreError PEM
-encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs
-  where bs = pemContent (keyToModernPEM privKey)
+encryptKeyToPEM alg pwd keyPair = toPEM <$> encrypt alg pwd bs
+  where bs = pemContent (keyToModernPEM keyPair)
         toPEM pkcs8 = mkPEM "ENCRYPTED PRIVATE KEY" (encodeASN1Object pkcs8)
 
-mkPEM :: String -> B.ByteString -> PEM
-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
 
-
 -- Private key formats: traditional (SSLeay compatible) and modern (PKCS #8)
 
 -- | Private-key serialization format.
@@ -303,49 +320,36 @@
 -- 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 keyPair) = modernPrivKeyASN1S attrs 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
@@ -482,13 +486,7 @@
                       , 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
 
 instance ASN1Object (FormattedKey X509.PrivKeyEC) where
@@ -631,69 +629,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 privKey) = asn1Container Sequence (v . alg . bs . att)
+  where
+    v     = gIntVal 0
+    alg   = asn1Container Sequence (gOID oid)
+    bs    = innerEddsaASN1S privKey
+    att   = attributesASN1S (Container Context 0) attrs
+
 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
+  skipVersion
+  onNextContainer Sequence $ do
+    OID oid <- getNext
+    when (oid /= expectedOid) $
+      throwParseError ("PKCS8: while parsing " ++ name ++ " expected OID " ++ show expectedOid ++ " while got " ++ show oid)
+  (attrs, bs) <- parseAttrKeys
+  Modern attrs <$> parseInnerEddsa name buildKey bs
+
 parseInnerEddsa :: Monoid e
                 => String
                 -> (B.ByteString -> CryptoFailable key)
@@ -712,3 +696,22 @@
             CryptoPassed privKey -> return privKey
             CryptoFailed _       ->
                 throwParseError ("PKCS8: parsed invalid " ++ name ++ " secret key")
+
+skipVersion :: Monoid e => ParseASN1 e ()
+skipVersion = do
+    IntVal v <- getNext
+    when (v /= 0 && v /= 1) $
+        throwParseError ("PKCS8: parsed invalid version: " ++ show v)
+
+-- todo: ideally should not skip but parse the public key and verify that it
+-- is consistent with the private key
+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)
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
+--
+-- 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 'PEM' element.
+pemToPubKey :: PEM -> Either StoreError X509.PubKey
+pemToPubKey pem = do
+    asn1 <- mapLeft DecodingError $ decodeASN1' BER (pemContent pem)
+    parser <- getParser (pemName pem)
+    (pubKey, unparsed) <- mapLeft ParseFailure $ parser asn1
+    case unparsed of
+        [] -> return pubKey
+        er -> Left $ ParseFailure ("pemToPubKey: remaining state " ++ show er)
 
-    getParser "PUBLIC KEY"           = fromASN1
-    getParser "RSA PUBLIC KEY"       = runParseASN1State rsapkParser
-    getParser _                      = const (Left undefined)
+  where
+    getParser "PUBLIC KEY"           = return fromASN1
+    getParser "RSA PUBLIC KEY"       = return (runParseASN1State rsapkParser)
+    getParser _                      = Left UnexpectedNameForPEM
 
     rsapkParser = (\(RSAPublicKey pub) -> X509.PubKeyRSA pub) <$> parse
 
@@ -149,9 +160,6 @@
 signedToPEM :: forall a. SignedObject a => X509.SignedExact a -> PEM
 signedToPEM obj = mkPEM (signedObjectName prx) (X509.encodeSignedObject obj)
   where prx = Proxy :: Proxy a
-
-mkPEM :: String -> B.ByteString -> PEM
-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
 
 
 -- RSA public keys
diff --git a/tests/CMS/Instances.hs b/tests/CMS/Instances.hs
--- a/tests/CMS/Instances.hs
+++ b/tests/CMS/Instances.hs
@@ -4,7 +4,6 @@
 module CMS.Instances
     ( arbitraryPassword
     , arbitraryAttributes
-    , arbitraryIntegrityDigest
     , arbitrarySigVer
     , arbitraryEnvDev
     ) where
@@ -12,6 +11,7 @@
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
+import           Data.Maybe (fromJust)
 import           Data.X509
 
 import GHC.TypeLits
@@ -22,6 +22,7 @@
 
 import Crypto.Store.CMS
 import Crypto.Store.Error
+import Crypto.Store.PKCS8
 
 import X509.Instances
 
@@ -126,7 +127,7 @@
 
 arbitraryNat :: Gen SomeNat
 arbitraryNat = unwrap <$> arbitrary
-  where unwrap (Positive i) = let Just n = someNatVal (127 + i) in n
+  where unwrap (Positive i) = let n = someNatVal (127 + i) in fromJust n
 
 instance Arbitrary DigestAlgorithm where
     arbitrary = oneof
@@ -138,25 +139,33 @@
         , pure $ DigestAlgorithm SHA256
         , pure $ DigestAlgorithm SHA384
         , pure $ DigestAlgorithm SHA512
+        , pure $ DigestAlgorithm SHA3_224
+        , pure $ DigestAlgorithm SHA3_256
+        , pure $ DigestAlgorithm SHA3_384
+        , pure $ DigestAlgorithm SHA3_512
         , pure $ DigestAlgorithm SHAKE128_256
         , pure $ DigestAlgorithm SHAKE256_512
         , (\(SomeNat p) -> DigestAlgorithm (SHAKE128 p)) <$> arbitraryNat
         , (\(SomeNat p) -> DigestAlgorithm (SHAKE256 p)) <$> arbitraryNat
         ]
 
-arbitraryIntegrityDigest :: Gen DigestAlgorithm
-arbitraryIntegrityDigest = elements
+arbitraryDigest :: Gen DigestAlgorithm
+arbitraryDigest = elements
     [ DigestAlgorithm MD5
     , DigestAlgorithm SHA1
     , DigestAlgorithm SHA224
     , DigestAlgorithm SHA256
     , DigestAlgorithm SHA384
     , DigestAlgorithm SHA512
+    , DigestAlgorithm SHA3_224
+    , DigestAlgorithm SHA3_256
+    , DigestAlgorithm SHA3_384
+    , DigestAlgorithm SHA3_512
     ]
 
 instance Arbitrary MACAlgorithm where
     arbitrary = oneof
-        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest
+        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryDigest
         , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx
         , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx
         ]
@@ -203,33 +212,30 @@
         , pure $ ECDSA (DigestAlgorithm SHA256)
         , pure $ ECDSA (DigestAlgorithm SHA384)
         , pure $ ECDSA (DigestAlgorithm SHA512)
+        , pure $ ECDSA (DigestAlgorithm SHA3_224)
+        , pure $ ECDSA (DigestAlgorithm SHA3_256)
+        , pure $ ECDSA (DigestAlgorithm SHA3_384)
+        , pure $ ECDSA (DigestAlgorithm SHA3_512)
 
         , pure Ed25519
         , pure Ed448
         ]
 
-arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)
-arbitraryKeyPair RSAAnyHash = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (RSA _) = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (RSAPSS _) = do
-    (pub, priv) <- arbitraryRSA
-    return (PubKeyRSA pub, PrivKeyRSA priv)
-arbitraryKeyPair (DSA _) = do
-    (pub, priv) <- arbitraryDSA
-    return (PubKeyDSA pub, PrivKeyDSA priv)
-arbitraryKeyPair (ECDSA _) = do
-    (pub, priv) <- arbitraryNamedEC
-    return (PubKeyEC pub, PrivKeyEC priv)
-arbitraryKeyPair Ed25519 = do
-    (pub, priv) <- arbitraryEd25519
-    return (PubKeyEd25519 pub, PrivKeyEd25519 priv)
-arbitraryKeyPair Ed448 = do
-    (pub, priv) <- arbitraryEd448
-    return (PubKeyEd448 pub, PrivKeyEd448 priv)
+arbitraryKeyPair :: SignatureAlg -> Gen KeyPair
+arbitraryKeyPair RSAAnyHash =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (RSA _) =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (RSAPSS _) =
+    keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA
+arbitraryKeyPair (DSA _) =
+    keyPairFromPrivKey . PrivKeyDSA <$> arbitraryPrivDSA
+arbitraryKeyPair (ECDSA _) =
+    keyPairFromPrivKey . PrivKeyEC . snd <$> arbitraryNamedEC
+arbitraryKeyPair Ed25519 =
+    keyPairFromPrivKey . PrivKeyEd25519 . snd <$> arbitraryEd25519
+arbitraryKeyPair Ed448 =
+    keyPairFromPrivKey . PrivKeyEd448 . snd <$> arbitraryEd448
 
 arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen)
 arbitrarySigVer alg = sized $ \n -> do
@@ -239,11 +245,12 @@
     return (sigFns, verFn)
   where
     onePair = do
-        (pub, priv) <- arbitraryKeyPair alg
+        pair <- arbitraryKeyPair alg
+        let pub = keyPairToPubKey pair
         chain <- arbitraryCertificateChain pub
         sAttrs <- oneof [ pure Nothing, Just <$> arbitraryAttributes ]
         uAttrs <- arbitraryAttributes
-        return (certSigner alg priv chain sAttrs uAttrs, withPublicKey pub)
+        return (certSigner alg pair chain sAttrs uAttrs, withPublicKey pub)
 
 instance Arbitrary PBKDF2_PRF where
     arbitrary = elements
@@ -276,13 +283,19 @@
         , CFB Camellia128
 
         , CTR Camellia128
+
+        , DeriveHKDF SHA256
         ]
 
 instance Arbitrary ContentEncryptionParams where
     arbitrary = arbitrary >>= gen
       where
+        gen (ECB c) = return (ecbParams c)
+        gen (CBC c) = generateCBCParams c
         gen CBC_RC2 = choose (24, 512) >>= generateRC2EncryptionParams
-        gen alg     = generateEncryptionParams alg
+        gen (CFB c) = generateCFBParams c
+        gen (CTR c) = generateCTRParams c
+        gen (DeriveHKDF _) = deriveEncryptionKey <$> arbitrary
 
 instance Arbitrary AuthContentEncryptionAlg where
     arbitrary = elements
@@ -297,22 +310,23 @@
         , GCM AES128
         , GCM AES192
         , GCM AES256
+
+        , AuthDeriveHKDF SHA256
         ]
 
 instance Arbitrary AuthContentEncryptionParams where
     arbitrary = do
         alg <- arbitrary
         case alg of
-            AUTH_ENC_128 -> arb3 generateAuthEnc128Params
-            AUTH_ENC_256 -> arb3 generateAuthEnc256Params
+            AUTH_ENC_128 -> arb3 authEnc128Params
+            AUTH_ENC_256 -> arb3 authEnc256Params
             CHACHA20_POLY1305 -> generateChaChaPoly1305Params
             CCM c -> do m <- arbitraryM
                         l <- arbitraryL
                         generateCCMParams c m l
             GCM c -> choose (12,16) >>= generateGCMParams c
-      where arb3 fn = do
-                a <- arbitrary; b <- arbitrary; c <- arbitrary
-                fn a b c
+            AuthDeriveHKDF _ -> authDeriveEncryptionKey <$> arbitrary
+      where arb3 fn = fn <$> arbitrary <*> arbitrary <*> arbitrary
 
 arbitraryM :: Gen CCM_M
 arbitraryM = elements
@@ -415,21 +429,22 @@
 
     arbitraryKT = do
         (pub, priv) <- arbitraryLargeRSA
+        let pair = keyPairFromPrivKey (PrivKeyRSA priv)
         cert <- arbitrarySignedCertificate (PubKeyRSA pub)
         ktp  <- arbitrary
         let envFn = forKeyTransRecipient cert ktp
-            devFn = withRecipientKeyTrans (PrivKeyRSA priv)
+            devFn = withRecipientKeyTrans pair
         return (envFn, devFn)
 
     arbitraryKA = do
-        (cert, priv) <- arbitraryDHParams
+        (cert, pair) <- arbitraryDHParams
         let allowCofactorDH =
-                case priv of
+                case keyPairToPrivKey pair of
                     PrivKeyEC _ -> True
                     _           -> False
         kap <- arbitraryAlg >>= arbitraryAgreeParams allowCofactorDH
         let envFn = forKeyAgreeRecipient cert kap
-            devFn = withRecipientKeyAgree priv cert
+            devFn = withRecipientKeyAgree pair cert
         return (envFn, devFn)
 
     arbitraryKEK = do
@@ -441,7 +456,7 @@
     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)
 
@@ -476,25 +491,29 @@
 
     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 =
+    -- 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
diff --git a/tests/KeyWrap/RC2.hs b/tests/KeyWrap/RC2.hs
--- a/tests/KeyWrap/RC2.hs
+++ b/tests/KeyWrap/RC2.hs
@@ -6,6 +6,7 @@
 
 import           Data.ByteString (ByteString)
 import qualified Data.ByteString as B
+import           Data.Maybe (fromJust)
 
 import Crypto.Cipher.Types
 import Crypto.Error
@@ -68,12 +69,11 @@
     initCipher ekl k = throwCryptoError (rc2WithEffectiveKeyLength ekl k)
 
     wrapUnwrapProperty :: TestKey RC2 -> TestIV RC2 -> Message -> Gen Property
-    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) = do
+    wrapUnwrapProperty (Key key) iv (Message msg) = do
         ekl <- choose (1, 1024)
         let ctx = initCipher ekl key
-        wrapped <- wrap ctx iv msg
+        wrapped <- wrap ctx (unIV iv) msg
         return $ (wrapped >>= unwrap ctx) === Right msg
-      where Just iv = makeIV ivBs
 
     makeTest :: Integer -> Vector -> TestTree
     makeTest i Vector{..} =
@@ -82,7 +82,7 @@
             , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)
             ]
       where ctx = initCipher vecEKL vecKey
-            Just iv = makeIV vecIV
+            iv = fromJust $ makeIV vecIV
             doWrap = wrap' Left withRandomPad
             withRandomPad f len
                 | B.length vecPad /= len = Left (InvalidInput "unexpected length")
diff --git a/tests/KeyWrap/TripleDES.hs b/tests/KeyWrap/TripleDES.hs
--- a/tests/KeyWrap/TripleDES.hs
+++ b/tests/KeyWrap/TripleDES.hs
@@ -5,6 +5,7 @@
 module KeyWrap.TripleDES (tripledeskwTests) where
 
 import Data.ByteString (ByteString, pack)
+import Data.Maybe (fromJust)
 
 import Crypto.Cipher.TripleDES
 import Crypto.Cipher.Types
@@ -62,10 +63,9 @@
     initCipher k = throwCryptoError (cipherInit k)
 
     wrapUnwrapProperty :: TestKey cipher -> TestIV cipher -> Message -> Property
-    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) =
-        (wrap ctx iv msg >>= unwrap ctx) === Right msg
+    wrapUnwrapProperty (Key key) iv (Message msg) =
+        (wrap ctx (unIV iv) msg >>= unwrap ctx) === Right msg
       where ctx = initCipher key
-            Just iv = makeIV ivBs
 
     makeTest :: Integer -> Vector -> TestTree
     makeTest i Vector{..} =
@@ -74,7 +74,7 @@
             , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)
             ]
       where ctx = initCipher vecKey
-            Just iv = makeIV vecIV
+            iv = fromJust $ makeIV vecIV
 
 tripledeskwTests :: TestTree
 tripledeskwTests = testGroup "KeyWrap.TripleDES"
diff --git a/tests/PKCS12/Instances.hs b/tests/PKCS12/Instances.hs
--- a/tests/PKCS12/Instances.hs
+++ b/tests/PKCS12/Instances.hs
@@ -1,18 +1,23 @@
+{-# LANGUAGE CPP #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module PKCS12.Instances
     ( arbitraryPassword
     , arbitraryAlias
-    , arbitraryIntegrityParams
+    , arbitraryTraditionalIntegrity
+    , arbitraryAuthSchemeIntegrity
     , arbitraryPKCS12
     ) where
 
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
+#if !(MIN_VERSION_base(4,11,0))
 import           Data.Semigroup
+#endif
 
 import Test.Tasty.QuickCheck
 
+import Crypto.Store.CMS
 import Crypto.Store.PKCS12
 import Crypto.Store.PKCS5
 
@@ -26,8 +31,30 @@
 arbitraryAlias = resize 16 asciiChar
   where asciiChar = listOf $ choose ('\x20','\x7f')
 
-arbitraryIntegrityParams :: Gen IntegrityParams
-arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
+instance Arbitrary PBMAC1Parameter where
+    arbitrary = PBMAC1Parameter <$> arbitrary <*> arbitrary
+
+arbitraryAuthScheme :: Gen AuthenticationScheme
+arbitraryAuthScheme = PBMAC1 <$> arbitrary
+
+arbitraryIntegrityDigest :: Gen DigestAlgorithm
+arbitraryIntegrityDigest = elements
+    [ DigestAlgorithm MD2
+    , DigestAlgorithm MD4
+    , DigestAlgorithm MD5
+    , DigestAlgorithm SHA1
+    , DigestAlgorithm SHA224
+    , DigestAlgorithm SHA256
+    , DigestAlgorithm SHA384
+    , DigestAlgorithm SHA512
+    ]
+
+arbitraryTraditionalIntegrity :: Gen IntegrityParams
+arbitraryTraditionalIntegrity =
+    TraditionalIntegrity <$> arbitraryIntegrityDigest <*> arbitrary
+
+arbitraryAuthSchemeIntegrity :: Gen IntegrityParams
+arbitraryAuthSchemeIntegrity = AuthSchemeIntegrity <$> arbitraryAuthScheme
 
 arbitraryPKCS12 :: ProtectionPassword -> Gen PKCS12
 arbitraryPKCS12 pwd = do
diff --git a/tests/PKCS12/Tests.hs b/tests/PKCS12/Tests.hs
--- a/tests/PKCS12/Tests.hs
+++ b/tests/PKCS12/Tests.hs
@@ -6,6 +6,7 @@
 import Data.PEM (pemContent)
 import Data.String (fromString)
 
+import Crypto.Store.Error
 import Crypto.Store.PKCS12
 import Crypto.Store.PKCS8
 import Crypto.Store.X509 (readSignedObject)
@@ -59,6 +60,8 @@
     integrityModes = [ "SHA-1"
                      , "SHA-256"
                      , "SHA-384"
+                     , "PBMAC1 with SHA-256"
+                     , "PBMAC1 with SHA-512"
                      ]
 
     privacyModes   = [ "aes-128-cbc"
@@ -92,6 +95,40 @@
             , ("GnuTLS with --null-password", 1, 1)
             ]
 
+testRfc9579 :: TestTree
+testRfc9579 = testCaseSteps "rfc9579" $ \step -> do
+    step "Reading PKCS #12 files"
+    pems <- readPEMs path
+    length pems @?= length infos
+
+    forM_ (zip infos pems) $ \((name, integrityError), pem) -> do
+        let r = readP12FileFromMemory (pemContent pem)
+
+        assertRight r $ \integrity -> case integrityError of
+            Nothing ->
+                assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->
+                    assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do
+                        step ("Testing " ++ name)
+                        assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->
+                            length keys @?= 1
+                        length (getAllSafeX509Certs scs) @?= 1
+            Just expectedErr ->
+                assertLeft (recoverAuthenticated pwd integrity) $ \err -> do
+                    err @?= expectedErr
+                    step ("Testing " ++ name)
+  where
+    pwd = fromString "1234"
+
+    path  = testFile "pkcs12-rfc9579.pem"
+    infos = [ ("SHA-256 HMAC and PRF", Nothing)
+            , ("SHA-256 HMAC and SHA-512 PRF", Nothing)
+            , ("SHA-512 HMAC and PRF", Nothing)
+            , ("Incorrect Iteration Count", Just BadContentMAC)
+            , ("Incorrect Salt", Just BadContentMAC)
+            , ("Missing Key Length",
+                Just $ InvalidParameter "KDF key length must be explicit")
+            ]
+
 propertyTests :: TestTree
 propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"
     [ testProperty "marshalling" $ do
@@ -100,27 +137,37 @@
         let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c
             unused = fromString "not-used"
         return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)
-    , testProperty "marshalling with authentication" $ do
-        params <- arbitraryIntegrityParams
+    , testProperty "marshalling with traditional authentication" $ do
+        params <- arbitraryTraditionalIntegrity
         c <- arbitrary >>= arbitraryPKCS12
         pI <- arbitrary
         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
             p = fromProtectionPassword pI
         return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)
+    , testProperty "marshalling with authentication scheme" $ do
+        params <- arbitraryAuthSchemeIntegrity
+        c <- arbitrary >>= arbitraryPKCS12
+        pI <- arbitrary `suchThat` (/= emptyNotTerminated)
+        let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
+            p = fromProtectionPassword pI
+        return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)
     , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $
         \pChain pKey privKey ->
+            supportedAsEncodedPubKey privKey ==>
             testCredConv privKey toCredential (fromCredential pChain pKey)
     , localOption (QuickCheckTests 20) $ testProperty "converting named credentials" $
-        \pChain pKey privKey -> do
-            name <- arbitraryAlias
-            testCredConv privKey
-                (toNamedCredential name)
-                (fromNamedCredential name pChain pKey)
+        \pChain pKey privKey ->
+            supportedAsEncodedPubKey privKey ==> do
+                name <- arbitraryAlias
+                testCredConv privKey
+                    (toNamedCredential name)
+                    (fromNamedCredential name pChain pKey)
     ]
   where
     testCredConv privKey to from = do
         pwd <- arbitrary
-        chain <- arbitrary >>= arbitraryCertificateChain
+        let pub = keyPairToPubKey (keyPairFromPrivKey privKey)
+        chain <- arbitraryCertificateChain pub
         chain' <- shuffleCertificateChain chain
         let cred = (chain, privKey)
             r = from pwd (chain', privKey) >>= recover pwd . to
@@ -132,5 +179,6 @@
         [ testType "RSA"                        "rsa"
         , testType "Ed25519"                    "ed25519"
         , testEmptyPassword
+        , testRfc9579
         , propertyTests
         ]
diff --git a/tests/PKCS8/Instances.hs b/tests/PKCS8/Instances.hs
--- a/tests/PKCS8/Instances.hs
+++ b/tests/PKCS8/Instances.hs
@@ -40,14 +40,18 @@
                       , PBE_SHA1_RC2_40 <$> arbitrary
                       ]
 
+instance Arbitrary KeyPair where
+    arbitrary = keyPairFromPrivKey <$> arbitrary
+
 instance Arbitrary PrivateKeyFormat where
     arbitrary = elements [ TraditionalFormat, PKCS8Format ]
 
-instance Arbitrary (FormattedKey PrivKey) where
+instance Arbitrary (FormattedKey KeyPair) where
     arbitrary = do
-        key <- arbitrary
+        pair <- arbitrary
+        let key = keyPairToPrivKey pair
         fmt <- if pkcs8only key then return PKCS8Format else arbitrary
-        return (FormattedKey fmt key)
+        return (FormattedKey fmt pair)
 
 pkcs8only :: PrivKey -> Bool
 pkcs8only (PrivKeyX25519  _)   = True
diff --git a/tests/PKCS8/Tests.hs b/tests/PKCS8/Tests.hs
--- a/tests/PKCS8/Tests.hs
+++ b/tests/PKCS8/Tests.hs
@@ -1,4 +1,5 @@
 -- | PKCS #8 tests.
+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
 module PKCS8.Tests (pkcs8Tests) where
 
 import qualified Data.ByteString as B
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
diff --git a/tests/files/cms-digested-data.pem b/tests/files/cms-digested-data.pem
--- a/tests/files/cms-digested-data.pem
+++ b/tests/files/cms-digested-data.pem
@@ -25,3 +25,22 @@
 EAQOaGVsbG8sIHdvcmxkDQoEQNmRYGENk6X3fR9Sk9k++QSKUPYBvF+nGKHL4FXN
 QVoNz4kKv9BI4MPnsV77ikuw7kWGR3OFLWO8waCyFK+EEu0=
 -----END CMS-----
+-----BEGIN CMS-----
+MFwGCSqGSIb3DQEHBaBPME0CAQAwCwYJYIZIAWUDBAIHMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQcrHRNG/sKxDCiVbjFsYvAvrXdZNaPFFkUnKb7eg==
+-----END CMS-----
+-----BEGIN CMS-----
+MGAGCSqGSIb3DQEHBaBTMFECAQAwCwYJYIZIAWUDBAIIMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQgead3JnYtNJOYL50QfPgWkqUtpmuiU9T7itIfOmfa
+3l4=
+-----END CMS-----
+-----BEGIN CMS-----
+MHAGCSqGSIb3DQEHBaBjMGECAQAwCwYJYIZIAWUDBAIJMB0GCSqGSIb3DQEHAaAQ
+BA5oZWxsbywgd29ybGQNCgQwPvv+XafJtPR2UXRqu2t6l3BuJeoOghvrAfPbbRQ2
+cr5IHYmzioNIZ1HlrP6RG0R3
+-----END CMS-----
+-----BEGIN CMS-----
+MIGABgkqhkiG9w0BBwWgczBxAgEAMAsGCWCGSAFlAwQCCjAdBgkqhkiG9w0BBwGg
+EAQOaGVsbG8sIHdvcmxkDQoEQHjsLFT6QOVe+FI6fBZhWEIHpXzDX9L4/ARGWEXp
+rKVTw6DfjRFICt66Mxxqn4WbRCa/FJXAnVY1PduwTN1Fiqw=
+-----END CMS-----
diff --git a/tests/files/ed25519-pkcs12.pem b/tests/files/ed25519-pkcs12.pem
--- a/tests/files/ed25519-pkcs12.pem
+++ b/tests/files/ed25519-pkcs12.pem
@@ -86,6 +86,64 @@
 AA==
 -----END PKCS12-----
 -----BEGIN PKCS12-----
+MIIE6QIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH
+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG
+SIb3DQEFDDAkBBADZyEJTf+SYDt1cuTOQqVaAgIIADAMBggqhkiG9w0CCQUAMB0G
+CWCGSAFlAwQBKgQQxjg/dHyDo8ty5b1MLNsCs4CCAkB15cjQKudM1ywzel7q2pYU
+tOU6nBPPd5oblD8fiH+TeOuNb9WXLlPB1Ra5ywqYrbqbPNpFNriigV20orksM1Dk
+/BCTXfxgaC4RO1Y3zNANiMCw8rafSpm5fX/tU12tc8kei0D1TcmaR3+i135Nl3xZ
+zFXmm0lQ/rPHHYwW8uzAQOT9QVUmkie7P54aCRj99PCpVcv0W5sxqKYmmyfJerqZ
+DtY6Q9ZMbe7jiu4bUxKZ06ggfl/UYwmIkT2xjN/EpWm0EO+hVIgIMq09kEkFQyiH
+LP37rxOrOQ1dzopztALm3+8o2UiWTm53CwDJx8+0Qvb6eldLNpgUDZcrvjyRUlzF
+gaQGq6OmkRxWme1A0xC/vc+fppW75YnFbKdMzDfmbxDwzjYprAiFlk+4bPmSZHuP
+/mKx+jYFXubOV/4G4CrO9toHtztJVRm2O48WY8JqFKSCvlW2/PqMry0v4pxHRrKc
+N1hWyhIxAYUf8+A4an5XEvuE3rwmFzHU3SlxvpWECVO+qzteY+/pMXEwdJiqSiH3
+ICsGaGcnWE+UOSsVkp3hje6MIkGYMec7dakG785Zwwp+zQgZ9vXKa4rmvFg79tnL
+VJbehXobf5tBbg8c525Pw4siYNaD6bk2VJbpew2F3OrwslmlhlxRPZEt/oGY+dgK
+haRPhAAfVxnFav+fApm2IkR9nbEqomthrCoD5VoIWEXS3IVrvu9RAlqBOpKPp+kZ
+cU472Uy+4MKJ5kME2f1b6AyuHzT/KfmItKTB4qdSCHgwggF6BgkqhkiG9w0BBwGg
+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w
+UjAxBgkqhkiG9w0BBQwwJAQQA137X5gsqDKTJ08FgEK6mAICCAAwDAYIKoZIhvcN
+AgkFADAdBglghkgBZQMEASoEEPkEeCkd5FOqDNhqcFGwngwEQIJ4pEDx6DBFChYf
+p+/XCMom7KTB9JwjxJPaYeroUVBEjz1MpOntyJetOjJzruEXGMB/cR4ARJNnS25+
+PnNJN5oxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G
+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg
+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAyADUANgAgAC0AcABiAG0AYQBjADEAXwBw
+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADIANQA2MH0wbTBJBgkqhkiG9w0BBQ4w
+PDAsBgkqhkiG9w0BBQwwHwQIH6958Xey3xgCAggAAgEgMAwGCCqGSIb3DQIJBQAw
+DAYIKoZIhvcNAgkFAAQgGHJF7GEkuv9ZXtkHlH9yDHwFRjTYsH0peoVdnVVTScME
+CB+vefF3st8YAgIIAA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIFCwIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH
+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG
+SIb3DQEFDDAkBBBBRdQTNqGIKpGPW0+w4J0FAgIIADAMBggqhkiG9w0CCQUAMB0G
+CWCGSAFlAwQBKgQQ0KQc53YHlFcirdn9bulXVoCCAkCGp/FB1JeyqO5yQqM3wRrd
+CBLA+0Cg8u64CoagWGmjfYsGWiobeaUOnFc62GORJNH/vozKfU70aYRbGw0iO7La
+VKzl+VPt9m5e2jC9QUVhtE/kH0jpx0fuYK68DWMklDHrLpSFHfNbZAwFzOcqojaG
+oknhWAVYDrIkjFGcWWBDbHHeSQA+kEisjrnUIL7cUavu4OQVxveXNQ0DOFlnfOc6
+HOZ2+9w4MvjbJUKo9cuqVPzISyLvp4jp2LGOdTOM2OCRWlZh8SelmKwfSJTKhGhx
+xPMOeN+Pg7MeVJvUWOdGEmQdGGti2cBX5ysORBcjgsh1cS+tVYCPwxP1RZVIAfac
+KitW8q4Q3E/RncomQs8R0lLi26kadPGj0OUoAhly7PQxe4HG0ZCHCdI2pld8YkJo
+mZAU5hOG5LI/q6Le5fXnPU1XHYwU0z9CQsN3NmYoufD1yZZtA1z7fpNbyxqcOu0P
+OXFE7UUNM0oF5HJpwZYM4asReGbXMnYB4DQvW35FhVp7rFD5Z0mteCF4GK/sgbv/
+HhtwtNVbHp35DIUceVIJrV9qYHN3y7YVjm5RNFzfCGL8T8CuNfB3pIHvIuvqSjyd
+5z/tm3/Kx/wMxqJqQIArXb531w7cac03JhH7OPAdTwneaV6VEPl1pEuIZeb129ap
+jn1Rp4E62YVsqgEuky3BGrRZNuwuP34X5c6O9kwVN2WNk2FltZ+Vwq8czLblxVmZ
+uQjhs6hh9GsT4tPwgX6Br+FRuTRAWG/4cQeVAU7G68EwggF6BgkqhkiG9w0BBwGg
+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w
+UjAxBgkqhkiG9w0BBQwwJAQQrcodfjPmwrpE2L8ETV+uvAICCAAwDAYIKoZIhvcN
+AgkFADAdBglghkgBZQMEASoEEAPPKSb0fhAIfNfyxOVQ2F0EQCRw+/jJY01yo0f3
+fUbcZVUpYQtvxMlpNoTV0hdi0DwC4AO5dkBmZdvCIBgo/c/BY8jL0NNsUxFb8VyX
+Xj0V7FMxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G
+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg
+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQA1ADEAMgAgAC0AcABiAG0AYQBjADEAXwBw
+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADUAMQAyMIGeMIGNMEkGCSqGSIb3DQEF
+DjA8MCwGCSqGSIb3DQEFDDAfBAhSQf9Bis7WlAICCAACAUAwDAYIKoZIhvcNAgsF
+ADAMBggqhkiG9w0CCwUABEDmbcmLz5XXSQPkGAd0VfCypwFm71EP4K+IlxTVAXAC
+VOChMbMfdeiCAsepDo9nWIOsg+r3KvQVtM98TyIBU4tsBAhSQf9Bis7WlAICCAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
 MIIDXwIBAzCCAyUGCSqGSIb3DQEHAaCCAxYEggMSMIIDDjCCAhMGCSqGSIb3DQEH
 AaCCAgQEggIAMIIB/DCCAfgGCyqGSIb3DQEMCgEDoIIBczCCAW8GCiqGSIb3DQEJ
 FgGgggFfBIIBWzCCAVcwggEJoAMCAQICFEInN18eR8QZFfy9Yb39ycd56f8PMAUG
diff --git a/tests/files/pkcs12-rfc9579.pem b/tests/files/pkcs12-rfc9579.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/pkcs12-rfc9579.pem
@@ -0,0 +1,354 @@
+-----BEGIN PKCS12-----
+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
+3QQITk9UIFVTRUQCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+6QQITk9UIFVTRUQCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIKrAIBAzCCCgUGCSqGSIb3DQEHAaCCCfYEggnyMIIJ7jCCBGIGCSqGSIb3DQEH
+BqCCBFMwggRPAgEAMIIESAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG
+SIb3DQEFDDAcBAisrqL8obSBaQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME
+ASoEECjXYYca0pwsgn1Imb9WqFGAggPgT7RcF5YzEJANZU9G3tSdpCHnyWatTlhm
+iCEcBGgwI5gz0+GoX+JCojgYY4g+KxeqznyCu+6GeD00T4Em7SWme9nzAfBFzng0
+3lYCSnahSEKfgHerbzAtq9kgXkclPVk0Liy92/buf0Mqotjjs/5o78AqP86Pwbj8
+xYNuXOU1ivO0JiW2c2HefKYvUvMYlOh99LCoZPLHPkaaZ4scAwDjFeTICU8oowVk
+LKvslrg1pHbfmXHMFJ4yqub37hRtj2CoJNy4+UA2hBYlBi9WnuAJIsjv0qS3kpLe
+4+J2DGe31GNG8pD01XD0l69OlailK1ykh4ap2u0KeD2z357+trCFbpWMMXQcSUCO
+OcVjxYqgv/l1++9huOHoPSt224x4wZfJ7cO2zbAAx/K2CPhdvi4CBaDHADsRq/c8
+SAi+LX5SCocGT51zL5KQD6pnr2ExaVum+U8a3nMPPMv9R2MfFUksYNGgFvS+lcZf
+R3qk/G9iXtSgray0mwRA8pWzoXl43vc9HJuuCU+ryOc/h36NChhQ9ltivUNaiUc2
+b9AAQSrZD8Z7KtxjbH3noS+gjDtimDB0Uh199zaCwQ95y463zdYsNCESm1OT979o
+Y+81BWFMFM/Hog5s7Ynhoi2E9+ZlyLK2UeKwvWjGzvcdPvxHR+5l/h6PyWROlpaZ
+zmzZBm+NKmbXtMD2AEa5+Q32ZqJQhijXZyIji3NS65y81j/a1ZrvU0lOVKA+MSPN
+KU27/eKZuF1LEL6qaazTUmpznLLdaVQy5aZ1qz5dyCziKcuHIclhh+RCblHU6XdE
+6pUTZSRQQiGUIkPUTnU9SFlZc7VwvxgeynLyXPCSzOKNWYGajy1LxDvv28uhMgNd
+WF51bNkl1QYl0fNunGO7YFt4wk+g7CQ/Yu2w4P7S3ZLMw0g4eYclcvyIMt4vxXfp
+VTKIPyzMqLr+0dp1eCPm8fIdaBZUhMUC/OVqLwgnPNY9cXCrn2R1cGKo5LtvtjbH
+2skz/D5DIOErfZSBJ8LE3De4j8MAjOeC8ia8LaM4PNfW/noQP1LBsZtTDTqEy01N
+Z5uliIocyQzlyWChErJv/Wxh+zBpbk1iXc2Owmh2GKjx0VSe7XbiqdoKkONUNUIE
+siseASiU/oXdJYUnBYVEUDJ1HPz7qnKiFhSgxNJZnoPfzbbx1hEzV+wxQqNnWIqQ
+U0s7Jt22wDBzPBHGao2tnGRLuBZWVePJGbsxThGKwrf3vYsNJTxme5KJiaxcPMwE
+r+ln2AqVOzzXHXgIxv/dvK0Qa7pH3AvGzcFjQChTRipgqiRrLor0//8580h+Ly2l
+IFo7bCuztmcwggWEBgkqhkiG9w0BBwGgggV1BIIFcTCCBW0wggVpBgsqhkiG9w0B
+DAoBAqCCBTEwggUtMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAi1c7S5
+IEG77wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEN6rzRtIdYxqOnY+
+aDS3AFYEggTQNdwUoZDXCryOFBUI/z71vfoyAxlnwJLRHNXQUlI7w0KkH22aNnSm
+xiaXHoCP1HgcmsYORS7p/ITi/9atCHqnGR4zHmePNhoMpNHFehdjlUUWgt004vUJ
+5ZwTdXweM+K4We6CfWA/tyvsyGNAsuunel+8243Zsv0mGLKpjA+ZyALt51s0knmX
+OD2DW49FckImUVnNC5LmvEIAmVC/ZNycryZQI+2EBkJKe+BC3834GexJnSwtUBg3
+Xg33ZV7X66kw8tK1Ws5zND5GQAJyIu47mnjZkIWQBY+XbWowrBZ8uXIQuxMZC0p8
+u62oIAtZaVQoVTR1LyR/7PISFW6ApwtbTn6uQxsb16qF8lEM0S1+x0AfJY6Zm11t
+yCqbb2tYZF+X34MoUkR/IYC/KCq/KJdpnd8Yqgfrwjg8dR2WGIxbp2GBHq6BK/DI
+ehOLMcLcsOuP0DEXppfcelMOGNIs+4h4KsjWiHVDMPsqLdozBdm6FLGcno3lY5FO
++avVrlElAOB+9evgaBbD2lSrEMoOjAoD090tgXXwYBEnWnIpdk+56cf5IpshrLBA
+/+H13LBLes+X1o5dd0Mu+3abp5RtAv7zLPRRtXkDYJPzgNcTvJ2Wxw2C+zrAclzZ
+7IRdcLESUa4CsN01aEvQgOtkCNVjSCtkJGP0FstsWM4hP7lfSB7P2tDL+ugy6GvB
+X1sz9fMC7QMAFL98nDm/yqcnejG1BcQXZho8n0svSfbcVByGlPZGMuI9t25+0B2M
+TAx0f6zoD8+fFmhcVgS6MQPybGKFawckYl0zulsePqs+G4voIW17owGKsRiv06Jm
+ZSwd3KoGmjM49ADzuG9yrQ5PSa0nhVk1tybNape4HNYHrAmmN0ILlN+E0Bs/Edz4
+ntYZuoc/Z35tCgm79dV4/Vl6HUZ1JrLsLrEWCByVytwVFyf3/MwTWdf+Ac+XzBuC
+yEMqPlvnPWswdnaid35pxios79fPl1Hr0/Q6+DoA5GyYq8SFdP7EYLrGMGa5GJ+x
+5nS7z6U4UmZ2sXuKYHnuhB0zi6Y04a+fhT71x02eTeC7aPlEB319UqysujJVJnso
+bkcwOu/Jj0Is9YeFd693dB44xeZuYyvlwoD19lqcim0TSa2Tw7D1W/yu47dKrVP2
+VKxRqomuAQOpoZiuSfq1/7ysrV8U4hIlIU2vnrSVJ8EtPQKsoBW5l70dQGwXyxBk
+BUTHqfJ4LG/kPGRMOtUzgqFw2DjJtbym1q1MZgp2ycMon4vp7DeQLGs2XfEANB+Y
+nRwtjpevqAnIuK6K3Y02LY4FXTNQpC37Xb04bmdIQAcE0MaoP4/hY87aS82PQ68g
+3bI79uKo4we2g+WaEJlEzQ7147ZzV2wbDq89W69x1MWTfaDwlEtd4UaacYchAv7B
+TVaaVFiRAUywWaHGePpZG2WV1feH/zd+temxWR9qMFgBZySg1jipBPVciwl0LqlW
+s/raIBYmLmAaMMgM3759UkNVznDoFHrY4z2EADXp0RHHVzJS1x+yYvp/9I+AcW55
+oN0UP/3uQ6eyz/ix22sovQwhMJ8rmgR6CfyRPKmXu1RPK3puNv7mbFTfTXpYN2vX
+vhEZReXY8hJF/9o4G3UrJ1F0MgUHMCG86cw1z0bhPSaXVoufOnx/fRoxJTAjBgkq
+hkiG9w0BCRUxFgQUwWO5DorvVWYF3BWUmAw0rUEajScwgZ0wgY0wSQYJKoZIhvcN
+AQUOMDwwLAYJKoZIhvcNAQUMMB8ECFDaXOUaOcUPAgIIAAIBQDAMBggqhkiG9w0C
+CwUAMAwGCCqGSIb3DQILBQAEQHIAM8C9OAsHUCj9CmOJioqf7YwD4O/b3UiZ3Wqo
+F6OmQIRDc68SdkZJ6024l4nWlnhTE7a4lb2Tru4k3NOTa1oECE5PVCBVU0VEAgEB
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+3QQITk9UIFVTRUQCAggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+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
+3QQIb0c8OLAuMXMCAQE=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIKiAIBAzCCCgUGCSqGSIb3DQEHAaCCCfYEggnyMIIJ7jCCBGIGCSqGSIb3DQEH
+BqCCBFMwggRPAgEAMIIESAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG
+SIb3DQEFDDAcBAg9pxXxY2yscwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME
+ASoEEK7yYaFQDi1pYwWzm9F/fs+AggPgFIT2XapyaFgDppdvLkdvaF3HXw+zjzKb
+7xFC76DtVPhVTWVHD+kIss+jsj+XyvMwY0aCuAhAG/Dig+vzWomnsqB5ssw5/kTb
++TMQ5PXLkNeoBmB6ArKeGc/QmCBQvQG/a6b+nXSWmxNpP+71772dmWmB8gcSJ0kF
+Fj75NrIbmNiDMCb71Q8gOzBMFf6BpXf/3xWAJtxyic+tSNETfOJa8zTZb0+lV0w9
+5eUmDrPUpuxEVbb0KJtIc63gRkcfrPtDd6Ii4Zzbzj2Evr4/S4hnrQBsiryVzJWy
+IEjaD0y6+DmG0JwMgRuGi1wBoGowi37GMrDCOyOZWC4n5wHLtYyhR6JaElxbrhxP
+H46z2USLKmZoF+YgEQgYcSBXMgP0t36+XQocFWYi2N5niy02TnctwF430FYsQlhJ
+Suma4I33E808dJuMv8T/soF66HsD4Zj46hOf4nWmas7IaoSAbGKXgIa7KhGRJvij
+xM3WOX0aqNi/8bhnxSA7fCmIy/7opyx5UYJFWGBSmHP1pBHBVmx7Ad8SAsB9MSsh
+nbGjGiUk4h0QcOi29/M9WwFlo4urePyI8PK2qtVAmpD3rTLlsmgzguZ69L0Q/CFU
+fbtqsMF0bgEuh8cfivd1DYFABEt1gypuwCUtCqQ7AXK2nQqOjsQCxVz9i9K8NDeD
+aau98VAl0To2sk3/VR/QUq0PRwU1jPN5BzUevhE7SOy/ImuJKwpGqqFljYdrQmj5
+jDe+LmYH9QGVRlfN8zuU+48FY8CAoeBeHn5AAPml0PYPVUnt3/jQN1+v+CahNVI+
+La8q1Nen+j1R44aa2I3y/pUgtzXRwK+tPrxTQbG030EU51LYJn8amPWmn3w75ZIA
+MJrXWeKj44de7u4zdUsEBVC2uM44rIHM8MFjyYAwYsey0rcp0emsaxzar+7ZA67r
+lDoXvvS3NqsnTXHcn3T9tkPRoee6L7Dh3x4Od96lcRwgdYT5BwyH7e34ld4VTUmJ
+bDEq7Ijvn4JKrwQJh1RCC+Z/ObfkC42xAm7G010u3g08xB0Qujpdg4a7VcuWrywF
+c7hLNquuaF4qoDaVwYXHH3iuX6YlJ/3siTKbYCVXPEZOAMBP9lF/OU76UMJBQNfU
+0xjDx+3AhUVgnGuCsmYlK6ETDp8qOZKGyV0KrNSGtqLx3uMhd7PETeW+ML3tDQ/0
+X9fMkcZHi4C2fXnoHV/qa2dGhBj4jjQ0Xh1poU6mxGn2Mebe2hDsBZkkBpnn7pK4
+wP/VqXdQTwqEuvzGHLVFsCuADe40ZFBmtBrf70wG7ZkO8SUZ8Zz1IX3+S024g7yj
+QRev/6x6TtkwggWEBgkqhkiG9w0BBwGgggV1BIIFcTCCBW0wggVpBgsqhkiG9w0B
+DAoBAqCCBTEwggUtMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhTxzw+
+VptrYAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEK9nSqc1I2t4tMVG
+bWHpdtQEggTQzCwI7j34gCTvfj6nuOSndAjShGv7mN2j7WMV0pslTpq2b9Bn3vn1
+Y0JMvL4E7sLrUzNU02pdOcfCnEpMFccNv2sQrLp1mOCKxu8OjSqHZLoKVL0ROVsZ
+8dMECLLigDlPKRiSyLErl14tErX4/zbkUaWMROO28kFbTbubQ8YoHlRUwsKW1xLg
+vfi0gRkG/zHXRfQHjX/8NStv7hXlehn7/Gy2EKPsRFhadm/iUHAfmCMkMgHTU248
+JER9+nsXltd59H+IeDpj/kbxZ+YvHow9XUZKu828d3MQnUpLZ1BfJGhMBPVwbVUD
+A40CiQBVdCoGtPJyalL28xoS3H0ILFCnwQOr6u0HwleNJPGHq78HUyH6Hwxnh0b0
+5o163r6wTFZn5cMOxpbs/Ttd+3TrxmrYpd2XnuRme3cnaYJ0ILvpc/8eLLR7SKjD
+T4JhZ0h/CfcV2WWvhpQugkY0pWrZ+EIMneB1dZB96mJVLxOi148OeSgi0PsxZMNi
+YM33rTpwQT5WqOsEyDwUQpne5b8Kkt/s7EN0LJNnPyJJRL1LcqOdr6j+6YqRtPa7
+a9oWJqMcuTP+bqzGRJh+3HDlFBw2Yzp9iadv4KmB2MzhStLUoi2MSjvnnkkd5Led
+sshAd6WbKfF7kLAHQHT4Ai6dMEO4EKkEVF9JBtxCR4JEn6C98Lpg+Lk+rfY7gHOf
+ZxtgGURwgXRY3aLUrdT55ZKgk3ExVKPzi5EhdpAau7JKhpOwyKozAp/OKWMNrz6h
+obu2Mbn1B+IA60psYHHxynBgsJHv7WQmbYh8HyGfHgVvaA8pZCYqxxjpLjSJrR8B
+Bu9H9xkTh7KlhxgreXYv19uAYbUd95kcox9izad6VPnovgFSb+Omdy6PJACPj6hF
+W6PJbucP0YPpO0VtWtQdZZ3df1P0hZ7qvKwOPFA+gKZSckgqASfygiP9V3Zc8jIi
+wjNzoDM2QT+UUJKiiGYXJUEOO9hxzFHlGj759DcNRhpgl5AgR57ofISD9yBuCAJY
+PQ/aZHPFuRTrcVG3RaIbCAS73nEznKyFaLOXfzyfyaSmyhsH253tnyL1MejC+2bR
+Eko/yldgFUxvU5JI+Q3KJ6Awj+PnduHXx71E4UwSuu2xXYMpxnQwI6rroQpZBX82
+HhqgcLV83P8lpzQwPdHjH5zkoxmWdC0+jU/tcQfNXYpJdyoaX7tDmVclLhwl9ps/
+O841pIsNLJWXwvxG6B+3LN/kw4QjwN194PopiOD7+oDm5mhttO78CrBrRxHMD/0Q
+qniZjKzSZepxlZq+J792u8vtMnuzzChxu0Bf3PhIXcJNcVhwUtr0yKe/N+NvC0tm
+p8wyik/BlndxN9eKbdTOi2wIi64h2QG8nOk66wQ/PSIJYwZl6eDNEQSzH/1mGCfU
+QnUT17UC/p+Qgenf6Auap2GWlvsJrB7u/pytz65rtjt/ouo6Ih6EwWqwVVpGXZD0
+7gVWH0Ke/Vr6aPGNvkLcmftPuDZsn9jiig3guhdeyRVf10Ox369kKWcG75q77hxE
+IzSzDyUlBNbnom9SIjut3r+qVYmWONatC6q/4D0I42Lnjd3dEyZx7jmH3g/S2ASM
+FzWr9pvXc61dsYOkdZ4PYa9XPUZxXFagZsoS3F1sU799+IJVU0tC0MExJTAjBgkq
+hkiG9w0BCRUxFgQUwWO5DorvVWYF3BWUmAw0rUEajScwejBqMEYGCSqGSIb3DQEF
+DjA5MCkGCSqGSIb3DQEFDDAcBAhvRzw4sC4xcwICCAAwDAYIKoZIhvcNAgkFADAM
+BggqhkiG9w0CCQUABCB6pW2FOdcCNj87zS64NUXG36K5aXDnFHctIk5Bf4kG3QQI
+b0c8OLAuMXMCAggA
+-----END PKCS12-----
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-----
+MIIH9wIBAzCCB08GCSqGSIb3DQEHAaCCB0AEggc8MIIHODCCA3oGCSqGSIb3DQEH
+BqCCA2swggNnAgEAMIIDYAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG
+SIb3DQEFDDAkBBBBagXbs+QLsSiBfP1cC26yAgIIADAMBggqhkiG9w0CCQUAMB0G
+CWCGSAFlAwQBKgQQh5Kyk0wJ+1r1fUCre3CNwICCAvCiRe3fIiE49y2J0LLcLMgN
+nN876EkN1s5JcNcBtx+mndbScrz3bOSRdt9uNO/HChrTpPIQVTFpAexJgFHOdgIR
+PTcxFFJom6rPWHFV1sUk1hv0inwgRratTE8H2rwS+LDNsXyeZ4uI6WU6XM4m4zgi
+sRe+JAnS62tNvyvLRFFUK54IFUVoArFAe23oHWkakhh2pHt4J9UI0BBoyu8023Rw
+g6+ikRIQADfWmhAVPAHfgr3/rMT5w9G+J8OebGe0+n4Fj/YWyNZ6J/XbdHpaa01a
+c27UGtII94GcE2BG7fEnKhB0cHnJDUTQ0qkAFR4Q5o/yYwQXfELP1VK6qY/4sjLS
+43jq0+L7QI/N9/P7kX4U2hYtoECuFk2sd4jvUE3XAgiwufJ/gVXcFq84Gm4NyO5S
+UrlP0lTUqrygAp1UkDMvNpaXtewlqUGwWFFAmiQIfcyGZSjoKLOvJaDmxQjDt2tj
+DXaBlx1HiZBDImY3JRB7NrBATkrLI+IHCPqziPtbSbCpIB+1iugAUssf2QGwKGb0
+0aKP+l2HahQfxHQwTKItCCWW0W64Xm7rnnRTpVTg1SM/XTxFXIgkEB3wCBKocYta
+e0lP7aY10n5MHkoGY/WbGUi9j2PwMQmKHnv4U8NXxb7d43CrlvRmwDRT0myipuL2
+Qaud8OmWPfS/Jirxeo5V+9+S4NDnf+95nObNDXmRB0IHAGUojjt5wfmmcTT8eYwk
+v47C/AyyoPgSIzj0okf6zlw5HRz5BV+p5Pf131anWnf4Y3WgeED6+5gypvsjNr83
+RDaObrAmfj+Rn7T4Ps+ISdgyF8eAiWY3p17kQj9PZEj9R5Ef6WTK+158gzvsxmK2
+HSar4b4ygp9++hOyBiyUfl9Iq9kxSinx5LOiF6qQeFKJKLxfmVBQnxNPSTDH0H8g
+V5uz6n+Bi3mVTL0G0xi+ya74RY64FRMqHwphPN/WjuBaJBgqbWojD7klLqhzUwYa
+D48/YXrFZp9pTh/kon451zCCA7YGCSqGSIb3DQEHAaCCA6cEggOjMIIDnzCCA5sG
+CyqGSIb3DQEMCgECoIIC6TCCAuUwXwYJKoZIhvcNAQUNMFIwMQYJKoZIhvcNAQUM
+MCQEEJHzVM2EmTy10vYn6mbG76QCAggAMAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUD
+BAEqBBDNNpq0+SaF5ZEuxjkdPp2sBIICgJccc0SvT8S0qXyL3WFkMQusi2BP+cDL
+hkgwJhQpKh/50q7fbk2zyHMxvD05qnJRIk19QGADm+kFXL6H8I+AjcxQVPfManok
+AYTbmqycPV4FUNDGT2ZAiDHUDgY80uc6QKq8Dy6dSQfhs0LDsxNcwYk8eWvHF19m
+a2rYJOxGjvp/3KtxG2GNyavarjEyCsFTrPr3iBKRIj256C+1O5V1b38SZjTZcCuP
+LglYM5Ls08Gy9wxMe3qsrSeOA1D0m61udW1r7YBnhHQCa4muH6N4sBX5DNHEEb09
++JaIehrKZF/1O0jFOC7tDcJ6pdoVPRG66Q6x5tR95OO56w9rDwsucpkxxvYyIrU4
+P4ajhRF8kV1BVbpeLqi2KIo997zs0Wfec3KTKWuLIUZWqFWjjAvtMmpermm4e68y
+I87htjb+8HwiPtYnE/7pwBH6XlupLWTSEk99RqVqXajUUmP0gudbzFO/2sFlCBkE
+1a3ORWOgdAN9PMv33S22xm+Wearr2u6wWSOfo+CxzFqaRd3fCTN6yzvCk6nsSCms
+ioYaFbpatU0inQGZKzUDZ23ACl0IgHWEZjYhFGz6htivz7nZ02MPnj/uqmflpdbO
+HcH2DZVmyxQWGBaqG+cfEzuZT2TWSSbZWO+xTh074UH0gpEJYBv5g9X1yt0n9BQ1
+XSoiisi4PYEI4Kx9emQpHVeCg4PNNPmYFl96aUOVPhctJswEM/p2fsZbDsSMzvgv
+/Lt0fpuuvhnEah/B23kNbXzqgoLDmApkBjKpMY7XTZyi9AORbZUSh4C1I6bYEk85
+G1YigUYi6GxazOODxbIPRoC6+i4FsO9mPLZWGX4NJSOZb3Fh+Kt3qWsxgZ4wIwYJ
+KoZIhvcNAQkVMRYEFMBsIkqiok5F1viLnqMYiOLTjQ6wMHcGCSqGSIb3DQEJFDFq
+HmgAUABLAEMAUwAxADIAIAAoAHIAcwBhACkAIAAtAG0AYQBjAGEAbABnACAAcwBo
+AGEANQAxADIAIAAtAHAAYgBtAGEAYwAxAF8AcABiAGsAZABmADIAXwBtAGQAIABz
+AGgAYQA1ADEAMjCBnjCBjTBJBgkqhkiG9w0BBQ4wPDAsBgkqhkiG9w0BBQwwHwQI
+b6vXMUPEaigCAggAAgFAMAwGCCqGSIb3DQILBQAwDAYIKoZIhvcNAgsFAARAOxjX
+K9RqFm7T8OJRde72xYo5KuUYU7+AIVMn53XqZ+pVM1T9ihWd7xZmvzPThpA78EvT
+E1VoEw6fFI8UgYExHAQIb6vXMUPEaigCAggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
 MIIGWwIBAzCCBiEGCSqGSIb3DQEHAaCCBhIEggYOMIIGCjCCAsQGCSqGSIb3DQEH
 AaCCArUEggKxMIICrTCCAqkGCyqGSIb3DQEMCgEDoIICLDCCAigGCiqGSIb3DQEJ
 FgGgggIYBIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF
