diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,36 @@
 # Revision history for cryptostore
 
-## 0.1.0.0 -- 2018-09-23
+## 0.2.0.0 - 2019-03-24
+
+* Added functions `toNamedCredential` and `fromNamedCredential` to handle
+  PKCS#12 elements with an alias (friendly name).
+
+* Functions `fromCredential` and `fromNamedCredential` now generate PKCS#12
+  elements with the `localKeyId` attribute.
+
+* Function `toCredential` is now able to locate the leaf certificate and issuers
+  more reliably.
+
+* Algorithms X25519, X448, Ed25519 and Ed448 are now supported.
+
+* CMS functions `digestVerify` and `verifySignedData` now return an `Either`
+  instead of a `Maybe`.  Errors `DigestMismatch` and `SignatureNotVerified` are
+  added to report failures.
+
+* CMS types `SignedData`, `DigestedData` and `AuthenticatedData` now retain the
+  encapsulated content in encoded form (with type alias `EncapsulatedContent`)
+  instead of a decoded and parsed `ContentInfo`.  The `ContentInfo` is parsed
+  and provided only when successfully unwrapping the encapsulated type.
+
+* The CMS interface is transformed to support detached content.  CMS types now
+  have a type parameter to distinguish between a direct reference to the
+  encapsulated or encrypted content, and the `Encap` indirection which denotes
+  an attached or detached content.  Functions building CMS types do not return
+  the `ContentInfo` directly anymore, but an intermediate type to be fed into
+  `toAttachedCI` or `toDetachedCI`.  Reverse transformation is possible with
+  utility functions `fromAttached` and `fromDetached` when unwrapping a
+  `ContentInfo`.
+
+## 0.1.0.0 - 2018-09-23
 
 * First version. Released on an unsuspecting world.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -137,8 +137,6 @@
 
 The API also provides functions to generate/extract a pair containing a private
 key and a certificate chain.  This pair is the type alias `Credential` in `tls`.
-Currently the functions assume that the PKCS #12 file contains no other data
-than the credential.
 
 ```haskell
 > :set -XOverloadedStrings
@@ -170,6 +168,9 @@
 > writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12'
 ```
 
+Variants `toNamedCredential` and `fromNamedCredential` are also available when
+PKCS #12 elements need an alias (friendly name).
+
 ## Cryptographic Message Syntax
 
 The API to read and write CMS content is available in `Crypto.Store.CMS`.  The
@@ -187,7 +188,6 @@
 
 Notable omissions:
 
-* detached content
 * streaming
 * compressed data
 * and S/MIME external format (only PEM is supported, i.e. the textual encoding
@@ -217,8 +217,10 @@
 > keParams <- generateEncryptionParams (CBC AES128)
 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)
 
--- Generate the enveloped structure for this single recipient
-> Right envelopedCI <- envelopData mempty ceKey ceParams [pri] [] info
+-- Generate the enveloped structure for this single recipient.  Encrypted
+-- content is kept attached in the structure.
+> Right envelopedData <- envelopData mempty ceKey ceParams [pri] [] info
+> let envelopedCI = toAttachedCI envelopedData
 > writeCMSFile "/path/to/enveloped.pem" [envelopedCI]
 ```
 
@@ -230,7 +232,8 @@
 
 -- Then this recipient just has to read the file and recover enveloped
 -- content using the password
-> [EnvelopedDataCI envelopedData] <- readCMSFile "/path/to/enveloped.pem"
+> [EnvelopedDataCI envelopedEncapData] <- readCMSFile "/path/to/enveloped.pem"
+> envelopedData <- fromAttached envelopedEncapData
 > openEnvelopedData (withRecipientPassword "mypassword") envelopedData
 Right (DataCI "Hi, what will you need from the cryptostore?")
 ```
diff --git a/cryptostore.cabal b/cryptostore.cabal
--- a/cryptostore.cabal
+++ b/cryptostore.cabal
@@ -1,5 +1,5 @@
 name:                cryptostore
-version:             0.1.0.0
+version:             0.2.0.0
 synopsis:            Serialization of cryptographic data types
 description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS
                      (Cryptographic Message Syntax).
@@ -35,7 +35,9 @@
                      , Crypto.Store.ASN1.Parse
                      , Crypto.Store.CMS.Algorithms
                      , Crypto.Store.CMS.Attribute
+                     , Crypto.Store.CMS.Authenticated
                      , Crypto.Store.CMS.AuthEnveloped
+                     , Crypto.Store.CMS.Digested
                      , Crypto.Store.CMS.Encrypted
                      , Crypto.Store.CMS.Enveloped
                      , Crypto.Store.CMS.Info
@@ -59,7 +61,8 @@
                      , asn1-types >= 0.3.1 && < 0.4
                      , asn1-encoding >= 0.9 && < 0.10
                      , hourglass >= 0.2
-                     , x509 >= 1.7.3
+                     , x509 >= 1.7.5
+                     , x509-validation >= 1.5
   default-language:    Haskell2010
   ghc-options:         -Wall
 
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
@@ -43,7 +43,7 @@
 import Control.Applicative
 import Control.Arrow (first)
 import Control.Monad (MonadPlus(..), liftM2)
-import Control.Monad.Fail
+import Control.Monad.Fail as Fail
 
 data State e = State [(ASN1, e)] !e
 
@@ -70,7 +70,7 @@
         case runP m1 s of
             Left err      -> Left err
             Right (a, s2) -> runP (m2 a) s2
-    fail        = throwParseError
+    fail        = Fail.fail
 instance MonadFail (ParseASN1 e) where
     fail = throwParseError
 instance MonadPlus (ParseASN1 e) where
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
@@ -19,6 +19,8 @@
 -- * <https://tools.ietf.org/html/rfc5084 RFC 5084>: Using AES-CCM and AES-GCM Authenticated Encryption in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc6476 RFC 6476>: Using Message Authentication Code (MAC) Encryption in the Cryptographic Message Syntax (CMS)
 -- * <https://tools.ietf.org/html/rfc8103 RFC 8103>: Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS)
+-- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS)
 {-# LANGUAGE RecordWildCards #-}
 module Crypto.Store.CMS
     ( ContentType(..)
@@ -26,9 +28,20 @@
     , getContentType
     -- * Reading and writing PEM files
     , module Crypto.Store.CMS.PEM
+    -- * Content encapsulation
+    , Encap(..)
+    , fromEncap
+    , Encapsulates
+    , isAttached
+    , fromAttached
+    , toAttachedCI
+    , isDetached
+    , fromDetached
+    , toDetachedCI
     -- * Signed data
     , SignatureValue
     , SignatureAlg(..)
+    , EncapsulatedContent
     , SignedData(..)
     , ProducerOfSI
     , ConsumerOfSI
@@ -75,7 +88,7 @@
     , forKeyRecipient
     , withRecipientKey
      -- ** Password recipients
-   , PasswordRecipientInfo(..)
+    , PasswordRecipientInfo(..)
     , forPasswordRecipient
     , withRecipientPassword
     -- * Digested data
@@ -141,14 +154,20 @@
     , ASN1ObjectExact
     ) where
 
+import Data.ASN1.BinaryEncoding
+import Data.ASN1.Encoding
+import Data.ByteString (ByteString)
 import Data.Maybe (isJust)
 import Data.List (nub, unzip3)
 
 import Crypto.Hash
 
+import Crypto.Store.ASN1.Parse
 import Crypto.Store.CMS.Algorithms
 import Crypto.Store.CMS.Attribute
+import Crypto.Store.CMS.Authenticated
 import Crypto.Store.CMS.AuthEnveloped
+import Crypto.Store.CMS.Digested
 import Crypto.Store.CMS.Encrypted
 import Crypto.Store.CMS.Enveloped
 import Crypto.Store.CMS.OriginatorInfo
@@ -158,25 +177,27 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
+import Crypto.Store.Util
 
 
 -- DigestedData
 
 -- | Add a digested-data layer on the specified content info.
-digestData :: DigestAlgorithm -> ContentInfo -> ContentInfo
-digestData (DigestAlgorithm alg) ci = DigestedDataCI dd
+digestData :: DigestAlgorithm -> ContentInfo -> DigestedData EncapsulatedContent
+digestData (DigestAlgorithm alg) ci = dd
   where dd = DigestedData
                  { ddDigestAlgorithm = alg
-                 , ddContentInfo     = ci
-                 , ddDigest          = hash (encapsulate ci)
+                 , ddContentType = getContentType ci
+                 , ddEncapsulatedContent = encapsulate ci
+                 , ddDigest = hash (encapsulate ci)
                  }
 
 -- | Return the inner content info but only if the digest is valid.
-digestVerify :: DigestedData -> Maybe ContentInfo
+digestVerify :: DigestedData EncapsulatedContent -> Either StoreError ContentInfo
 digestVerify DigestedData{..} =
-    if ddDigest == hash (encapsulate ddContentInfo)
-        then Just ddContentInfo
-        else Nothing
+    if ddDigest == hash ddEncapsulatedContent
+        then decapsulate ddContentType ddEncapsulatedContent
+        else Left DigestMismatch
 
 
 -- EncryptedData
@@ -189,9 +210,9 @@
             -> ContentEncryptionParams
             -> [Attribute]
             -> ContentInfo
-            -> Either StoreError ContentInfo
+            -> Either StoreError (EncryptedData EncryptedContent)
 encryptData key params attrs ci =
-    EncryptedDataCI . build <$> contentEncrypt key params (encapsulate ci)
+    build <$> contentEncrypt key params (encapsulate ci)
   where
     build ec = EncryptedData
                    { edContentType = getContentType ci
@@ -202,7 +223,7 @@
 
 -- | Decrypt an encrypted content info using the specified key.
 decryptData :: ContentEncryptionKey
-            -> EncryptedData
+            -> EncryptedData EncryptedContent
             -> Either StoreError ContentInfo
 decryptData key EncryptedData{..} = do
     decrypted <- contentDecrypt key edContentEncryptionParams edEncryptedContent
@@ -223,12 +244,12 @@
             -> [ProducerOfRI f]
             -> [Attribute]
             -> ContentInfo
-            -> f (Either StoreError ContentInfo)
+            -> f (Either StoreError (EnvelopedData EncryptedContent))
 envelopData oinfo key params envFns attrs ci =
     f <$> (sequence <$> traverse ($ key) envFns)
   where
     ebs = contentEncrypt key params (encapsulate ci)
-    f ris = EnvelopedDataCI <$> (build <$> ebs <*> ris)
+    f ris = build <$> ebs <*> ris
     build bs ris = EnvelopedData
                        { evOriginatorInfo = oinfo
                        , evRecipientInfos = ris
@@ -242,7 +263,7 @@
 -- function.
 openEnvelopedData :: Monad m
                   => ConsumerOfRI m
-                  -> EnvelopedData
+                  -> EnvelopedData EncryptedContent
                   -> m (Either StoreError ContentInfo)
 openEnvelopedData devFn EnvelopedData{..} = do
     r <- riAttempts (map (fmap (>>= decr) . devFn) evRecipientInfos)
@@ -273,7 +294,7 @@
                           -> [Attribute]
                           -> [Attribute]
                           -> ContentInfo
-                          -> f (Either StoreError ContentInfo)
+                          -> f (Either StoreError (AuthenticatedData EncapsulatedContent))
 generateAuthenticatedData oinfo key macAlg digAlg envFns aAttrs uAttrs ci =
     f <$> (sequence <$> traverse ($ key) envFns)
   where
@@ -289,13 +310,14 @@
                 in (l, encodeAuthAttrs l)
 
     ebs   = mac macAlg key input
-    f ris = AuthenticatedDataCI <$> (build ebs <$> ris)
+    f ris = build ebs <$> ris
     build authTag ris = AuthenticatedData
                             { adOriginatorInfo = oinfo
                             , adRecipientInfos = ris
                             , adMACAlgorithm = macAlg
                             , adDigestAlgorithm = digAlg
-                            , adContentInfo = ci
+                            , adContentType = getContentType ci
+                            , adEncapsulatedContent = encapsulate ci
                             , adAuthAttrs = aAttrs'
                             , adMAC = authTag
                             , adUnauthAttrs = uAttrs
@@ -306,13 +328,13 @@
 -- could be verified.
 verifyAuthenticatedData :: Monad m
                         => ConsumerOfRI m
-                        -> AuthenticatedData
+                        -> AuthenticatedData EncapsulatedContent
                         -> m (Either StoreError ContentInfo)
 verifyAuthenticatedData devFn AuthenticatedData{..} =
     riAttempts (map (fmap (>>= unwrap) . devFn) adRecipientInfos)
   where
-    msg = encapsulate adContentInfo
-    ct  = getContentType adContentInfo
+    msg = adEncapsulatedContent
+    ct  = adContentType
 
     noAttr    = null adAuthAttrs
     mdMatch   = case adDigestAlgorithm of
@@ -327,7 +349,7 @@
         | isJust adDigestAlgorithm && noAttr  = Left (InvalidInput "Missing auth attributes")
         | not noAttr && not attrMatch         = Left (InvalidInput "Invalid auth attributes")
         | adMAC /= mac adMACAlgorithm k input = Left BadContentMAC
-        | otherwise                           = Right adContentInfo
+        | otherwise                           = decapsulate adContentType adEncapsulatedContent
 
 
 -- AuthEnvelopedData
@@ -347,14 +369,14 @@
                 -> [Attribute]
                 -> [Attribute]
                 -> ContentInfo
-                -> f (Either StoreError ContentInfo)
+                -> f (Either StoreError (AuthEnvelopedData EncryptedContent))
 authEnvelopData oinfo key params envFns aAttrs uAttrs ci =
     f <$> (sequence <$> traverse ($ key) envFns)
   where
     raw = encodeASN1Object params
     aad = encodeAuthAttrs aAttrs
     ebs = authContentEncrypt key params raw aad (encapsulate ci)
-    f ris = AuthEnvelopedDataCI <$> (build <$> ebs <*> ris)
+    f ris = build <$> ebs <*> ris
     build (authTag, bs) ris = AuthEnvelopedData
                        { aeOriginatorInfo = oinfo
                        , aeRecipientInfos = ris
@@ -370,7 +392,7 @@
 -- 'ConsumerOfRI' function.
 openAuthEnvelopedData :: Monad m
                       => ConsumerOfRI m
-                      -> AuthEnvelopedData
+                      -> AuthEnvelopedData EncryptedContent
                       -> m (Either StoreError ContentInfo)
 openAuthEnvelopedData devFn AuthEnvelopedData{..} = do
     r <- riAttempts (map (fmap (>>= decr) . devFn) aeRecipientInfos)
@@ -389,18 +411,19 @@
 -- processed by one or several 'ProducerOfSI' functions to create signer info
 -- elements.
 signData :: Applicative f
-         => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError ContentInfo)
+         => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError (SignedData EncapsulatedContent))
 signData sigFns ci =
     f <$> (sequence <$> traverse (\fn -> fn ct msg) sigFns)
   where
     msg = encapsulate ci
     ct  = getContentType ci
-    f   = fmap (SignedDataCI . build . unzip3)
+    f   = fmap (build . unzip3)
 
     build (sis, certLists, crlLists) =
         SignedData
             { sdDigestAlgorithms = nub (map siDigestAlgorithm sis)
-            , sdContentInfo = ci
+            , sdContentType = getContentType ci
+            , sdEncapsulatedContent = encapsulate ci
             , sdCertificates = concat certLists
             , sdCRLs = concat crlLists
             , sdSignerInfos = sis
@@ -410,14 +433,15 @@
 -- Verification of at least one signer info must be successful in order to
 -- return the inner content info.
 verifySignedData :: Monad m
-                 => ConsumerOfSI m -> SignedData -> m (Maybe ContentInfo)
+                 => ConsumerOfSI m -> SignedData EncapsulatedContent -> m (Either StoreError ContentInfo)
 verifySignedData verFn SignedData{..} =
     f <$> siAttemps valid sdSignerInfos
   where
-    msg      = encapsulate sdContentInfo
-    ct       = getContentType sdContentInfo
+    msg      = sdEncapsulatedContent
+    ct       = sdContentType
     valid si = verFn ct msg si sdCertificates sdCRLs
-    f bool   = if bool then Just sdContentInfo else Nothing
+    f bool   = if bool then decapsulate sdContentType sdEncapsulatedContent
+                       else Left SignatureNotVerified
 
 
 -- Utilities
@@ -437,3 +461,27 @@
 siAttemps _ []     = pure False
 siAttemps f (x:xs) = f x >>= orTail
   where orTail bool = if bool then return True else siAttemps f xs
+
+decode :: ParseASN1 [ASN1Event] a -> ByteString -> Either StoreError a
+decode parser bs = vals >>= mapLeft ParseFailure . runParseASN1_ parser
+  where vals = mapLeft DecodingError (decodeASN1Repr' BER bs)
+
+-- | Encode the information for encapsulation in another content info.
+encapsulate :: ContentInfo -> ByteString
+encapsulate (DataCI bs)              = bs
+encapsulate (SignedDataCI ed)        = encodeASN1Object ed
+encapsulate (EnvelopedDataCI ed)     = encodeASN1Object ed
+encapsulate (DigestedDataCI dd)      = encodeASN1Object dd
+encapsulate (EncryptedDataCI ed)     = encodeASN1Object ed
+encapsulate (AuthenticatedDataCI ad) = encodeASN1Object ad
+encapsulate (AuthEnvelopedDataCI ae) = encodeASN1Object ae
+
+-- | Decode the information from encapsulated content.
+decapsulate :: ContentType -> ByteString -> Either StoreError ContentInfo
+decapsulate DataType bs              = pure (DataCI bs)
+decapsulate SignedDataType bs        = SignedDataCI <$> decode parse bs
+decapsulate EnvelopedDataType bs     = EnvelopedDataCI <$> decode parse bs
+decapsulate DigestedDataType bs      = DigestedDataCI <$> decode parse bs
+decapsulate EncryptedDataType bs     = EncryptedDataCI <$> decode parse bs
+decapsulate AuthenticatedDataType bs = AuthenticatedDataCI <$> decode parse bs
+decapsulate AuthEnvelopedDataType bs = AuthEnvelopedDataCI <$> decode parse bs
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
@@ -6,6 +6,7 @@
 -- Portability : unknown
 --
 -- Cryptographic Message Syntax algorithms
+{-# LANGUAGE DataKinds #-}
 {-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE GADTs #-}
@@ -60,7 +61,9 @@
     , transportEncrypt
     , transportDecrypt
     , KeyAgreementParams(..)
+    , ECDHPair
     , ecdhGenerate
+    , ecdhPublic
     , ecdhEncrypt
     , ecdhDecrypt
     , MaskGenerationFunc(..)
@@ -86,10 +89,13 @@
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
 import           Data.Maybe (fromMaybe)
+import           Data.Proxy
 import           Data.Word
 import qualified Data.X509 as X509
 import           Data.X509.EC
 
+import GHC.TypeLits
+
 import qualified Crypto.Cipher.AES as Cipher
 import qualified Crypto.Cipher.CAST5 as Cipher
 import qualified Crypto.Cipher.Camellia as Cipher
@@ -98,6 +104,7 @@
 import qualified Crypto.Cipher.TripleDES as Cipher
 import           Crypto.Cipher.Types
 import           Crypto.Data.Padding
+import           Crypto.ECC (Curve_X25519, Curve_X448, ecdh)
 import           Crypto.Error
 import qualified Crypto.Hash as Hash
 import qualified Crypto.KDF.PBKDF2 as PBKDF2
@@ -105,10 +112,14 @@
 import qualified Crypto.MAC.HMAC as HMAC
 import qualified Crypto.MAC.Poly1305 as Poly1305
 import           Crypto.Number.Serialize
+import qualified Crypto.PubKey.Curve25519 as X25519
+import qualified Crypto.PubKey.Curve448 as X448
 import qualified Crypto.PubKey.DSA as DSA
 import qualified Crypto.PubKey.ECC.DH as ECDH
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
 import qualified Crypto.PubKey.ECC.Types as ECC
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
 import qualified Crypto.PubKey.MaskGenFunction as MGF
 import qualified Crypto.PubKey.RSA.PSS as RSAPSS
 import qualified Crypto.PubKey.RSA.OAEP as RSAOAEP
@@ -126,6 +137,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.PKCS8.EC
 import           Crypto.Store.Util
 
 
@@ -150,6 +162,14 @@
     SHA384 :: DigestProxy Hash.SHA384
     -- | SHA-512
     SHA512 :: DigestProxy Hash.SHA512
+    -- | SHAKE128 (256 bits)
+    SHAKE128_256 :: DigestProxy (Hash.SHAKE128 256)
+    -- | SHAKE256 (512 bits)
+    SHAKE256_512 :: DigestProxy (Hash.SHAKE256 512)
+    -- | SHAKE128 (variable size)
+    SHAKE128 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE128 n)
+    -- | SHAKE256 (variable size)
+    SHAKE256 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE256 n)
 
 deriving instance Show (DigestProxy hashAlg)
 deriving instance Eq (DigestProxy hashAlg)
@@ -171,43 +191,95 @@
     DigestAlgorithm SHA256       == DigestAlgorithm SHA256       = True
     DigestAlgorithm SHA384       == DigestAlgorithm SHA384       = True
     DigestAlgorithm SHA512       == DigestAlgorithm SHA512       = 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
+    DigestAlgorithm (SHAKE256 a) == DigestAlgorithm (SHAKE256 b) = natVal a == natVal b
     _                            == _                            = False
 
-instance Enumerable DigestAlgorithm where
-    values = [ DigestAlgorithm MD2
-             , DigestAlgorithm MD4
-             , DigestAlgorithm MD5
-             , DigestAlgorithm SHA1
-             , DigestAlgorithm SHA224
-             , DigestAlgorithm SHA256
-             , DigestAlgorithm SHA384
-             , DigestAlgorithm SHA512
+data DigestType
+    = Type_MD2
+    | Type_MD4
+    | Type_MD5
+    | Type_SHA1
+    | Type_SHA224
+    | Type_SHA256
+    | Type_SHA384
+    | Type_SHA512
+    | Type_SHAKE128_256
+    | Type_SHAKE256_512
+    | Type_SHAKE128_Len
+    | Type_SHAKE256_Len
+
+instance Enumerable DigestType where
+    values = [ Type_MD2
+             , Type_MD4
+             , Type_MD5
+             , Type_SHA1
+             , Type_SHA224
+             , Type_SHA256
+             , Type_SHA384
+             , Type_SHA512
+             , Type_SHAKE128_256
+             , Type_SHAKE256_512
+             , Type_SHAKE128_Len
+             , Type_SHAKE256_Len
              ]
 
-instance OIDable DigestAlgorithm where
-    getObjectID (DigestAlgorithm MD2)    = [1,2,840,113549,2,2]
-    getObjectID (DigestAlgorithm MD4)    = [1,2,840,113549,2,4]
-    getObjectID (DigestAlgorithm MD5)    = [1,2,840,113549,2,5]
-    getObjectID (DigestAlgorithm SHA1)   = [1,3,14,3,2,26]
-    getObjectID (DigestAlgorithm SHA224) = [2,16,840,1,101,3,4,2,4]
-    getObjectID (DigestAlgorithm SHA256) = [2,16,840,1,101,3,4,2,1]
-    getObjectID (DigestAlgorithm SHA384) = [2,16,840,1,101,3,4,2,2]
-    getObjectID (DigestAlgorithm SHA512) = [2,16,840,1,101,3,4,2,3]
+instance OIDable DigestType where
+    getObjectID Type_MD2          = [1,2,840,113549,2,2]
+    getObjectID Type_MD4          = [1,2,840,113549,2,4]
+    getObjectID Type_MD5          = [1,2,840,113549,2,5]
+    getObjectID Type_SHA1         = [1,3,14,3,2,26]
+    getObjectID Type_SHA224       = [2,16,840,1,101,3,4,2,4]
+    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_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]
+    getObjectID Type_SHAKE256_Len = [2,16,840,1,101,3,4,2,18]
 
-instance OIDNameable DigestAlgorithm where
+instance OIDNameable DigestType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
 
 instance AlgorithmId DigestAlgorithm where
-    type AlgorithmType DigestAlgorithm = DigestAlgorithm
+    type AlgorithmType DigestAlgorithm = DigestType
     algorithmName _  = "digest algorithm"
-    algorithmType    = id
 
-    -- MD5 has NULL parameter, other algorithms have no parameter
-    parameterASN1S (DigestAlgorithm MD5) = gNull
-    parameterASN1S _                     = id
+    algorithmType (DigestAlgorithm MD2)          = Type_MD2
+    algorithmType (DigestAlgorithm MD4)          = Type_MD4
+    algorithmType (DigestAlgorithm MD5)          = Type_MD5
+    algorithmType (DigestAlgorithm SHA1)         = Type_SHA1
+    algorithmType (DigestAlgorithm SHA224)       = Type_SHA224
+    algorithmType (DigestAlgorithm SHA256)       = Type_SHA256
+    algorithmType (DigestAlgorithm SHA384)       = Type_SHA384
+    algorithmType (DigestAlgorithm SHA512)       = Type_SHA512
+    algorithmType (DigestAlgorithm SHAKE128_256) = Type_SHAKE128_256
+    algorithmType (DigestAlgorithm SHAKE256_512) = Type_SHAKE256_512
+    algorithmType (DigestAlgorithm (SHAKE128 _)) = Type_SHAKE128_Len
+    algorithmType (DigestAlgorithm (SHAKE256 _)) = Type_SHAKE256_Len
 
-    parseParameter p = getNextMaybe nullOrNothing >> return p
+    -- MD5 has NULL parameter, SHAKE128 and SHAKE256 have the bitsize as
+    -- parameter, other algorithms have no parameter
+    parameterASN1S (DigestAlgorithm MD5)          = gNull
+    parameterASN1S (DigestAlgorithm (SHAKE128 n)) = gIntVal (natVal n)
+    parameterASN1S (DigestAlgorithm (SHAKE256 n)) = gIntVal (natVal n)
+    parameterASN1S _                              = id
 
+    parseParameter Type_MD2          = parseDigestParam (DigestAlgorithm MD2)
+    parseParameter Type_MD4          = parseDigestParam (DigestAlgorithm MD4)
+    parseParameter Type_MD5          = parseDigestParam (DigestAlgorithm MD5)
+    parseParameter Type_SHA1         = parseDigestParam (DigestAlgorithm SHA1)
+    parseParameter Type_SHA224       = parseDigestParam (DigestAlgorithm SHA224)
+    parseParameter Type_SHA256       = parseDigestParam (DigestAlgorithm SHA256)
+    parseParameter Type_SHA384       = parseDigestParam (DigestAlgorithm SHA384)
+    parseParameter Type_SHA512       = parseDigestParam (DigestAlgorithm SHA512)
+    parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)
+    parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)
+    parseParameter Type_SHAKE128_Len = parseBitLen 256 (DigestAlgorithm $ SHAKE128 p256)
+    parseParameter Type_SHAKE256_Len = parseBitLen 512 (DigestAlgorithm $ SHAKE256 p512)
+
 -- | Compute the digest of a message.
 digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString
 digest (DigestAlgorithm hashAlg) message = B.convert (doHash hashAlg message)
@@ -219,7 +291,25 @@
 hashFromProxy :: proxy a -> a
 hashFromProxy _ = undefined
 
+parseDigestParam :: Monoid e => DigestAlgorithm -> ParseASN1 e DigestAlgorithm
+parseDigestParam p = getNextMaybe nullOrNothing >> return p
 
+parseBitLen :: Monoid e => Integer -> a -> ParseASN1 e a
+parseBitLen expected res = do
+    IntVal n <- getNext
+    if n == expected
+        then return res
+        else throwParseError ("Invalid bit length: " ++ show n)
+        -- FIXME: should allow any input size but cryptonite requires
+        -- divisibility by 8.  For now we restrict to 256/512 bits only.
+
+p256 :: Proxy 256
+p256 = Proxy
+
+p512 :: Proxy 512
+p512 = Proxy
+
+
 -- Cipher-like things
 
 -- | Algorithms that are based on a secret key.  This includes ciphers but also
@@ -1497,57 +1587,97 @@
                       (gOctetString $ toWord32 outBits)
          in encodeASN1S $ asn1Container Sequence (ki . eui . spi)
 
+-- | Key pair for ECDH.
+data ECDHPair
+    = PairEC ECC.Curve ECC.PrivateNumber ECC.Point
+    | PairX25519 X25519.SecretKey X25519.PublicKey
+    | PairX448 X448.SecretKey X448.PublicKey
+
 -- | Generate an ephemeral ECDH key.
-ecdhGenerate :: MonadRandom m => X509.PubKey -> m (Either StoreError (ECC.Curve, ECC.PrivateNumber, X509.SerializedPoint))
+ecdhGenerate :: MonadRandom m => X509.PubKey -> m (Either StoreError ECDHPair)
 ecdhGenerate (X509.PubKeyEC pub) =
     case ecPubKeyCurveName pub of
         Nothing -> return $ Left NamedCurveRequired
         Just n  -> do
             let curve = ECC.getCurveByName n
             priv <- ECDH.generatePrivate curve
-            return $ Right (curve, priv, X509.pubkeyEC_pub pub)
+            return $ case unserializePoint curve (X509.pubkeyEC_pub pub) of
+                Nothing -> Left (InvalidInput "Invalid serialized point")
+                Just pt -> Right (PairEC curve priv pt)
+ecdhGenerate (X509.PubKeyX25519 pub) = do
+    priv <- X25519.generateSecretKey
+    return $ Right (PairX25519 priv pub)
+ecdhGenerate (X509.PubKeyX448 pub) = do
+    priv <- X448.generateSecretKey
+    return $ Right (PairX448 priv pub)
 ecdhGenerate _ = return $ Left UnexpectedPublicKeyType
 
+-- | Return the serialized public key corresponding to the ECDH private key.
+ecdhPublic :: ECDHPair -> ByteString
+ecdhPublic (PairEC curve d _)  = unSerialize (getSerializedPoint curve d)
+  where unSerialize (X509.SerializedPoint pt) = pt
+ecdhPublic (PairX25519 priv _) = B.convert (X25519.toPublic priv)
+ecdhPublic (PairX448 priv _)   = B.convert (X448.toPublic priv)
+
 -- | Encrypt the specified content with an ECDH key pair and key-agreement
 -- algorithm.
 ecdhEncrypt :: (MonadRandom m, ByteArray ba)
-            => KeyAgreementParams -> Maybe ByteString -> ECC.Curve -> ECC.PrivateNumber -> X509.SerializedPoint -> ba -> m (Either StoreError ba)
-ecdhEncrypt (StdDH dig kep) ukm curve d pt bs =
-    case unserializePoint curve pt of
-        Nothing  -> return $ Left (InvalidInput "Invalid serialized point")
-        Just pub -> do
-            let s = ECDH.getShared curve d pub
-                k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
-            keyEncrypt k kep bs
-ecdhEncrypt (CofactorDH dig kep) ukm curve d pt bs =
-    case unserializePoint curve pt of
-        Nothing  -> return $ Left (InvalidInput "Invalid serialized point")
-        Just pub -> do
-            let h = ECC.ecc_h (ECC.common_curve curve)
-                s = ECDH.getShared curve (h * d) pub
-                k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
-            keyEncrypt k kep bs
+            => KeyAgreementParams -> Maybe ByteString -> ECDHPair -> ba -> m (Either StoreError ba)
+ecdhEncrypt (StdDH dig kep) ukm (PairEC curve d pub) bs = do
+    let s = ECDH.getShared curve d pub
+        k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
+    keyEncrypt k kep bs
+ecdhEncrypt (StdDH dig kep) ukm (PairX25519 priv pub) bs =
+    case fromCryptoFailable (ecdh x25519 priv pub) of
+        Left e  -> return (Left e)
+        Right s ->
+            let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
+             in keyEncrypt k kep bs
+ecdhEncrypt (StdDH dig kep) ukm (PairX448 priv pub) bs =
+    case fromCryptoFailable (ecdh x448 priv pub) of
+        Left e  -> return (Left e)
+        Right s ->
+            let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
+             in keyEncrypt k kep bs
+ecdhEncrypt (CofactorDH dig kep) ukm (PairEC curve d pub) bs = do
+    let h = ECC.ecc_h (ECC.common_curve curve)
+        s = ECDH.getShared curve (h * d) pub
+        k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
+    keyEncrypt k kep bs
+ecdhEncrypt (CofactorDH _ _) _ (PairX25519 _ _) _ =
+    return $ Left (InvalidInput "X25519 is not supported for cofactor DH")
+ecdhEncrypt (CofactorDH _ _) _ (PairX448 _ _) _ =
+    return $ Left (InvalidInput "X448 is not supported for cofactor DH")
 
 -- | Decrypt the specified content with an ECDH key pair and key-agreement
 -- algorithm.
 ecdhDecrypt :: ByteArray ba
-            => KeyAgreementParams -> Maybe ByteString -> X509.PrivKeyEC -> X509.SerializedPoint -> ba -> Either StoreError ba
-ecdhDecrypt (StdDH dig kep) ukm priv pt bs =
+            => KeyAgreementParams -> Maybe ByteString -> X509.PrivKey -> ByteString -> ba -> Either StoreError ba
+ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
-            case unserializePoint curve pt of
+            case unserializePoint curve (X509.SerializedPoint pt) of
                 Nothing  -> Left (InvalidInput "Invalid serialized point")
                 Just pub -> do
                     let d = X509.privkeyEC_priv priv
                         s = ECDH.getShared curve d pub
                         k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
                     keyDecrypt k kep bs
-ecdhDecrypt (CofactorDH dig kep) ukm priv pt bs =
+ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX25519 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
+    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 =
     case ecPrivKeyCurve priv of
         Nothing    -> Left UnsupportedEllipticCurve
         Just curve ->
-            case unserializePoint curve pt of
+            case unserializePoint curve (X509.SerializedPoint pt) of
                 Nothing  -> Left (InvalidInput "Invalid serialized point")
                 Just pub -> do
                     let h = ECC.ecc_h (ECC.common_curve curve)
@@ -1555,8 +1685,15 @@
                         s = ECDH.getShared curve (h * d) pub
                         k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes
                     keyDecrypt k kep bs
+ecdhDecrypt (CofactorDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType
 
+x25519 :: Proxy Curve_X25519
+x25519 = Proxy
 
+x448 :: Proxy Curve_X448
+x448 = Proxy
+
+
 -- Utilities
 
 getCipher :: (BlockCipher cipher, ByteArray key)
@@ -1713,6 +1850,8 @@
                    | TypeRSAPSS
                    | TypeDSA DigestAlgorithm
                    | TypeECDSA DigestAlgorithm
+                   | TypeEd25519
+                   | TypeEd448
     deriving (Show,Eq)
 
 instance Enumerable SignatureType where
@@ -1737,6 +1876,9 @@
              , TypeECDSA (DigestAlgorithm SHA256)
              , TypeECDSA (DigestAlgorithm SHA384)
              , TypeECDSA (DigestAlgorithm SHA512)
+
+             , TypeEd25519
+             , TypeEd448
              ]
 
 instance OIDable SignatureType where
@@ -1762,6 +1904,9 @@
     getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3]
     getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4]
 
+    getObjectID TypeEd25519                          = [1,3,101,112]
+    getObjectID TypeEd448                            = [1,3,101,113]
+
     getObjectID ty = error ("Unsupported SignatureType: " ++ show ty)
 
 instance OIDNameable SignatureType where
@@ -1773,6 +1918,8 @@
                   | RSAPSS PSSParams
                   | DSA DigestAlgorithm
                   | ECDSA DigestAlgorithm
+                  | Ed25519
+                  | Ed448
     deriving (Show,Eq)
 
 instance AlgorithmId SignatureAlg where
@@ -1784,35 +1931,42 @@
     algorithmType (RSAPSS _)  = TypeRSAPSS
     algorithmType (DSA alg)   = TypeDSA alg
     algorithmType (ECDSA alg) = TypeECDSA alg
+    algorithmType Ed25519     = TypeEd25519
+    algorithmType Ed448       = TypeEd448
 
     parameterASN1S RSAAnyHash = gNull
     parameterASN1S (RSA _)    = gNull
     parameterASN1S (RSAPSS p) = asn1s p
     parameterASN1S (DSA _)    = id
     parameterASN1S (ECDSA _)  = id
+    parameterASN1S Ed25519    = id
+    parameterASN1S Ed448      = id
 
     parseParameter TypeRSAAnyHash   = getNextMaybe nullOrNothing >> return RSAAnyHash
     parseParameter (TypeRSA alg)    = getNextMaybe nullOrNothing >> return (RSA alg)
     parseParameter TypeRSAPSS       = RSAPSS <$> parse
     parseParameter (TypeDSA alg)    = return (DSA alg)
     parseParameter (TypeECDSA alg)  = return (ECDSA alg)
+    parseParameter TypeEd25519      = return Ed25519
+    parseParameter TypeEd448        = return Ed448
 
--- | Sign a message using the specified algorithm and private key.
-signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> ByteString -> m (Either StoreError SignatureValue)
-signatureGenerate RSAAnyHash _ _ =
+-- | 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 _ _ _ =
     error "signatureGenerate: should call signatureResolveHash first"
-signatureGenerate (RSA alg)   (X509.PrivKeyRSA priv) msg =
+signatureGenerate (RSA alg)   (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) 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) msg =
+signatureGenerate (RSAPSS p)  (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =
     withPSSParams p $ \params ->
         mapLeft RSAError <$> RSAPSS.signSafer params priv msg
-signatureGenerate (DSA alg)   (X509.PrivKeyDSA priv) msg =
+signatureGenerate (DSA alg)   (X509.PrivKeyDSA priv) (X509.PubKeyDSA _) msg =
     case alg of
         DigestAlgorithm t ->
             Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg
-signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv)  msg =
+signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv)  (X509.PubKeyEC _)  msg =
     case alg of
         DigestAlgorithm t ->
             case ecdsaToPrivateKey priv of
@@ -1820,7 +1974,11 @@
                 Just p  ->
                     let h = hashFromProxy t
                      in Right . ecdsaFromSignature <$> ECDSA.sign p h msg
-signatureGenerate _ _ _ = return (Left UnexpectedPrivateKeyType)
+signatureGenerate Ed25519 (X509.PrivKeyEd25519 priv) (X509.PubKeyEd25519 pub) msg =
+    return . Right . B.convert $ Ed25519.sign priv pub msg
+signatureGenerate Ed448 (X509.PrivKeyEd448 priv) (X509.PubKeyEd448 pub) msg =
+    return . Right . B.convert $ Ed448.sign priv pub msg
+signatureGenerate _ _ _ _ = return (Left UnexpectedPrivateKeyType)
 
 -- | Verify a message signature using the specified algorithm and public key.
 signatureVerify :: SignatureAlg -> X509.PubKey -> ByteString -> SignatureValue -> Bool
@@ -1840,6 +1998,14 @@
     s <- ecdsaToSignature sig
     case alg of
         DigestAlgorithm t -> return $ ECDSA.verify (hashFromProxy t) p s msg
+signatureVerify Ed25519 (X509.PubKeyEd25519 pub) msg sig =
+    case Ed25519.signature sig of
+        CryptoFailed _ -> False
+        CryptoPassed s -> Ed25519.verify pub msg s
+signatureVerify Ed448 (X509.PubKeyEd448 pub) msg sig =
+    case Ed448.signature sig of
+        CryptoFailed _ -> False
+        CryptoPassed s -> Ed448.verify pub msg s
 signatureVerify _                 _                    _   _   = False
 
 withHashAlgorithmASN1 :: DigestAlgorithm
@@ -1858,12 +2024,15 @@
 -- | Return on which digest algorithm the specified signature algorithm is
 -- based, as well as a substitution algorithm for when a default digest
 -- algorithm is required.
-signatureResolveHash :: DigestAlgorithm -> SignatureAlg -> (DigestAlgorithm, SignatureAlg)
-signatureResolveHash d RSAAnyHash     = (d, RSA d)
-signatureResolveHash _ alg@(RSA d)    = (d, alg)
-signatureResolveHash _ alg@(RSAPSS p) = (pssHashAlgorithm p, alg)
-signatureResolveHash _ alg@(DSA d)    = (d, alg)
-signatureResolveHash _ alg@(ECDSA d)  = (d, alg)
+signatureResolveHash :: Bool -> DigestAlgorithm -> SignatureAlg -> (DigestAlgorithm, SignatureAlg)
+signatureResolveHash _     d RSAAnyHash     = (d, RSA d)
+signatureResolveHash _     _ alg@(RSA d)    = (d, alg)
+signatureResolveHash _     _ alg@(RSAPSS p) = (pssHashAlgorithm p, alg)
+signatureResolveHash _     _ alg@(DSA d)    = (d, alg)
+signatureResolveHash _     _ alg@(ECDSA d)  = (d, alg)
+signatureResolveHash _     _ alg@Ed25519    = (DigestAlgorithm SHA512, alg)
+signatureResolveHash True  _ alg@Ed448      = (DigestAlgorithm SHAKE256_512, alg)
+signatureResolveHash False _ alg@Ed448      = (DigestAlgorithm (SHAKE256 p512), alg)
 
 -- | Check that a signature algorithm is based on the specified digest algorithm
 -- and return a substitution algorithm for when a default digest algorithm is
@@ -1882,6 +2051,13 @@
 signatureCheckHash expected alg@(ECDSA found)
     | expected == found = Just alg
     | otherwise         = Nothing
+signatureCheckHash expected alg@Ed25519
+    | expected == DigestAlgorithm SHA512 = Just alg
+    | otherwise                          = Nothing
+signatureCheckHash expected alg@Ed448
+    | expected == DigestAlgorithm SHAKE256_512    = Just alg
+    | expected == DigestAlgorithm (SHAKE256 p512) = Just alg
+    | otherwise                                   = Nothing
 
 dsaToSignature :: ByteString -> Maybe DSA.Signature
 dsaToSignature b = tryDecodeAndParse b $ onNextContainer Sequence $ do
diff --git a/src/Crypto/Store/CMS/AuthEnveloped.hs b/src/Crypto/Store/CMS/AuthEnveloped.hs
--- a/src/Crypto/Store/CMS/AuthEnveloped.hs
+++ b/src/Crypto/Store/CMS/AuthEnveloped.hs
@@ -34,7 +34,7 @@
 import Crypto.Store.CMS.Util
 
 -- | Authenticated-enveloped content information.
-data AuthEnvelopedData = AuthEnvelopedData
+data AuthEnvelopedData content = AuthEnvelopedData
     { aeOriginatorInfo :: OriginatorInfo
       -- ^ Optional information about the originator
     , aeRecipientInfos :: [RecipientInfo]
@@ -43,7 +43,7 @@
       -- ^ Inner content type
     , aeContentEncryptionParams :: ASN1ObjectExact AuthContentEncryptionParams
       -- ^ Encryption algorithm
-    , aeEncryptedContent :: EncryptedContent
+    , aeEncryptedContent :: content
       -- ^ Encrypted content info
     , aeAuthAttrs :: [Attribute]
       -- ^ Optional authenticated attributes
@@ -54,7 +54,7 @@
     }
     deriving (Show,Eq)
 
-instance ProduceASN1Object ASN1P AuthEnvelopedData where
+instance ProduceASN1Object ASN1P (AuthEnvelopedData (Encap EncryptedContent)) where
     asn1s AuthEnvelopedData{..} =
         asn1Container Sequence (ver . oi . ris . eci . aa . tag . ua)
       where
@@ -69,7 +69,7 @@
         oi | aeOriginatorInfo == mempty = id
            | otherwise = originatorInfoASN1S (Container Context 0) aeOriginatorInfo
 
-instance ParseASN1Object [ASN1Event] AuthEnvelopedData where
+instance ParseASN1Object [ASN1Event] (AuthEnvelopedData (Encap EncryptedContent)) where
     parse =
         onNextContainer Sequence $ do
             IntVal v <- getNext
diff --git a/src/Crypto/Store/CMS/Authenticated.hs b/src/Crypto/Store/CMS/Authenticated.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Store/CMS/Authenticated.hs
@@ -0,0 +1,99 @@
+-- |
+-- Module      : Crypto.Store.CMS.Authenticated
+-- License     : BSD-style
+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
+-- Stability   : experimental
+-- Portability : unknown
+--
+--
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards #-}
+module Crypto.Store.CMS.Authenticated
+    ( AuthenticatedData(..)
+    ) where
+
+import Control.Applicative
+import Control.Monad
+
+import           Data.ASN1.Types
+import qualified Data.ByteArray as B
+
+import Crypto.Cipher.Types
+
+import Crypto.Store.ASN1.Generate
+import Crypto.Store.ASN1.Parse
+import Crypto.Store.CMS.Algorithms
+import Crypto.Store.CMS.Attribute
+import Crypto.Store.CMS.Enveloped
+import Crypto.Store.CMS.OriginatorInfo
+import Crypto.Store.CMS.Signed
+import Crypto.Store.CMS.Type
+import Crypto.Store.CMS.Util
+
+-- | Authenticated content information.
+data AuthenticatedData content = AuthenticatedData
+    { adOriginatorInfo :: OriginatorInfo
+      -- ^ Optional information about the originator
+    , adRecipientInfos :: [RecipientInfo]
+      -- ^ Information for recipients, allowing to authenticate the content
+    , adMACAlgorithm :: MACAlgorithm
+      -- ^ MAC algorithm
+    , adDigestAlgorithm :: Maybe DigestAlgorithm
+      -- ^ Optional digest algorithm
+    , adContentType :: ContentType
+      -- ^ Inner content type
+    , adEncapsulatedContent :: content
+      -- ^ Encapsulated content
+    , adAuthAttrs :: [Attribute]
+      -- ^ Optional authenticated attributes
+    , adMAC :: MessageAuthenticationCode
+      -- ^ Message authentication code
+    , adUnauthAttrs :: [Attribute]
+      -- ^ Optional unauthenticated attributes
+    }
+    deriving (Show,Eq)
+
+instance ProduceASN1Object ASN1P (AuthenticatedData (Encap EncapsulatedContent)) where
+    asn1s AuthenticatedData{..} =
+        asn1Container Sequence (ver . oi . ris . alg . dig . ci . aa . tag . ua)
+      where
+        ver = gIntVal v
+        ris = asn1Container Set (asn1s adRecipientInfos)
+        alg = algorithmASN1S Sequence adMACAlgorithm
+        dig = algorithmMaybeASN1S (Container Context 1) adDigestAlgorithm
+        ci  = encapsulatedContentInfoASN1S adContentType adEncapsulatedContent
+        aa  = attributesASN1S(Container Context 2) adAuthAttrs
+        tag = gOctetString (B.convert adMAC)
+        ua  = attributesASN1S (Container Context 3) adUnauthAttrs
+
+        oi | adOriginatorInfo == mempty = id
+           | otherwise = originatorInfoASN1S (Container Context 0) adOriginatorInfo
+
+        v | hasChoiceOther adOriginatorInfo = 3
+          | otherwise                       = 0
+
+instance ParseASN1Object [ASN1Event] (AuthenticatedData (Encap EncapsulatedContent)) where
+    parse =
+        onNextContainer Sequence $ do
+            IntVal v <- getNext
+            when (v `notElem` [0, 1, 3]) $
+                throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v)
+            oi <- parseOriginatorInfo (Container Context 0) <|> return mempty
+            ris <- onNextContainer Set parse
+            alg <- parseAlgorithm Sequence
+            dig <- parseAlgorithmMaybe (Container Context 1)
+            (ct, bs) <- parseEncapsulatedContentInfo
+            aAttrs <- parseAttributes (Container Context 2)
+            OctetString tag <- getNext
+            uAttrs <- parseAttributes (Container Context 3)
+            return AuthenticatedData { adOriginatorInfo = oi
+                                     , adRecipientInfos = ris
+                                     , adMACAlgorithm = alg
+                                     , adDigestAlgorithm = dig
+                                     , adContentType = ct
+                                     , adEncapsulatedContent = bs
+                                     , adAuthAttrs = aAttrs
+                                     , adMAC = AuthTag $ B.convert tag
+                                     , adUnauthAttrs = uAttrs
+                                     }
diff --git a/src/Crypto/Store/CMS/Digested.hs b/src/Crypto/Store/CMS/Digested.hs
new file mode 100644
--- /dev/null
+++ b/src/Crypto/Store/CMS/Digested.hs
@@ -0,0 +1,82 @@
+-- |
+-- Module      : Crypto.Store.CMS.Digested
+-- License     : BSD-style
+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>
+-- Stability   : experimental
+-- Portability : unknown
+--
+--
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE RecordWildCards #-}
+module Crypto.Store.CMS.Digested
+    ( DigestedData(..)
+    ) where
+
+import Control.Monad
+
+import           Data.ASN1.Types
+import qualified Data.ByteArray as B
+
+import Crypto.Hash hiding (MD5)
+
+import Crypto.Store.ASN1.Generate
+import Crypto.Store.ASN1.Parse
+import Crypto.Store.CMS.Algorithms
+import Crypto.Store.CMS.Signed
+import Crypto.Store.CMS.Type
+import Crypto.Store.CMS.Util
+
+-- | Digested content information.
+data DigestedData content = forall hashAlg. HashAlgorithm hashAlg => DigestedData
+    { ddDigestAlgorithm :: DigestProxy hashAlg     -- ^ Digest algorithm
+    , ddContentType :: ContentType                 -- ^ Inner content type
+    , ddEncapsulatedContent :: content             -- ^ Encapsulated content
+    , ddDigest :: Digest hashAlg                   -- ^ Digest value
+    }
+
+instance Show content => Show (DigestedData content) where
+    showsPrec d DigestedData{..} = showParen (d > 10) $
+        showString "DigestedData "
+            . showString "{ ddDigestAlgorithm = " . shows ddDigestAlgorithm
+            . showString ", ddContentType = " . shows ddContentType
+            . showString ", ddEncapsulatedContent = " . shows ddEncapsulatedContent
+            . showString ", ddDigest = " . shows ddDigest
+            . showString " }"
+
+instance Eq content => Eq (DigestedData content) where
+    DigestedData a1 t1 e1 d1 == DigestedData a2 t2 e2 d2 =
+        DigestAlgorithm a1 == DigestAlgorithm a2 && d1 `B.eq` d2 && t1 == t2 && e1 == e2
+
+instance ASN1Elem e => ProduceASN1Object e (DigestedData (Encap EncapsulatedContent)) where
+    asn1s DigestedData{..} =
+        asn1Container Sequence (ver . alg . ci . dig)
+      where
+        v = if ddContentType == DataType then 0 else 2
+        d = DigestAlgorithm ddDigestAlgorithm
+
+        ver = gIntVal v
+        alg = algorithmASN1S Sequence d
+        ci  = encapsulatedContentInfoASN1S ddContentType ddEncapsulatedContent
+        dig = gOctetString (B.convert ddDigest)
+
+instance Monoid e => ParseASN1Object e (DigestedData (Encap EncapsulatedContent)) where
+    parse =
+        onNextContainer Sequence $ do
+            IntVal v <- getNext
+            when (v /= 0 && v /= 2) $
+                throwParseError ("DigestedData: parsed invalid version: " ++ show v)
+            alg <- parseAlgorithm Sequence
+            (ct, bs) <- parseEncapsulatedContentInfo
+            OctetString digValue <- getNext
+            case alg of
+                DigestAlgorithm digAlg ->
+                    case digestFromByteString digValue of
+                        Nothing -> throwParseError "DigestedData: parsed invalid digest"
+                        Just d  ->
+                            return DigestedData { ddDigestAlgorithm = digAlg
+                                                , ddContentType = ct
+                                                , ddEncapsulatedContent = bs
+                                                , ddDigest = d
+                                                }
diff --git a/src/Crypto/Store/CMS/Encrypted.hs b/src/Crypto/Store/CMS/Encrypted.hs
--- a/src/Crypto/Store/CMS/Encrypted.hs
+++ b/src/Crypto/Store/CMS/Encrypted.hs
@@ -38,19 +38,19 @@
 type EncryptedContent = B.ByteString
 
 -- | Encrypted content information.
-data EncryptedData = EncryptedData
+data EncryptedData content = EncryptedData
     { edContentType :: ContentType
       -- ^ Inner content type
     , edContentEncryptionParams :: ContentEncryptionParams
       -- ^ Encryption algorithm
-    , edEncryptedContent :: EncryptedContent
+    , edEncryptedContent :: content
       -- ^ Encrypted content info
     , edUnprotectedAttrs :: [Attribute]
       -- ^ Optional unprotected attributes
     }
     deriving (Show,Eq)
 
-instance ASN1Elem e => ProduceASN1Object e EncryptedData where
+instance ASN1Elem e => ProduceASN1Object e (EncryptedData (Encap EncryptedContent)) where
     asn1s EncryptedData{..} =
         asn1Container Sequence (ver . eci . ua)
       where
@@ -59,7 +59,7 @@
                   (edContentType, edContentEncryptionParams, edEncryptedContent)
         ua  = attributesASN1S (Container Context 1) edUnprotectedAttrs
 
-instance Monoid e => ParseASN1Object e EncryptedData where
+instance Monoid e => ParseASN1Object e (EncryptedData (Encap EncryptedContent)) where
     parse =
         onNextContainer Sequence $ do
             IntVal v <- getNext
@@ -75,21 +75,27 @@
 
 -- | Generate ASN.1 for EncryptedContentInfo.
 encryptedContentInfoASN1S :: (ASN1Elem e, ProduceASN1Object e alg)
-                          => (ContentType, alg, B.ByteString) -> ASN1Stream e
+                          => (ContentType, alg, Encap EncryptedContent) -> ASN1Stream e
 encryptedContentInfoASN1S (ct, alg, ec) =
     asn1Container Sequence (ct' . alg' . ec')
   where
     ct'  = gOID (getObjectID ct)
     alg' = asn1s alg
-    ec'  = asn1Container (Container Context 0) (gOctetString ec)
+    ec'  = encapsulatedASN1S (Container Context 0) ec
 
+encapsulatedASN1S :: ASN1Elem e
+                  => ASN1ConstructionType -> Encap EncryptedContent -> ASN1Stream e
+encapsulatedASN1S _   Detached     = id
+encapsulatedASN1S ty (Attached bs) = asn1Container ty (gOctetString bs)
+
 -- | Parse EncryptedContentInfo from ASN.1.
 parseEncryptedContentInfo :: ParseASN1Object e alg
-                          => ParseASN1 e (ContentType, alg, B.ByteString)
+                          => ParseASN1 e (ContentType, alg, Encap EncryptedContent)
 parseEncryptedContentInfo = onNextContainer Sequence $ do
     OID oid <- getNext
     alg <- parse
-    ec <- parseEncryptedContent
+    b <- hasNext
+    ec <- if b then Attached <$> parseEncryptedContent else return Detached
     withObjectID "content type" oid $ \ct -> return (ct, alg, ec)
   where
     parseEncryptedContent = parseWrapped <|> parsePrimitive
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,7 +67,6 @@
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
-import Crypto.Store.PKCS8.EC
 
 -- | Encrypted key.
 type EncryptedKey = ByteString
@@ -424,7 +423,7 @@
 isPwriOri (PasswordRI _) = True
 
 -- | Enveloped content information.
-data EnvelopedData = EnvelopedData
+data EnvelopedData content = EnvelopedData
     { evOriginatorInfo :: OriginatorInfo
       -- ^ Optional information about the originator
     , evRecipientInfos :: [RecipientInfo]
@@ -433,14 +432,14 @@
       -- ^ Inner content type
     , evContentEncryptionParams :: ContentEncryptionParams
       -- ^ Encryption algorithm
-    , evEncryptedContent :: EncryptedContent
+    , evEncryptedContent :: content
       -- ^ Encrypted content info
     , evUnprotectedAttrs :: [Attribute]
       -- ^ Optional unprotected attributes
     }
     deriving (Show,Eq)
 
-instance ProduceASN1Object ASN1P EnvelopedData where
+instance ProduceASN1Object ASN1P (EnvelopedData (Encap EncryptedContent)) where
     asn1s EnvelopedData{..} =
         asn1Container Sequence (ver . oi . ris . eci . ua)
       where
@@ -460,7 +459,7 @@
           | all isVersion0 evRecipientInfos = 0
           | otherwise                       = 2
 
-instance ParseASN1Object [ASN1Event] EnvelopedData where
+instance ParseASN1Object [ASN1Event] (EnvelopedData (Encap EncryptedContent)) where
     parse =
         onNextContainer Sequence $ do
             IntVal v <- getNext
@@ -524,10 +523,10 @@
 forKeyAgreeRecipient cert params inkey = do
     ephemeral <- ecdhGenerate (certPubKey obj)
     case ephemeral of
-        Right (curve, d, sp) -> do
-            let SerializedPoint pt = getSerializedPoint curve d
+        Right pair -> do
+            let pt = ecdhPublic pair
                 aPub = OriginatorPublicKeyEC [] (toBitArray pt 0)
-            ek <- ecdhEncrypt params Nothing curve d sp inkey
+            ek <- ecdhEncrypt params Nothing pair inkey
             return (KARI . build aPub <$> ek)
         Left err -> return $ Left err
   where
@@ -553,16 +552,15 @@
 -- This function can be used as parameter to
 -- 'Crypto.Store.CMS.openEnvelopedData'.
 withRecipientKeyAgree :: MonadRandom m => PrivKey -> SignedCertificate -> ConsumerOfRI m
-withRecipientKeyAgree (PrivKeyEC priv) cert (KARI KARecipientInfo{..}) =
+withRecipientKeyAgree priv cert (KARI KARecipientInfo{..}) =
     case kaOriginator of
         OriginatorPublic (OriginatorPublicKeyEC _ ba) ->
             case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of
                 Nothing -> pure (Left RecipientKeyNotFound)
                 Just ek ->
-                    let pub = SerializedPoint (bitArrayGetData ba)
+                    let pub = bitArrayGetData ba
                      in pure (ecdhDecrypt kaKeyAgreementParams kaUkm priv pub ek)
         _ -> pure (Left UnsupportedOriginatorFormat)
-withRecipientKeyAgree _ _ (KARI _) = pure (Left UnexpectedPrivateKeyType)
 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,49 +6,38 @@
 -- Portability : unknown
 --
 -- CMS content information.
-{-# LANGUAGE ExistentialQuantification #-}
 {-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE GADTs #-}
 {-# LANGUAGE MultiParamTypeClasses #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE TypeFamilies #-}
 module Crypto.Store.CMS.Info
     ( ContentInfo(..)
     , getContentType
-    , SignedData(..)
-    , DigestedData(..)
-    , AuthenticatedData(..)
-    , encapsulate
-    , decapsulate
+    , Encapsulates
+    , isAttached
+    , fromAttached
+    , toAttachedCI
+    , isDetached
+    , fromDetached
+    , toDetachedCI
     ) where
 
-import Control.Applicative
-import Control.Monad
-
-import           Data.ASN1.BinaryEncoding
-import           Data.ASN1.Encoding
-import           Data.ASN1.Types
-import           Data.ByteString (ByteString)
-import qualified Data.ByteArray as B
-import           Data.Maybe (fromMaybe)
+import Control.Monad.Fail (MonadFail)
 
-import Crypto.Cipher.Types
-import Crypto.Hash hiding (MD5)
+import Data.ASN1.Types
+import Data.ByteString (ByteString)
+import Data.Functor.Identity
+import Data.Maybe (isJust, isNothing)
 
 import Crypto.Store.ASN1.Generate
 import Crypto.Store.ASN1.Parse
-import Crypto.Store.CMS.Algorithms
-import Crypto.Store.CMS.Attribute
+import Crypto.Store.CMS.Authenticated
 import Crypto.Store.CMS.AuthEnveloped
+import Crypto.Store.CMS.Digested
 import Crypto.Store.CMS.Encrypted
 import Crypto.Store.CMS.Enveloped
-import Crypto.Store.CMS.OriginatorInfo
 import Crypto.Store.CMS.Signed
 import Crypto.Store.CMS.Type
 import Crypto.Store.CMS.Util
-import Crypto.Store.Error
-import Crypto.Store.Util
 
 -- | Get the type of a content info.
 getContentType :: ContentInfo -> ContentType
@@ -64,13 +53,20 @@
 -- ContentInfo
 
 -- | CMS content information.
-data ContentInfo = DataCI ByteString                     -- ^ Arbitrary octet string
-                 | SignedDataCI SignedData               -- ^ Signed content info
-                 | EnvelopedDataCI EnvelopedData         -- ^ Enveloped content info
-                 | DigestedDataCI DigestedData           -- ^ Content info with associated digest
-                 | EncryptedDataCI EncryptedData         -- ^ Encrypted content info
-                 | AuthenticatedDataCI AuthenticatedData -- ^ Authenticatedcontent info
-                 | AuthEnvelopedDataCI AuthEnvelopedData -- ^ Authenticated-enveloped content info
+data ContentInfo = DataCI ByteString
+                   -- ^ Arbitrary octet string
+                 | SignedDataCI (SignedData (Encap EncapsulatedContent))
+                   -- ^ Signed content info
+                 | EnvelopedDataCI (EnvelopedData (Encap EncryptedContent))
+                   -- ^ Enveloped content info
+                 | DigestedDataCI (DigestedData (Encap EncapsulatedContent))
+                   -- ^ Content info with associated digest
+                 | EncryptedDataCI (EncryptedData (Encap EncryptedContent))
+                   -- ^ Encrypted content info
+                 | AuthenticatedDataCI (AuthenticatedData (Encap EncapsulatedContent))
+                   -- ^ Authenticatedcontent info
+                 | AuthEnvelopedDataCI (AuthEnvelopedData (Encap EncryptedContent))
+                   -- ^ Authenticated-enveloped content info
                  deriving (Show,Eq)
 
 instance ProduceASN1Object ASN1P ContentInfo where
@@ -115,239 +111,89 @@
         OctetString bs -> return bs
         _              -> throwParseError "Data: parsed unexpected content"
 
-isData :: ContentInfo -> Bool
-isData (DataCI _) = True
-isData _          = False
 
-
--- SignedData
-
--- | Signed content information.
-data SignedData = SignedData
-    { sdDigestAlgorithms :: [DigestAlgorithm] -- ^ Digest algorithms
-    , sdContentInfo :: ContentInfo            -- ^ Inner content info
-    , sdCertificates :: [CertificateChoice]   -- ^ The collection of certificates
-    , sdCRLs  :: [RevocationInfoChoice]       -- ^ The collection of CRLs
-    , sdSignerInfos :: [SignerInfo]           -- ^ Per-signer information
-    }
-    deriving (Show,Eq)
-
-instance ProduceASN1Object ASN1P SignedData where
-    asn1s SignedData{..} =
-        asn1Container Sequence (ver . dig . ci . certs . crls . sis)
-      where
-        ver = gIntVal v
-        dig = asn1Container Set (digestTypesASN1S sdDigestAlgorithms)
-        ci  = encapsulatedContentInfoASN1S sdContentInfo
-        certs = gen 0 sdCertificates
-        crls  = gen 1 sdCRLs
-        sis = asn1Container Set (asn1s sdSignerInfos)
-
-        gen tag list
-            | null list = id
-            | otherwise = asn1Container (Container Context tag) (asn1s list)
-
-        v | hasChoiceOther sdCertificates = 5
-          | hasChoiceOther sdCRLs         = 5
-          | any isVersion3 sdSignerInfos  = 3
-          | isData sdContentInfo          = 1
-          | otherwise                     = 3
-
-
-instance ParseASN1Object [ASN1Event] SignedData where
-    parse =
-        onNextContainer Sequence $ do
-            IntVal v <- getNext
-            when (v > 5) $
-                throwParseError ("SignedData: parsed invalid version: " ++ show v)
-            dig <- onNextContainer Set parseDigestTypes
-            inner <- parseEncapsulatedContentInfo
-            certs <- parseOptList 0
-            crls  <- parseOptList 1
-            sis <- onNextContainer Set parse
-            return SignedData { sdDigestAlgorithms = dig
-                              , sdContentInfo = inner
-                              , sdCertificates = certs
-                              , sdCRLs = crls
-                              , sdSignerInfos = sis
-                              }
-      where
-        parseOptList tag =
-            fromMaybe [] <$> onNextContainerMaybe (Container Context tag) parse
-
-digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e
-digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list
-
-parseDigestTypes :: Monoid e => ParseASN1 e [DigestAlgorithm]
-parseDigestTypes = getMany (parseAlgorithm Sequence)
-
-
--- DigestedData
-
--- | Digested content information.
-data DigestedData = forall hashAlg. HashAlgorithm hashAlg => DigestedData
-    { ddDigestAlgorithm :: DigestProxy hashAlg     -- ^ Digest algorithm
-    , ddContentInfo :: ContentInfo                 -- ^ Inner content info
-    , ddDigest :: Digest hashAlg                   -- ^ Digest value
-    }
-
-instance Show DigestedData where
-    showsPrec d DigestedData{..} = showParen (d > 10) $
-        showString "DigestedData "
-            . showString "{ ddDigestAlgorithm = " . shows ddDigestAlgorithm
-            . showString ", ddContentInfo = " . shows ddContentInfo
-            . showString ", ddDigest = " . shows ddDigest
-            . showString " }"
-
-instance Eq DigestedData where
-    DigestedData a1 i1 d1 == DigestedData a2 i2 d2 =
-        DigestAlgorithm a1 == DigestAlgorithm a2 && d1 `B.eq` d2 && i1 == i2
-
-instance ASN1Elem e => ProduceASN1Object e DigestedData where
-    asn1s DigestedData{..} =
-        asn1Container Sequence (ver . alg . ci . dig)
-      where
-        v = if isData ddContentInfo then 0 else 2
-        d = DigestAlgorithm ddDigestAlgorithm
-
-        ver = gIntVal v
-        alg = algorithmASN1S Sequence d
-        ci  = encapsulatedContentInfoASN1S ddContentInfo
-        dig = gOctetString (B.convert ddDigest)
-
-instance Monoid e => ParseASN1Object e DigestedData where
-    parse =
-        onNextContainer Sequence $ do
-            IntVal v <- getNext
-            when (v /= 0 && v /= 2) $
-                throwParseError ("DigestedData: parsed invalid version: " ++ show v)
-            alg <- parseAlgorithm Sequence
-            inner <- parseEncapsulatedContentInfo
-            OctetString bs <- getNext
-            case alg of
-                DigestAlgorithm digAlg ->
-                    case digestFromByteString bs of
-                        Nothing -> throwParseError "DigestedData: parsed invalid digest"
-                        Just d  ->
-                            return DigestedData { ddDigestAlgorithm = digAlg
-                                                , ddContentInfo = inner
-                                                , ddDigest = d
-                                                }
+-- Encapsulation
 
+-- | Class of data structures with inner content that may be stored externally.
+-- This class has instances for each CMS content type containing other
+-- encapsulated or encrypted content info.
+--
+-- Functions 'fromAttached' and 'fromDetached' are used to introspect
+-- encapsulation state (attached or detached), and recover a data structure with
+-- actionable content.
+--
+-- Functions 'toAttachedCI' and 'toDetachedCI' are needed to decide about the
+-- outer encapsulation state and build a 'ContentInfo'.
+class Encapsulates struct where
+    lens :: Functor f => (a -> f b) -> struct a -> f (struct b)
+    toCI :: struct (Encap ByteString) -> ContentInfo
 
--- Authenticated data
+instance Encapsulates SignedData where
+    lens f s = let g a = s { sdEncapsulatedContent = a }
+                in fmap g (f $ sdEncapsulatedContent s)
+    toCI = SignedDataCI
 
--- | Authenticated content information.
-data AuthenticatedData = AuthenticatedData
-    { adOriginatorInfo :: OriginatorInfo
-      -- ^ Optional information about the originator
-    , adRecipientInfos :: [RecipientInfo]
-      -- ^ Information for recipients, allowing to authenticate the content
-    , adMACAlgorithm :: MACAlgorithm
-      -- ^ MAC algorithm
-    , adDigestAlgorithm :: Maybe DigestAlgorithm
-      -- ^ Optional digest algorithm
-    , adContentInfo :: ContentInfo
-      -- ^ Inner content info
-    , adAuthAttrs :: [Attribute]
-      -- ^ Optional authenticated attributes
-    , adMAC :: MessageAuthenticationCode
-      -- ^ Message authentication code
-    , adUnauthAttrs :: [Attribute]
-      -- ^ Optional unauthenticated attributes
-    }
-    deriving (Show,Eq)
+instance Encapsulates EnvelopedData where
+    lens f s = let g a = s { evEncryptedContent = a }
+                in fmap g (f $ evEncryptedContent s)
+    toCI = EnvelopedDataCI
 
-instance ProduceASN1Object ASN1P AuthenticatedData where
-    asn1s AuthenticatedData{..} =
-        asn1Container Sequence (ver . oi . ris . alg . dig . ci . aa . tag . ua)
-      where
-        ver = gIntVal v
-        ris = asn1Container Set (asn1s adRecipientInfos)
-        alg = algorithmASN1S Sequence adMACAlgorithm
-        dig = algorithmMaybeASN1S (Container Context 1) adDigestAlgorithm
-        ci  = encapsulatedContentInfoASN1S adContentInfo
-        aa  = attributesASN1S(Container Context 2) adAuthAttrs
-        tag = gOctetString (B.convert adMAC)
-        ua  = attributesASN1S (Container Context 3) adUnauthAttrs
+instance Encapsulates DigestedData where
+    lens f s = let g a = s { ddEncapsulatedContent = a }
+                in fmap g (f $ ddEncapsulatedContent s)
+    toCI = DigestedDataCI
 
-        oi | adOriginatorInfo == mempty = id
-           | otherwise = originatorInfoASN1S (Container Context 0) adOriginatorInfo
+instance Encapsulates EncryptedData where
+    lens f s = let g a = s { edEncryptedContent = a }
+                in fmap g (f $ edEncryptedContent s)
+    toCI = EncryptedDataCI
 
-        v | hasChoiceOther adOriginatorInfo = 3
-          | otherwise                       = 0
+instance Encapsulates AuthenticatedData where
+    lens f s = let g a = s { adEncapsulatedContent = a }
+                in fmap g (f $ adEncapsulatedContent s)
+    toCI = AuthenticatedDataCI
 
-instance ParseASN1Object [ASN1Event] AuthenticatedData where
-    parse =
-        onNextContainer Sequence $ do
-            IntVal v <- getNext
-            when (v `notElem` [0, 1, 3]) $
-                throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v)
-            oi <- parseOriginatorInfo (Container Context 0) <|> return mempty
-            ris <- onNextContainer Set parse
-            alg <- parseAlgorithm Sequence
-            dig <- parseAlgorithmMaybe (Container Context 1)
-            inner <- parseEncapsulatedContentInfo
-            aAttrs <- parseAttributes (Container Context 2)
-            OctetString tag <- getNext
-            uAttrs <- parseAttributes (Container Context 3)
-            return AuthenticatedData { adOriginatorInfo = oi
-                                     , adRecipientInfos = ris
-                                     , adMACAlgorithm = alg
-                                     , adDigestAlgorithm = dig
-                                     , adContentInfo = inner
-                                     , adAuthAttrs = aAttrs
-                                     , adMAC = AuthTag $ B.convert tag
-                                     , adUnauthAttrs = uAttrs
-                                     }
+instance Encapsulates AuthEnvelopedData where
+    lens f s = let g a = s { aeEncryptedContent = a }
+                in fmap g (f $ aeEncryptedContent s)
+    toCI = AuthEnvelopedDataCI
 
+-- | Return 'True' when the encapsulated content is attached.
+isAttached :: Encapsulates struct => struct (Encap a) -> Bool
+isAttached = isJust . fromAttached
 
--- Utilities
+-- | Unwrap the encapsulation, assuming the inner content is inside the data
+-- structure.  The monadic computation fails if the content was detached.
+fromAttached :: (MonadFail m, Encapsulates struct) => struct (Encap a) -> m (struct a)
+fromAttached = lens (fromEncap err return)
+  where err = fail "fromAttached: detached"
 
-decode :: ParseASN1 [ASN1Event] a -> ByteString -> Either StoreError a
-decode parser bs = vals >>= mapLeft ParseFailure . runParseASN1_ parser
-  where vals = mapLeft DecodingError (decodeASN1Repr' BER bs)
+-- | Keep the content inside the data structure.
+toAttached :: Encapsulates struct => struct a -> struct (Encap a)
+toAttached = runIdentity . lens (Identity . Attached)
 
--- | Encode the information for encapsulation in another content info.
-encapsulate :: ContentInfo -> ByteString
-encapsulate (DataCI bs)              = bs
-encapsulate (SignedDataCI ed)        = encodeASN1Object ed
-encapsulate (EnvelopedDataCI ed)     = encodeASN1Object ed
-encapsulate (DigestedDataCI dd)      = encodeASN1Object dd
-encapsulate (EncryptedDataCI ed)     = encodeASN1Object ed
-encapsulate (AuthenticatedDataCI ad) = encodeASN1Object ad
-encapsulate (AuthEnvelopedDataCI ae) = encodeASN1Object ae
+-- | Transform the data structure into a content info, keeping the encapsulated
+-- content attached.  May be applied to structures with 'EncapsulatedContent' or
+-- 'EncryptedContent'.
+toAttachedCI :: Encapsulates struct => struct ByteString -> ContentInfo
+toAttachedCI = toCI . toAttached
 
--- | Decode the information from encapsulated content.
-decapsulate :: ContentType -> ByteString -> Either StoreError ContentInfo
-decapsulate DataType bs              = pure (DataCI bs)
-decapsulate SignedDataType bs        = SignedDataCI <$> decode parse bs
-decapsulate EnvelopedDataType bs     = EnvelopedDataCI <$> decode parse bs
-decapsulate DigestedDataType bs      = DigestedDataCI <$> decode parse bs
-decapsulate EncryptedDataType bs     = EncryptedDataCI <$> decode parse bs
-decapsulate AuthenticatedDataType bs = AuthenticatedDataCI <$> decode parse bs
-decapsulate AuthEnvelopedDataType bs = AuthEnvelopedDataCI <$> decode parse bs
+-- | Return 'True' when the encapsulated content is detached.
+isDetached :: Encapsulates struct => struct (Encap a) -> Bool
+isDetached = isNothing . fromAttached
 
-encapsulatedContentInfoASN1S :: ASN1Elem e => ContentInfo -> ASN1Stream e
-encapsulatedContentInfoASN1S ci = asn1Container Sequence (oid . cont)
-  where oid = gOID $ getObjectID $ getContentType ci
-        cont = asn1Container (Container Context 0) inner
-        inner = gOctetString (encapsulate ci)
+-- | Recover the original data structure from a detached encapsulation and the
+-- external content.  The monadic computation fails if the content was attached.
+fromDetached :: (MonadFail m, Encapsulates struct) => b -> struct (Encap a) -> m (struct b)
+fromDetached c = lens (fromEncap (return c) err)
+  where err _ = fail "fromDetached: attached"
 
-parseEncapsulatedContentInfo :: Monoid e => ParseASN1 e ContentInfo
-parseEncapsulatedContentInfo =
-    onNextContainer Sequence $ do
-        OID oid <- getNext
-        withObjectID "content type" oid $ \ct ->
-            onNextContainer (Container Context 0) (parseInner ct)
-  where
-    parseInner ct = do
-        bs <- parseContentSingle <|> parseContentChunks
-        case decapsulate ct bs of
-            Left err -> throwParseError
-                ("Unable to decode and parse encapsulated ASN.1: " ++ show err)
-            Right ci -> return ci
+-- | Remove the content from the data structure to store it externally.
+toDetached :: Encapsulates struct => struct a -> (a, struct (Encap a))
+toDetached = let f a = (a, Detached) in lens f
 
-    parseContentSingle = do { OctetString bs <- getNext; return bs }
-    parseContentChunks = onNextContainer (Container Universal 4) $
-        B.concat <$> getMany parseContentSingle
+-- | Transform the data structure into a content info, detaching the
+-- encapsulated content.  May be applied to structures with
+-- 'EncapsulatedContent' or 'EncryptedContent'.
+toDetachedCI :: Encapsulates struct => struct ByteString -> (ByteString, ContentInfo)
+toDetachedCI = fmap toCI . toDetached
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
@@ -54,8 +54,7 @@
 
 instance Monoid OriginatorInfo where
     mempty = OriginatorInfo [] []
-    mappend (OriginatorInfo a b) (OriginatorInfo c d) =
-        OriginatorInfo (mappend a c) (mappend b d)
+    mappend = (<>)
 
 instance HasChoiceOther OriginatorInfo where
     hasChoiceOther OriginatorInfo{..} =
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
@@ -10,26 +10,30 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# LANGUAGE RecordWildCards #-}
 module Crypto.Store.CMS.Signed
-    ( SignerInfo(..)
+    ( EncapsulatedContent
+    , SignedData(..)
+    , SignerInfo(..)
     , SignerIdentifier(..)
     , IssuerAndSerialNumber(..)
-    , isVersion3
     , ProducerOfSI
     , ConsumerOfSI
     , certSigner
     , withPublicKey
     , withSignerKey
     , withSignerCertificate
+    , encapsulatedContentInfoASN1S
+    , parseEncapsulatedContentInfo
     ) where
 
 import Control.Applicative
 import Control.Monad
 
-import Data.ASN1.Types
-import Data.ByteString (ByteString)
-import Data.List
-import Data.Maybe
-import Data.X509
+import           Data.ASN1.Types
+import           Data.ByteString (ByteString)
+import qualified Data.ByteString as B
+import           Data.List
+import           Data.Maybe
+import           Data.X509
 
 import Crypto.Random (MonadRandom)
 
@@ -44,6 +48,9 @@
 import Crypto.Store.CMS.Util
 import Crypto.Store.Error
 
+-- | Encapsulated content.
+type EncapsulatedContent = ByteString
+
 -- | Information related to a signer of a 'Crypto.Store.CMS.SignedData'.  An
 -- element contains the signature material that was produced.
 data SignerInfo = SignerInfo
@@ -173,9 +180,11 @@
     cert = head chain
     obj  = signedObject (getSigned cert)
     isn  = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)
+    pub  = certPubKey obj
 
-    (dig, alg') = signatureResolveHash def alg
+    (dig, alg') = signatureResolveHash noAttr def alg
 
+    noAttr          = null sAttrs
     (sAttrs, input) =
         case sAttrsM of
             Nothing    -> ([], msg)
@@ -183,7 +192,7 @@
                 let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs
                  in (l, encodeAuthAttrs l)
 
-    generate  = signatureGenerate alg' priv input
+    generate  = signatureGenerate alg' priv pub input
     build sig =
         let si = SignerInfo { siSignerId = SignerIASN isn
                             , siDigestAlgorithm = dig
@@ -238,3 +247,92 @@
 
     asX509 (CertificateCertificate c) = Just c
     asX509 _                          = Nothing
+
+-- | Signed content information.
+data SignedData content = SignedData
+    { sdDigestAlgorithms :: [DigestAlgorithm]      -- ^ Digest algorithms
+    , sdContentType :: ContentType                 -- ^ Inner content type
+    , sdEncapsulatedContent :: content             -- ^ Encapsulated content
+    , sdCertificates :: [CertificateChoice]        -- ^ The collection of certificates
+    , sdCRLs  :: [RevocationInfoChoice]            -- ^ The collection of CRLs
+    , sdSignerInfos :: [SignerInfo]                -- ^ Per-signer information
+    }
+    deriving (Show,Eq)
+
+instance ProduceASN1Object ASN1P (SignedData (Encap EncapsulatedContent)) where
+    asn1s SignedData{..} =
+        asn1Container Sequence (ver . dig . ci . certs . crls . sis)
+      where
+        ver = gIntVal v
+        dig = asn1Container Set (digestTypesASN1S sdDigestAlgorithms)
+        ci  = encapsulatedContentInfoASN1S sdContentType sdEncapsulatedContent
+        certs = gen 0 sdCertificates
+        crls  = gen 1 sdCRLs
+        sis = asn1Container Set (asn1s sdSignerInfos)
+
+        gen tag list
+            | null list = id
+            | otherwise = asn1Container (Container Context tag) (asn1s list)
+
+        v | hasChoiceOther sdCertificates = 5
+          | hasChoiceOther sdCRLs         = 5
+          | any isVersion3 sdSignerInfos  = 3
+          | sdContentType == DataType     = 1
+          | otherwise                     = 3
+
+
+instance ParseASN1Object [ASN1Event] (SignedData (Encap EncapsulatedContent)) where
+    parse =
+        onNextContainer Sequence $ do
+            IntVal v <- getNext
+            when (v > 5) $
+                throwParseError ("SignedData: parsed invalid version: " ++ show v)
+            dig <- onNextContainer Set parseDigestTypes
+            (ct, bs) <- parseEncapsulatedContentInfo
+            certs <- parseOptList 0
+            crls  <- parseOptList 1
+            sis <- onNextContainer Set parse
+            return SignedData { sdDigestAlgorithms = dig
+                              , sdContentType = ct
+                              , sdEncapsulatedContent = bs
+                              , sdCertificates = certs
+                              , sdCRLs = crls
+                              , sdSignerInfos = sis
+                              }
+      where
+        parseOptList tag =
+            fromMaybe [] <$> onNextContainerMaybe (Container Context tag) parse
+
+-- | Generate ASN.1 for EncapsulatedContentInfo.
+encapsulatedContentInfoASN1S :: ASN1Elem e => ContentType -> Encap EncapsulatedContent -> ASN1Stream e
+encapsulatedContentInfoASN1S ct ec = asn1Container Sequence (oid . cont)
+  where oid = gOID (getObjectID ct)
+        cont = encapsulatedASN1S (Container Context 0) ec
+
+encapsulatedASN1S :: ASN1Elem e
+                  => ASN1ConstructionType -> Encap B.ByteString -> ASN1Stream e
+encapsulatedASN1S _   Detached     = id
+encapsulatedASN1S ty (Attached bs) = asn1Container ty (gOctetString bs)
+
+-- | Parse EncapsulatedContentInfo from ASN.1.
+parseEncapsulatedContentInfo :: Monoid e => ParseASN1 e (ContentType, Encap EncapsulatedContent)
+parseEncapsulatedContentInfo =
+    onNextContainer Sequence $ do
+        OID oid <- getNext
+        withObjectID "content type" oid $ \ct ->
+            wrap ct <$> onNextContainerMaybe (Container Context 0) parseInner
+  where
+    wrap ct Nothing  = (ct, Detached)
+    wrap ct (Just c) = (ct, Attached c)
+
+    parseInner = parseContentSingle <|> parseContentChunks
+
+    parseContentSingle = do { OctetString bs <- getNext; return bs }
+    parseContentChunks = onNextContainer (Container Universal 4) $
+        B.concat <$> getMany parseContentSingle
+
+digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e
+digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list
+
+parseDigestTypes :: Monoid e => ParseASN1 e [DigestAlgorithm]
+parseDigestTypes = getMany (parseAlgorithm Sequence)
diff --git a/src/Crypto/Store/CMS/Type.hs b/src/Crypto/Store/CMS/Type.hs
--- a/src/Crypto/Store/CMS/Type.hs
+++ b/src/Crypto/Store/CMS/Type.hs
@@ -11,6 +11,8 @@
 {-# LANGUAGE TypeFamilies #-}
 module Crypto.Store.CMS.Type
     ( ContentType(..)
+    , Encap(..)
+    , fromEncap
     ) where
 
 import Data.ASN1.OID
@@ -48,3 +50,40 @@
 
 instance OIDNameable ContentType where
     fromObjectID oid = unOIDNW <$> fromObjectID oid
+
+-- | Denotes the state of encapsulated content in a CMS data structure.  This
+-- type is isomorphic to 'Maybe'.
+data Encap a
+    = Detached    -- content is stored externally to the structure
+    | Attached a  -- content is stored inside the CMS struture
+    deriving (Show,Eq)
+
+instance Functor Encap where
+    fmap _ Detached = Detached
+    fmap f (Attached c) = Attached (f c)
+
+instance Applicative Encap where
+    pure = Attached
+
+    Attached f <*> e = fmap f e
+    Detached   <*> _ = Detached
+
+instance Foldable Encap where
+    foldMap = fromEncap mempty
+
+    foldr _ d Detached     = d
+    foldr f d (Attached c) = f c d
+
+    foldl _ d Detached     = d
+    foldl f d (Attached c) = f d c
+
+instance Traversable Encap where
+    traverse _ Detached     = pure Detached
+    traverse f (Attached c) = Attached <$> f c
+
+-- | Fold over an 'Encap' value.  This is similar to function 'maybe'.  If the
+-- content is detached, the first argument is returned.  Otherwise the second
+-- argument is applied to the content.
+fromEncap :: b -> (a -> b) -> Encap a -> b
+fromEncap d _ Detached     = d
+fromEncap _ f (Attached c) = f c
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
@@ -32,6 +32,10 @@
       -- ^ MAC verification failed, incorrect key or password?
     | BadChecksum
       -- ^ Checksum verification failed, incorrect key or password?
+    | DigestMismatch
+      -- ^ Digest verification failed
+    | SignatureNotVerified
+      -- ^ Signature verification failed
     | InvalidInput String
       -- ^ Some condition is not met about input to algorithm
     | InvalidPassword String
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
@@ -53,7 +53,9 @@
     , setLocalKeyId
     -- * Credentials
     , fromCredential
+    , fromNamedCredential
     , toCredential
+    , toNamedCredential
     -- * Password-based protection
     , Password
     , OptProtected(..)
@@ -68,9 +70,11 @@
 import           Data.ASN1.Encoding
 import qualified Data.ByteArray as B
 import qualified Data.ByteString as BS
-import           Data.Maybe (fromMaybe)
+import           Data.List (partition)
+import           Data.Maybe (fromMaybe, mapMaybe)
 import           Data.Semigroup
 import qualified Data.X509 as X509
+import qualified Data.X509.Validation as X509
 
 import Crypto.Cipher.Types
 
@@ -280,7 +284,7 @@
         cont = asn1Container (Container Context 0) inner
         inner = asn1Container Sequence (gIntVal 0 . eci)
         eci = encryptedContentInfoASN1S
-                  (DataType, encryptionAlgorithm, encryptedData)
+                  (DataType, encryptionAlgorithm, Attached encryptedData)
 
 instance Monoid e => ParseASN1Object e ASElement where
     parse = onNextContainer Sequence $ do
@@ -296,7 +300,7 @@
         parseUnencrypted = parseOctetStringObject "PKCS12"
         parseEncrypted = onNextContainer Sequence $ do
             IntVal 0 <- getNext
-            (DataType, eScheme, ed) <- parseEncryptedContentInfo
+            (DataType, eScheme, Attached ed) <- parseEncryptedContentInfo
             return PKCS5 { encryptionAlgorithm = eScheme, encryptedData = ed }
 
 
@@ -454,23 +458,43 @@
 instance Monoid e => ParseASN1Object e SafeContents where
     parse = SafeContents <$> onNextContainer Sequence parse
 
--- | Return all private keys contained in the safe contents.
-getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]
-getSafeKeys (SafeContents scs) = loop scs
+filterBags :: ([Attribute] -> Bool) -> SafeContents -> SafeContents
+filterBags p (SafeContents scs) = SafeContents (mapMaybe f scs)
   where
+    f (Bag (SafeContentsBag inner) attrs) =
+        Just (Bag (SafeContentsBag $ filterBags p inner) attrs)
+    f bag | p (bagAttributes bag)         = Just bag
+          | otherwise                     = Nothing
+
+filterByFriendlyName :: String -> SafeContents -> SafeContents
+filterByFriendlyName name = filterBags ((== Just name) . getFriendlyName)
+
+filterByLocalKeyId :: BS.ByteString -> SafeContents -> SafeContents
+filterByLocalKeyId d = filterBags ((== Just d) . getLocalKeyId)
+
+getSafeKeysId :: SafeContents -> [OptProtected (Id X509.PrivKey)]
+getSafeKeysId (SafeContents scs) = loop scs
+  where
     loop []           = []
     loop (bag : bags) =
         case bagInfo bag of
-            KeyBag (FormattedKey _ k) -> Unprotected k : loop bags
-            PKCS8ShroudedKeyBag k     -> Protected (unshroud k) : loop bags
-            SafeContentsBag inner     -> getSafeKeys inner ++ loop bags
+            KeyBag (FormattedKey _ k) -> Unprotected (mkId k bag) : loop bags
+            PKCS8ShroudedKeyBag k     -> Protected (unshroud k bag) : loop bags
+            SafeContentsBag inner     -> getSafeKeysId inner ++ loop bags
             _                         -> loop bags
 
-    unshroud shrouded pwd = do
+    unshroud shrouded bag pwd = do
         bs <- decrypt shrouded pwd
         FormattedKey _ k <- decode bs
-        return k
+        return (mkId k bag)
 
+-- | Return all private keys contained in the safe contents.
+getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]
+getSafeKeys = map (fmap unId) . getSafeKeysId
+
+getAllSafeKeysId :: [SafeContents] -> OptProtected [Id X509.PrivKey]
+getAllSafeKeysId = applySamePassword . concatMap getSafeKeysId
+
 -- | Return all private keys contained in the safe content list.  All shrouded
 -- private keys must derive from the same password.
 --
@@ -481,32 +505,38 @@
 getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey]
 getAllSafeKeys = applySamePassword . concatMap getSafeKeys
 
--- | Return all X.509 certificates contained in the safe contents.
-getSafeX509Certs :: SafeContents -> [X509.SignedCertificate]
-getSafeX509Certs (SafeContents scs) = loop scs
+getSafeX509CertsId :: SafeContents -> [Id X509.SignedCertificate]
+getSafeX509CertsId (SafeContents scs) = loop scs
   where
     loop []           = []
     loop (bag : bags) =
         case bagInfo bag of
-            CertBag (Bag (CertX509 c) _) -> c : loop bags
-            SafeContentsBag inner        -> getSafeX509Certs inner ++ loop bags
+            CertBag (Bag (CertX509 c) _) -> mkId c bag : loop bags
+            SafeContentsBag inner        -> getSafeX509CertsId inner ++ loop bags
             _                            -> loop bags
 
+-- | Return all X.509 certificates contained in the safe contents.
+getSafeX509Certs :: SafeContents -> [X509.SignedCertificate]
+getSafeX509Certs = map unId . getSafeX509CertsId
+
 -- | Return all X.509 certificates contained in the safe content list.
 getAllSafeX509Certs :: [SafeContents] -> [X509.SignedCertificate]
 getAllSafeX509Certs = concatMap getSafeX509Certs
 
--- | Return all X.509 CRLs contained in the safe contents.
-getSafeX509CRLs :: SafeContents -> [X509.SignedCRL]
-getSafeX509CRLs (SafeContents scs) = loop scs
+getSafeX509CRLsId :: SafeContents -> [Id X509.SignedCRL]
+getSafeX509CRLsId (SafeContents scs) = loop scs
   where
     loop []           = []
     loop (bag : bags) =
         case bagInfo bag of
-            CRLBag (Bag (CRLX509 c) _) -> c : loop bags
-            SafeContentsBag inner      -> getSafeX509CRLs inner ++ loop bags
+            CRLBag (Bag (CRLX509 c) _) -> mkId c bag : loop bags
+            SafeContentsBag inner      -> getSafeX509CRLsId inner ++ loop bags
             _                          -> loop bags
 
+-- | Return all X.509 CRLs contained in the safe contents.
+getSafeX509CRLs :: SafeContents -> [X509.SignedCRL]
+getSafeX509CRLs = map unId . getSafeX509CRLsId
+
 -- | Return all X.509 CRLs contained in the safe content list.
 getAllSafeX509CRLs :: [SafeContents] -> [X509.SignedCRL]
 getAllSafeX509CRLs = concatMap getSafeX509CRLs
@@ -515,14 +545,31 @@
 -- Conversion to/from credentials
 
 getInnerCredential :: [SafeContents] -> SamePassword (Maybe (X509.CertificateChain, X509.PrivKey))
-getInnerCredential l = SamePassword (fn <$> getAllSafeKeys l)
+getInnerCredential l = SamePassword (fn <$> getAllSafeKeysId l)
   where
-    certs = getAllSafeX509Certs l
-
-    fn []               = Nothing
-    fn [k] | null certs = Nothing
-           | otherwise  = Just (X509.CertificateChain certs, k)
-    fn _                = Nothing
+    certs     = getAllSafeX509Certs l
+    fn idKeys = do
+        iKey <- single idKeys
+        let k = unId iKey
+        case idKeyId iKey of
+            Just d  -> do
+                -- locate a single certificate with same ID as private key
+                -- 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)
+            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)
+                    Nothing   -> do
+                        -- no identifier available, so we simply return all
+                        -- certificates with input order
+                        guard (not $ null certs)
+                        pure (X509.CertificateChain certs, k)
 
 -- | Extract the private key and certificate chain from a 'PKCS12' value.  A
 -- credential is returned when the structure contains exactly one private key
@@ -531,6 +578,23 @@
 toCredential p12 =
     unSamePassword (SamePassword (unPKCS12 p12) >>= getInnerCredential)
 
+getInnerCredentialNamed :: String -> [SafeContents] -> SamePassword (Maybe (X509.CertificateChain, X509.PrivKey))
+getInnerCredentialNamed name l = SamePassword (fn <$> getAllSafeKeys filtered)
+  where
+    certs    = getAllSafeX509Certs l
+    filtered = map (filterByFriendlyName name) l
+    fn keys  = do
+        k <- single keys
+        leaf <- single (getAllSafeX509Certs filtered)
+        pure (buildCertificateChain leaf certs, k)
+
+-- | Extract a private key and certificate chain with the specified friendly
+-- name from a 'PKCS12' value.  A credential is returned when the structure
+-- contains exactly one private key and one X.509 certificate with the name.
+toNamedCredential :: String -> PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))
+toNamedCredential name p12 = unSamePassword $
+    SamePassword (unPKCS12 p12) >>= getInnerCredentialNamed name
+
 -- | Build a 'PKCS12' value containing a private key and certificate chain.
 -- Distinct encryption is applied for both.  Encrypting the certificate chain is
 -- optional.
@@ -542,7 +606,30 @@
                -> Password
                -> (X509.CertificateChain, X509.PrivKey)
                -> Either StoreError PKCS12
-fromCredential algChain algKey pwd (X509.CertificateChain certs, key)
+fromCredential = fromCredential' id
+
+-- | Build a 'PKCS12' value containing a private key and certificate chain
+-- identified with the specified friendly name.  Distinct encryption is applied
+-- for private key and certificates.  Encrypting the certificate chain is
+-- optional.
+--
+-- Note: advice is to always generate fresh and independent 'EncryptionScheme'
+-- values so that the salt is not reused twice in the encryption process.
+fromNamedCredential :: String
+                    -> Maybe EncryptionScheme -- for certificates
+                    -> EncryptionScheme       -- for private key
+                    -> Password
+                    -> (X509.CertificateChain, X509.PrivKey)
+                    -> Either StoreError PKCS12
+fromNamedCredential name = fromCredential' (setFriendlyName name)
+
+fromCredential' :: ([Attribute] -> [Attribute])
+                -> Maybe EncryptionScheme -- for certificates
+                -> EncryptionScheme       -- for private key
+                -> Password
+                -> (X509.CertificateChain, X509.PrivKey)
+                -> Either StoreError PKCS12
+fromCredential' trans algChain algKey pwd (X509.CertificateChain certs, key)
     | null certs = Left (InvalidInput "Empty certificate chain")
     | otherwise  = (<>) <$> pkcs12Chain <*> pkcs12Key
   where
@@ -552,14 +639,17 @@
             Just alg -> encrypted alg pwd scChain
             Nothing  -> Right (unencrypted scChain)
 
-    scChain     = SafeContents (map toCertBag certs)
-    toCertBag c = Bag (CertBag (Bag (CertX509 c) [])) []
+    scChain       = SafeContents (zipWith toCertBag certAttrs certs)
+    certAttrs     = attrs : repeat []
+    toCertBag a c = Bag (CertBag (Bag (CertX509 c) [])) a
 
     scKeyOrError = wrap <$> encrypt algKey pwd encodedKey
 
-    wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) []]
+    wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) attrs]
     encodedKey    = encodeASN1Object (FormattedKey PKCS8Format key)
 
+    X509.Fingerprint keyId = X509.getFingerprint (head certs) X509.HashSHA1
+    attrs = trans (setLocalKeyId keyId [])
 
 -- Standard attributes
 
@@ -633,6 +723,20 @@
 applySamePassword :: [OptProtected a] -> OptProtected [a]
 applySamePassword = unSamePassword . traverse SamePassword
 
+single :: [a] -> Maybe a
+single [x] = Just x
+single _   = Nothing
+
+data Id a = Id
+    { unId    :: a
+    , idKeyId :: Maybe BS.ByteString
+    , idName  :: Maybe String
+    }
+
+mkId :: a -> Bag info -> Id a
+mkId val bag = val `seq` Id val (getLocalKeyId attrs) (getFriendlyName attrs)
+  where attrs = bagAttributes bag
+
 decode :: ParseASN1Object [ASN1Event] obj => BS.ByteString -> Either StoreError obj
 decode bs =
     case decodeASN1Repr' BER bs of
@@ -650,3 +754,20 @@
     case decode bs of
         Left e  -> throwParseError (name ++ ": " ++ show e)
         Right c -> return c
+
+buildCertificateChain :: X509.SignedCertificate -> [X509.SignedCertificate]
+                      -> X509.CertificateChain
+buildCertificateChain leaf authorities =
+    X509.CertificateChain (leaf : findAuthorities leaf authorities)
+  where
+    findAuthorities cert others
+        | subject cert == issuer cert = []
+        | otherwise                   =
+            case partition (\c -> subject c == issuer cert) others of
+                ([c], others') -> c : findAuthorities c others'
+                _              -> []
+
+    signedCert = X509.signedObject . X509.getSigned
+
+    subject c = X509.certSubjectDN (signedCert c)
+    issuer c  = X509.certIssuerDN (signedCert c)
diff --git a/src/Crypto/Store/PKCS5/PBES1.hs b/src/Crypto/Store/PKCS5/PBES1.hs
--- a/src/Crypto/Store/PKCS5/PBES1.hs
+++ b/src/Crypto/Store/PKCS5/PBES1.hs
@@ -267,6 +267,7 @@
 getV (DigestAlgorithm SHA256) = 64
 getV (DigestAlgorithm SHA384) = 128
 getV (DigestAlgorithm SHA512) = 128
+getV t                        = error ("pkcs12Derive: unsupported hash: " ++ show t)
 
 hashFromProxy :: proxy a -> a
 hashFromProxy _ = undefined
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
@@ -51,12 +51,18 @@
 import Data.ASN1.BinaryEncoding
 import Data.ASN1.BitArray
 import Data.ASN1.Encoding
+import Data.ByteArray (ByteArrayAccess, convert)
 import Data.Maybe
 import qualified Data.X509 as X509
 import qualified Data.ByteString as B
+import           Crypto.Error
 import           Crypto.Number.Serialize (i2osp, i2ospOf_, os2ip)
+import qualified Crypto.PubKey.Curve25519 as X25519
+import qualified Crypto.PubKey.Curve448 as X448
 import qualified Crypto.PubKey.DSA as DSA
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
 import qualified Crypto.PubKey.RSA as RSA
 
 import Crypto.Store.ASN1.Generate
@@ -129,12 +135,20 @@
     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
     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
 
@@ -198,8 +212,13 @@
         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)
   where
     traditional a = asn1s (Traditional a)
+    tradModern a  = asn1s (Modern [] a)
 
 keyToModernPEM :: X509.PrivKey -> PEM
 keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)
@@ -211,6 +230,10 @@
         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
   where
     modern a = asn1s (Modern attrs a)
 
@@ -263,7 +286,7 @@
     asn1s (FormattedKey PKCS8Format k)       = asn1s (Modern [] k)
 
 instance (Monoid e, ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a)) => ParseASN1Object e (FormattedKey a) where
-    parse = (traditional <$> parseTraditional) <|> (modern <$> parseModern)
+    parse = (modern <$> parseModern) <|> (traditional <$> parseTraditional)
       where
         traditional = FormattedKey TraditionalFormat
         modern      = FormattedKey PKCS8Format
@@ -292,11 +315,15 @@
     asn1s (Modern attrs privKey) = modernPrivKeyASN1S attrs privKey
 
 instance Monoid e => ParseASN1Object e (Modern X509.PrivKey) where
-    parse = rsa <|> dsa <|> ecdsa
+    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
@@ -349,11 +376,7 @@
         IntVal pexp1 <- getNext
         IntVal pexp2 <- getNext
         IntVal pcoef <- getNext
-        let calculate_modulus m i =
-                if (2 ^ (i * 8)) > m
-                    then i
-                    else calculate_modulus m (i+1)
-            pubKey  = RSA.PublicKey { RSA.public_size = calculate_modulus n 1
+        let pubKey  = RSA.PublicKey { RSA.public_size = numBytes n
                                     , RSA.public_n    = n
                                     , RSA.public_e    = e
                                     }
@@ -598,3 +621,89 @@
             then do BitString seed <- getNext
                     return (os2ip $ bitArrayGetData seed)
             else return 0
+
+
+-- 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
+
+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
+
+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
+
+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
+
+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
+
+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
+
+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
+
+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
+
+innerEddsaASN1S :: (ASN1Elem e, ByteArrayAccess key) => key -> ASN1Stream e
+innerEddsaASN1S key = gOctetString (encodeASN1S inner)
+  where inner = gOctetString (convert key)
+
+parseInnerEddsa :: Monoid e
+                => String
+                -> (B.ByteString -> CryptoFailable key)
+                -> B.ByteString
+                -> ParseASN1 e key
+parseInnerEddsa name buildKey input =
+    case either strError (runParseASN1 parser) (decodeASN1' BER input) of
+        Left err -> throwParseError ("PKCS8: error parsing inner " ++ name ++ ": " ++ err)
+        Right privKey -> return privKey
+  where
+    innerMsg = "PKCS8: error decoding inner " ++ name ++ ": "
+    strError = Left . (innerMsg ++) . show
+    parser   = do
+        OctetString bs <- getNext
+        case buildKey bs of
+            CryptoPassed privKey -> return privKey
+            CryptoFailed _       ->
+                throwParseError ("PKCS8: parsed invalid " ++ name ++ " secret key")
diff --git a/src/Crypto/Store/PKCS8/EC.hs b/src/Crypto/Store/PKCS8/EC.hs
--- a/src/Crypto/Store/PKCS8/EC.hs
+++ b/src/Crypto/Store/PKCS8/EC.hs
@@ -22,7 +22,7 @@
 import Data.X509
 import Data.X509.EC
 
-import Crypto.Number.Basic (numBits)
+import Crypto.Number.Basic (numBits, numBytes)
 import Crypto.Number.Serialize (i2ospOf_)
 import Crypto.PubKey.ECC.Prim
 import Crypto.PubKey.ECC.Types
@@ -32,10 +32,6 @@
 -- | Number of bytes necessary to serialize n bits.
 bitsToBytes :: Int -> Int
 bitsToBytes n = (n + 7) `div` 8
-
--- | Number of bytes necessary to serialize an integer.
-numBytes :: Integer -> Int
-numBytes = bitsToBytes . numBits
 
 -- | Number of bytes to serialize a field element.
 curveSizeBytes :: Curve -> Int
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
@@ -7,6 +7,7 @@
 --
 --
 {-# LANGUAGE BangPatterns #-}
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE MagicHash #-}
 module Crypto.Store.Util
     ( (&&!)
@@ -30,8 +31,12 @@
 infixr 3 &&!
 
 -- | Reverse a bytearray.
-reverseBytes :: (ByteArrayAccess bin, ByteArray bout) => bin -> bout
+reverseBytes :: ByteArray ba => ba -> ba
+#if MIN_VERSION_memory(0,14,18)
+reverseBytes = B.reverse
+#else
 reverseBytes = B.pack . reverse . B.unpack
+#endif
 
 -- | Test if all bytes in a bytearray are equal to the value specified.  Runs in
 -- constant time.
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
@@ -40,6 +40,7 @@
 import Data.Proxy
 import qualified Data.X509 as X509
 import qualified Data.ByteString as B
+import           Crypto.Number.Basic (numBytes)
 import qualified Crypto.PubKey.RSA as RSA
 
 import Crypto.Store.ASN1.Generate
@@ -167,12 +168,8 @@
     parse = onNextContainer Sequence $ do
         IntVal modulus <- getNext
         IntVal pubexp <- getNext
-        let pub = RSA.PublicKey { RSA.public_size = calculate_modulus modulus 1
+        let pub = RSA.PublicKey { RSA.public_size = numBytes modulus
                                 , RSA.public_n    = modulus
                                 , RSA.public_e    = pubexp
                                 }
         return (RSAPublicKey pub)
-      where
-        calculate_modulus n i
-            | (2 ^ (i * 8)) > n = i
-            | otherwise         = calculate_modulus n (i + 1)
diff --git a/tests/CMS/Instances.hs b/tests/CMS/Instances.hs
--- a/tests/CMS/Instances.hs
+++ b/tests/CMS/Instances.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE DataKinds #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module CMS.Instances
@@ -11,6 +12,7 @@
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import           Data.ByteString (ByteString)
+import           Data.Proxy
 import           Data.X509
 
 import Test.Tasty.QuickCheck
@@ -34,25 +36,28 @@
         if n == 0
             then DataCI <$> arbitraryMessage
             else oneof [ DataCI <$> arbitraryMessage
-                       , arbitrarySignedData
-                       , arbitraryEnvelopedData
-                       , arbitraryDigestedData
-                       , arbitraryEncryptedData
-                       , arbitraryAuthenticatedData
-                       , arbitraryAuthEnvelopedData
+                       , arbitraryMode <*> arbitrarySignedData
+                       , arbitraryMode <*> arbitraryEnvelopedData
+                       , arbitraryMode <*> arbitraryDigestedData
+                       , arbitraryMode <*> arbitraryEncryptedData
+                       , arbitraryMode <*> arbitraryAuthenticatedData
+                       , arbitraryMode <*> arbitraryAuthEnvelopedData
                        ]
       where
         arbitraryMessage :: Gen ByteString
         arbitraryMessage = resize 2048 (B.pack <$> arbitrary)
 
-        arbitrarySignedData :: Gen ContentInfo
+        arbitraryMode :: Encapsulates struct => Gen (struct ByteString -> ContentInfo)
+        arbitraryMode = elements [ toAttachedCI, snd . toDetachedCI ]
+
+        arbitrarySignedData :: Gen (SignedData EncapsulatedContent)
         arbitrarySignedData = do
             alg   <- arbitrary
             (sigFns, _) <- arbitrarySigVer alg
             inner <- scale (subtract $ length sigFns) arbitrary
             signData sigFns inner >>= failIfError
 
-        arbitraryEnvelopedData :: Gen ContentInfo
+        arbitraryEnvelopedData :: Gen (EnvelopedData EncryptedContent)
         arbitraryEnvelopedData = do
             oinfo <- arbitrary
             (alg, key, attrs) <- getCommon
@@ -60,19 +65,19 @@
             inner <- scale (subtract $ length envFns) arbitrary
             envelopData oinfo key alg envFns attrs inner >>= failIfError
 
-        arbitraryDigestedData :: Gen ContentInfo
+        arbitraryDigestedData :: Gen (DigestedData EncapsulatedContent)
         arbitraryDigestedData = do
             inner <- scale pred arbitrary
             dt <- arbitrary
             return $ digestData dt inner
 
-        arbitraryEncryptedData :: Gen ContentInfo
+        arbitraryEncryptedData :: Gen (EncryptedData EncryptedContent)
         arbitraryEncryptedData = do
             (alg, key, attrs) <- getCommon
             inner <- scale pred arbitrary
             failIfError $ encryptData key alg attrs inner
 
-        arbitraryAuthenticatedData :: Gen ContentInfo
+        arbitraryAuthenticatedData :: Gen (AuthenticatedData EncapsulatedContent)
         arbitraryAuthenticatedData = do
             (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth
             dig <- arbitrary
@@ -80,7 +85,7 @@
             generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs inner
                 >>= failIfError
 
-        arbitraryAuthEnvelopedData :: Gen ContentInfo
+        arbitraryAuthEnvelopedData :: Gen (AuthEnvelopedData EncryptedContent)
         arbitraryAuthEnvelopedData = do
             (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth
             inner <- scale (subtract $ length envFns) arbitrary
@@ -118,16 +123,26 @@
 arbitraryAttributes :: Gen [Attribute]
 arbitraryAttributes = resize 3 arbitrary
 
+p256 :: Proxy 256
+p256 = Proxy
+
+p512 :: Proxy 512
+p512 = Proxy
+
 instance Arbitrary DigestAlgorithm where
-    arbitrary = elements
-        [ DigestAlgorithm MD2
-        , DigestAlgorithm MD4
-        , DigestAlgorithm MD5
-        , DigestAlgorithm SHA1
-        , DigestAlgorithm SHA224
-        , DigestAlgorithm SHA256
-        , DigestAlgorithm SHA384
-        , DigestAlgorithm SHA512
+    arbitrary = oneof
+        [ pure $ DigestAlgorithm MD2
+        , pure $ DigestAlgorithm MD4
+        , pure $ DigestAlgorithm MD5
+        , pure $ DigestAlgorithm SHA1
+        , pure $ DigestAlgorithm SHA224
+        , pure $ DigestAlgorithm SHA256
+        , pure $ DigestAlgorithm SHA384
+        , pure $ DigestAlgorithm SHA512
+        , pure $ DigestAlgorithm SHAKE128_256
+        , pure $ DigestAlgorithm SHAKE256_512
+        , pure $ DigestAlgorithm (SHAKE128 p256)
+        , pure $ DigestAlgorithm (SHAKE256 p512)
         ]
 
 arbitraryIntegrityDigest :: Gen DigestAlgorithm
@@ -184,6 +199,9 @@
         , pure $ ECDSA (DigestAlgorithm SHA256)
         , pure $ ECDSA (DigestAlgorithm SHA384)
         , pure $ ECDSA (DigestAlgorithm SHA512)
+
+        , pure Ed25519
+        , pure Ed448
         ]
 
 arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)
@@ -202,6 +220,12 @@
 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)
 
 arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen)
 arbitrarySigVer alg = sized $ \n -> do
@@ -352,11 +376,13 @@
         kid <- arbitrarySmall
         KeyIdentifier kid Nothing <$> arbitrary
 
-arbitraryAgreeParams :: KeyEncryptionParams -> Gen KeyAgreementParams
-arbitraryAgreeParams alg = oneof
-    [ flip StdDH alg <$> arbitraryDigest
-    , flip CofactorDH alg <$> arbitraryDigest
-    ]
+arbitraryAgreeParams :: Bool -> KeyEncryptionParams -> Gen KeyAgreementParams
+arbitraryAgreeParams allowCofactorDH alg
+    | allowCofactorDH = oneof
+        [ flip StdDH alg <$> arbitraryDigest
+        , flip CofactorDH alg <$> arbitraryDigest
+        ]
+    | otherwise = flip StdDH alg <$> arbitraryDigest
   where
     arbitraryDigest =
         elements
@@ -387,11 +413,14 @@
         return (envFn, devFn)
 
     arbitraryKA = do
-        (pub, priv) <- arbitraryNamedEC
-        cert <- arbitrarySignedCertificate (PubKeyEC pub)
-        kap  <- arbitraryAlg >>= arbitraryAgreeParams
+        (cert, priv) <- arbitraryDHParams
+        let allowCofactorDH =
+                case priv of
+                    PrivKeyEC _ -> True
+                    _           -> False
+        kap <- arbitraryAlg >>= arbitraryAgreeParams allowCofactorDH
         let envFn = forKeyAgreeRecipient cert kap
-            devFn = withRecipientKeyAgree (PrivKeyEC priv) cert
+            devFn = withRecipientKeyAgree priv cert
         return (envFn, devFn)
 
     arbitraryKEK = do
@@ -430,6 +459,26 @@
                                  , return AES256_WRAP_PAD
                                  , RC2_WRAP <$> choose (1, 1024)
                                  ]
+
+    arbitraryDHParams = oneof [ arbitraryCredNamedEC
+                              , arbitraryCredX25519
+                              , arbitraryCredX448
+                              ]
+
+    arbitraryCredNamedEC = do
+        (pub, priv) <- arbitraryNamedEC
+        cert <- arbitrarySignedCertificate (PubKeyEC pub)
+        return (cert, PrivKeyEC priv)
+
+    arbitraryCredX25519 = do
+        (pub, priv) <- arbitraryX25519
+        cert <- arbitrarySignedCertificate (PubKeyX25519 pub)
+        return (cert, PrivKeyX25519 priv)
+
+    arbitraryCredX448 = do
+        (pub, priv) <- arbitraryX448
+        cert <- arbitrarySignedCertificate (PubKeyX448 pub)
+        return (cert, PrivKeyX448 priv)
 
     -- key wrapping in PWRIKEK is incompatible with CTR mode so we must never
     -- generate this combination
diff --git a/tests/CMS/Tests.hs b/tests/CMS/Tests.hs
--- a/tests/CMS/Tests.hs
+++ b/tests/CMS/Tests.hs
@@ -49,8 +49,17 @@
         count = 1
 
 signedDataTests :: TestTree
-signedDataTests =
-    testCaseSteps "SignedData" $ \step -> do
+signedDataTests = signedDataAnyTests "SignedData" path getAttached
+  where path  = testFile "cms-signed-data.pem"
+
+signedDataDetachedTests :: TestTree
+signedDataDetachedTests =
+    signedDataAnyTests "SignedDataDetached" path (getDetached message)
+  where path  = testFile "cms-signed-data-detached.pem"
+
+signedDataAnyTests :: TestName -> FilePath -> (SignedData (Encap EncapsulatedContent) -> IO (SignedData EncapsulatedContent)) -> TestTree
+signedDataAnyTests caseName path getInner =
+    testCaseSteps caseName $ \step -> do
         cms <- readCMSFile path
         assertEqual "unexpected parse count" (length names) (length cms)
 
@@ -59,11 +68,11 @@
 
             step ("verifying " ++ name)
             assertBool "unexpected type" (hasType SignedDataType ci)
-            let SignedDataCI sd = ci
+            let SignedDataCI sdEncap = ci
+            sd <- getInner sdEncap
             result <- verifySignedData withSignerKey sd
-            assertJust result (verifyInnerMessage message)
-  where path  = testFile "cms-signed-data.pem"
-        names = [ "RSA"
+            assertRight result (verifyInnerMessage message)
+  where names = [ "RSA"
                 , "DSA"
                 , "EC (named curve)"
                 , "EC (explicit prime curve)"
@@ -86,8 +95,7 @@
                 let (name, key) = keys !! index
 
                 step ("testing " ++ name)
-                assertBool "unexpected type" (hasType EnvelopedDataType ci)
-                let EnvelopedDataCI ev = ci
+                ev <- getEnveloppedAttached ci
                 result <- openEnvelopedData (f key) ev
                 assertRight result (verifyInnerMessage message)
         testKT caseName path keys = testCaseSteps caseName $ \step -> do
@@ -100,8 +108,7 @@
             let pairs = [ (c, m) | c <- map fst keys1, m <- modes ]
             forM_ (zip pairs cms) $ \((c, m), ci) -> do
                 step ("testing " ++ c ++ " with " ++ m)
-                assertBool "unexpected type" (hasType EnvelopedDataType ci)
-                let EnvelopedDataCI ev = ci
+                ev <- getEnveloppedAttached ci
                 result <- openEnvelopedData (withRecipientKeyTrans priv) ev
                 assertRight result (verifyInnerMessage message)
         testKA caseName path keys = testCaseSteps caseName $ \step -> do
@@ -116,10 +123,13 @@
             let pairs = [ (c, h) | c <- map fst keys, h <- mds ]
             forM_ (zip pairs cms) $ \((c, h), ci) -> do
                 step ("testing " ++ c ++ " with " ++ h)
-                assertBool "unexpected type" (hasType EnvelopedDataType ci)
-                let EnvelopedDataCI ev = ci
+                ev <- getEnveloppedAttached ci
                 result <- openEnvelopedData (withRecipientKeyAgree priv cert) ev
                 assertRight result (verifyInnerMessage message)
+        getEnveloppedAttached ci = do
+            assertBool "unexpected type" (hasType EnvelopedDataType ci)
+            let EnvelopedDataCI evEncap = ci
+            getAttached evEncap
         path1 = testFile "cms-enveloped-kekri-data.pem"
         path2 = testFile "cms-enveloped-pwri-data.pem"
         path3 = testFile "cms-enveloped-ktri-data.pem"
@@ -160,12 +170,14 @@
 
             step ("verifying " ++ name)
             assertBool "unexpected type" (hasType DigestedDataType ci)
-            let DigestedDataCI dd = ci
-                result = digestVerify dd
-            assertJust result (verifyInnerMessage message)
+            let DigestedDataCI ddEncap = ci
+            dd <- getAttached ddEncap
+            let result = digestVerify dd
+            assertRight result (verifyInnerMessage message)
 
             step ("digesting " ++ name)
-            let ci' = digestData alg (DataCI message)
+            let dd' = digestData alg (DataCI message)
+                ci' = toAttachedCI dd'
             ci @?= ci'
   where path  = testFile "cms-digested-data.pem"
         algs  = [ ("MD5",    DigestAlgorithm MD5)
@@ -187,13 +199,15 @@
 
             step ("decrypting " ++ name)
             assertBool "unexpected type" (hasType EncryptedDataType ci)
-            let EncryptedDataCI ed = ci
-                result = decryptData key ed
+            let EncryptedDataCI edEncap = ci
+            ed <- getAttached edEncap
+            let result = decryptData key ed
             assertRight result (verifyInnerMessage message)
 
             step ("encrypting " ++ name)
             let params = edContentEncryptionParams ed
-                ci'    = encryptData key params [] (DataCI message)
+                ed'    = encryptData key params [] (DataCI message)
+                ci'    = toAttachedCI <$> ed'
             Right ci @?= ci'
   where path  = testFile "cms-encrypted-data.pem"
         keys  = [ ("DES_CBC",              testKey  8)
@@ -223,13 +237,15 @@
         forM_ (zip [0..] cms) $ \(index, ci) -> do
             step ("testing vector " ++ show (index :: Int))
             assertBool "unexpected type" (hasType AuthEnvelopedDataType ci)
-            let AuthEnvelopedDataCI ae = ci
+            let AuthEnvelopedDataCI aeEncap = ci
+            ae <- getAttached aeEncap
             result <- openAuthEnvelopedData (withRecipientPassword pwd) ae
             assertRight result (verifyInnerMessage msg)
 
             step ("testing encoded vector " ++ show index)
             let [Just ci'] = pemToContentInfo [] (contentInfoToPEM ci)
-                AuthEnvelopedDataCI ae' = ci'
+                AuthEnvelopedDataCI aeEncap' = ci'
+            ae' <- getAttached aeEncap'
             result' <- openAuthEnvelopedData (withRecipientPassword pwd) ae'
             assertRight result' (verifyInnerMessage msg)
   where path  = testFile "cms-auth-enveloped-data-rfc6476.pem"
@@ -246,32 +262,32 @@
         collect alg $ do
             (sigFns, verFn) <- scale succ (arbitrarySigVer alg)
             r <- signData sigFns ci
-            let Right (SignedDataCI sd) = r
+            let Right sd = r
             r' <- verifySignedData verFn sd
-            return (Just ci === r')
+            return (Right ci === r')
     , testProperty "enveloping" $ \alg ci ->
         collect alg $ do
             (oinfo, key, envFns, devFn, attrs) <- getCommon alg
             r <- envelopData oinfo key alg envFns attrs ci
-            let Right (EnvelopedDataCI ev) = r
+            let Right ev = r
             r' <- openEnvelopedData devFn ev
             return (Right ci === r')
     , testProperty "digesting" $ \alg ci ->
         collect alg $
-            let DigestedDataCI dd = digestData alg ci
-             in Just ci === digestVerify dd
+            let dd = digestData alg ci
+             in Right ci === digestVerify dd
     , testProperty "encrypting" $ \alg ci ->
         collect alg $ do
             key <- generateKey alg
             attrs <- arbitraryAttributes
-            let Right (EncryptedDataCI ed) = encryptData key alg attrs ci
+            let Right ed = encryptData key alg attrs ci
             return (Right ci === decryptData key ed)
     , testProperty "authenticating" $ \alg dig ci ->
         collect alg $ do
             (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg
             aAttrs <- if isNothing dig then pure [] else arbitraryAttributes
             r <- generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs ci
-            let Right (AuthenticatedDataCI ad) = r
+            let Right ad = r
             r' <- verifyAuthenticatedData devFn ad
             return (Right ci === r')
     , testProperty "enveloping with authentication" $ \alg ci ->
@@ -279,7 +295,7 @@
             (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg
             aAttrs <- arbitraryAttributes
             r <- authEnvelopData oinfo key alg envFns aAttrs uAttrs ci
-            let Right (AuthEnvelopedDataCI ae) = r
+            let Right ae = r
             r' <- openAuthEnvelopedData devFn ae
             return (Right ci === r')
     ]
@@ -303,6 +319,7 @@
     testGroup "CMS"
         [ dataTests
         , signedDataTests
+        , signedDataDetachedTests
         , envelopedDataTests
         , digestedDataTests
         , encryptedDataTests
diff --git a/tests/PKCS12/Instances.hs b/tests/PKCS12/Instances.hs
--- a/tests/PKCS12/Instances.hs
+++ b/tests/PKCS12/Instances.hs
@@ -2,6 +2,7 @@
 -- | Orphan instances.
 module PKCS12.Instances
     ( arbitraryPassword
+    , arbitraryAlias
     , arbitraryIntegrityParams
     , arbitraryPKCS12
     ) where
@@ -20,6 +21,10 @@
 
 arbitrarySmall :: Gen ByteString
 arbitrarySmall = resize 10 (B.pack <$> arbitrary)
+
+arbitraryAlias :: Gen String
+arbitraryAlias = resize 16 asciiChar
+  where asciiChar = listOf $ choose ('\x20','\x7f')
 
 arbitraryIntegrityParams :: Gen IntegrityParams
 arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary
diff --git a/tests/PKCS12/Tests.hs b/tests/PKCS12/Tests.hs
--- a/tests/PKCS12/Tests.hs
+++ b/tests/PKCS12/Tests.hs
@@ -81,19 +81,28 @@
         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c
         return $ Right (Right (Right c)) === (fmap (recover pI) <$> r)
     , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $
+        \pChain pKey privKey ->
+            testCredConv privKey toCredential (fromCredential pChain pKey)
+    , localOption (QuickCheckTests 20) $ testProperty "converting named credentials" $
         \pChain pKey privKey -> do
-            pwd <- arbitraryPassword
-            chain <- arbitrary >>= arbitraryCertificateChain
-            let cred = (chain, privKey)
-                pkcs12 = fromCredential pChain pKey pwd cred
-                r = pkcs12 >>= recover pwd . toCredential
-            return $ Right (Just cred) === r
+            name <- arbitraryAlias
+            testCredConv privKey
+                (toNamedCredential name)
+                (fromNamedCredential name pChain pKey)
     ]
+  where
+    testCredConv privKey to from = do
+        pwd <- arbitraryPassword
+        chain <- arbitrary >>= arbitraryCertificateChain
+        chain' <- shuffleCertificateChain chain
+        let cred = (chain, privKey)
+            r = from pwd (chain', privKey) >>= recover pwd . to
+        return $ Right (Just cred) === r
 
 pkcs12Tests :: TestTree
 pkcs12Tests =
     testGroup "PKCS12"
         [ testType "RSA"                        "rsa"
-        , testType "EC (named curve)"           "ecdsa-p256"
+        , testType "Ed25519"                    "ed25519"
         , propertyTests
         ]
diff --git a/tests/PKCS8/Instances.hs b/tests/PKCS8/Instances.hs
--- a/tests/PKCS8/Instances.hs
+++ b/tests/PKCS8/Instances.hs
@@ -1,9 +1,12 @@
+{-# LANGUAGE FlexibleInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 -- | Orphan instances.
 module PKCS8.Instances
     ( arbitraryPassword
     ) where
 
+import Data.X509
+
 import Test.Tasty.QuickCheck
 
 import Crypto.Store.CMS
@@ -35,5 +38,13 @@
 instance Arbitrary PrivateKeyFormat where
     arbitrary = elements [ TraditionalFormat, PKCS8Format ]
 
-instance Arbitrary a => Arbitrary (FormattedKey a) where
-    arbitrary = FormattedKey <$> arbitrary <*> arbitrary
+instance Arbitrary (FormattedKey PrivKey) where
+    arbitrary = do
+        key <- arbitrary
+        fmt <- case key of
+                   PrivKeyX25519  _ -> return PKCS8Format
+                   PrivKeyX448    _ -> return PKCS8Format
+                   PrivKeyEd25519 _ -> return PKCS8Format
+                   PrivKeyEd448   _ -> return PKCS8Format
+                   _                -> arbitrary
+        return (FormattedKey fmt key)
diff --git a/tests/PKCS8/Tests.hs b/tests/PKCS8/Tests.hs
--- a/tests/PKCS8/Tests.hs
+++ b/tests/PKCS8/Tests.hs
@@ -97,5 +97,9 @@
         , testType "DSA"                        "dsa"
         , testType "EC (named curve)"           "ecdsa-p256"
         , testType "EC (explicit prime curve)"  "ecdsa-epc"
+        , testType "X25519"                     "x25519"
+        , testType "X448"                       "x448"
+        , testType "Ed25519"                    "ed25519"
+        , testType "Ed448"                      "ed448"
         , propertyTests
         ]
diff --git a/tests/Util.hs b/tests/Util.hs
--- a/tests/Util.hs
+++ b/tests/Util.hs
@@ -3,14 +3,20 @@
 module Util
     ( assertJust
     , assertRight
+    , getAttached
+    , getDetached
     , testFile
     , TestKey(..)
     , TestIV(..)
     ) where
 
+import Control.Monad (when)
+
 import Data.ByteString (ByteString, pack)
+import Data.Maybe (isNothing)
 
 import Crypto.Cipher.Types
+import Crypto.Store.CMS
 
 import Test.Tasty.HUnit
 import Test.Tasty.QuickCheck
@@ -23,6 +29,20 @@
 assertRight (Right b)  f = f b
 assertRight (Left val) _ =
     assertFailure ("expecting Right but got: Left " ++ show val)
+
+getAttached :: Encapsulates struct => struct (Encap a) -> IO (struct a)
+getAttached e = do
+    let m = fromAttached e
+    when (isNothing m) $
+        assertFailure "expecting attached but got detached content"
+    let Just r = m in return r
+
+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
 
 testFile :: String -> FilePath
 testFile name = "tests/files/" ++ name
diff --git a/tests/X509/Instances.hs b/tests/X509/Instances.hs
--- a/tests/X509/Instances.hs
+++ b/tests/X509/Instances.hs
@@ -6,22 +6,34 @@
     , arbitraryLargeRSA
     , arbitraryDSA
     , arbitraryNamedEC
+    , arbitraryX25519
+    , arbitraryX448
+    , arbitraryEd25519
+    , arbitraryEd448
     , arbitrarySignedCertificate
     , arbitraryCertificateChain
+    , shuffleCertificateChain
     ) where
 
+import           Control.Monad (zipWithM)
+
 import           Data.ASN1.Types
 import qualified Data.ByteArray as B
 import           Data.Hourglass
+import           Data.List (nub)
 import           Data.X509
 
 import Test.Tasty.QuickCheck
 
 import           Crypto.Number.Serialize (i2ospOf_)
+import qualified Crypto.PubKey.Curve25519 as X25519
+import qualified Crypto.PubKey.Curve448 as X448
 import qualified Crypto.PubKey.DSA as DSA
 import qualified Crypto.PubKey.ECC.ECDSA as ECDSA
 import qualified Crypto.PubKey.ECC.Generate as ECC
 import qualified Crypto.PubKey.ECC.Types as ECC
+import qualified Crypto.PubKey.Ed25519 as Ed25519
+import qualified Crypto.PubKey.Ed448 as Ed448
 import qualified Crypto.PubKey.RSA as RSA
 import           Crypto.Random
 
@@ -49,6 +61,10 @@
                       , PubKeyDSA . fst <$> arbitraryDSA
                       , PubKeyEC . fst  <$> arbitraryNamedEC
                       --, PubKeyEC . fst  <$> arbitraryExplicitPrimeCurve
+                      , PubKeyX25519 . fst <$> arbitraryX25519
+                      , PubKeyX448 . fst   <$> arbitraryX448
+                      , PubKeyEd25519 . fst <$> arbitraryEd25519
+                      , PubKeyEd448 . fst   <$> arbitraryEd448
                       ]
 
 instance Arbitrary PrivKey where
@@ -56,6 +72,10 @@
                       , PrivKeyDSA . snd <$> arbitraryDSA
                       , PrivKeyEC . snd  <$> arbitraryNamedEC
                       , PrivKeyEC . snd  <$> arbitraryExplicitPrimeCurve
+                      , PrivKeyX25519 . snd <$> arbitraryX25519
+                      , PrivKeyX448 . snd <$> arbitraryX448
+                      , PrivKeyEd25519 . snd <$> arbitraryEd25519
+                      , PrivKeyEd448 . snd <$> arbitraryEd448
                       ]
 
 arbitraryRSA :: Gen (RSA.PublicKey, RSA.PrivateKey)
@@ -193,6 +213,26 @@
 curveSizeBytes :: ECC.Curve -> Int
 curveSizeBytes curve = (ECC.curveSizeBits curve + 7) `div` 8
 
+arbitraryX25519 :: Gen (X25519.PublicKey, X25519.SecretKey)
+arbitraryX25519 = do
+    priv <- X25519.generateSecretKey
+    return (X25519.toPublic priv, priv)
+
+arbitraryX448 :: Gen (X448.PublicKey, X448.SecretKey)
+arbitraryX448 = do
+    priv <- X448.generateSecretKey
+    return (X448.toPublic priv, priv)
+
+arbitraryEd25519 :: Gen (Ed25519.PublicKey, Ed25519.SecretKey)
+arbitraryEd25519 = do
+    priv <- Ed25519.generateSecretKey
+    return (Ed25519.toPublic priv, priv)
+
+arbitraryEd448 :: Gen (Ed448.PublicKey, Ed448.SecretKey)
+arbitraryEd448 = do
+    priv <- Ed448.generateSecretKey
+    return (Ed448.toPublic priv, priv)
+
 instance Arbitrary SignatureALG where
     arbitrary = elements
         [ SignatureALG HashSHA1   PubKeyALG_RSA
@@ -211,6 +251,9 @@
         , SignatureALG HashSHA256 PubKeyALG_EC
         , SignatureALG HashSHA384 PubKeyALG_EC
         , SignatureALG HashSHA512 PubKeyALG_EC
+
+        , SignatureALG_IntrinsicHash PubKeyALG_Ed25519
+        , SignatureALG_IntrinsicHash PubKeyALG_Ed448
         ]
 
 instance Arbitrary DateTime where
@@ -218,17 +261,26 @@
         let arbitraryElapsed = Elapsed . Seconds <$> choose (1, 100000000)
          in timeConvert <$> arbitraryElapsed
 
-arbitraryCertificate :: PubKey -> Gen Certificate
-arbitraryCertificate pubKey =
+arbitraryCertificateWithDNs :: PubKey
+                            -> DistinguishedName -- issuer
+                            -> DistinguishedName -- subject
+                            -> Gen Certificate
+arbitraryCertificateWithDNs pubKey issuerDN subjectDN =
     Certificate <$> pure 2
                 <*> arbitrary
                 <*> arbitrary
-                <*> arbitraryDN
+                <*> pure issuerDN
                 <*> arbitrary
-                <*> arbitraryDN
+                <*> pure subjectDN
                 <*> pure pubKey
                 <*> pure (Extensions Nothing)
 
+arbitraryCertificate :: PubKey -> Gen Certificate
+arbitraryCertificate pubKey = do
+    issuerDN  <- arbitraryDN
+    subjectDN <- arbitraryDN
+    arbitraryCertificateWithDNs pubKey issuerDN subjectDN
+
 instance Arbitrary Certificate where
     arbitrary = arbitrary >>= arbitraryCertificate
 
@@ -262,6 +314,25 @@
 
 arbitraryCertificateChain :: PubKey -> Gen CertificateChain
 arbitraryCertificateChain pubKey = do
-    leaf <- arbitrarySignedCertificate pubKey
-    others <- resize 3 $ listOf (arbitrary >>= arbitrarySignedCertificate)
-    return $ CertificateChain (leaf:others)
+    rootDN <- arbitraryDN
+    otherDNs <- nub <$> resize 3 (listOf (arbitraryDN `suchThat` (/= rootDN)))
+    if null otherDNs
+        then do
+            root <- generateCert pubKey (rootDN, rootDN)
+            return $ CertificateChain [root]
+        else do
+            rootKey <- arbitrary
+            root <- generateCert rootKey (rootDN, rootDN)
+            let dnPairs = zip (rootDN : otherDNs) otherDNs -- (issuer, subject)
+            keys <- vectorOf (length otherDNs - 1) arbitrary
+            certs <- zipWithM generateCert (keys ++ [pubKey]) dnPairs
+            return $ CertificateChain $ reverse (root : certs)
+  where
+    generateCert key (issuerDN, subjectDN) =
+        arbitraryCertificateWithDNs key issuerDN subjectDN
+            >>= arbitrarySignedExact
+
+shuffleCertificateChain :: CertificateChain -> Gen CertificateChain
+shuffleCertificateChain (CertificateChain []) = fail "empty certificate chain"
+shuffleCertificateChain (CertificateChain (leaf : auths)) =
+    CertificateChain . (leaf :) <$> shuffle auths
diff --git a/tests/X509/Tests.hs b/tests/X509/Tests.hs
--- a/tests/X509/Tests.hs
+++ b/tests/X509/Tests.hs
@@ -66,5 +66,9 @@
         , keyTests "DSA"                        "dsa"        1
         , keyTests "EC (named curve)"           "ecdsa-p256" 1
 --        , keyTests "EC (explicit prime curve)"  "ecdsa-epc"  1
+        , keyTests "X25519"                     "x25519"     1
+        , keyTests "X448"                       "x448"       1
+        , keyTests "Ed25519"                    "ed25519"    1
+        , keyTests "Ed448"                      "ed448"      1
         , propertyTests
         ]
diff --git a/tests/files/cms-signed-data-detached.pem b/tests/files/cms-signed-data-detached.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/cms-signed-data-detached.pem
@@ -0,0 +1,120 @@
+-----BEGIN CMS-----
+MIIECwYJKoZIhvcNAQcCoIID/DCCA/gCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF
+ADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5
+MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt
+cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtBG2kKWfYT1jUXmb
+DlrkD3Wfw0rKDjTdjjSswM2HggMtEbiutc+cxnoW+DaeP1SzSdY7NXlaqvzftCPL
+vzJNaRM4HpG/XLqIdub5hzisbrwN1/Q77zmUvr1ka7NaXKZDuuepORJsNh5lHkp2
+2VR0O0pQ3zLNjX8IVAGkdpoE5iMCAwEAAaNQME4wHQYDVR0OBBYEFPyU0yv/vEPO
+p1kMDzLvtT6U+gYOMB8GA1UdIwQYMBaAFPyU0yv/vEPOp1kMDzLvtT6U+gYOMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAn2yZT/WNupO34PxVdWU65X9q
+mCE4AtZKLbRa0iSkkEY961oqDiMVWiaN0sPVeV1GoNCz9J8lLKaEkR8b0F4KlJ0K
+44V+5P0Hvb1UnKV2SsNxkmNbxwjO8Q+9aNjd4IqxAnSyQ45yMN4K5x1COJFXQETJ
+ApFWhLZxgA0hqnotQGcxggG9MIIBuQIBATAuMCExHzAdBgkqhkiG9w0BCQEWEHRl
+c3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzALBglghkgBZQMEAgGggeQwGAYJKoZI
+hvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzE2MDkwMDM4
+WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok/0ZRdD/WDHPN
+aCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsG
+CWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcN
+AwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwDQYJKoZIhvcNAQEBBQAEgYCz
+vhp05pvQNr25On3965Z+JXk6auQV9tl1bxI39jxIPxykJgoVKIC5b6FIMnzpPzZA
+I66bK6Tx7dY5PTRcYImjkKgFpubNObwhFaNGeOyKh+W+o7Kic/Zy2nlWwGVMQ3ad
+fCDfp0iR46yzoL94pWXrt+2DSQ7QzMRWvKy0SvNdWw==
+-----END CMS-----
+-----BEGIN CMS-----
+MIIEegYJKoZIhvcNAQcCoIIEazCCBGcCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIIC1jCCAtIwggKPoAMCAQICCQCEbw8q6JYKTTALBglghkgBZQMEAwIw
+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTAeFw0xODA3MDEwOTM1
+MTRaFw0xODA3MzEwOTM1MTRaMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs
+ZS5jb20wggG2MIIBKwYHKoZIzjgEATCCAR4CgYEAu1pOSXz76spaDcSOsEx8vzAB
++xC1LNppXIxeP/mQHim7ADlf5uNPAavPt1Ky7kkJHbdxj9sWXUG+BvgC1CSNoEkO
+0b4A2OTkYdfgCjk8CSjNK4LgsLZcNDW+MMa4G7AooGAxTL+WbvMxMKmWly9eDODd
+VVN2M7S2UCKPlIkoDjcCFQDcpQZHCPm2jIkDaMNf3WJggezQxwKBgDKLgJAp/VlG
+YYflUdnJzcAWS04vg+FQaRIQhocrzaUJQ/ZfPCYtjCozt3gL9KNwFghXBklo082s
+Qwin52NgohB2fBKIOsvBPX1VU8oXmtQ4vIN1L6kmPZkeZvEkOJdVojHBJehU1CDh
+UDT+PEH3joy2s3N9AC1SE1UplbHVBsE5A4GEAAKBgH/LWxi51YZsuzDiIkXdqPAB
++jiJqfccumYpG4m+nIbCaBDCMXxxKmfr/uQBcYFIbdAU4bK4WL7joHjCjXeA/DDz
+zjD3wqFSKax7xFKbBmAK0eC0UdwVlnx9qeJ9Ypduk58+CBWqQkyFJCIlf0aJFlKE
+y8BEvuPC6Hlu2nTc9SHeo1AwTjAdBgNVHQ4EFgQUTAOzcwRJKUUZaP/WO6LOlOLC
+YwEwHwYDVR0jBBgwFoAUTAOzcwRJKUUZaP/WO6LOlOLCYwEwDAYDVR0TBAUwAwEB
+/zALBglghkgBZQMEAwIDMAAwLQIVAKC36+0syuqwTPBd7MLJ7NBJS10XAhQRKGb0
+d0Q8f19UOAr5ROp10HSxdTGCAWowggFmAgEBMC4wITEfMB0GCSqGSIb3DQEJARYQ
+dGVzdEBleGFtcGxlLmNvbQIJAIRvDyrolgpNMAsGCWCGSAFlAwQCAaCB5DAYBgkq
+hkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xOTAzMTYwOTAw
+MzhaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg5gWUNm8k8C8x6iT/RlF0P9YM
+c81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFlAwQBKjALBglghkgBZQMEARYw
+CwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG
+9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDALBglghkgBZQMEAwIEMDAu
+AhUAqTVaVNHUEv4tM84y/QLPWSr0cP0CFQCvlZDKBaa0qlBWE2NyJ574gyXqkQ==
+-----END CMS-----
+-----BEGIN CMS-----
+MIIDRwYJKoZIhvcNAQcCoIIDODCCAzQCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIIBjTCCAYkwggEvoAMCAQICCQCX+mbx0uA+ADAKBggqhkjOPQQDAjAh
+MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx
+NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
+LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHYqvg1E3p4xXBUK+72zfa3M
+LmJIkfZDsYqRsZwRXJ7TzRxsbeVgBDVy3Zj6Y4ZSB/dK7jxs54KTECoDcXLvEASj
+UDBOMB0GA1UdDgQWBBRT2nMGMWO1FT6OJajOnO9gp1hHVzAfBgNVHSMEGDAWgBRT
+2nMGMWO1FT6OJajOnO9gp1hHVzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA
+MEUCIAvA93qcJRkeyRffr9IKzYQXkZE30qnCBzlEfmfUClagAiEA0GQKzKiwGTv6
+uV1pwjprqyI7h0dObEESk3KMQRa+5zcxggGAMIIBfAIBATAuMCExHzAdBgkqhkiG
+9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+ADALBglghkgBZQMEAgGg
+geQwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkw
+MzE2MDkwMDM4WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok
+/0ZRdD/WDHPNaCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZI
+AWUDBAEWMAsGCWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAw
+DQYIKoZIhvcNAwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwCgYIKoZIzj0E
+AwIERzBFAiEA0zBWegmlEC9Zp6Nql04cSiCD0ZdlFzecaGoF+xmctUICID+cT8s9
+dF/iJdrrTKh4+UmuAXO+PMfVRXwjP1TFBq4z
+-----END CMS-----
+-----BEGIN CMS-----
+MIIEPQYJKoZIhvcNAQcCoIIELjCCBCoCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIICgjCCAn4wggIjoAMCAQICCQCgEjyokIqTbTAKBggqhkjOPQQDAjAh
+MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx
+NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
+LmNvbTCCAUswggEDBgcqhkjOPQIBMIH3AgEBMCwGByqGSM49AQECIQD/////AAAA
+AQAAAAAAAAAAAAAAAP///////////////zBbBCD/////AAAAAQAAAAAAAAAAAAAA
+AP///////////////AQgWsY12Ko6k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsD
+FQDEnTYIhucEk2pmeOETnSa3gZ9+kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLesz
+oPShOUXYmMKWT+NC4v4af5uO5+tKfA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////
+AAAAAP//////////vOb6racXnoTzucrC/GMlUQIBAQNCAASRj2SKQoEgjz6+KYQx
+zT4ayoZgTTCBYWY3EiaJl5ok0cqIU7SBaPdN+qTVczCMjUvrflk8wDmKmgdrVauL
+H/l0o1AwTjAdBgNVHQ4EFgQUgvJMdTkQVKStsiGY0/eY/NB/nHswHwYDVR0jBBgw
+FoAUgvJMdTkQVKStsiGY0/eY/NB/nHswDAYDVR0TBAUwAwEB/zAKBggqhkjOPQQD
+AgNJADBGAiEA2aHN4T0QP85J2+vz2Rmpk+DEJzwb8Fv9DGj4gwcNiScCIQDwUiXY
+FcmbsiZc8oQxVcMKHXelMQUbkPq12rSnAOJNaTGCAYEwggF9AgEBMC4wITEfMB0G
+CSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAKASPKiQipNtMAsGCWCGSAFl
+AwQCAaCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEP
+Fw0xOTAzMTYwOTAwMzhaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg5gWUNm8k
+8C8x6iT/RlF0P9YMc81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFlAwQBKjAL
+BglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqGSIb3DQMC
+AgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIBKDAKBggq
+hkjOPQQDAgRIMEYCIQDtHZXf8lH3LgaqQR6AiCXWWn1rs+76ilGdbZtWy3ZVtgIh
+APRXOwsrj/zkjQ4gSvCgi0swPjceDvNqayJ+zc0c5ecz
+-----END CMS-----
+-----BEGIN CMS-----
+MIIEOwYJKoZIhvcNAQcCoIIELDCCBCgCAQExDTALBglghkgBZQMEAgEwCwYJKoZI
+hvcNAQcBoIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF
+ADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5
+MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt
+cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtBG2kKWfYT1jUXmb
+DlrkD3Wfw0rKDjTdjjSswM2HggMtEbiutc+cxnoW+DaeP1SzSdY7NXlaqvzftCPL
+vzJNaRM4HpG/XLqIdub5hzisbrwN1/Q77zmUvr1ka7NaXKZDuuepORJsNh5lHkp2
+2VR0O0pQ3zLNjX8IVAGkdpoE5iMCAwEAAaNQME4wHQYDVR0OBBYEFPyU0yv/vEPO
+p1kMDzLvtT6U+gYOMB8GA1UdIwQYMBaAFPyU0yv/vEPOp1kMDzLvtT6U+gYOMAwG
+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAn2yZT/WNupO34PxVdWU65X9q
+mCE4AtZKLbRa0iSkkEY961oqDiMVWiaN0sPVeV1GoNCz9J8lLKaEkR8b0F4KlJ0K
+44V+5P0Hvb1UnKV2SsNxkmNbxwjO8Q+9aNjd4IqxAnSyQ45yMN4K5x1COJFXQETJ
+ApFWhLZxgA0hqnotQGcxggHtMIIB6QIBATAuMCExHzAdBgkqhkiG9w0BCQEWEHRl
+c3RAZXhhbXBsZS5jb20CCQD3DetZLCk3mzALBglghkgBZQMEAgGggeQwGAYJKoZI
+hvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTkwMzE2MDkwMDM4
+WjAvBgkqhkiG9w0BCQQxIgQgu747Zx6FPf4woOYFlDZvJPAvMeok/0ZRdD/WDHPN
+aCIweQYJKoZIhvcNAQkPMWwwajALBglghkgBZQMEASowCwYJYIZIAWUDBAEWMAsG
+CWCGSAFlAwQBAjAKBggqhkiG9w0DBzAOBggqhkiG9w0DAgICAIAwDQYIKoZIhvcN
+AwICAUAwBwYFKw4DAgcwDQYIKoZIhvcNAwICASgwPQYJKoZIhvcNAQEKMDCgDTAL
+BglghkgBZQMEAgGhGjAYBgkqhkiG9w0BAQgwCwYJYIZIAWUDBAIBogMCAV4EgYBS
+HLzcxWJqmnUPO4IpwKEf5Xsh359SPjFzcJUMQcAI/NswyRKN85WRqYrWuXbqBRX/
+d1dO7o4hZEcU9y8VvWo6sZCba+teSvUG8qYmu13KE/UXYVW1ZIWbNWRsDhxDmlaL
+PGIwcx501+iCuwogqFYvGzualDuMc+/ErbSQ6ZVN5Q==
+-----END CMS-----
diff --git a/tests/files/ecdsa-p256-pkcs12.pem b/tests/files/ecdsa-p256-pkcs12.pem
deleted file mode 100644
--- a/tests/files/ecdsa-p256-pkcs12.pem
+++ /dev/null
@@ -1,305 +0,0 @@
------BEGIN PKCS12-----
-MIID6AIBAzCCA+EGCSqGSIb3DQEHAaCCA9IEggPOMIIDyjCCAncGCSqGSIb3DQEH
-BqCCAmgwggJkAgEAMIICXQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIFH0q
-sY0hx1QCAggAgIICMPbvE6ijIbACCeQG+LUnydn7HbMHXK0C9fl3+gT6I3j01OiG
-b9RAobZtnwLV1Zbvgw/hUMG3YMKJZ00ydG+C54Cvn/Ta7O4NJfrxJXq2IPL/0RZW
-GtnbIMm71NL5c4wmbbn3nPXWXbcpTS2TC7bnLc6N+ePjKybtynqdFKcRQxiiLqtl
-yzlh7nym+kJV5KSdaCOSUArU699Ymk99OAkYrJhceIJo6v3A0oxxTfYnoJjuxVIM
-sVo3GTfK+kFORJ3Spxia5vCPoN301hfOaieCrd1RpCaB6Q1Y50nJbZI/bjQ0kjEY
-O+7Cnw5XYl2AHHCTzcQ4mwzDDD75r7E4R7tA/XuTbmC6mFB3TyBFtsUtXZtCRFWD
-vrao0ae9sY7GRe//GTI587ZQAXRaLgmbEnUGMkMubsrh/xwPSEDtpzvPtZW81Qf2
-krkO0rwb7FJTzajoklpsgFgVw/VMKNElAO8YSNJ+JX/sagldvfjNHUIQMSgUignW
-dQzSPBRwdOfdjqwgaYVwcNJ0JpgdMZpomNgJmEB10GJblaKXyzbRSH7AuO8Mrr7+
-lrriXEinopnJCAb6YO+Wzc1bsSeVLu2ofeHblbMWb3yofLCuTtvJXR06Xs+ulIjx
-J/sGL/P/JPt8LsnPIrGd67dHy3ChmTtBPNGjDoOzZ+EOOQmLTnsVHR6LDABTaXvR
-+U+MHjc9QIpWVxaE/osyLhBPOHIDqyIGAX1WqHa4aPUzNxwgXn6TMgIfoUZGMIIB
-SwYJKoZIhvcNAQcBoIIBPASCATgwggE0MIIBMAYLKoZIhvcNAQwKAQKggbQwgbEw
-HAYKKoZIhvcNAQwBAzAOBAj7Rnc8QaBBAgICCAAEgZA1tAy4JyoPS9EXxO2e7Vow
-MtDsVwyJzuitmD44ICjPxrJgmyKBFtGDLXBfadLI/VuD9oqlI8lP176hTYwwM+72
-/hwKGXmJfP1s2r7sH28sVmbJO2dYjQXtaanyQ5aDGaFMbcMiuPK4y0vB98TyguD9
-S+mWoT/MVE9F20xh82gxQycHhpdT664UxeA6YBJru4sxajAjBgkqhkiG9w0BCRUx
-FgQUSzL+96khVinAc/v3rkVpgmeGLu4wQwYJKoZIhvcNAQkUMTYeNABQAEsAQwBT
-ADEAMgAgACgAZQBjAGQAcwBhAC0AcAAyADUANgApACAALQBuAG8AbQBhAGM=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIELwIBAzCCA/UGCSqGSIb3DQEHAaCCA+YEggPiMIID3jCCAn8GCSqGSIb3DQEH
-BqCCAnAwggJsAgEAMIICZQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIzybx
-opbqZnkCAggAgIICOJEdutTDfLmiKQEJCzoT33Z6rEqdA2STzm1qL2Ir9RMTl2wL
-zKqqMT9ORFErYaYuIjatj4uSxn//JumW7/fD1d1fJeY+0KajhGpMbp7hmU/wn4mL
-nyUCcSqI+2VoXjQlmH2yRFTgfSBU91+4caap4HN/wGefTZsRG6EwyXkHS7JV9Ocu
-qm1SYBRjxIcMt/4JSMNYVwuSDffFEIE8U9FjnuJKTl49GFdcKwBRJVUyTnYOlPse
-YtFw7Wt/FESxG1IxGV9bU+Xf/SR1AfVlRdqofoCNVavwd8GFtWEfUdXlHjqo2ufN
-fTLTzxQSGNakGknJHDxQFgUUAkrB+KyWBf+YItGfb5DPp4R3TJJvBH6lNTq1w8j4
-aSncKElU/Wo75ULUmZqKra2B7gnIZ9JdTxijRaHothgwd6zDRVlNPSct+xDg4mwO
-zFtjg4AEyCcK/NjbUsq17qbKXDyLkfwA6P3xQc3gepgj6xG+102WOMu0ZD25ucNa
-Te0By0PjkrSrP2kW7PFnBWZFI7abNP00JRvQ0gLuj3ax8JW7Qie6DEOXOPqeeljK
-gPguFb9GAX3o6lZed4K9dIMXB4btyl8mA6PIIPr23hHnyRcqno/Eq1FQQUwmN+XE
-o7s22Tc/y/DQ4fgiMZkFZhW7DweRPrx4flqRmp8QIaG05wGgMW9q+u7s5Djew9kA
-UkHmTAoHbSWal2XXyqkDfbKQp1QdC5fTxOmdFZdQ+xdh/jCknwvKVaDcp0Xs5ZWz
-ZICgq4AwggFXBgkqhkiG9w0BBwGgggFIBIIBRDCCAUAwggE8BgsqhkiG9w0BDAoB
-AqCBtDCBsTAcBgoqhkiG9w0BDAEDMA4ECNIHMplVd+ksAgIIAASBkBjKaNoqEBSQ
-vnDxd3cf49x2w5su0kKScrVMXRjKS0EdhvkEZ/Ybmg9ro4VHvkmTmWv54eY+no3r
-UbvVJ2Btct5LQdg6WgvB1/v1wJGuOH/Y8f9toZ+FjsLsOSkpzOP21iVX8pLJOTvK
-F4dGPyyLGx8xtZZOeA8eevzFlrf99uwlzCkv4lG4ZS3FSKgCdb5DUjF2MCMGCSqG
-SIb3DQEJFTEWBBRLMv73qSFWKcBz+/euRWmCZ4Yu7jBPBgkqhkiG9w0BCRQxQh5A
-AFAASwBDAFMAMQAyACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAG0AYQBj
-AGEAbABnACAAcwBoAGEAMTAxMCEwCQYFKw4DAhoFAAQUtk2zS8R+SstIRhAjP6LT
-PfQ0BEUECOhUkmBy8gJ0AgIIAA==
------END PKCS12-----
------BEGIN PKCS12-----
-MIIESwIBAzCCBAEGCSqGSIb3DQEHAaCCA/IEggPuMIID6jCCAocGCSqGSIb3DQEH
-BqCCAngwggJ0AgEAMIICbQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIC5r4
-uzdR0uECAggAgIICQHbHiFhHuPenOSHe/jkUiyUJE2ItJlxxYGWJylZI41Sz5Hlg
-O75NCrJq4WFytBD7qeFt4bdJ5u3L4TmusDhZfjgi1iW2dtPmp+rwksikWITK4eHU
-d8HJOx+PqtvtN+UI9rTObfVxg8SPQ76vR3NLaQCaLtqNwGmAWSzOlivDdXCoumez
-VG7m7CX3luLSqhG2aag8EhV15dBc2eIj5Vd6zSRlybV06/MEdasAoeiARAFK4c+e
-mC7gK1sxvECWnFwCVrYyWEn1EDLvGYjpUnLfHmTu+fV2LFCTxWmV7gXM0tODaYDM
-hsHtMw9zlSAhVa2iBkL37mjPeSM9q6Un6NWw645+8DyTbWxGkrrpm1vGc/xJYp0G
-5t05jzrFY/AQ9USVELw0PzBFwQiGsNxAHkR8z6k9wxfIES9IpdFAbNBzWmurlEtK
-5OXP17hEmQlSc4EaOivN17g0oJxRcTXglHN6zwiP8iE5OfJehHgGWynytJ9wnN+W
-fkQrX3NNVBxXT7jPMgVHkWBWytZeex6Wy4wUb2uKSYd3I3XEMGAFzg/3bcfHoTBN
-FRwPCFX4DyrSkLsFDv4uAwtNkZjRzRbV9mW9hW3chkcNvH7HfS8plsm19LhayqHC
-LnaM45zOmU1R7zPL269HUq4IZ/qEGspwDoN6wTRhbiXZMaYQ7BA1XoNVsTBCzijT
-FxIO7Oi+IJNffXtSW6AuRtq7I99p5iSVrB1QTZr1pVD9KAsCzbGGMnUrei+LFScz
-zuqYmWbsms/WtCqX9TCCAVsGCSqGSIb3DQEHAaCCAUwEggFIMIIBRDCCAUAGCyqG
-SIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3DQEMAQMwDgQI+WOkpHQZfYgCAggABIGQ
-K6ntubGW+rZ1/c7EqUNvRVnoyxJRp/VG+vEQPf/VoyZpAvYGZPzmVseP2nSTZ9zk
-ooElFmVR7tMVi86/wPhwyuotGdpou2s49lx4prH1mjdQY6W+Ybgj5HdI/vdo6Bsr
-rjOewC1j4WG6Gz6lTQr+ADA/21vn/f2ooxHGf3Uh53yPe+MfCV8RYg6c5fhG5CaI
-MXowIwYJKoZIhvcNAQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFMGCSqGSIb3
-DQEJFDFGHkQAUABLAEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAg
-AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAyADUANjBBMDEwDQYJYIZIAWUDBAIBBQAE
-IOKPMwzYN46vB1aOSoIKQSfDo1A3CJH0PZleWonCGwdwBAjCHBkcsCYB1wICCAA=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEWwIBAzCCBAEGCSqGSIb3DQEHAaCCA/IEggPuMIID6jCCAocGCSqGSIb3DQEH
-BqCCAngwggJ0AgEAMIICbQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIn89b
-Z3GPMpUCAggAgIICQGKf889YFGdAEDzO/krxYBRVQaf6j0ZOWH5G/VyKDbyXl8hm
-9A4LQtfQa+q+LO8DhIea9AfA6aK7WKuAz5vnQIA4DpFF9r1qmGOFQsIbzB9Jo/ik
-wRIp8bylPIdwfbyaUqH+0ITun1NjgdZCpGkV1Onay0uUPZq6St22T6Kv5g/b/x9f
-MwaUwikXQVNNfOR+8wrW0djK0MOp9b8zZnmTvUDURK6nJEaDMeQgP0CFcb4F9Xv2
-8r5v56zSU6edePQuxS5uALWSZQg+7TqQlD4d5nwT0+wkIU4QnDMHcZJGwblyunsy
-d5RTJADCrky0V4BTz8l7/avwK66CkNdw2k2dbN3uJhaIjSZuoWkHmD/iAYS7fAQe
-fHwBElEqOPS+KLsBtdOtwyhWnbqIFd1cL3p+ndZn5UEA07PWSHfpg+SejMK8RRff
-HSBGVgdgvbXKH6PKrz/NoJVb7BO8xKJjIdVjC4GOVNlazhFdUNXryMgEC9n/tg+U
-EZJNcMq74jR9tinCnE6sU1IAP8CpOtrMTpCXnnMIBl3JQCgfem7ahLDfY+3YIp8B
-1Q0DnDG441e1iTcOBaxcOPWaTN8Lyc3SDDgE5D9YWr+Ez3s5OALtb0jy1AS6ODRJ
-v1wgunlHqpapH9iA+ArOa4VTXp60MrUqhCdGkMv/EmiDRJ7P4Se/f2Tl7epCPCl3
-/SEy/4EMX0hhpPGAAASaWhhDtTUsuNrX+G71NeJiyDFh/k55Wu3djblBkMszVsgY
-P26MwEnl451bVHGzQzCCAVsGCSqGSIb3DQEHAaCCAUwEggFIMIIBRDCCAUAGCyqG
-SIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3DQEMAQMwDgQIlX6PffUsFMMCAggABIGQ
-JI4+RRpUEffRaz3qRdXoM1w6rHCxyefKTxfRp/jsDvsHkBEOeWFE5cKm+3BXEm0T
-RMGJlxB52ceYvVA9/Bo0StUKuxYw5dsbftBhjSsH/5i6i8DGVOCFycD0C3B1hn3e
-8Xt+kQSCrZcc6vVd8gjiPvHhynPPnn2lli0NlE5j0DH+as3sxjtfEdPeQunKSFE1
-MXowIwYJKoZIhvcNAQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFMGCSqGSIb3
-DQEJFDFGHkQAUABLAEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAg
-AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAzADgANDBRMEEwDQYJYIZIAWUDBAICBQAE
-MC8cVO84hC0SnVOrqDYNQMWor1IyVaU/CaepQiEq949CrUONcjVM4BfgxoVse2WB
-uAQIkqDilpIjT5oCAggA
------END PKCS12-----
------BEGIN PKCS12-----
-MIID/QIBAzCCA8MGCSqGSIb3DQEHAaCCA7QEggOwMIIDrDCCAksGCSqGSIb3DQEH
-AaCCAjwEggI4MIICNDCCAjAGCyqGSIb3DQEMCgEDoIIBpTCCAaEGCiqGSIb3DQEJ
-FgGgggGRBIIBjTCCAYkwggEvoAMCAQICCQCX+mbx0uA+ADAKBggqhkjOPQQDAjAh
-MR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUx
-NFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxl
-LmNvbTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHYqvg1E3p4xXBUK+72zfa3M
-LmJIkfZDsYqRsZwRXJ7TzRxsbeVgBDVy3Zj6Y4ZSB/dK7jxs54KTECoDcXLvEASj
-UDBOMB0GA1UdDgQWBBRT2nMGMWO1FT6OJajOnO9gp1hHVzAfBgNVHSMEGDAWgBRT
-2nMGMWO1FT6OJajOnO9gp1hHVzAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0gA
-MEUCIAvA93qcJRkeyRffr9IKzYQXkZE30qnCBzlEfmfUClagAiEA0GQKzKiwGTv6
-uV1pwjprqyI7h0dObEESk3KMQRa+5zcxeDAjBgkqhkiG9w0BCRUxFgQUSzL+96kh
-VinAc/v3rkVpgmeGLu4wUQYJKoZIhvcNAQkUMUQeQgBQAEsAQwBTADEAMgAgACgA
-ZQBjAGQAcwBhAC0AcAAyADUANgApACAALQBjAGUAcgB0AHAAYgBlACAATgBPAE4A
-RTCCAVkGCSqGSIb3DQEHAaCCAUoEggFGMIIBQjCCAT4GCyqGSIb3DQEMCgECoIG0
-MIGxMBwGCiqGSIb3DQEMAQMwDgQIQrFaHl+yq4oCAggABIGQQw+qnMDWAzFgExmo
-n/TFr+viTkGMvUHp1ku0i/84uJ5qZh8M8VxNz+kHMdqpICLo1QQNjzUfxNZGcdrG
-9d4cUPTBJolOnAUgIJsB92V7gbrsfou+sTWe3Tcg71hVQIgGgrJAKuKFIyTByviX
-m4Dm0TzfxmNJwK3RU8YuLnJkm3QHmGUYPJ3IJqFWNGYVXiNbMXgwIwYJKoZIhvcN
-AQkVMRYEFEsy/vepIVYpwHP7965FaYJnhi7uMFEGCSqGSIb3DQEJFDFEHkIAUABL
-AEMAUwAxADIAIAAoAGUAYwBkAHMAYQAtAHAAMgA1ADYAKQAgAC0AYwBlAHIAdABw
-AGIAZQAgAE4ATwBOAEUwMTAhMAkGBSsOAwIaBQAEFAKLUf6d7uvD54jrsI9j+DpK
-2CroBAhjf4XZU/977QICCAA=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEkwIBAzCCBFkGCSqGSIb3DQEHAaCCBEoEggRGMIIEQjCCAtIGCSqGSIb3DQEH
-BqCCAsMwggK/AgEAMIICuAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG
-SIb3DQEFDDAcBAiggrBG/lAmGgICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME
-AQIEEHMNnXuYwnMbjOSFmm5xEl2AggJQRvqqPaLjdtyoaWULLjqu1g0bPuZuwsV7
-yBRI3/2sTrFfqBasHMl2nQOA3udFVtlCq9lUuaI/vyDRTqlM2xKqjzvoeFVCoXEB
-qGBfYMPova4443zYYczv+WJLi56Y0z/rOwon9tZfQWWCPrRhhp1w/CqXLeuK06R1
-/+AvNUGK9ihu3/DZ9wHTGHcfs7ciLe4U9YubiVHXwLzhMxZMvXgfs8ogXcDlckrv
-xKuOl7Fww92TKni8IpYU1PWpeD5Qb5IYveaEGF7ELw4USHfYG38G7pBnj1Yy4+KT
-+SC9rx5ZCbuELGtoWTJlcvI54kkLZ6sn+ZqxVVxOZql71f1nUqLiJmt5V8r6kgEt
-+ARauqbrNMcPm4kPJmTADCLYLtYyriQyqbvb/X0bXgAevND/mrl3XTrqj1SwXRxP
-+380fXmNYlz28QFgM1Z16RjxLfGzo/mMQx/vT27oGbwOkar8+wqcR9acZnjuVDEr
-Fq3b0ski6mn6gM73H2F4xzv30UD+BUxgg/YJbcgLiCNBNMy5cr06j4ebvDKGWbgj
-qAZBShbgOzpYMhUpX+KfN0eq7k6CHn9ukn3s6A/blak6oy7Lf+pQfg/ZxubAgA1I
-q6Z19FH3QS6WYOaMvXLXKNe6T4YLtnu154iV430GMCEwhhFVL82ieuCKKGWXJey1
-yQZAxr4m05rouZ6ul8rd1bxTsh6AZlZ5ylIi6pk+h7HD1cZxYthvPnrkWrAI849e
-WYUu5VBykQUMTt6fUUltQiDTez9FtC1RN2+9qFrJacnxFm1PEPD0QjCCAWgGCSqG
-SIb3DQEHAaCCAVkEggFVMIIBUTCCAU0GCyqGSIb3DQEMCgECoIG0MIGxMBwGCiqG
-SIb3DQEMAQMwDgQI5D7IqrU5mUICAggABIGQh+/XDU/wsMCf7Gh0SY+9okbbdgLO
-Hj3PlneCOMeJMJoIK2upJUU0MHety7Xeu8Xv4jMcspCTTqbIBwJtkMdZo7M7JlZ2
-LZ1DgG6xRXYLTYUvWK5aTXZqwISEriTyWNetpBrfuCK48tvo/FP4WmmDHWwVUcTB
-5g/Rsvdcu/c8chKWD+m4jtaXG1fO7kW9qcK9MYGGMCMGCSqGSIb3DQEJFTEWBBRL
-Mv73qSFWKcBz+/euRWmCZ4Yu7jBfBgkqhkiG9w0BCRQxUh5QAFAASwBDAFMAMQAy
-ACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAGMAZQByAHQAcABiAGUAIABh
-AGUAcwAtADEAMgA4AC0AYwBiAGMwMTAhMAkGBSsOAwIaBQAEFJOaeethrlOXj6Bd
-WTDuuxNrbxOTBAjuBPHa1rrDLwICCAA=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEagIBAzCCBDAGCSqGSIb3DQEHAaCCBCEEggQdMIIEGTCCAp8GCSqGSIb3DQEH
-BqCCApAwggKMAgEAMIIChQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQUwDgQIout0
-wIR7cikCAggAgIICWC7qqjpGtFkZwlykv0FzU3eNU0c0L8Et3yaH1frNkhpnX7U0
-oksJ55AsKNCYHxjCGfBi4/pFLJjzmIm7jw1EUNpw7Tz6/8xwov9JS7VhHLgIJ+Ak
-SGUOEcrGBEtCWcwpovXlkqoawvmM304m81b2wSjNeOD+gD6q1nx0s6SOoy6ZyUK1
-bSGnK/mdR4WEs2KeQ/MZK01UR5XI+E4qX9tmF9yL3IU0yokqWY91XKBR+vS4+pRd
-YxVjKZYoMtuO+QdczAQSa4F36Lf/IXW7N45HeCnijCVWz/9r7S2KBjlDQ1duWF5s
-nHucl6VTX5vr41hcTqWH+DQCi2w0y2zbAyqwz8xyronh2OF0mus25rKBuXaxo6BQ
-C4msotkVk7K1E8d9Mo9HbrbjTr6q7q2XRTsD+YKFa8TcDFUTZ5gYsmpUAjevR3Hc
-S5Db0nlLyMXJAasBFvuMfNKoCqWkgm2abrYAd6ROcIK9Mf7Vvs/Q++ED6PDDsew4
-7TIleg7lVJ/VvK1Bf/r7w6ISR3R8G5rpjYX1Z5UKBx9JGdRsE92qfI3cpXy7jTo7
-EDlvIXK1EE0iVOAbDRphQw7z682nzdqAuwwSUvLYIlekxjx+Trtig7xLOrnl0ARI
-ivqCwqCWf+BeD6fC6jqyC75s97PRO1CwyrwjermHqZ29LAbzlsp3su4UgE/q+8zT
-C1HeK6uSnOvsAXCMGyLI9N6w+IK0vMhYY2v5KRqJE9UTdJ/99t71rWooBkZfemF6
-Kk7A8Q0a3x7bTyN1QgOlO9cIIeHkSYn65ObsWankSYm4Nff+LzCCAXIGCSqGSIb3
-DQEHAaCCAWMEggFfMIIBWzCCAVcGCyqGSIb3DQEMCgECoIG0MIGxMBwGCiqGSIb3
-DQEMAQMwDgQIQ0XzzqZ/BJkCAggABIGQH/cfMsPknp6dnjzVV9kj1rxmqLvkxB30
-jCqwxsaOVJmRdPc+lRU3KTOeltejSHgPP8K/v2F8VqQZA0yXniLoNsL4Rlx1GLp7
-qXbxZ/oD+3+ESLJyTjB74uVpi9L56ojJpvjYtZfHzlMBZtEpZBYO6CJh9dRVJt1i
-ODacdYaAmvTZ+Gh6mT87AKugwDuLiQhWMYGQMCMGCSqGSIb3DQEJFTEWBBRLMv73
-qSFWKcBz+/euRWmCZ4Yu7jBpBgkqhkiG9w0BCRQxXB5aAFAASwBDAFMAMQAyACAA
-KABlAGMAZABzAGEALQBwADIANQA2ACkAIAAtAGMAZQByAHQAcABiAGUAIABQAEIA
-RQAtAFMASABBADEALQBSAEMAMgAtADEAMgA4MDEwITAJBgUrDgMCGgUABBRXtjhP
-ScsUZNfJPD+XGatgpre9TgQI6/JvBnA5OYECAggA
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEYAIBAzCCBCYGCSqGSIb3DQEHAaCCBBcEggQTMIIEDzCCApcGCSqGSIb3DQEH
-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+UgP
-cFrlQ6MCAggAgIICUJKytD8kl8Tjhl80ilyB/NxrxFUoFLx3ySy5PVMNEzpOM2Oc
-bHDvAVle2ehL1ZR8c812a4EkNeKAMaRi9ymiRRrN+FRqmjfVqt+lPQidwXkpnS/b
-Nd3nZvKQVsp3sQzeNNkBMobt1eUxA6mzp92/MZSQPvAS0tU92aNDj/PqiQzDA17e
-qEn2Ns/u8JSzvk2d2fRWYeAdgibFebFxyhI6rdJdenE9RNf7JgALJiGZghM5f7NM
-734cz3v1eP+CrxwFq0igXQW/qDqky2xe0p1k0uFwVjfs0SfajtBBSKXEBA88bTnf
-+NCNiX7pzpp/eu8re3oRvx2o17FDxdezLj1Ez4zp1yM+O35abSpAIRW+pmSukqLy
-1t6zfETxlSOwKGEGuX81rZzag4OT0zlmayuXijQfdn2Uv1dYoMvJRbyfIOG5Wbg4
-6rWXIIy5AG6+nzzYWjMBh2e7Rts1rFrhx0hTklBpLJLeQfTnlJrwQN0SIdUIN9gg
-4oOFRqrS0BUdYYi8qhizOoLeju1AUZFDQ0ShBC0kXAAYwsKhxvBANYQDi1+h0MY+
-EAXJLGoSKgXqRB3F0jsXFlcGg3bPTfY8gAs9QBGYv3jfcy4A6fxNbXvUFOTRfoUg
-ZGDXZLysvqarb45E36Mr3BtbvOCKLG5hmazAPteUpdcKepgAM8WBoWWIfoPcOh29
-XRlIx+x93kjrQjDoo2EH8SmfWrh5vYIsDD2K9fozg844hZqq763SDXVYQ+BmTJWu
-4XHaEu6wCyGX6AeE7flDl6Bc/1th2yfw8KQsF2MwggFwBgkqhkiG9w0BBwGgggFh
-BIIBXTCCAVkwggFVBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEDMA4E
-CNNFERdaKdyRAgIIAASBkAcC2AyLZzIegZfk/G8eA0EMOyjaneHbbqDficRNg0Ok
-PkQXZEWNddUdgBggNwo9Y4rGUooHpxnbDEsmgh/iEvnr0UoqZ72uLGOidNnjqfy8
-hOE0jotsl7LI/hIlH24Jniz4jwOoYhE94iEM1JfD4PvRNrbnBfLUcJ5zGCCptdXq
-EVloYf236K3tHSXRADes+DGBjjAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3
-rkVpgmeGLu4wZwYJKoZIhvcNAQkUMVoeWABQAEsAQwBTADEAMgAgACgAZQBjAGQA
-cwBhAC0AcAAyADUANgApACAALQBjAGUAcgB0AHAAYgBlACAAUABCAEUALQBTAEgA
-QQAxAC0AUgBDADIALQA0ADAwMTAhMAkGBSsOAwIaBQAEFB+JS38oqVLgJ/LV3sE+
-rTlusdnvBAhD3Yt2Q836RAICCAA=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEBQIBAzCCA8sGCSqGSIb3DQEHAaCCA7wEggO4MIIDtDCCAn8GCSqGSIb3DQEH
-BqCCAnAwggJsAgEAMIICZQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI36cy
-Nl2ETYkCAggAgIICOJDnrzg0U67gXWn/F3HiUgpK5ZhdaTzoSYoZPEVgCXlx04fa
-WADXrFywcbF2HTU2wiasHws+9DHqRRsCKSzd7DcWiniSREXenhhl6ZjVUL1/kDX9
-uVh8vpetzyIv5eO3LDvJw3TSbBBptH4vO1FCJSXNYA7vQCnKVBOxSpli4TW7PUjZ
-TcQMofIHeFT11HCtlZ4cdV6NqePvc9m0UkyU8bxi/2ynqIyf4YtQiIBa5NsxXWOt
-ZaKw2o7dKK7Hr8STnuLaSdJOkY7qh2TBeFfXNe9rjG7bUM0cjO+ELdHoNM3aJ1l2
-T5Zx6kLIn99CFLUB89yt6Z4MideotQQA8K04uwh23vd3FDwj9G31u2Cm/sEgeO/g
-5dataSR/Eo4+cDTLE8qlNjTBJdjlz2qVVPr/x0eMbMAc/WnDmvtSZEE7dzQfawtw
-TubaChURw6zdKKwNOFesNvfDyIN98lsLuOhAWgNwj+gcsvA/MFBnodPTLiUEEMu5
-R5oIjXmI2JYTcaPMrQX3dCgGCrOW3ddq2at1d+Lbnhvv2jPMCpesBjMPwv555GNj
-O7tculdYrKYh/0WiqRE8AF9M/vlPomNIk0o6SXTGew/7VRFSi4O5zeOmSrxbiu2x
-MORhd/fycw0eB/OIKQUYOxBGsFLMc3HNwGSFtVqRJTO6v9F0e+QtHlcD2VWTKqR4
-w+wexcarA6N0qlDOCBGRAS3vJ5d7YQJTfV/n/4SnLDmWIhsrvmwwNiie+Eea6ABm
-JpzknGQwggEtBgkqhkiG9w0BBwGgggEeBIIBGjCCARYwggESBgsqhkiG9w0BDAoB
-AaCBijCBhwIBADATBgcqhkjOPQIBBggqhkjOPQMBBwRtMGsCAQEEIN1BcbroS4Rr
-SL98OeRm0Hzo0POI2k/SkGxKn2EObv3/oUQDQgAEdiq+DUTenjFcFQr7vbN9rcwu
-YkiR9kOxipGxnBFcntPNHGxt5WAENXLdmPpjhlIH90ruPGzngpMQKgNxcu8QBDF2
-MCMGCSqGSIb3DQEJFTEWBBRLMv73qSFWKcBz+/euRWmCZ4Yu7jBPBgkqhkiG9w0B
-CRQxQh5AAFAASwBDAFMAMQAyACAAKABlAGMAZABzAGEALQBwADIANQA2ACkAIAAt
-AGsAZQB5AHAAYgBlACAATgBPAE4ARTAxMCEwCQYFKw4DAhoFAAQUjqGpn9xugean
-d4IruG5yTl2yAl4ECEFTBPWXAettAgIIAA==
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEiQIBAzCCBE8GCSqGSIb3DQEHAaCCBEAEggQ8MIIEODCCAo8GCSqGSIb3DQEH
-BqCCAoAwggJ8AgEAMIICdQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQImZIF
-k9BCNzQCAggAgIICSKgLbeRZWeciVOjIMDaAOkLV0MP2cVad59E767+6nlAsNflO
-qidcopd5snW9RUQ+eprSx9DAS2Rhqax1H3QWskpGDIVdD91LOHUhDUqx2akwDD2C
-sOPlooaaTIoVZ9zbTpl3JDdJ7NHdRyHoWtbCu1xEvK+akcqK2fLvI8xwDSwU/DRE
-RU7ELR7GX/jW+WuOvBdBGC8RyetVlC93sUjW7Cfl1QOTbJ28A/UCgD9zANXmMRez
-nEiRw4iG30UX71EBVUJdNvLREqSF2l463G/dTphr4ak5IoLACHuQlVYfOWYCevXN
-4R9LDY1pB56XedHkPATcrXAwxy/gapdIdYdVbUAIWUQzxXxtmZgeoBboVG68Ceq2
-5zsM/Ktl3jnnAwFPx2EuuLHV+5yaNOGZFkz4NsFc0lSMlI24tf6J6GIXo4Crupw/
-dywPn4dX68ILjdSiF9xf8lB4xysVEHfG8CWJvCg5KLNPgvRszCxbCDoSnZX4VnSy
-Ra7Dg7x4ZWQyYCgt6wLoqEuiFTxNhtOI5UMxZDK08UhKdKOPWAEZnuZgnUC2Nnpd
-6Bj/Gtd4N+SjqATYCACQDTIP9uDj7iMsTBmIwK3WjPF3hflSsh6ekIF1J/t8uG3/
-lIDfhZnwlkj3Tli8pub5A/kIyPHyhYxUZaY+7QTwQB1Fws4v9K+nj6QCduBuV6or
-MObYTnrlzz/sqKxohlfaiZroiJmYicjgSHiziBo/fE/miUgSWANin5quolzB/HY5
-eZ0k04F9Im8xLFCA/PIEzt+SV3XNMIIBoQYJKoZIhvcNAQcBoIIBkgSCAY4wggGK
-MIIBhgYLKoZIhvcNAQwKAQKgge8wgewwVwYJKoZIhvcNAQUNMEowKQYJKoZIhvcN
-AQUMMBwECIpOphpdh/ohAgIIADAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBAgQQ
-Juna4/Yf0krRZ6mE2g9HKQSBkF8wVESzEUBn1Dw4eEU+2Hu5/Zl2pk082BCqAd+W
-ig4eOZ1w2E0/TWSxYf3mzoLmwCrqn0GSmPfpK8D8ulP1MU6fr89YI1QPL5PhV4WU
-NHI2Og5dRagkT8wcptOM4CVHivgbrceTMWwrp/SWZsBkSxBQEl+RkCwo7mx2KUYP
-NosSC1cwEDgEmYa85ok6Kbk++DGBhDAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinA
-c/v3rkVpgmeGLu4wXQYJKoZIhvcNAQkUMVAeTgBQAEsAQwBTADEAMgAgACgAZQBj
-AGQAcwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAGEAZQBzAC0AMQAy
-ADgALQBjAGIAYzAxMCEwCQYFKw4DAhoFAAQULemygmsvFuzhulUVO5CzQbDYIzYE
-CFkpGLVxDD7KAgIIAA==
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEYAIBAzCCBCYGCSqGSIb3DQEHAaCCBBcEggQTMIIEDzCCApcGCSqGSIb3DQEH
-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIrVmM
-qkskLmMCAggAgIICUGUvn0KkHcJIGDZGDtYe5gbUX8HB5Ug3j4GF/tOT4losFD7A
-YEQVsZnW1ywS5eemKuw/1oCsQ7YaJf0WFUTk3J26C9yQqsnQWxvPVGs8qOqHQoi9
-C9L1KTRpAvWj6envRFkk9skr5YVNm6FqShPc/LbWBf5JJCw/G4yR3zvPZtpkKaaz
-BMb7JWVRbyXuA6mbr7QaeymY8Zg602TNB3Awk8F9c/tiM6dk3/SSdZFL1aH7Fiog
-W9qDJ+3MoLKwwZcBjZxknuimNWQgjsXihcog6idwaako5a8Vzzh+9Yr2u4Cd4jWU
-TPY5DkspEpU0Se9lmjn4cKdX3AdNQNSPbn1UpBagirbM8/lfUkkbVGezUnTusy5Q
-Yc7rTTJ6WDWpilP89ZMTIEnriLvF92GhqQvb6Q6IJ1prD/iuJG8RZEZ7Rdd/phJP
-55dadyVlQb2mNOQb2zR6ikqn8F1WX+cEW609Xv/fsURblUFndhP+2DsxBbmdbrpP
-rZoboP3S5eyK53dSoH0jE+dDwLmTCRaBHRBdTHEaN7/qNefK+TaSJ7/ZhkT6QrZx
-JwM+7T0gWLTY19QSEbQDW+nmw/mT7YF9Y4AJJRjKWONZDlzz2JOuft+6gDqfivkz
-oJt0sNklvCv+fwxIxQJINIEshhiqTmCiYb//LDlskKZw/X71FE2UeekmA5zdCXhW
-ezaBLXifFYKHG3njl3IUxLSfZXz0SVmCXZb8Zvn0pOfAz5WQqIXak9nIgSMzh1cG
-OQGVeNxiKE7M1rd+Iwx7S/y8QXIVhAUeXCyOjY0wggFwBgkqhkiG9w0BBwGgggFh
-BIIBXTCCAVkwggFVBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEFMA4E
-CHluHyg/OSArAgIIAASBkAL+Y4024MWAZN/tETNU9kDN4pn7kRwdvF/tE5qWJblk
-BDoYOzUydhQgQNTxQloKxJwwQ/aYG/fmCZy9n7XF4wvSXjrIIqQ1fTiwsg5hpO/I
-NfEBjAQMDQe2PZZn0v6Z2drzagtKvP+5dcKR9Rm4hdhlT3+sTQFPMuqE/naW0RUu
-VgyufgDvrdo3eGkNV0cNcDGBjjAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3
-rkVpgmeGLu4wZwYJKoZIhvcNAQkUMVoeWABQAEsAQwBTADEAMgAgACgAZQBjAGQA
-cwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAFAAQgBFAC0AUwBIAEEA
-MQAtAFIAQwAyAC0AMQAyADgwMTAhMAkGBSsOAwIaBQAEFM1Mf/02bHn5IeF/chYj
-fTf91X+wBAhxy4p/PLmCegICCAA=
------END PKCS12-----
------BEGIN PKCS12-----
-MIIEXgIBAzCCBCQGCSqGSIb3DQEHAaCCBBUEggQRMIIEDTCCApcGCSqGSIb3DQEH
-BqCCAogwggKEAgEAMIICfQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIgDZr
-yESnGsUCAggAgIICUI0Xi/tNudGKSqEhCSc2kdmly1P1UspJQiROTByJAGEGpiw6
-MbKJ6HhfxJ2vJvtlz+1iA2iYKA6LnFUGaU57aBFZMjbPjTs4K7gILwHpMCtv+DPx
-u4zROBGqYw5ufsvuEFQSaSeWZSwRYMPM8yVfQTZopdHSHGIhxbKFdtRMlnVOVyNh
-p+IGo0yOsELkRIxrmhdawmNsjo3ua5pSRb8nitrKIxc+LatWpp4wbwb/Z5KwaMOf
-1Xz1zKDNL4BlZVI9c3bPWM/sYWoLRxl4LF8xQK6G1lk+s9RR9n9i2r8BDZ2c2LRI
-X7KVfUhi+BWlkRzjwkO8w0pL5acoc3hYg/rvm1TCh7b2sSE1JYP5xZziOl2TSaWh
-1FNrAASb84F5XDKnA8DL+jSRevMERfP+uuWX26R3bO6D/Y/qDegq63VRSdFjSVDn
-zxwUxrb93GLSbLBJ4vW14R2Dl5WrwLzCXvP3jTTjfDVg74Av9ODE9QqEw5eABJx1
-uCUM8zhXx8NVzsRQ8U9aUOhJXK+HXVbrN4gRMZ5fqKtHeELG9mjV4guZ60cd8u4X
-fcdzyOMQzedbGhoxHl3vmcSbJb+DUgzTF+Sw85V1nqV7oPRgJgx11ib74cdAfs4f
-GSlnEY/wdP20unPGGTS5k9I7GoUFLM2bPXeue983JIEFrZXefdSXUyY8ug2UFxMl
-Ta/7aFFTmz5SBfqXUuZX48FOL1GlUvl+8o5ISbTGNvt8jwunxWJcCJCWMgQ2s82k
-PJpzqJlz8sE+4xz8qdTW1MR1HELw+v6bkRPDHH0wggFuBgkqhkiG9w0BBwGgggFf
-BIIBWzCCAVcwggFTBgsqhkiG9w0BDAoBAqCBtDCBsTAcBgoqhkiG9w0BDAEGMA4E
-CFdmR0tc6KJ+AgIIAASBkFi0RoWkd1KWd22KzsQ0u5Hl/3W8n1GgVKe1aAVx/YVo
-W+/PNJPpFZ3r9X1rx39aWMnDVa6c5wsMQ5wJ2vVzobrdEMZkvxYRXoAeyoxRRHR3
-01SFdSgqE3i6D/25ashBZWgyjHiwFd1xpK/uac9watUdcJ1JvFP5ZZ38T85E6IPF
-hnXk/GYbvedO9Wtklvbt7DGBjDAjBgkqhkiG9w0BCRUxFgQUSzL+96khVinAc/v3
-rkVpgmeGLu4wZQYJKoZIhvcNAQkUMVgeVgBQAEsAQwBTADEAMgAgACgAZQBjAGQA
-cwBhAC0AcAAyADUANgApACAALQBrAGUAeQBwAGIAZQAgAFAAQgBFAC0AUwBIAEEA
-MQAtAFIAQwAyAC0ANAAwMDEwITAJBgUrDgMCGgUABBTtLc+uuG76aVNLcrEgZ+1M
-peI07AQIkVLG3UDA77sCAggA
------END PKCS12-----
diff --git a/tests/files/ed25519-encrypted-pbes1.pem b/tests/files/ed25519-encrypted-pbes1.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-encrypted-pbes1.pem
@@ -0,0 +1,32 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFcwGwYJKoZIhvcNAQUDMA4ECLtv2JchVem9AgIIAAQ4cVbXStmw+OANljhnJppr
+YqzWhH0zLXu+SohCnQ5cb+R0AsffwhT118Fedfi6w2b9RAL8EhvLk3Q=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFcwGwYJKoZIhvcNAQUKMA4ECAHGvbJ4ns9TAgIIAAQ4GXgJkD9MzX1OzBP/Joo3
+5ORepS0PLaDUk6yYU7jfD9ov3JZXnxxAyQsgfwIJcJHE0lDm8IL5qUA=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFAwHAYKKoZIhvcNAQwBATAOBAhN+aPvlVgjrgICCAAEMEd+AOxYLmDMzouXzEo/
+Z4XCVMZrBGGrQIYSL3d7r/sqMHklH+SC0JtdrVHzJyCuhQ==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFAwHAYKKoZIhvcNAQwBAjAOBAjCex3tcisjiAICCAAEMAgOe/PJs/m84I+QR2TY
+ojYndqNX4lWnQs6rl5YK94QB0V5rDZWN9YvmZGmX+/JwkA==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBAzAOBAgmU1ToGVo9cQICCAAEODaxyq/B/HNpbpsDCL/w
+fYohLS5QA3s5OIKHo9uELz2GrTS7aI7idAEPY+iFo+eFD6Ql5gy7GVkb
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBDAOBAgiVSOpDt2NGQICCAAEOLpMO1GOWTMQQ6MN0F+u
+LyTzAruvs3lbad5wd0EyGE10m8c+2vXSsl6DMEkaXrjRovsCebZwVzK8
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBTAOBAjdfsUUZ9k3AwICCAAEOERTz7/lepT7BrKWJ1L+
+IilhhSxYjjeHzCNYGoz8+WIRhcykLlZHnAcVTNXWkhNjMf8MEUmfh9My
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBjAOBAhwossvpdyt4AICCAAEOBQ2Yh3yt8lrCppVUJGv
+W7cdL+6gH1H6dL+cGeOsVnpFUNwnEQS92OLQYENAO4B9vqYgOhnq3rDF
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed25519-encrypted-pbkdf2.pem b/tests/files/ed25519-encrypted-pbkdf2.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-encrypted-pbkdf2.pem
@@ -0,0 +1,39 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAhYNXHDae7APAICCAAw
+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQII/ySuK0RdrIEOA/iQWXFUgSRi59Z/okt
+ab+6coDwGEB6ywo+ysTpEwSTMICq6xWcp+BnACzs1FWrlYS/wWDxzeKy
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAiKSUa4hjdNcwICCAAw
+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQICGrlfM+5tqAEOAtOZfvoEAPcFs3P
+k/D46jpfdBroO7vU3Ki/6+2gRWrnjWWL4UyYoMkT8ZHVk4uOa/4mvnakkU16
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGLME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAhMNKAxJTPu/AICCAAw
+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECHiEUPBMhqlQBDjXy5F/zsQz/cVA
+F2QfS7D6zLuLjwPsNGX0m3+070d3usNVdyogbViSEhcbvTmOtjp0DB0BNMPr0A==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGdMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAgrm/IrFyklqwICCAAw
+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQCsvDlOh1VOEXubKKcLMWugRA
+GRNH/la87zQtE7qPP8SsTQgY2/f10rEHLMZ80ysfVq0oGmRfrN79QZETYLdsckWt
+HmJyslMCCCswvM8o6rt1wg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAhuP6SU4nsQrgICCAAC
+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAhgU/sm47TbAgQ4wCSj
+KK1+rp72Ucw107jbsrffscMFx8NoYYK2RzxnwDZzjiy23bBQlxlEZ94Y2BzNT1Hi
+WHJBcls=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAiXc8D7B3BAMQICCAAC
+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIQBrLaUdGxWoEOJvN
+6DN4SgKuaFDap+JvqTmx2yKEEr0+0HbUMIVUOZXaiw1kVmLCR/jaoLUJGz6rjSGv
+9RPCtrvw
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAjxi1l3WQPQPAICCAAC
+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAgj4x/sdHkh0QQ4eEY9
+UwHGaNE9AsBfjCUTjVRzvrJedZvbPTNIAjipMYW3uYzVcg5I+GE/4V7yhKUzBipS
+RLxOGtk=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed25519-encrypted-scrypt.pem b/tests/files/ed25519-encrypted-scrypt.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-encrypted-scrypt.pem
@@ -0,0 +1,18 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgale+OTVw8jgICQAAC
+AQgCAQEwHQYJYIZIAWUDBAECBBDYon9Yg2PXUt8HMaRZW75QBECKmy2N93fMD8+d
+dpTnG3bXuLETOhn/SGCG35Pgzvp3gZoRR/tksLS0IBXdmbvjRGmMiWcMZ57Zcd1a
+PJvyO9p6
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjA3W23hPPCvwICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEWBBDIidXH5KqQ5BsAcSf+VpjxBEBuN9xS3T/q39Sc
+gVXmgBTsfD9XI1owhsiG8r49GF6iXztnyJPMIYDMkjsnk4ypdffSTXlRpRUv8lIj
+vGE7LUbr
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgrfV0wImuOLAICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEqBBDglOp/wHw9J8ue05R8hroDBEBP2X9ptHWTKrb+
+WwyGW+s2MuVulF4In3RjYNld1ZYQeeQk4Vnqwq9t+2djgXsKL4hhwEfhk0wCH64b
+z0EbHIzA
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed25519-pkcs12.pem b/tests/files/ed25519-pkcs12.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-pkcs12.pem
@@ -0,0 +1,269 @@
+-----BEGIN PKCS12-----
+MIIDSgIBAzCCA0MGCSqGSIb3DQEHAaCCAzQEggMwMIIDLDCCAj8GCSqGSIb3DQEH
+BqCCAjAwggIsAgEAMIICJQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIGFEN
+2HUjTJMCAggAgIIB+MiKzSuuknXax1GE1S9jQ2WfuaNablKCYNUB6wAQJkqiwUjC
+DhW1hAcz+4o8Ow1NmuGNiTR/9RKp9ZJmU/GFQCfKInNRL/4WzgQ1l5pKIYYKmKgH
+YA5Ci1bnVA8SEXvQxto7K2maWrxvuSxLktHo7ajYfI+Ze5qiGYET9TqWXRiql9ld
+oxbtTFDkSzNC4VxqU8HYkZFx/4ld6oKJZ71QaSZgf4t9+BkHNfOknOhAp39WKOhy
+3UBmopdBUPpqJAdAPQmR5BUeRibIC+3RllvRW/FLOW/FwfyVoQESUu1u2nl4C/8z
+t3i14EVcKuNbBvz0Qja+4fO+p6e9+q6clFVYCU6UvZQncKX8mRdCSGDZOla018L4
+Lf4SauXnnyyGk8bEI6kqATtkPh/pK3nVBcjboX437+ZbGRj4H3viC7rBijaTizlx
+gb8FhVaryk5bwlzBJQUrtcndd5WdjCYhZPMaB/xFxqSotWWzvxya3A0Z/SgBKj3I
+6Dbv+hGIGACVfcT7k9W7pPC8QaESOCZJOqzckik4h9huEyyDQyvxKa18l+RXA9AQ
+uop3gtG4WRiosSxzgNPh2e5RGVM40AY3ktrsuilZP9skDMF5viTin1C4p7rPxD3s
+dzs5augp0VppJTA74czDilJs3f/u18PPQS2r92ckcM836SDbAjCB5gYJKoZIhvcN
+AQcBoIHYBIHVMIHSMIHPBgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAO
+BAgHTXORITxbIwICCAAEOHln0Ez/AkpJiI7hvzh/t01Rsl0foaq6Mt28OJNcaLLH
+CoyMtg2TTSDWlxrwcTc8A2Ug5mf5jzFPMWQwIwYJKoZIhvcNAQkVMRYEFBUJzG3C
+AW0LFRvdr7FoWoQ3wIq6MD0GCSqGSIb3DQEJFDEwHi4AUABLAEMAUwAxADIAIAAo
+AGUAZAAyADUANQAxADkAKQAgAC0AbgBvAG0AYQBj
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDkQIBAzCCA1cGCSqGSIb3DQEHAaCCA0gEggNEMIIDQDCCAkcGCSqGSIb3DQEH
+BqCCAjgwggI0AgEAMIICLQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIyxVd
+QGw+wjsCAggAgIICABwReIhjPnmxn4HZySDDd8+z43vHNIlplmHIKyKuWkdPZNHi
+knA/nqebK2gdLx5hcauatxUVEQCaob/wzbxPfK86NDbf/2LsZqm8TiZbMz69sqxY
+2M9QpjRY5WKPnbA8s0uhnsdm5UxCHF4E2omZA6yqPcGIioleCJ+d3b7U74v0uxn9
+SjdAY6fw2mJo155sTZHaK0GucDmzz0h4N8RzDhURTwxMy9eXEaBy07rRfDJ1iFTB
+YTWNOu++XcwvzWxV8h6EEEBbSE0kuLbnPwN4L8kcxZV/DhlW6QbiwVOJWL4dyQ84
+FzxkdIC5lzhvw0lzOGM1WMI27fvFhHXzmRDTPJRZhsjLAZjeJwlw6CDY+04VNi5k
+PzoC/mQgRjf8BDCWMyHcHyimrlm43OuIr4lW3qfLP2bUHue6f8ppZq6napC1V86P
+bTsYjoDrS/gFwjpieA8JjTVV9yWu+IRsA22th/ensplyfwU/wadDOXJFLvJrP1tL
+EavSjphDR1+VSXI3Xp8k/g+RJzxd2P0sTcUyqCTGDCJKplYCD8V/azfitlGfretp
+SFHqVVV/1yaFAZu+fI1IFN3YiSqeJrjbGeus9hhLs5jsiFzol3udh9whmp09rYCS
+6BPXi0m+izvcLgffoLwjidx2hvwSbRjTUTj9iJGlz/Bn16djzDgyTlJVOm1fMIHy
+BgkqhkiG9w0BBwGggeQEgeEwgd4wgdsGCyqGSIb3DQEMCgECoFowWDAcBgoqhkiG
+9w0BDAEDMA4ECLuL8XSr9Wt3AgIIAAQ4sIhPGPPiVe7pC3a9af5gSKLNIdbbdYXg
+DvnsCB76bZQDkWXfe3+eKwU0RjX4Tf7sEKBSitUQdyMxcDAjBgkqhkiG9w0BCRUx
+FgQUFQnMbcIBbQsVG92vsWhahDfAirowSQYJKoZIhvcNAQkUMTweOgBQAEsAQwBT
+ADEAMgAgACgAZQBkADIANQA1ADEAOQApACAALQBtAGEAYwBhAGwAZwAgAHMAaABh
+ADEwMTAhMAkGBSsOAwIaBQAEFHPqC5cmwg+m5BlOEYjZTtH8iD8wBAj8srMZ8dqO
+YAICCAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDrQIBAzCCA2MGCSqGSIb3DQEHAaCCA1QEggNQMIIDTDCCAk8GCSqGSIb3DQEH
+BqCCAkAwggI8AgEAMIICNQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI+m6G
+P6kpcWcCAggAgIICCL51B8K3PIhgUNA8wpwp4dad0LOS8IbVvrN522K3/urilvQl
+ZuxvHgWhbg8vm47LYCZD+M8k3RHXSR5Rdd9qCOD44bVwFlM8Fq0yYg9hMjhGPgMk
+SYKGErU2OppX3FJPAL397BlWTMpvpvH9t0nEnvAcEOEFHWytTN8/80Pl2j2lseGS
+OEMCahKZ5yfLcNDl+SGA2hfi34he62i80LWWJye5UzPEKJgRU2KAIR1OMciGnpmN
+E90ozaZbgrZqnwI3bYYpAGMPBWwgpp4CVvBpJxT5wkw82TaC9WXNutXQmBWri1cC
+HXrkeqG7lO2E79AkJANVq0LSaVhcDb5lYoqif7oom9W7gA3SoFQdN8j6LE9XyHTc
+wGCP4jspnA66fGECOIerziF7b4EfQEIFtmX6DSpE9WCjL9MoU8Ftxt1UzY6WzXSi
+LrsSUb9R4sdkzjzkgw67r3UAyvO845qnXxNaUtCPPOq7pCSOwPNaecqSwNxQgxZg
+xo0yyZbm3bjyrLK1suvhegk9TANTNjmyaYhQ/DyO07+V3ee01rMYVOre0hWliQRW
+bfYh9cGLFKfQcMmwRv1bCUV0D8keOL3iTxQM7ReyIZeJDIqHfDckhWgbvpBs4dBX
+GFEimDEsLfXjFPK8M+z3V9Syi5ywigwICyvXIat1NspD0xcTkp4BhKCVNct0m2Mo
+7r2LA3IwgfYGCSqGSIb3DQEHAaCB6ASB5TCB4jCB3wYLKoZIhvcNAQwKAQKgWjBY
+MBwGCiqGSIb3DQEMAQMwDgQI5KEgbc5PTYACAggABDhD49uTQNK70taieGdPG4+F
+jDIhYIWX4BqI4Op+i5RrLmZLLh8IIwS9u54L2mU8iLixcUGJM7hwdjF0MCMGCSqG
+SIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CKujBNBgkqhkiG9w0BCRQxQB4+
+AFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAG0AYQBjAGEAbABn
+ACAAcwBoAGEAMgA1ADYwQTAxMA0GCWCGSAFlAwQCAQUABCA9iAPHbYTZUVBGKEN8
+m6ecISHakuyKxNOMdo9sjrPOWQQI1CwiN3JXU8MCAggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDvQIBAzCCA2MGCSqGSIb3DQEHAaCCA1QEggNQMIIDTDCCAk8GCSqGSIb3DQEH
+BqCCAkAwggI8AgEAMIICNQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIhOBk
+A4W2LFMCAggAgIICCLYRPF4zD4p8DxHKzg+OJXB2KIb7k/3LvCOt5XdpFuicw9Z9
+oBVviniBOI9AnzYwIm8lKCtfEazSY7jfRqX3tbLNltN6uBr8EaQM6mu5glgHccEF
+LG4A7CNgks85IwFovCj4zT1zE5PrT7B8xUmLlMCGup+dImlmBPvzUaNcqq4O3mg+
+m4NkCI0qW12LTczdy54mPiaNXaklqQHjHBSEGugtMexm9CWV8t2bXuZsZv161NKj
+d9lu2EfVN+GutIervK5MOa0o91+VjdFWh4UW7GjWmlFfvCO92dg2BXnm+J+sSV45
+PM3UyM9DhxjJjYpv8E4Qm0AH3ylx/WY+23AKa5LzCokfdrPOfYC4tQWDZhtxclvl
+bZ2KDJ9TUaNoXB0pdaSYppgn+AfMTPacgtDRTVfWE7TbnyTT9GV67kNhZeeJnyuP
+LwjjGT/F1HqjiQswz3+ssdIjsfXv2M3laCF30kWrsRj+nUx9aFbplovYhm+Bsimo
+GBsk+JSV8aWDO5v9WInQ0nX1qdMj70edt3U/jU+8fXrdoGshyJlJL5vhTN0lrUKR
+y+Rugp6DGu2Jh4C+8Plzt3rsck+VR1anY3sLqYs4Nm8NTmrlO5W6iVJI96i6Wkbi
+kCkQ+8FZAw+a5NvNX9fY1BfFLGCGAcYBJsKXATMP4e5Wu2rpefYMv+gtLBVgVoDh
+A5vGr0UwgfYGCSqGSIb3DQEHAaCB6ASB5TCB4jCB3wYLKoZIhvcNAQwKAQKgWjBY
+MBwGCiqGSIb3DQEMAQMwDgQIEbbZB/ZOFvICAggABDgz1+uoucIRZUyWxQCnM2Z1
+VrIDzkjSRnzfh63gtKAYax2kaYUzpGnZMY4DrYi1WQDTmnfTY7XE7zF0MCMGCSqG
+SIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CKujBNBgkqhkiG9w0BCRQxQB4+
+AFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAG0AYQBjAGEAbABn
+ACAAcwBoAGEAMwA4ADQwUTBBMA0GCWCGSAFlAwQCAgUABDAzqjArAvU6yK/h2AkT
++5m4+jU4enOBGOCCtPDy0+0jaM1EkyDTqYyxoxsQBDXwSS0ECLaGyXHiSBnYAgII
+AA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDXwIBAzCCAyUGCSqGSIb3DQEHAaCCAxYEggMSMIIDDjCCAhMGCSqGSIb3DQEH
+AaCCAgQEggIAMIIB/DCCAfgGCyqGSIb3DQEMCgEDoIIBczCCAW8GCiqGSIb3DQEJ
+FgGgggFfBIIBWzCCAVcwggEJoAMCAQICFEInN18eR8QZFfy9Yb39ycd56f8PMAUG
+AytlcDAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDkw
+MjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEfMB0GCSqGSIb3DQEJARYQdGVzdEBl
+eGFtcGxlLmNvbTAqMAUGAytlcAMhAPb4Tt742Tc9pS4/MMDFCjYzApuBncDQ5JWS
+0QL4ejEUo1MwUTAdBgNVHQ4EFgQUV4Bp7nHkyDQxsCEBe5xqQauBKXgwHwYDVR0j
+BBgwFoAUV4Bp7nHkyDQxsCEBe5xqQauBKXgwDwYDVR0TAQH/BAUwAwEB/zAFBgMr
+ZXADQQB+hVuzD7hONYSSQAZ60fo3ZBjs0R/guNoOWIGBCgSxaqhs//Np31/mmiZF
+bsT67I2lYUyh0ogms4Y2av5dJ40FMXIwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0L
+FRvdr7FoWoQ3wIq6MEsGCSqGSIb3DQEJFDE+HjwAUABLAEMAUwAxADIAIAAoAGUA
+ZAAyADUANQAxADkAKQAgAC0AYwBlAHIAdABwAGIAZQAgAE4ATwBOAEUwgfQGCSqG
+SIb3DQEHAaCB5gSB4zCB4DCB3QYLKoZIhvcNAQwKAQKgWjBYMBwGCiqGSIb3DQEM
+AQMwDgQIX1+nSa1n/HwCAggABDjgLTq2CBAruDxaYp+1TvD9bU2VV5rzE8J4e20O
+1lH1ERi0gHw7nLtjAsxBWVI20NOyalAB0cG4LTFyMCMGCSqGSIb3DQEJFTEWBBQV
+CcxtwgFtCxUb3a+xaFqEN8CKujBLBgkqhkiG9w0BCRQxPh48AFAASwBDAFMAMQAy
+ACAAKABlAGQAMgA1ADUAMQA5ACkAIAAtAGMAZQByAHQAcABiAGUAIABOAE8ATgBF
+MDEwITAJBgUrDgMCGgUABBRTDkUp0GY2M6X+Hz5M0GBAj6ZDYAQI8L4NQjeGa+4C
+AggA
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIID7gIBAzCCA7QGCSqGSIb3DQEHAaCCA6UEggOhMIIDnTCCApIGCSqGSIb3DQEH
+BqCCAoMwggJ/AgEAMIICeAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG
+SIb3DQEFDDAcBAjB8PuFLlFLeAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME
+AQIEEBPci+1MGZrIWpKRWObpG4mAggIQ0CxiJz+08OHzg/h0QX5++RjcH3HeyNs+
+C84tjzJOcnRe1xRZ9ritRaPuFN+FrEXn73dKJmTogpXvGGlfxomoHyVuLRl1HBbf
+PUz+rhj4NHoRfTVBwqaP3GNO7zOmipqQrMi7wZTtUtxaXgRzsEruHCIe99T+5MUQ
+OCLRqhoHh7ukwZlAyYz/dtdnk3Q0GfQAmQjhf1VEsIMdE1YwcQBInA3B08uPFSnR
+JG0J7Hs164wU/o7oust8uPxuGnmEJjcQ3DwCIHN+5cLiRmO5qnZc6lGKhJnHGEWI
+BoziLgj993gkB/2Ir1RwWsze05bp6XPI6irRUyLOkC4WuSOFzTu2ck3fL+tVgmtK
+wUSO93WwWspDNdQX5kR/gIl9WbAYWkCjAhk145dlXRTlJxpksp5taQF4dY3GHXs4
+NrjCe5773t/xW1k1kioZL5mIjT2p9MUM2DT5wkbpd9KI76pIB2BEjiAo+cZTJ9e+
+Ck9JnUrck2gSYg4CloGabMXt9nsJ+V4UpcJm2eXAlswno4cxFmXKlT+GtqzzKQH1
+sp7YAxrdqxATlwsyD52rEJo/rKOoFt6dyDcc8PLdXrErfoY1WAl+Z6p4iDp2VYqr
+GMx0R5uArjhR+R7rmGqTfwDFgc6pp4HWBwGoQ2ZfWcpQBUNJPaZ4o4uEpd270XNR
+mW+BQMXXbsRTUK9AT0iL7vCFVdyPQg4DMIIBAwYJKoZIhvcNAQcBoIH1BIHyMIHv
+MIHsBgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAOBAgfExpEdcn3jAIC
+CAAEOOoKa1gzF3tuBnPxpeEqkeIrPQl+t4jyzFj11QC2QswhPTcc0NLIisoTllk0
+m8VrMAhZzbz7xHcTMYGAMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqE
+N8CKujBZBgkqhkiG9w0BCRQxTB5KAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUA
+MQA5ACkAIAAtAGMAZQByAHQAcABiAGUAIABhAGUAcwAtADEAMgA4AC0AYwBiAGMw
+MTAhMAkGBSsOAwIaBQAEFF0dkSA5il/b6lVIdVS9t3Yh092sBAi2pnHLHUDLgAIC
+CAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDzQIBAzCCA5MGCSqGSIb3DQEHAaCCA4QEggOAMIIDfDCCAmcGCSqGSIb3DQEH
+BqCCAlgwggJUAgEAMIICTQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQUwDgQIksEU
+f1NeOYYCAggAgIICIJSOWj0qBbk5tlV/M5hekb8fYtjFVjbN287fAaWFEHty1EXo
+6B1wKULhaIBJdlFDa8feE6icJOtYHwtiGKRAfo2HfAeu4IsHEY9Ejvbq/vT7QTH2
+GvKWmE45mZTavedRnSLGBq9/JAOZMvVnIoW5DKji1e+piXP8sTAn7R6QWIELvf7l
+LU/u1dWOZGWxWkztLAAU4E/PhAYlVMkOD0imZIYcF1wKhqpZUgzsq53AdOWBghz5
+7Y54rULci1kZ9H0e/YvUGsB2ahmJtgO6vCLwi06+ohQ5ONzHPYOaZ+yKiBou+w4I
+YeOLlk7zTDOFKAVgEHsg2XSMvFRVnoPDm/UxYzITKlmyu9WWptmQnVtlf0YKK6aE
+lirn10ZUR4oO5AxlvNdJyuBOHFb5z0eEGXta+dKse381Y6lRAA2/nA7z5+3fosch
+MeU0Jc7D/ZWWCmPOsLzPvWWOGYTxU7iba10iMhEvT55qCCMrYQAEJ45IwWItKCMY
+aKiNZythBWiajke41tZYWgsVcl0PcDTlrmqaOcGCKJt797hH9QrfGlnpRZ65dIVY
+LR8QSjZJl30doV6ga56Y2oktPvQCgs6MLcCU/QlAGTGmneMRnBwm0EgU/fQwg7yj
+tIGwbkd4z8Fw4quRnAcvjrlmePM1vRo5cX49KGnq26WS+oMLWh6cGotVPRHZLzbA
+gpLUBEv5HUYLS7vEbTp+TCn57OFrXgXXuxR1UmUwggENBgkqhkiG9w0BBwGggf8E
+gfwwgfkwgfYGCyqGSIb3DQEMCgECoFowWDAcBgoqhkiG9w0BDAEDMA4ECFwonOIg
++flzAgIIAAQ4kpdDC/If2Ro8CTME3LIf3OvZw38/CliPX6Yy4MLI7JSME5RQUrhY
+/Saw4/L6B68mukeWZMBnOmExgYowIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvd
+r7FoWoQ3wIq6MGMGCSqGSIb3DQEJFDFWHlQAUABLAEMAUwAxADIAIAAoAGUAZAAy
+ADUANQAxADkAKQAgAC0AYwBlAHIAdABwAGIAZQAgAFAAQgBFAC0AUwBIAEEAMQAt
+AFIAQwAyAC0AMQAyADgwMTAhMAkGBSsOAwIaBQAEFGs4YBeZuWInWICuG68IQgRV
+jLS2BAh8zZrG9uqvuQICCAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDwwIBAzCCA4kGCSqGSIb3DQEHAaCCA3oEggN2MIIDcjCCAl8GCSqGSIb3DQEH
+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIk4KE
+evBBgTMCAggAgIICGNsQ9DokOToB4WN9X0SQpj0yKDTMzWWiJWouEdbnH9rpsLHB
+oxPJRSdvl6qYD9BDIIyQBb72/oJEyv9x4vevZ4Y+dr7TI5iam8cqf/jqCRDcJngJ
+F1OeoGw/48yniY+wLLX9stA7mLVlGOqmkv5ubr4oS3B2DvMgouaiqOzBY970zepk
+aI8v5bDciknvNxLcqwb8gef9qSi2lXjSS+da1r/Auv8wy1DWMb0wXppkr6iE4eRD
+GBrJBCV0+0GkdlEQdZlUwF9L6Rk5Gh6T5sfqb6MfetbKdJ9o6ZZsgBiePVco0yrV
+gPVCaDkZwoIH+iSixn0YOqVjzWrnARyWKdf3esXGloIQhQnrfuDIdHK8YBlXuN3z
+EfSmfRh/+aWG2DW4s9wcAB1t3GMZewMZ+z+Wrie/sYgp2o/105Q8dUQqNDu4JBYH
+1xjfqyjlWOtzEyJk1wrUefOWIFlHkzwZcGrY7kUSJNWY+ljm+ZZbvJWQE7mPXet/
+yZFSXv42FwFwU0C/Lo8BFI2c0KC4CVuFNmfNWToJfa9nnEvInh2rra/XyuyeHHRe
++Bc9CMCkKWnOMoC/p3jnjatqjsJdHhXVViJQcvxjJXwyJpRi3G2v1SWz6oWFx0eX
+7K+rZWnrTT3QI9vSX6aCQXcgc9XNRtdObvQ1tToOt+qgvUBerqDjmvTp6eKsNw5a
+Z5ZxH7oqjcWgnQIYfT2aEKMQ38RMMIIBCwYJKoZIhvcNAQcBoIH9BIH6MIH3MIH0
+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBAzAOBAi7tsvABCrGugICCAAE
+OL5EeTq4HVyV26RNPe2S3AKQyGxNzkwMJMBGyhIG1XPPLqumvDgJqrPcAD/QwRVj
+MiJb51N45xC0MYGIMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK
+ujBhBgkqhkiG9w0BCRQxVB5SAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5
+ACkAIAAtAGMAZQByAHQAcABiAGUAIABQAEIARQAtAFMASABBADEALQBSAEMAMgAt
+ADQAMDAxMCEwCQYFKw4DAhoFAAQUzx421ERBMyF/yNxR0GS1DLlMANMECPiknVVT
+vYXVAgIIAA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDZwIBAzCCAy0GCSqGSIb3DQEHAaCCAx4EggMaMIIDFjCCAkcGCSqGSIb3DQEH
+BqCCAjgwggI0AgEAMIICLQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI70+G
+ehLEN/oCAggAgIICAHqLWEksJuuLz0gv91JjhDKOXcFZFi0FPLkid4IvSGamAOBt
+ABgWh2v4GydPxAnLdeggMYwL/mjudrWuymgUu4fjIvTLZi54duYzBr4B+iMShlo5
+C/kc5qTqhWBqPfRi6b9q1ryWZQuJR5j101BLRU90axIUpT7g8UEWzmLtEZfbZj7m
+5cXWI21H/3WmzrbhBT+MBL7PeSmAL7SB3z+jGpqjIAlgDSABWpINyKQIGTpwAQK9
+v1y8nJ+VfE+V/MMz4nVBdzpiP8eczg7v4CeJq/AhLU1zYS/1wRs0Smgh4azn+Hpt
+AoY7KfiashHgZnQOQ/YCKoUwDBoZg9A3tFiFHR6FNXXX0VChUzPEbfAKtzQ+J9vz
+prl3uIBvOmNbPLK9QUOgCRfmMqpSzqx7WCBU1KHynS69KBtEQCrxphItLzLon5K3
+uFjuavUkGab6WexWZC7yuVihFZbRunXjrqfP0AMG98f6iUhSLKmyzHqho1Xx/vdJ
+ly9BFt0lo4ZV4dySYLy8fOSOw2GqvxFEVj+Giz0a4RCC2vbK5IjyTf95lsp3qiFC
+fBwMhv39AtapGQVdVqMrA5P5TFplSZoz2dpnIlPwCbpyvpEe8LKc6oUpu5eKmCeU
+RQ0zj4+J8KAzfVbzS1yMJe+umu95i5vfEUCs98C8RvDCnHsx87T/x7w2z8b2MIHI
+BgkqhkiG9w0BBwGggboEgbcwgbQwgbEGCyqGSIb3DQEMCgEBoDAwLgIBADAFBgMr
+ZXAEIgQg6so49BSrEJR5mdkomLkSagSPGcNJIanjlT+3V6/L/e8xcDAjBgkqhkiG
+9w0BCRUxFgQUFQnMbcIBbQsVG92vsWhahDfAirowSQYJKoZIhvcNAQkUMTweOgBQ
+AEsAQwBTADEAMgAgACgAZQBkADIANQA1ADEAOQApACAALQBrAGUAeQBwAGIAZQAg
+AE4ATwBOAEUwMTAhMAkGBSsOAwIaBQAEFG+LXFJAkw20X9gqruZyFGH+jWmOBAgD
+bdl51a1P3gICCAA=
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIID+QIBAzCCA78GCSqGSIb3DQEHAaCCA7AEggOsMIIDqDCCAlcGCSqGSIb3DQEH
+BqCCAkgwggJEAgEAMIICPQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIow4v
+Lsy+8FoCAggAgIICEA1VfaX02WNEBrOhNyi065Y2/GOi/rq48aMVrxk66tdu8J1A
+scvnzo7ZKJAb0f93U+NUwP4BTvd93vYmAPa++3Mq+alBZvkT+OM+fmIbI8ZACvlr
+6B0FjU4+GOLferGud5k7GopaRjVnNErXPBtfmCi3xqfrloRZNaJo1PQIrVXUpGfK
+xB5iBQVIyTFZa9CGc0OFenGbegoWhR0NEa7M/M4zynaY+4HNEqv+yY8mm+aiim2E
+r/Q3e5zHdr63k90ltnA+xpNyjeaLhBHixgD1njzPvHkhNXenss+GIMi54pmSz52N
+gRX3K9ZwT4eSOQwh19fAJmK2TpCgGyffuoQMRWJfhrAsZdLoLzpOdaL9CR0FEFwy
+mB1j41xMcKe2yoSOGi8A92Lbkk3xN0c57fECcLbwoaZZyegAqAYA94xr4M6yzvpq
+7qVZKNaB4F2n4iGbfwRgendan7LjTfPhyesjuph71/ee4WVygOm1HEq1lqLS4LzX
+FFwINo39oQTAIYxoXK7u8V5f/3Qj6DgiQkVMdvwn9qi8Sf6BG6AvJw4cjSbb8g7Z
+RQ9lUaRtVx0vSjxd11fOp2etNTcvxDfM9du4lT2lKxJUP4ZHiwWveCGukXwaVs7J
+BGzhCB9X9Lju9OdSjzWRPe/esHZ3uyyKif+uKR2st9pMq5QHwK53lkiWfCdnLKYA
+U+bgNu+U+gZs3xPpNzCCAUkGCSqGSIb3DQEHAaCCAToEggE2MIIBMjCCAS4GCyqG
+SIb3DQEMCgECoIGeMIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAj3
+PgR467622wICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEELWZsHeLIm5n
+za4zYKSmtdYEQNaOREFJzeUI9EuL/4xTKOTRWK4kmQ6rYPc8/xbtUTdARKblfWGd
+POrYXUivZPNJEn38GCAWmNsIWGGOVc5kO8UxfjAjBgkqhkiG9w0BCRUxFgQUFQnM
+bcIBbQsVG92vsWhahDfAirowVwYJKoZIhvcNAQkUMUoeSABQAEsAQwBTADEAMgAg
+ACgAZQBkADIANQA1ADEAOQApACAALQBrAGUAeQBwAGIAZQAgAGEAZQBzAC0AMQAy
+ADgALQBjAGIAYzAxMCEwCQYFKw4DAhoFAAQUi8/XCc5c6tAh7OhhCR2ZKE8sCdEE
+CEeT/cMXItocAgIIAA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDwwIBAzCCA4kGCSqGSIb3DQEHAaCCA3oEggN2MIIDcjCCAl8GCSqGSIb3DQEH
+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIRRt1
+aGJ9pBoCAggAgIICGFNFd2VuEF5PLthNtYaAYLz2s4nHfzIM0J5BloKddcY7yeIB
+mj6xMGPO0U2BKmRHk2lBtWkLRI1C7La+aLv/wqccnSq+gpXVfHGx++rOx6SDqjXK
+53VdIrEGWIfYkJE3HqF/WCeuvot+qBK1JiRoJN1Sb8MSXHuoIjR21ic9vJzjGLI3
+hyxmzzekk0TZ/7yfdoWc3MU76ZlBGMzVhbfHlz6Oi1GG7J+TMs6hnuO2BDJMSAAV
+9C8xwU1B9RAiDsZXL3lCq8My2+u5BCNoYOcuPnvMlk1quZjuMuQJvFJH0ZfRnf97
+zItYGcqNqxWbs3S61ma8T8jYZi1eL0hvD96n6Im+FRF63aAgeXWXLvzgGJ2+u58W
++OrKEhpu+ipPtdwdWR46A3m8+jyctNolC8X/1goLs7EDDGfjQ77KIWaHxP5G3Ez7
+v4dB7IlRIjHwoFwCd5TXiORrsQQWwtnSgr16st6cw2ObLjUeFKlAGBPYE1hP3rbR
+6r3+nMsH8J58cfvqmpL0+ZnFvIVZcJtb5GG7FEoeVhQM3ZdIZjmXreF7cwe6K3s9
+cnF2v3ikQ7rH835VUtWAPxPmh6e33jJLyF1LmmYMSv81R50jKqJdkqCI2dDUa1xG
+ZgI6mJwtnwi4jSOCRs6icJR5XXbuyPOACACV1hrXEVTAXCnkvrB0vZ8FjTqbacS+
+1PI1v77ypPIn95aFn2fn7i8ojYf2MIIBCwYJKoZIhvcNAQcBoIH9BIH6MIH3MIH0
+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBBTAOBAh05qDI2Ar72QICCAAE
+OKOQzu+R5I48MSppj8vB6pLOR2z7g54qi3BqyZOeeDe2ACd6O3NfG/PYhBlaf0+3
+Ve18P3K7uxS7MYGIMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK
+ujBhBgkqhkiG9w0BCRQxVB5SAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5
+ACkAIAAtAGsAZQB5AHAAYgBlACAAUABCAEUALQBTAEgAQQAxAC0AUgBDADIALQAx
+ADIAODAxMCEwCQYFKw4DAhoFAAQU3eBhLahYNnEAfOqstFWLRhkGW64ECIM22J8l
+0UAiAgIIAA==
+-----END PKCS12-----
+-----BEGIN PKCS12-----
+MIIDwQIBAzCCA4cGCSqGSIb3DQEHAaCCA3gEggN0MIIDcDCCAl8GCSqGSIb3DQEH
+BqCCAlAwggJMAgEAMIICRQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQI8DlD
+f/FkAioCAggAgIICGKfq8w8XKEbdolrBnthXWJAkm8mw8JWavB//y97b4r7HjtPw
+0uUvfXbhIRnYNu76tac9zlaH66n1xLLqcUAEK3RdwPLN2qx0hs3vulUV16TVao+U
+smU65HjazxXC/Oa3lfxAYhlxX44Nfe9p6uCDdf30G4yDKJNNSiuw5K83jpxrlIeH
+UIGakE7XQ9gmROuOSAXJzmBOjw6rUbBtbUgP/J//yvP9ZeJkB3OFAYhohGo/9ych
+L0wlk6Md6Vtq0nv/zYQP9PofXzfZUh+rZha3rxKioH7xAhnRNxvgh4mksqTyJM6k
+OsXZ3rLvxehTKgSm5clEzr/T3qxjTusuNovTlt/avBnpnZIwS1qzTeq0zrd/m7H9
+yM6NV7Cbl3LtHzQEQkrJGxVACDc6QcCrmHb7tJBO9dAHiZds7OXlI3QJ4D/aGAru
+mlKD4ZFp0XurJOu/p6BocEyXo1D0yYEMVoo9/KGH8F4ugwyIQmc0XxrO5mmeRzQk
+PH1Em4Im4DCKXX0wrlBEPmVuevlh3JmWQ0UEtmMxrhaiQ+Ufjr9TD0Hn7D5wZGVu
+B5ymH68fPVe/HwksVHIC28uYM4EDMdaKs0BiPRxI5u2jBcWfSlow/jVsK86TcZ2J
+4+p2X1949rD9Rz5YKXkaOGnkbkIWAyYDFZF5LW4+jNXxCoo+zloz/CIJmNk3Sn3F
+v7Ao4VDmb8FANwwLzzYoZ6ZI/KmPMIIBCQYJKoZIhvcNAQcBoIH7BIH4MIH1MIHy
+BgsqhkiG9w0BDAoBAqBaMFgwHAYKKoZIhvcNAQwBBjAOBAjrE5kdbf4WxwICCAAE
+OL0cHLqeh5j6Mpts0fF20kwdBzS1/9yXPUtcD+CzwEvFaJCMBpt8s4inBnxN9/vi
+D7hg66rk27phMYGGMCMGCSqGSIb3DQEJFTEWBBQVCcxtwgFtCxUb3a+xaFqEN8CK
+ujBfBgkqhkiG9w0BCRQxUh5QAFAASwBDAFMAMQAyACAAKABlAGQAMgA1ADUAMQA5
+ACkAIAAtAGsAZQB5AHAAYgBlACAAUABCAEUALQBTAEgAQQAxAC0AUgBDADIALQA0
+ADAwMTAhMAkGBSsOAwIaBQAEFKL1V7ctReB15tch/MxfPTTEW0x3BAiQJg8kHcKT
+2gICCAA=
+-----END PKCS12-----
diff --git a/tests/files/ed25519-public.pem b/tests/files/ed25519-public.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VwAyEA9vhO3vjZNz2lLj8wwMUKNjMCm4GdwNDklZLRAvh6MRQ=
+-----END PUBLIC KEY-----
diff --git a/tests/files/ed25519-self-signed-cert.pem b/tests/files/ed25519-self-signed-cert.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-self-signed-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBVzCCAQmgAwIBAgIUQic3Xx5HxBkV/L1hvf3Jx3np/w8wBQYDK2VwMCExHzAd
+BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwOTAyMTU1MDUzWhcN
+MTgxMDAyMTU1MDUzWjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29t
+MCowBQYDK2VwAyEA9vhO3vjZNz2lLj8wwMUKNjMCm4GdwNDklZLRAvh6MRSjUzBR
+MB0GA1UdDgQWBBRXgGnuceTINDGwIQF7nGpBq4EpeDAfBgNVHSMEGDAWgBRXgGnu
+ceTINDGwIQF7nGpBq4EpeDAPBgNVHRMBAf8EBTADAQH/MAUGAytlcANBAH6FW7MP
+uE41hJJABnrR+jdkGOzRH+C42g5YgYEKBLFqqGz/82nfX+aaJkVuxPrsjaVhTKHS
+iCazhjZq/l0njQU=
+-----END CERTIFICATE-----
diff --git a/tests/files/ed25519-unencrypted-pkcs8.pem b/tests/files/ed25519-unencrypted-pkcs8.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-unencrypted-pkcs8.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIOrKOPQUqxCUeZnZKJi5EmoEjxnDSSGp45U/t1evy/3v
+-----END PRIVATE KEY-----
diff --git a/tests/files/ed25519-unencrypted-trad.pem b/tests/files/ed25519-unencrypted-trad.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed25519-unencrypted-trad.pem
@@ -0,0 +1,3 @@
+-----BEGIN ED25519 PRIVATE KEY-----
+MC4CAQAwBQYDK2VwBCIEIOrKOPQUqxCUeZnZKJi5EmoEjxnDSSGp45U/t1evy/3v
+-----END ED25519 PRIVATE KEY-----
diff --git a/tests/files/ed448-encrypted-pbes1.pem b/tests/files/ed448-encrypted-pbes1.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-encrypted-pbes1.pem
@@ -0,0 +1,40 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MG8wGwYJKoZIhvcNAQUDMA4ECFWox81pCznKAgIIAARQzXqhO+QND3jDKs9zv1Kj
+MNU8LPm/PpazS6ec7qkxavm3P+5IFMk8eQl61ZlzgsZh3mLzERa3PI6bWlZLSe8V
+7UHVIpw3KCQcQP42hfAQSTc=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MG8wGwYJKoZIhvcNAQUKMA4ECDioAMUqN3XrAgIIAARQL2hyA+GU61GYy+T1t9aE
+ImSrEUBls7Xl3Ot4hOiLjOAP6jrZqTV05RD9QQwxZHbRDVq/HX/6A3WK0cWd5LXJ
+u9UUhF0M4QTSJ2YvgzC8GZU=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MGkwHAYKKoZIhvcNAQwBATAOBAj+fxvGvZUmFgICCAAESR52DDqYruEbDKjnQYPb
+4pqdQ98Tenh9F9vWcYvlPHKroHOSrPuW5p0ISNcTMjs6WcwfzrRVRLKMIUwUxyOc
+j4+KPXziQpkTCeg=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MGkwHAYKKoZIhvcNAQwBAjAOBAifgbGu7z4HxAICCAAESWH3dIM8ezNr3m5cuklg
+rOFbFNZqS9bNjvRVX2rQ4UsNl1PVNw3cxe9Mg6AYg7mXffibNOVZWwzbYi/8jHv3
+f2atBdH64mI2ADg=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBAzAOBAjiQ6AN6cgfbAICCAAEUNi/xo5EN11/48aKiymw
+3iiopFH549Yhzl7h/R8PMkR/73xSokPpShMUFiG9dfBHiJU+JrlCg7oINFK44+R0
+YAN8vHdg6gXJHyxQuONZQZ7l
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBDAOBAgG3+c8DYCcnwICCAAEUARVfrP73l/qnbQwBVpZ
+RxBDrm3QT3Xn/VmclpSGcGBurOV8qKO2nYj7WzvY6KOxXVfGsrVuh/uOP7hn9l4L
+A2KiGc0TM0sa9GGwg7nVWydN
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBTAOBAjD/E31ow0hhwICCAAEUIEjJ4SJ9Aa5lUhe3a/q
++1sf4bi9tFscXczGy19qKwja3zyisb7A4lFhFFojzN9Xf72qLQZlWerxG/E2xbaI
+/toDPUlkEqareLJSm30dqltD
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBjAOBAgGWbPgRlLvWAICCAAEUBBrF4fOikjttSlIQSyj
+Uc84dgLgNVoxjSgEw5TFVD1hnHf6IhkDE/ke+sKKn897QzW/8UtLdHwdHBE1F04W
+BOx5TskfCIfvbw2U6OAFQUK2
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed448-encrypted-pbkdf2.pem b/tests/files/ed448-encrypted-pbkdf2.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-encrypted-pbkdf2.pem
@@ -0,0 +1,42 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGfMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAgyO4eJgChTHgICCAAw
+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIjCW/gTtbGrQEUBOPwmDji8JmdtYKneqP
+eHoOJVTC4h7EvPI8L69/qCWmdoQwb5FZQ4swhLYhEi6HUzaKF9rmF71SG0yj8x94
+Ys5H6aRYf4yJ8mTYq/jNdYKf
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGiME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAgPnWL5vcrxMQICCAAw
+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIk8VyO/qFnksEUDDZtPg1FOH8PvST
+LKW9GEgmQVzqxYyQPUDVL0EjVbP5TS4Cs/bA/Kx4vX3z312PeY3I8JZtVoBLjWX/
+sy3xgpqnGSmuNcvMwWaaWzcYD06F
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAi08Idt7k1mcQICCAAw
+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECHBod624v49oBFBLW/h5PDwAyvm9
+NpmnMEgv1BHHfVpimwOGrHb29zrB8vxNA0h+8Lo0BcSZTpnL8jbN+IRLfBmyy8zx
+EON4rhK8NS/auC1Wz/a03e4bCtMtGA==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGtMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAiULPupkmkOnQICCAAw
+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQUeP3K6y6H4/WAYH3t0rKXgRQ
++Oo9san10yxV3ZjXi4VJcXiW/efe8T6aomjdLAfKx0hNtnf4YngHtBVyAHPLfI1x
+9zWCBTzEt+agp4Umazk59dBhiIB7eiOoNhUZOFvHTtc=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiA7abz6Y7GFgICCAAC
+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAiq6CcB7kKwXARQKLMY
+sHJoPrAEufRO5/iMNEvWbhrOTY1pNnZkryJTfnJSW5mnNYOdTfYFiItrd/ivH5+g
+yMdFYFBRNDT27J1OBDSJHQQkrXmf+pb966ElVy8=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGrMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAjOvvRIconlRAICCAAC
+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIX+HtJbu7CmEEUAC0
+aGZdMCNUA7aBhThDoH4DhK3F+u4TejId5xx00znWPd5lB/yXWJhaK6Rce/+k69oF
+UUu9NYZnwURQ6SpoMeu7jLRvuasVNDZs9NKPm+MH
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAgGFmNY/ilxeAICCAAC
+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAggk5ndlY33agRQSd7A
+aeL5zykrjjIGNEM6DyzjN7ooiAd4qHHC70FkaNQ8RJneqXXxi1nNZDdXhaLa/raU
+ifdM8M8566L7O/s6N5uKR8W8+ieDlmmwAIZDEGI=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed448-encrypted-scrypt.pem b/tests/files/ed448-encrypted-scrypt.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-encrypted-scrypt.pem
@@ -0,0 +1,18 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjsra6FzTgntAICQAAC
+AQgCAQEwHQYJYIZIAWUDBAECBBBBzcngO3oAta7DS9ulmjcgBFB4gmQnHQmI+rbK
+RFBjSrSrvHGeYzEGZO16iD0bQnPUTwStLE4y4rLE7AvD0/6cjjW9DnvPmPBW26ZT
+IyWW/JZMykzfM0dk6ccNLg0MHibNcg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgbq3ygXPa8GgICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEWBBA3IcY5TlZ2FzxzTGljkaVTBFAEJRrLXzngWHUH
+No+Hq9zGFJ6yBekd403KmlaHCdVPd1JrmgnYoPS7RYxhTMKQ518Zn218Vtf4Pdjl
+XrN2MQ1O/eiQM1lp4lvw7gt4XImUFA==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhoIC6uL0dKvwICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEqBBA3aL14nbkyRn7MPVVqbgZFBFAY3ogxHUTT8ozS
+tDmIogHDjazBBs98CIwdwTYrES20oZ41NCezpXlpPVFrzjqHm1rP6ZudloRPUHG/
+xSEr1JeEKm5mBGTA1sjRJFyT5Chd2w==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/ed448-public.pem b/tests/files/ed448-public.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEMwBQYDK2VxAzoAWopNPKAV3+c61CJqDrnMnRntbN8QSNHZwdKEXjxV0q522y9I
+vhkuuwOJeZpMNqlLL8frTS1ds3SA
+-----END PUBLIC KEY-----
diff --git a/tests/files/ed448-self-signed-cert.pem b/tests/files/ed448-self-signed-cert.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-self-signed-cert.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE-----
+MIIBojCCASKgAwIBAgIUP+cIZ3jsepQ7zNuhZtdcyzaGHa8wBQYDK2VxMCExHzAd
+BgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwOTAyMTU1MDUzWhcN
+MTgxMDAyMTU1MDUzWjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29t
+MEMwBQYDK2VxAzoAWopNPKAV3+c61CJqDrnMnRntbN8QSNHZwdKEXjxV0q522y9I
+vhkuuwOJeZpMNqlLL8frTS1ds3SAo1MwUTAdBgNVHQ4EFgQUvzGR+t5kH8Ta2XUs
+KP+uQfMQFUcwHwYDVR0jBBgwFoAUvzGR+t5kH8Ta2XUsKP+uQfMQFUcwDwYDVR0T
+AQH/BAUwAwEB/zAFBgMrZXEDcwCrI8049W/mjhgaJm79n4Zwb1ryBg8aiFqmvszd
+pFhGmhU1TWD+qPmBWOIdu0laToDvBiITaLAa94Ck0BZdNFsbqq1FRFXOzsLwV6kW
+lLwG53nY7ITnC1MVytl8qjA0tsrnHqPwm5pp8rIzJudNk4S+AwA=
+-----END CERTIFICATE-----
diff --git a/tests/files/ed448-unencrypted-pkcs8.pem b/tests/files/ed448-unencrypted-pkcs8.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-unencrypted-pkcs8.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEcCAQAwBQYDK2VxBDsEOceJ7Mc21YZq6/qusCHA5d3wARXuJxMKSRkvsyD4GEKq
+hlubDZL0FQiS3OgjbeuKN+63xa+OVVz9XA==
+-----END PRIVATE KEY-----
diff --git a/tests/files/ed448-unencrypted-trad.pem b/tests/files/ed448-unencrypted-trad.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/ed448-unencrypted-trad.pem
@@ -0,0 +1,4 @@
+-----BEGIN ED448 PRIVATE KEY-----
+MEcCAQAwBQYDK2VxBDsEOceJ7Mc21YZq6/qusCHA5d3wARXuJxMKSRkvsyD4GEKq
+hlubDZL0FQiS3OgjbeuKN+63xa+OVVz9XA==
+-----END ED448 PRIVATE KEY-----
diff --git a/tests/files/x25519-encrypted-pbes1.pem b/tests/files/x25519-encrypted-pbes1.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-encrypted-pbes1.pem
@@ -0,0 +1,32 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFcwGwYJKoZIhvcNAQUDMA4ECOlAaqWwdDweAgIIAAQ42XsuUzVmkZkOq+m4uBNW
+6qzsaTJ7FPa4itB+tyv4BzPgFxAQTwzzJgSr9WkbYy6LKZIPporO/Ko=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFcwGwYJKoZIhvcNAQUKMA4ECH0TpXboY9MNAgIIAAQ4dojWsXYBrtraqLi1pBeF
+uOyGGClKoj7XpGvXkNShtEnWTKjlNUebf02xQlcmxuksdXNffTMdsww=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFAwHAYKKoZIhvcNAQwBATAOBAjV52wnNhxkeAICCAAEMN5Tklnapb+agqTYnDle
+pHKk61c0d0la81SFGz5pqFBPllX5zmnSWzzHFW7/B8cxLw==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFAwHAYKKoZIhvcNAQwBAjAOBAh7Iwy1tOrlZAICCAAEMK0FLzIhsz8+1ox7grnf
++u+eD8k9LJ0X77X9GeNKyvIJl5lkkWaCblFwd2reMH3eug==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBAzAOBAgWrZKj/deGGQICCAAEOJOPMtVYLKsvzajTT5wG
+hjhbX7RHWL9t9h1H1m/CAW/qJqALZNPQcQ8BiheRJv8LrgYr+Fzl4e6O
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBDAOBAjPUjiYWb4CwwICCAAEOASPWNTFFZaViFQ+WQ4C
+Upl6k5aTHq6KHpai/N6ABiwV8dcXAL5CXMzAxJWKW+ZdUgyydM7xAVn6
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBTAOBAgbZrk3W2y+WgICCAAEONIxa0JMYEvV9curAwn5
+5epAnnwv7vtqO/IT4mqkc0WLHPEZVhxL2EQWBGemGQWPxeRV5jK8RJvS
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MFgwHAYKKoZIhvcNAQwBBjAOBAgfr3469Ve4gAICCAAEOMHa3S4OYcEBCb+HHvJW
+zpNlWtmW4/djMndGWpznIuguxK/7V+A3R7PgvA8K0FhpSPDR3bs/2QHW
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x25519-encrypted-pbkdf2.pem b/tests/files/x25519-encrypted-pbkdf2.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-encrypted-pbkdf2.pem
@@ -0,0 +1,39 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGHMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAj6ZyFipSgetQICCAAw
+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIjgzX3kQ5dXgEOKlxrj9EtG1DNXBDgzDW
+vnFReEN4zACwoxMQ41sAASIW1vCT1TuTIDXLxGM5CwfkyawnexrKlKmt
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGKME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAj4aVxRNkgd/QICCAAw
+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIu/UnAKsbyacEOE3fiHQdWKYI5Inf
+Wndw5d7UX0nRmCjwNQLK1UL+QXujoVmi+3hEZSRfYcqj5M4t7sJlzZ63w8Td
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGLME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAgJmEUBljH9VAICCAAw
+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECB9jZKjkRqv6BDg7458AElgxBUGx
+9iPo02FNkzdUO23NB66AV0puFZAYz4NUY/wgJLA3HMwBERdSP74hBjh+GUT3sw==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGdMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAjyCgD/RmiEIgICCAAw
+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQFNpqaqXLMkVwoDSdtl+SEwRA
+XXRprNwSjtJ/m6V3vFKq+VCHOrj7o2MSpt/pvjVnbHKaHgX9V3TP8e1KOiiXJoo1
+PBv4M1beoCokVjhCECZWyg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiTO27y8UQ3LwICCAAC
+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAibXxpEYv15IAQ4zEoc
+lFpScRoeJ/6aaSI6f4NFG4N4cVFTXvi5YIDlbpwmz4xdIr2uU+jhiGknXDcE2Z+m
+ERCITjQ=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAgxud22g8Bm+AICCAAC
+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQISGwHgT4bmFkEOC8m
+drQbC9WA1ZvACMJWLXEL4JUgejLRDtfcvCC5kAhT2iy23RllpUftspZ4V/SzAePa
+EFsn0jy8
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGSMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAiQqqDVfFc5OwICCAAC
+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAgmnqFFlLCrwAQ4elCp
+HTXrBbgtWoMW/hG5AUn2Vc9W3ycJ9tw9d4pxx3ZHV+FGiEMJsxqPwGVEDJvTgxy8
+G55j3UE=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x25519-encrypted-scrypt.pem b/tests/files/x25519-encrypted-scrypt.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-encrypted-scrypt.pem
@@ -0,0 +1,18 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjTIFxP0Hwq3wICQAAC
+AQgCAQEwHQYJYIZIAWUDBAECBBDJmyEj4MkNJQMLM4t25IIuBECqSAJnq6gSp0Nc
+OwqkgspkwyYGxHfZAJ/aw7BHSGTs+LQexB738lqY/B5Ri2udxqfE/kjKzyn2KTHJ
+nqHQk8+J
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAi0kO+7kfp0NwICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEWBBBnSlCJdiXvxblUH3GgOP3iBECaLsAsyLbHLKcA
+JCxrNQ3JrGL0TVQHPu/6tYFAWJoM4ZCIww1i5nXZUIigNDYERUJbm9od6E2a8apA
+TH5JE4yf
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGTME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjC70jTpl9lAQICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEqBBCKvOsxUwHu7QYwy/j409InBECFy0tXOpJYZD1V
+S+x0XsC/nHHesRzKKbs86f//VcOQBOwbA044IcUNPiaSkuedtlAX84I1rtvtJGY6
+b7fVHtJn
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x25519-public.pem b/tests/files/x25519-public.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-public.pem
@@ -0,0 +1,3 @@
+-----BEGIN PUBLIC KEY-----
+MCowBQYDK2VuAyEAdNNs0T64alqPXbwrJt+rCdkerW6PgrQOUKHUlbPyahA=
+-----END PUBLIC KEY-----
diff --git a/tests/files/x25519-self-signed-cert.pem b/tests/files/x25519-self-signed-cert.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-self-signed-cert.pem
@@ -0,0 +1,9 @@
+-----BEGIN CERTIFICATE-----
+MIIBOjCBpAIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0
+QGV4YW1wbGUuY29tMB4XDTE4MDkwMjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEf
+MB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTAqMAUGAytlbgMhAHTTbNE+
+uGpaj128KybfqwnZHq1uj4K0DlCh1JWz8moQMA0GCSqGSIb3DQEBCwUAA4GBAE5F
+wEzoWRJKgS6OYunevbkvDMOmAjhXg+P2zcuXSndAi6HLlHTsx0tdiTclrNqNkFcw
+XaSBmGhpWy/qI4eM/lrWZV3+dfeNRBb6oqezjgbepC+J1akDNzseW1+Kr16ryQz9
+STwnOqpQhrTQdBOaLlgofw3qqj88bWBsNciJWCX4
+-----END CERTIFICATE-----
diff --git a/tests/files/x25519-unencrypted-pkcs8.pem b/tests/files/x25519-unencrypted-pkcs8.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-unencrypted-pkcs8.pem
@@ -0,0 +1,3 @@
+-----BEGIN PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIIhgx84XP3vLVdFwZWT88BG/gGRXl6YYUeo0lWX2E8RY
+-----END PRIVATE KEY-----
diff --git a/tests/files/x25519-unencrypted-trad.pem b/tests/files/x25519-unencrypted-trad.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x25519-unencrypted-trad.pem
@@ -0,0 +1,3 @@
+-----BEGIN X25519 PRIVATE KEY-----
+MC4CAQAwBQYDK2VuBCIEIIhgx84XP3vLVdFwZWT88BG/gGRXl6YYUeo0lWX2E8RY
+-----END X25519 PRIVATE KEY-----
diff --git a/tests/files/x448-encrypted-pbes1.pem b/tests/files/x448-encrypted-pbes1.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-encrypted-pbes1.pem
@@ -0,0 +1,40 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MG8wGwYJKoZIhvcNAQUDMA4ECB4xXEg3WSinAgIIAARQM2CTxuxMnzkPDjXsonc3
+lI7dqFNYNxBfgGoEur60JvnZyl7atO+lsCY08XYqLOAVwf/5aZC9Crkd0iHdtYZs
+k8xCAG/KXBwdrrMiKgoRdXE=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MG8wGwYJKoZIhvcNAQUKMA4ECJFkCK4Fg7SgAgIIAARQ8CiLyhuksya2/pWvoemD
+etLFhTSKFln9XfLXSyG70yCsPcH8kC5Xv3ppmNcgbYDC8iwxWwjH4b0QM/QuzaGK
+F3itJG5sgWypa9Yt2rSwYUk=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MGgwHAYKKoZIhvcNAQwBATAOBAi75GJorMne3AICCAAESLScu+UVIvP1/FqkKBa1
+lYTe0Z1nQWFs5bFdFsAAvw5RCSee2vcgFA5KhIqlPwdPgxmgAzIYgoSV0RqudMfq
+OpABx2fMhpMhfA==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MGgwHAYKKoZIhvcNAQwBAjAOBAjxiQQdgW2rXwICCAAESHEAGY2q5vNhLbN7Swb1
+UmDvXibgMjhTpunsmPouCahSf6Zvv8ahes53gvPg/j14hplD2d/MRDz5VRxYO7dP
+v2mzZzuqhegssw==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBAzAOBAhQmGA2NmFbsQICCAAEUAxfvgmmGrViSF3kj82o
+EbNw0fbLqoaa/SdNfV6KRJhZ8j/+6URQ0xKMw3sSCp/fwSon8h9I633VkGyxEtyF
+KRYOakvY5kBOt+JYiZcbovFT
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBDAOBAhziSywPrS4zQICCAAEUJaEyqSGPu9FZ9CLURVW
+6ZRWj6TIaehX0cGGFCkTk3JqhpFFRNLte0SLGPkankzXKOV3D5mBUPF+UjvXSwaJ
+FcyeszvN5xR6sNQkETZqpTD3
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBTAOBAj1Ch4rgGz1ywICCAAEUDJ1FbgIb/MHcuoLexYq
+dqMB7sGctwBmrFZRTpbbHGNvEUV6MFlBkximrCb9nnHjCKj2sZcfQp0R5Q1YODn8
+gzEAEjVuh2yFCKlwumPknt+O
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MHAwHAYKKoZIhvcNAQwBBjAOBAhCm0/fGye6+gICCAAEUHqw28mTyCaI6k+ILt/o
+3qleloo0f4bL9cJiOBYnUB/S1AElr/85ULA70bECsjsWjoWsRE8IxJ5a3FtWJaat
+gaNyLYQlMtVBMoK6feFFT8aE
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x448-encrypted-pbkdf2.pem b/tests/files/x448-encrypted-pbkdf2.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-encrypted-pbkdf2.pem
@@ -0,0 +1,42 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGfMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAgJDIKD8sQsoQICCAAw
+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIuYP9T2Y/nD4EUBXfNNfUENf19FHDdt9Z
+b3s0OVeXeMbu+80TA2mtbk+wOucvB69hr8V/ZFW2HDiYJ2qMBMioOw197lTtVMIT
+xS6gGSpl+tyPoWIoycGcnsq6
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGiME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAgu+SJlXxKQDAICCAAw
+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIeyJz7DTK2+QEUIlmFpQp5DB+gwVp
+6G6dhe10uhETbfSTN8+lmYiqb+0p91DnV+3kpaTrT6fuU3hsjTH1xx4a2oElc+T1
+m8xZhvmmyued7UPBrQdy2RcNUToQ
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAiHVuSXubRGxwICCAAw
+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECJoupjD8iD9gBFAIaJQUeSI3WfZn
+vJ5oM88JZrDdxVKlZlOL4ffXYu3PIUSlV0r3V08m/sPMY/7hCmtvyxBkw6ncB9su
++0tC+fIu1+VVotbarL2eaZaxC217ew==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGtMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAjkfqkzdtzUKQICCAAw
+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQG/iQQd82wsC5mQN+bFY58ARQ
+1QsQhP+TfWeZP9oztCd7s0Y1PnTM7WsMW2YtyS6Sn1Duyj9gx17cXLWvzQz9mS1Y
+EoORulT1bTOzj585xSrNQLSqlnqTHcczNPKHCpU7EWE=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAg5ABTk7x6EhgICCAAC
+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAiwam2gNHnwhgRQdpEG
++fxlriH5rAAFneldLBFHSjBMSKJZNftQv0HFOQzwPhoiYKGXrcldx4bq4B1cbSJR
+XFNvROPF/F3g0B/xn3YiU/5weo5B6Rsu1ojqTBA=
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGrMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAiWX47sRP/MnQICCAAC
+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIsvG8iVYWSzMEUBmO
+u0pBmQkmBM3VI8A9cCRob5hAUojyd4NpodMJvpY4Da7mz20LgLyL5q2DsKgTfnzn
+VTf+tP8vTv8i76Pr7aU89P784jI8nbtZgkcYizq+
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGqMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAhkgv+PZmmr5gICCAAC
+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAjKasCGhxd/5ARQa6xv
+vi/LtSLtwvrYHgWVGELLgkwqBJ6d2MM+ysEUeQILq5gbGcdnfGHtrakVtFir2ga6
+lyrpv9Y0m5gLqgU0QPbm9XRqu1hdEKanozFs2H4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x448-encrypted-scrypt.pem b/tests/files/x448-encrypted-scrypt.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-encrypted-scrypt.pem
@@ -0,0 +1,18 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAiW/EHVo6GeIQICQAAC
+AQgCAQEwHQYJYIZIAWUDBAECBBAmafVnJcoBMlXmITIT+/hxBFCP8hh1dr4EGyMt
+yqivyYBBAlCfjbAquHwPcys7kELHgwHG0PAgspAKkOLCqSgZVuomuS1Ya6bNzgeI
+S2iW5nYwXL8QjIYYEXAxLx8ZQMVb0A==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhixLMqBNK2tAICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEWBBAA5uc3FQno7xKz2V8kxSCeBFACaBwzab3adlDg
+bAIOfR1ytgNEI8+jLDxcPtEI6TjtmcLsT6NI3MaQfOnLy/gt/Uo5XbbiMJUGZ4Mm
+ggEjLTPc7M2kLgGdKcLpnc8mWBCgyg==
+-----END ENCRYPTED PRIVATE KEY-----
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIGjME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAhULg3jQtrIRwICQAAC
+AQgCAQEwHQYJYIZIAWUDBAEqBBBk6w5yfaTk8et4FwnFTrfRBFBl+mFvAdi6X6Wm
+44LkvMjiVCZ1xGDwLcZOyc5OnyNOHnW5tLBa7qvghaQljOyHS+na0z9uKTalbvRP
+GaYKE3JRIsi9MzXOSXlZHLl6fDiVRw==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/files/x448-public.pem b/tests/files/x448-public.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-public.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MEIwBQYDK2VvAzkA3cHetiJ/T/5Oo62IF1sDHfTBN9TUjxcTMhX3lHxw8W5ryxQ8
+LWc2VD0RcfBn1BI3+QVEefPUd/c=
+-----END PUBLIC KEY-----
diff --git a/tests/files/x448-self-signed-cert.pem b/tests/files/x448-self-signed-cert.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-self-signed-cert.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBUjCBvAIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0
+QGV4YW1wbGUuY29tMB4XDTE4MDkwMjE1NTA1M1oXDTE4MTAwMjE1NTA1M1owITEf
+MB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTBCMAUGAytlbwM5AN3B3rYi
+f0/+TqOtiBdbAx30wTfU1I8XEzIV95R8cPFua8sUPC1nNlQ9EXHwZ9QSN/kFRHnz
+1Hf3MA0GCSqGSIb3DQEBCwUAA4GBACwDI7X1kRJwPjDXTB1lkmf0U806ng0NuFuY
+mXgt+PYiUhR3g76FC0chw2+WeeQ6WE4VkQsfdZz10bSWGtcaH4fCYVkQXF/BmNz8
+7Mv0L9iK2438SBcK3WAYB+bC9CZw/gKObzDHxml4tlDaMmIsfYVqiHXuEAPI3wjw
+qoHmxWqY
+-----END CERTIFICATE-----
diff --git a/tests/files/x448-unencrypted-pkcs8.pem b/tests/files/x448-unencrypted-pkcs8.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-unencrypted-pkcs8.pem
@@ -0,0 +1,4 @@
+-----BEGIN PRIVATE KEY-----
+MEYCAQAwBQYDK2VvBDoEOOC8GlnbqcOSJ+ISHV8HJTD3WGdC1sQdZ+0Gkx7sUvL0
+Bm0KxY3cO9Rg9MQb3qkfVngRes0sb86Q
+-----END PRIVATE KEY-----
diff --git a/tests/files/x448-unencrypted-trad.pem b/tests/files/x448-unencrypted-trad.pem
new file mode 100644
--- /dev/null
+++ b/tests/files/x448-unencrypted-trad.pem
@@ -0,0 +1,4 @@
+-----BEGIN X448 PRIVATE KEY-----
+MEYCAQAwBQYDK2VvBDoEOOC8GlnbqcOSJ+ISHV8HJTD3WGdC1sQdZ+0Gkx7sUvL0
+Bm0KxY3cO9Rg9MQb3qkfVngRes0sb86Q
+-----END X448 PRIVATE KEY-----
