packages feed

cryptostore (empty) → 0.1.0.0

raw patch · 87 files changed

+12755/−0 lines, 87 filesdep +asn1-encodingdep +asn1-typesdep +basesetup-changed

Dependencies added: asn1-encoding, asn1-types, base, basement, bytestring, cryptonite, cryptostore, hourglass, memory, pem, tasty, tasty-hunit, tasty-quickcheck, x509

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for cryptostore++## 0.1.0.0 -- 2018-09-23++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2018, Olivier Chéron++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Olivier Chéron nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,269 @@+# cryptostore++[![Build Status](https://travis-ci.org/ocheron/cryptostore.png?branch=master)](https://travis-ci.org/ocheron/cryptostore)+[![BSD](https://b.repl.ca/v1/license-BSD-blue.png)](https://en.wikipedia.org/wiki/BSD_licenses)+[![Haskell](https://b.repl.ca/v1/language-haskell-lightgrey.png)](https://haskell.org/)++This package allows to read and write cryptographic objects to/from ASN.1.++Currently the following is implemented:++* Reading and writing private keys with optional encryption (this extends+  [x509-store](https://hackage.haskell.org/package/x509-store) API)++* Reading and writing public keys, certificates and CRLs++* PKCS #12 container format (password-based only)++* Many parts of Cryptographic Message Syntax++Please have a look at the examples below as well as some warnings about+cryptographic algorithms.++## Private Keys++The API to read and write private keys is available in module+`Crypto.Store.PKCS8`.  When encrypting, some types and functions from module+`Crypto.Store.PKCS5` are also necessary.++Reading a private key from disk:++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.PKCS8+> (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key+> recover "mypassword" key+Right (PrivKeyRSA ...)+```++Generating a private key and writing to disk, without encryption:++```haskell+> :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509+> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001+> writeKeyFile PKCS8Format "/path/to/privkey.pem" [privKey]+```++Generating a private key and writing to disk, with password-based encryption:++```haskell+> :set -XOverloadedStrings+> :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5+> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001+> salt <- generateSalt 8+> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> encParams <- generateEncryptionParams (CBC AES256)+> let pbes = PBES2 (PBES2Parameter kdf encParams)+> writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey+Right ()+```++Parameters used in this example are AES-256-CBC as cipher, PBKDF2 as+key-derivation function, with an 8-byte salt, 2048 iterations and SHA-256 as+pseudorandom function.++## Public Keys and Signed Objects++Module `Crypto.Store.X509` provides functions to read/write PEM files containing+public keys, X.509 certificates and CRLs.  These files are never encrypted.++Reading a public key and certificate from disk:++```haskell+> :m Data.X509 Crypto.Store.X509+> readPubKeyFile "/path/to/pubkey.pem"+[PubKeyRSA ...]+> readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]+[SignedExact ...]+```++Writing back to disk:++```haskell+> :m Crypto.Store.X509+> writePubKeyFile "/path/to/pubkey.pem" [pubKey]+> writeSignedObject "/path/to/cert.pem" [cert]+```++## PKCS #12++PKCS #12 is a complex format with multiple layers of protection, providing+usually both privacy and integrity, with a single password for all or not.  The+API to read PKCS #12 files requires some password at each layer.  This API is+available in module `Crypto.Store.PKCS12`.++Reading a binary PKCS #12 file using distinct integrity and privacy passwords:++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.PKCS12+> Right p12 <- readP12File "/path/to/file.p12"+> let Right pkcs12 = recover "myintegrityassword" p12+> let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12)+> getAllSafeX509Certs contents+[SignedExact {getSigned = ...}]+> recover "myprivacypassword" (getAllSafeKeys contents)+Right [PrivKeyRSA ...]+```++Generating a PKCS #12 file containing a private key:++```haskell+> :set -XOverloadedStrings++-- Generate a private key+> :m Crypto.PubKey.RSA Data.X509+> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001++-- Put the key inside a bag+> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS+> let attrs = setFriendlyName "Some Key" []+>     keyBag = Bag (KeyBag $ FormattedKey PKCS8Format privKey) attrs+>     contents = SafeContents [keyBag]++-- Encrypt the contents+> salt <- generateSalt 8+> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> encParams <- generateEncryptionParams (CBC AES256)+> let pbes = PBES2 (PBES2Parameter kdf encParams)+>     Right pkcs12 = encrypted pbes "mypassword" contents++-- Save to PKCS #12 with integrity protection (same password)+> salt' <- generateSalt 8+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 2048)+> writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12+Right ()+```++The API also provides functions to generate/extract a pair containing a private+key and a certificate chain.  This pair is the type alias `Credential` in `tls`.+Currently the functions assume that the PKCS #12 file contains no other data+than the credential.++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS++-- Read PKCS #12 content as credential+> Right p12 <- readP12File "/path/to/file.p12"+> let Right pkcs12 = recover "myintegrityassword" p12+> let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12)+> cred+(CertificateChain [...], PrivKeyRSA (...))++-- Scheme to reencrypt the key+> saltK <- generateSalt 8+> let kdfK = PBKDF2 saltK 2048 Nothing PBKDF2_SHA256+> encParamsK <- generateEncryptionParams (CBC AES256)+> let sKey = PBES2 (PBES2Parameter kdfK encParamsK)++-- Scheme to reencrypt the certificate chain+> saltC <- generateSalt 8+> let kdfC = PBKDF2 saltC 1024 Nothing PBKDF2_SHA256+> encParamsC <- generateEncryptionParams (CBC AES128)+> let sCert = PBES2 (PBES2Parameter kdfC encParamsC)++-- Write the content back to a new file+> let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred+> salt <- generateSalt 8+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 2048)+> writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12'+```++## Cryptographic Message Syntax++The API to read and write CMS content is available in `Crypto.Store.CMS`.  The+main data type `ContentInfo` represents a CMS structure.++Implemented content types are:++* data+* signed data+* enveloped data+* digested data+* encrypted data+* authenticated data+* and authenticated-enveloped data++Notable omissions:++* detached content+* streaming+* compressed data+* and S/MIME external format (only PEM is supported, i.e. the textual encoding+  of [RFC 7468](https://tools.ietf.org/html/rfc7468))++The following examples generate a CMS structure enveloping some data to a+password recipient, then decrypt the data to recover the content.++### Generating enveloped data++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.CMS++-- Input content info+> let info = DataCI "Hi, what will you need from the cryptostore?"++-- Content encryption will use AES-128-CBC+> ceParams <- generateEncryptionParams (CBC AES128)+> ceKey <- generateKey ceParams :: IO ContentEncryptionKey++-- Encrypt the Content Encryption Key with a Password Recipient Info,+-- i.e. a KDF will derive the Key Encryption Key from a password+-- that the recipient will need to know+> salt <- generateSalt 8+> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> keParams <- generateEncryptionParams (CBC AES128)+> let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams)++-- Generate the enveloped structure for this single recipient+> Right envelopedCI <- envelopData mempty ceKey ceParams [pri] [] info+> writeCMSFile "/path/to/enveloped.pem" [envelopedCI]+```++### Opening the enveloped data++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.CMS++-- Then this recipient just has to read the file and recover enveloped+-- content using the password+> [EnvelopedDataCI envelopedData] <- readCMSFile "/path/to/enveloped.pem"+> openEnvelopedData (withRecipientPassword "mypassword") envelopedData+Right (DataCI "Hi, what will you need from the cryptostore?")+```++## Algorithms and security++For compatibility reasons cryptostore implements many outdated algorithms that+are still in use in data formats.  Please check your security requirements.  New+applications should favor PBKDF2 or Scrypt and AEAD ciphers.++Additionally, the package is designed exclusively for store and forward+scenarios, as most algorithms will not be perfectly safe for interactive use.+ECDSA signature generation uses the generic ECC implementation from cryptonite+and could leak the private key under timing attack.  A padding oracle on+CBC-encrypted ciphertext allows to recover the plaintext.++## Design++Main dependencies are:++* [cryptonite](https://hackage.haskell.org/package/cryptonite) implementation of+  public-key systems, symmetric ciphers, KDFs, MAC, and one-way hash functions+* [asn1-types](https://hackage.haskell.org/package/asn1-types) and+  [asn1-encoding](https://hackage.haskell.org/package/asn1-encoding) to encode+  and decode ASN.1 content+* [pem](https://hackage.haskell.org/package/pem) to read and write PEM files+* [x509](https://hackage.haskell.org/package/x509) contains the certificate and+  private-key data types++Internally the ASN.1 parser used is a local implementation extending the code of+[asn1-parse](https://hackage.haskell.org/package/asn1-parse).  This extension is+able to parse `ASN1Repr`, i.e. a stream of ASN.1 tags associated with the binary+decoding events the tags were originated from.  Similarly generation of ASN.1+content does not use the `ASN1S` type but an extension which is able to encode a+stream where some parts have already been encoded.  Retaining the original+BER/DER encoding is required when incorporating MACed or signed content.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ cryptostore.cabal view
@@ -0,0 +1,96 @@+name:                cryptostore+version:             0.1.0.0+synopsis:            Serialization of cryptographic data types+description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS+                     (Cryptographic Message Syntax).+license:             BSD3+license-file:        LICENSE+author:              Olivier Chéron+maintainer:          olivier.cheron@gmail.com+copyright:           Olivier Chéron+category:            Cryptography, Codec+homepage:            https://github.com/ocheron/cryptostore+bug-reports:         https://github.com/ocheron/cryptostore/issues+build-type:          Simple+extra-source-files:  README.md ChangeLog.md tests/files/*.pem+cabal-version:       >=1.10++source-repository head+  type:     git+  location: https://github.com/ocheron/cryptostore++library+  hs-source-dirs:      src+  exposed-modules:     Crypto.Store.CMS+                     , Crypto.Store.Cipher.RC2+                     , Crypto.Store.Error+                     , Crypto.Store.PKCS5+                     , Crypto.Store.KeyWrap.AES+                     , Crypto.Store.KeyWrap.TripleDES+                     , Crypto.Store.KeyWrap.RC2+                     , Crypto.Store.PKCS12+                     , Crypto.Store.PKCS8+                     , Crypto.Store.X509+  other-modules:       Crypto.Store.ASN1.Generate+                     , Crypto.Store.ASN1.Parse+                     , Crypto.Store.CMS.Algorithms+                     , Crypto.Store.CMS.Attribute+                     , Crypto.Store.CMS.AuthEnveloped+                     , Crypto.Store.CMS.Encrypted+                     , Crypto.Store.CMS.Enveloped+                     , Crypto.Store.CMS.Info+                     , Crypto.Store.CMS.OriginatorInfo+                     , Crypto.Store.CMS.PEM+                     , Crypto.Store.CMS.Signed+                     , Crypto.Store.CMS.Type+                     , Crypto.Store.CMS.Util+                     , Crypto.Store.Cipher.RC2.Primitive+                     , Crypto.Store.PEM+                     , Crypto.Store.PKCS5.PBES1+                     , Crypto.Store.PKCS8.EC+                     , Crypto.Store.Util+  -- other-extensions:+  build-depends:       base >= 4.9 && < 5+                     , bytestring+                     , basement+                     , memory+                     , cryptonite >= 0.25+                     , pem >= 0.1 && < 0.3+                     , asn1-types >= 0.3.1 && < 0.4+                     , asn1-encoding >= 0.9 && < 0.10+                     , hourglass >= 0.2+                     , x509 >= 1.7.3+  default-language:    Haskell2010+  ghc-options:         -Wall++test-suite test-cryptostore+  type:                exitcode-stdio-1.0+  hs-source-dirs:      tests+  main-is:             Main.hs+  other-modules:       KeyWrap.AES+                     , KeyWrap.TripleDES+                     , KeyWrap.RC2+                     , CMS.Instances+                     , CMS.Tests+                     , Cipher.RC2+                     , PKCS12.Instances+                     , PKCS12.Tests+                     , PKCS8.Instances+                     , PKCS8.Tests+                     , Util+                     , X509.Instances+                     , X509.Tests+  build-depends:       base >= 4.9 && < 5+                     , bytestring+                     , asn1-types >= 0.3.1 && < 0.4+                     , cryptonite >= 0.25+                     , memory+                     , tasty+                     , tasty-hunit+                     , tasty-quickcheck+                     , hourglass+                     , pem+                     , x509+                     , cryptostore+  default-language:    Haskell2010+  ghc-options:         -Wall
+ src/Crypto/Store/ASN1/Generate.hs view
@@ -0,0 +1,147 @@+-- |+-- Module      : Crypto.Store.ASN1.Generate+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Generating ASN.1+module Crypto.Store.ASN1.Generate+    ( ASN1Stream+    , ASN1Elem()+    , ASN1P()+    , ASN1PS+    , asn1Container+    , gNull+    , gIntVal+    , gOID+    , gASN1String+    , gBMPString+    , gOctetString+    , gBitString+    , gASN1Time+    , gMany+    , gEncoded+    , optASN1S+    , encodeASN1S+    ) where++import           Data.ASN1.BinaryEncoding+import           Data.ASN1.BinaryEncoding.Raw+import           Data.ASN1.BitArray+import           Data.ASN1.Encoding+import           Data.ASN1.OID+import           Data.ASN1.Types+import qualified Data.ByteArray as B+import           Data.ByteString (ByteString)++import Time.Types (DateTime, TimezoneOffset)++-- | A stream of ASN.1 elements.+type ASN1Stream e = [e] -> [e]++-- | Elements in an ASN.1 stream.+class ASN1Elem e where+    -- | Create a container from an inner ASN.1 stream.+    asn1Container :: ASN1ConstructionType -> ASN1Stream e -> ASN1Stream e+    -- | Generate a list of ASN.1 elements.+    gMany :: [ASN1] -> ASN1Stream e+    -- | Generate one ASN.1 element.+    gOne :: ASN1 -> ASN1Stream e++instance ASN1Elem ASN1 where+    asn1Container ty f = (Start ty :) . f . (End ty :)+    gMany = (++)+    gOne = (:)++-- | Extend the 'ASN1' type to be able to encode to ASN.1 even when some parts+-- of the stream have already been encoded.+data ASN1P+    = ASN1Prim [ASN1]+      -- ^ Primitive elements (or constructed types that are fully terminated)+    | ASN1Container !ASN1ConstructionType [ASN1P]+      -- ^ Constructed type with inner structure kept+    | ASN1Encoded !ByteString+      -- ^ A part which has already been encoded++instance ASN1Elem ASN1P where+    asn1Container ty f = (ASN1Container ty (f []) :)+    gMany asn1 = (ASN1Prim asn1 :)+    gOne = gMany . (:[])++-- | Prepend a list of 'ASN1P'.+type ASN1PS = ASN1Stream ASN1P++-- | Encode to ASN.1 a list of 'ASN1P' elements.  Outer encoding will be DER,+-- but partially encoded inner 'ASN1Encoded' elements many have any encoding.+pEncode :: [ASN1P] -> ByteString+pEncode x = let (_, f) = run x in f B.empty+  where+    run []                   = (0, id)+    run (ASN1Prim asn1 : as) = (B.length p + r, B.append p . ps)+      where p       = encodeASN1' DER asn1+            (r, ps) = run as+    run (ASN1Encoded p : as) = (B.length p + r, B.append p . ps)+      where (r, ps) = run as+    run (ASN1Container ty children : as) =+        (B.length header + l + r, B.append header . p . ps)+      where (l, p)  = run children+            (r, ps) = run as+            header  = toByteString [Header $ ASN1Header cl tg True $ makeLen l]+            (cl, tg) =+                case ty of+                    Container tyClass tyTag -> (tyClass, tyTag)+                    Sequence -> (Universal, 0x10)+                    Set -> (Universal, 0x11)++    makeLen len+        | len < 0x80 = LenShort len+        | otherwise  = LenLong (nbBytes len) len+    nbBytes nb = if nb > 255 then 1 + nbBytes (nb `div` 256) else 1++-- | Generate a 'Null' ASN.1 element.+gNull :: ASN1Elem e => ASN1Stream e+gNull = gOne Null++-- | Generate an 'IntVal' ASN.1 element.+gIntVal :: ASN1Elem e => Integer -> ASN1Stream e+gIntVal = gOne . IntVal++-- | Generate an 'OID' ASN.1 element.+gOID :: ASN1Elem e => OID -> ASN1Stream e+gOID = gOne . OID++-- | Generate an 'ASN1String' ASN.1 element.+gASN1String :: ASN1Elem e => ASN1CharacterString -> ASN1Stream e+gASN1String = gOne . ASN1String++-- | Generate an @BMPString@ ASN.1 element.+gBMPString :: ASN1Elem e => String -> ASN1Stream e+gBMPString = gASN1String . asn1CharacterString BMP++-- | Generate an 'OctetString' ASN.1 element.+gOctetString :: ASN1Elem e => ByteString -> ASN1Stream e+gOctetString = gOne . OctetString++-- | Generate a 'BitString' ASN.1 element.+gBitString :: ASN1Elem e => BitArray -> ASN1Stream e+gBitString = gOne . BitString++-- | Generate an 'ASN1Time' ASN.1 element.+gASN1Time :: ASN1Elem e+          => ASN1TimeType -> DateTime -> Maybe TimezoneOffset -> ASN1Stream e+gASN1Time a b c = gOne (ASN1Time a b c)++-- | Generate ASN.1 for an optional value.+optASN1S :: Maybe a -> (a -> ASN1Stream e) -> ASN1Stream e+optASN1S Nothing    _  = id+optASN1S (Just val) fn = fn val++-- | Generate ASN.1 for a part of the stream which is already encoded.+gEncoded :: ByteString -> ASN1PS+gEncoded = (:) . ASN1Encoded++-- | Encode the ASN.1 stream to DER format (except for inner parts that are+-- already encoded and may use another format).+encodeASN1S :: ASN1PS -> ByteString+encodeASN1S asn1 = pEncode (asn1 [])
+ src/Crypto/Store/ASN1/Parse.hs view
@@ -0,0 +1,210 @@+-- |+-- Module      : Crypto.Store.ASN1.Parse+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Parser combinators for ASN.1 stream.  Similar to "Data.ASN1.Parse" but+-- with the following additions:+--+-- * Parsed stream is annotated, i.e. parser input is @('ASN1', e)@ instead of+--   @'ASN1'@.  Main motivation is to allow to parse a sequence of 'ASN1Repr'+--   and hold the exact binary content that has been parsed.  As consequence,+--   no @getObject@ function is provided.  Function 'withAnnotations' runs+--   a parser and returns all annotations consumed in a monoid concatenation.+--+-- * The parser implements 'Alternative' and 'MonadPlus'.+--+-- * The 'fail' function returns a parse error so that pattern matching makes+--   monadic parsing code easier to write.+module Crypto.Store.ASN1.Parse+    ( ParseASN1+    -- * run+    , runParseASN1State+    , runParseASN1State_+    , runParseASN1+    , runParseASN1_+    , throwParseError+    -- * combinators+    , onNextContainer+    , onNextContainerMaybe+    , getNextContainer+    , getNextContainerMaybe+    , getNext+    , getNextMaybe+    , hasNext+    , getMany+    , withAnnotations+    ) where++import Data.ASN1.Types+import Data.Monoid+import Control.Applicative+import Control.Arrow (first)+import Control.Monad (MonadPlus(..), liftM2)+import Control.Monad.Fail++data State e = State [(ASN1, e)] !e++-- | ASN1 parse monad+newtype ParseASN1 e a = P { runP :: State e -> Either String (a, State e) }++instance Functor (ParseASN1 e) where+    fmap f m = P (fmap (first f) . runP m)+instance Applicative (ParseASN1 e) where+    pure a = P $ \s -> Right (a, s)+    (<*>) mf ma = P $ \s ->+        case runP mf s of+            Left err      -> Left err+            Right (f, s2) ->+                case runP ma s2 of+                    Left err      -> Left err+                    Right (a, s3) -> Right (f a, s3)+instance Alternative (ParseASN1 e) where+    empty = throwParseError "empty"+    (<|>) = mplus+instance Monad (ParseASN1 e) where+    return      = pure+    (>>=) m1 m2 = P $ \s ->+        case runP m1 s of+            Left err      -> Left err+            Right (a, s2) -> runP (m2 a) s2+    fail        = throwParseError+instance MonadFail (ParseASN1 e) where+    fail = throwParseError+instance MonadPlus (ParseASN1 e) where+    mzero = throwParseError "mzero"+    mplus m1 m2 = P $ \s ->+        case runP m1 s of+            Left  _ -> runP m2 s+            success -> success++get :: ParseASN1 e (State e)+get = P $ \stream -> Right (stream, stream)++put :: State e -> ParseASN1 e ()+put stream = P $ \_ -> Right ((), stream)++-- | throw a parse error+throwParseError :: String -> ParseASN1 e a+throwParseError s = P $ \_ -> Left s++wrap :: ASN1 -> (ASN1, ())+wrap a = (a, ())++unwrap :: (ASN1, ()) -> ASN1+unwrap (a, ()) = a++-- | run the parse monad over a stream and returns the result and the remaining ASN1 Stream.+runParseASN1State :: ParseASN1 () a -> [ASN1] -> Either String (a, [ASN1])+runParseASN1State f a = do+    (a', list) <- runParseASN1State_ f (map wrap a)+    return (a', map unwrap list)++-- | run the parse monad over a stream and returns the result and the remaining ASN1 Stream.+runParseASN1State_ :: Monoid e => ParseASN1 e a -> [(ASN1, e)] -> Either String (a, [(ASN1, e)])+runParseASN1State_ f a = do+    (r, State a' _) <- runP f (State a mempty)+    return (r, a')++-- | run the parse monad over a stream and returns the result.+--+-- If there's still some asn1 object in the state after calling f,+-- an error will be raised.+runParseASN1 :: ParseASN1 () a -> [ASN1] -> Either String a+runParseASN1 f s = runParseASN1_ f (map wrap s)++-- | run the parse monad over a stream and returns the result.+--+-- If there's still some asn1 object in the state after calling f,+-- an error will be raised.+runParseASN1_ :: Monoid e => ParseASN1 e a -> [(ASN1, e)] -> Either String a+runParseASN1_ f s =+    case runP f (State s mempty) of+        Left err              -> Left err+        Right (o, State [] _) -> Right o+        Right (_, State er _) ->+            Left ("runParseASN1_: remaining state " ++ show (map fst er))++-- | get next element from the stream+getNext :: Monoid e => ParseASN1 e ASN1+getNext = do+    list <- get+    case list of+        State []        _  -> throwParseError "empty"+        State ((h,e):l) es -> put (State l (es <> e)) >> return h++-- | get many elements until there's nothing left+getMany :: ParseASN1 e a -> ParseASN1 e [a]+getMany getOne = do+    next <- hasNext+    if next+        then liftM2 (:) getOne (getMany getOne)+        else return []++-- | get next element from the stream maybe+getNextMaybe :: Monoid e => (ASN1 -> Maybe a) -> ParseASN1 e (Maybe a)+getNextMaybe f = do+    list <- get+    case list of+        State []        _  -> return Nothing+        State ((h,e):l) es -> let r = f h+                               in do case r of+                                         Nothing -> put list+                                         Just _  -> put (State l (es <> e))+                                     return r++-- | get next container of specified type and return all its elements+getNextContainer :: Monoid e => ASN1ConstructionType -> ParseASN1 e [(ASN1, e)]+getNextContainer ty = do+    list <- get+    case list of+        State []        _                  -> throwParseError "empty"+        State ((h,e):l) es | h == Start ty -> do let (l1, l2) = getConstructedEnd 0 (State l (es <> e))+                                                 put l2 >> return l1+                           | otherwise     -> throwParseError "not an expected container"+++-- | run a function of the next elements of a container of specified type+onNextContainer :: Monoid e => ASN1ConstructionType -> ParseASN1 e a -> ParseASN1 e a+onNextContainer ty f = getNextContainer ty >>= either throwParseError return . runParseASN1_ f++-- | just like getNextContainer, except it doesn't throw an error if the container doesn't exists.+getNextContainerMaybe :: Monoid e => ASN1ConstructionType -> ParseASN1 e (Maybe [(ASN1, e)])+getNextContainerMaybe ty = do+    list <- get+    case list of+        State []        _                  -> return Nothing+        State ((h,e):l) es | h == Start ty -> do let (l1, l2) = getConstructedEnd 0 (State l (es <> e))+                                                 put l2 >> return (Just l1)+                           | otherwise     -> return Nothing++-- | just like onNextContainer, except it doesn't throw an error if the container doesn't exists.+onNextContainerMaybe :: Monoid e => ASN1ConstructionType -> ParseASN1 e a -> ParseASN1 e (Maybe a)+onNextContainerMaybe ty f = do+    n <- getNextContainerMaybe ty+    case n of+        Just l  -> either throwParseError (return . Just) $ runParseASN1_ f l+        Nothing -> return Nothing++-- | returns if there's more elements in the stream.+hasNext :: ParseASN1 e Bool+hasNext = do State l _ <- get; return . not . null $ l++-- | run a parser and return its result as well as all annotations that were used+withAnnotations :: Monoid e => ParseASN1 e a -> ParseASN1 e (a, e)+withAnnotations f = do+    State l es <- get+    case runP f (State l mempty) of+        Left err                -> throwParseError err+        Right (a, State l' es') -> do put (State l' (es <> es'))+                                      return (a, es')++getConstructedEnd :: Monoid e => Int -> State e -> ([(ASN1, e)], State e)+getConstructedEnd _ xs@(State [] _)                = ([], xs)+getConstructedEnd i (State (x@(Start _, e):xs) es) = let (yz, zs) = getConstructedEnd (i+1) (State xs (es <> e)) in (x:yz, zs)+getConstructedEnd i (State (x@(End _, e):xs) es)+    | i == 0    = ([], State xs (es <> e))+    | otherwise = let (ys, zs) = getConstructedEnd (i-1) (State xs (es <> e)) in (x:ys, zs)+getConstructedEnd i (State (x@(_, e):xs) es)       = let (ys, zs) = getConstructedEnd i (State xs (es <> e)) in (x:ys, zs)
+ src/Crypto/Store/CMS.hs view
@@ -0,0 +1,439 @@+-- |+-- Module      : Crypto.Store.CMS+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Cryptographic Message Syntax+--+-- * <https://tools.ietf.org/html/rfc5652 RFC 5652>: Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc3370 RFC 3370>: Cryptographic Message Syntax (CMS) Algorithms+-- * <https://tools.ietf.org/html/rfc3560 RFC 3560>: Use of the RSAES-OAEP Key Transport Algorithm in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc4056 RFC 4056>: Use of the RSASSA-PSS Signature Algorithm in Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc3565 RFC 3565>: Use of the Advanced Encryption Standard (AES) Encryption Algorithm in Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc5753 RFC 5753>: Use of Elliptic Curve Cryptography (ECC) Algorithms in Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc5754 RFC 5754>: Using SHA2 Algorithms with Cryptographic Message Syntax+-- * <https://tools.ietf.org/html/rfc3211 RFC 3211>: Password-based Encryption for CMS+-- * <https://tools.ietf.org/html/rfc5083 RFC 5083>: Cryptographic Message Syntax (CMS) Authenticated-Enveloped-Data Content Type+-- * <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)+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.CMS+    ( ContentType(..)+    , ContentInfo(..)+    , getContentType+    -- * Reading and writing PEM files+    , module Crypto.Store.CMS.PEM+    -- * Signed data+    , SignatureValue+    , SignatureAlg(..)+    , SignedData(..)+    , ProducerOfSI+    , ConsumerOfSI+    , signData+    , verifySignedData+    -- ** Signer information+    , SignerInfo(..)+    , SignerIdentifier(..)+    , IssuerAndSerialNumber(..)+    , certSigner+    , withPublicKey+    , withSignerKey+    , withSignerCertificate+    -- * Enveloped data+    , EncryptedKey+    , KeyEncryptionParams(..)+    , KeyTransportParams(..)+    , KeyAgreementParams(..)+    , RecipientInfo(..)+    , EnvelopedData(..)+    , ProducerOfRI+    , ConsumerOfRI+    , envelopData+    , openEnvelopedData+    -- ** Key Transport recipients+    , KTRecipientInfo(..)+    , RecipientIdentifier(..)+    , forKeyTransRecipient+    , withRecipientKeyTrans+    -- ** Key Agreement recipients+    , KARecipientInfo(..)+    , OriginatorIdentifierOrKey(..)+    , OriginatorPublicKey+    , RecipientEncryptedKey(..)+    , KeyAgreeRecipientIdentifier(..)+    , UserKeyingMaterial+    , forKeyAgreeRecipient+    , withRecipientKeyAgree+    -- ** Key Encryption Key recipients+    , KEKRecipientInfo(..)+    , KeyIdentifier(..)+    , OtherKeyAttribute(..)+    , KeyEncryptionKey+    , forKeyRecipient+    , withRecipientKey+     -- ** Password recipients+   , PasswordRecipientInfo(..)+    , forPasswordRecipient+    , withRecipientPassword+    -- * Digested data+    , DigestProxy(..)+    , DigestAlgorithm(..)+    , DigestedData(..)+    , digestData+    , digestVerify+    -- * Encrypted data+    , ContentEncryptionKey+    , ContentEncryptionCipher(..)+    , ContentEncryptionAlg(..)+    , ContentEncryptionParams+    , EncryptedContent+    , EncryptedData(..)+    , generateEncryptionParams+    , generateRC2EncryptionParams+    , getContentEncryptionAlg+    , encryptData+    , decryptData+    -- * Authenticated data+    , AuthenticationKey+    , MACAlgorithm(..)+    , MessageAuthenticationCode+    , AuthenticatedData(..)+    , generateAuthenticatedData+    , verifyAuthenticatedData+    -- * Authenticated-enveloped data+    , AuthContentEncryptionAlg(..)+    , AuthContentEncryptionParams+    , AuthEnvelopedData(..)+    , generateAuthEnc128Params+    , generateAuthEnc256Params+    , generateChaChaPoly1305Params+    , generateCCMParams+    , generateGCMParams+    , authEnvelopData+    , openAuthEnvelopedData+    -- * Key derivation+    , Salt+    , generateSalt+    , KeyDerivationFunc(..)+    , PBKDF2_PRF(..)+    -- * Secret-key algorithms+    , HasKeySize(..)+    , generateKey+    -- * RSA padding modes+    , MaskGenerationFunc(..)+    , OAEPParams(..)+    , PSSParams(..)+    -- * CMS attributes+    , Attribute(..)+    , findAttribute+    , setAttribute+    , filterAttributes+    -- * Originator information+    , OriginatorInfo(..)+    , CertificateChoice(..)+    , OtherCertificateFormat(..)+    , RevocationInfoChoice(..)+    , OtherRevocationInfoFormat(..)+    -- * ASN.1 representation+    , ASN1ObjectExact+    ) where++import Data.Maybe (isJust)+import Data.List (nub, unzip3)++import Crypto.Hash++import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.AuthEnveloped+import Crypto.Store.CMS.Encrypted+import Crypto.Store.CMS.Enveloped+import Crypto.Store.CMS.OriginatorInfo+import Crypto.Store.CMS.Info+import Crypto.Store.CMS.PEM+import Crypto.Store.CMS.Signed+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util+import Crypto.Store.Error+++-- DigestedData++-- | Add a digested-data layer on the specified content info.+digestData :: DigestAlgorithm -> ContentInfo -> ContentInfo+digestData (DigestAlgorithm alg) ci = DigestedDataCI dd+  where dd = DigestedData+                 { ddDigestAlgorithm = alg+                 , ddContentInfo     = ci+                 , ddDigest          = hash (encapsulate ci)+                 }++-- | Return the inner content info but only if the digest is valid.+digestVerify :: DigestedData -> Maybe ContentInfo+digestVerify DigestedData{..} =+    if ddDigest == hash (encapsulate ddContentInfo)+        then Just ddContentInfo+        else Nothing+++-- EncryptedData++-- | Add an encrypted-data layer on the specified content info.  The content is+-- encrypted with specified key and algorithm.+--+-- Some optional attributes can be added but will not be encrypted.+encryptData :: ContentEncryptionKey+            -> ContentEncryptionParams+            -> [Attribute]+            -> ContentInfo+            -> Either StoreError ContentInfo+encryptData key params attrs ci =+    EncryptedDataCI . build <$> contentEncrypt key params (encapsulate ci)+  where+    build ec = EncryptedData+                   { edContentType = getContentType ci+                   , edContentEncryptionParams = params+                   , edEncryptedContent = ec+                   , edUnprotectedAttrs = attrs+                   }++-- | Decrypt an encrypted content info using the specified key.+decryptData :: ContentEncryptionKey+            -> EncryptedData+            -> Either StoreError ContentInfo+decryptData key EncryptedData{..} = do+    decrypted <- contentDecrypt key edContentEncryptionParams edEncryptedContent+    decapsulate edContentType decrypted+++-- EnvelopedData++-- | Add an enveloped-data layer on the specified content info.  The content is+-- encrypted with specified key and algorithm.  The key is then processed by+-- one or several 'ProducerOfRI' functions to create recipient info elements.+--+-- Some optional attributes can be added but will not be encrypted.+envelopData :: Applicative f+            => OriginatorInfo+            -> ContentEncryptionKey+            -> ContentEncryptionParams+            -> [ProducerOfRI f]+            -> [Attribute]+            -> ContentInfo+            -> f (Either StoreError ContentInfo)+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)+    build bs ris = EnvelopedData+                       { evOriginatorInfo = oinfo+                       , evRecipientInfos = ris+                       , evContentType = getContentType ci+                       , evContentEncryptionParams = params+                       , evEncryptedContent = bs+                       , evUnprotectedAttrs = attrs+                       }++-- | Recover an enveloped content info using the specified 'ConsumerOfRI'+-- function.+openEnvelopedData :: Monad m+                  => ConsumerOfRI m+                  -> EnvelopedData+                  -> m (Either StoreError ContentInfo)+openEnvelopedData devFn EnvelopedData{..} = do+    r <- riAttempts (map (fmap (>>= decr) . devFn) evRecipientInfos)+    return (r >>= decapsulate ct)+  where+    ct       = evContentType+    params   = evContentEncryptionParams+    decr k   = contentDecrypt k params evEncryptedContent+++-- AuthenticatedData++-- | Key used for authentication.+type AuthenticationKey = ContentEncryptionKey++-- | Add an authenticated-data layer on the specified content info.  The content+-- is MACed with the specified key and algorithms.  The key is then processed by+-- one or several 'ProducerOfRI' functions to create recipient info elements.+--+-- Two lists of optional attributes can be provided.  The attributes will be+-- part of message authentication when provided in the first list.+generateAuthenticatedData :: Applicative f+                          => OriginatorInfo+                          -> AuthenticationKey+                          -> MACAlgorithm+                          -> Maybe DigestAlgorithm+                          -> [ProducerOfRI f]+                          -> [Attribute]+                          -> [Attribute]+                          -> ContentInfo+                          -> f (Either StoreError ContentInfo)+generateAuthenticatedData oinfo key macAlg digAlg envFns aAttrs uAttrs ci =+    f <$> (sequence <$> traverse ($ key) envFns)+  where+    msg = encapsulate ci+    ct  = getContentType ci++    (aAttrs', input) =+        case digAlg of+            Nothing  -> (aAttrs, msg)+            Just dig ->+                let md = digest dig msg+                    l  = setContentTypeAttr ct $ setMessageDigestAttr md aAttrs+                in (l, encodeAuthAttrs l)++    ebs   = mac macAlg key input+    f ris = AuthenticatedDataCI <$> (build ebs <$> ris)+    build authTag ris = AuthenticatedData+                            { adOriginatorInfo = oinfo+                            , adRecipientInfos = ris+                            , adMACAlgorithm = macAlg+                            , adDigestAlgorithm = digAlg+                            , adContentInfo = ci+                            , adAuthAttrs = aAttrs'+                            , adMAC = authTag+                            , adUnauthAttrs = uAttrs+                            }++-- | Verify the integrity of an authenticated content info using the specified+-- 'ConsumerOfRI' function.  The inner content info is returned only if the MAC+-- could be verified.+verifyAuthenticatedData :: Monad m+                        => ConsumerOfRI m+                        -> AuthenticatedData+                        -> m (Either StoreError ContentInfo)+verifyAuthenticatedData devFn AuthenticatedData{..} =+    riAttempts (map (fmap (>>= unwrap) . devFn) adRecipientInfos)+  where+    msg = encapsulate adContentInfo+    ct  = getContentType adContentInfo++    noAttr    = null adAuthAttrs+    mdMatch   = case adDigestAlgorithm of+                    Nothing  -> False+                    Just dig -> mdAttr == Just (digest dig msg)+    attrMatch = ctAttr == Just ct && mdMatch+    mdAttr    = getMessageDigestAttr adAuthAttrs+    ctAttr    = getContentTypeAttr adAuthAttrs+    input     = if noAttr then msg else encodeAuthAttrs adAuthAttrs++    unwrap k+        | 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+++-- AuthEnvelopedData++-- | Add an authenticated-enveloped-data layer on the specified content info.+-- The content is encrypted with specified key and algorithm.  The key is then+-- processed by one or several 'ProducerOfRI' functions to create recipient info+-- elements.+--+-- Some attributes can be added but will not be encrypted.  The attributes+-- will be part of message authentication when provided in the first list.+authEnvelopData :: Applicative f+                => OriginatorInfo+                -> ContentEncryptionKey+                -> AuthContentEncryptionParams+                -> [ProducerOfRI f]+                -> [Attribute]+                -> [Attribute]+                -> ContentInfo+                -> f (Either StoreError ContentInfo)+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)+    build (authTag, bs) ris = AuthEnvelopedData+                       { aeOriginatorInfo = oinfo+                       , aeRecipientInfos = ris+                       , aeContentType = getContentType ci+                       , aeContentEncryptionParams = ASN1ObjectExact params raw+                       , aeEncryptedContent = bs+                       , aeAuthAttrs = aAttrs+                       , aeMAC = authTag+                       , aeUnauthAttrs = uAttrs+                       }++-- | Recover an authenticated-enveloped content info using the specified+-- 'ConsumerOfRI' function.+openAuthEnvelopedData :: Monad m+                      => ConsumerOfRI m+                      -> AuthEnvelopedData+                      -> m (Either StoreError ContentInfo)+openAuthEnvelopedData devFn AuthEnvelopedData{..} = do+    r <- riAttempts (map (fmap (>>= decr) . devFn) aeRecipientInfos)+    return (r >>= decapsulate ct)+  where+    ct       = aeContentType+    params   = exactObject aeContentEncryptionParams+    raw      = exactObjectRaw aeContentEncryptionParams+    aad      = encodeAuthAttrs aeAuthAttrs+    decr k   = authContentDecrypt k params raw aad aeEncryptedContent aeMAC+++-- SignedData++-- | Add a signed-data layer on the specified content info.  The content is+-- processed by one or several 'ProducerOfSI' functions to create signer info+-- elements.+signData :: Applicative f+         => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError ContentInfo)+signData sigFns ci =+    f <$> (sequence <$> traverse (\fn -> fn ct msg) sigFns)+  where+    msg = encapsulate ci+    ct  = getContentType ci+    f   = fmap (SignedDataCI . build . unzip3)++    build (sis, certLists, crlLists) =+        SignedData+            { sdDigestAlgorithms = nub (map siDigestAlgorithm sis)+            , sdContentInfo = ci+            , sdCertificates = concat certLists+            , sdCRLs = concat crlLists+            , sdSignerInfos = sis+            }++-- | Verify a signed content info using the specified 'ConsumerOfSI' function.+-- 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)+verifySignedData verFn SignedData{..} =+    f <$> siAttemps valid sdSignerInfos+  where+    msg      = encapsulate sdContentInfo+    ct       = getContentType sdContentInfo+    valid si = verFn ct msg si sdCertificates sdCRLs+    f bool   = if bool then Just sdContentInfo else Nothing+++-- Utilities++riAttempts :: Monad m => [m (Either StoreError b)] -> m (Either StoreError b)+riAttempts []       = return (Left NoRecipientInfoFound)+riAttempts [single] = single+riAttempts list     = loop list+  where+    loop []     = return (Left NoRecipientInfoMatched)+    loop (x:xs) = x >>= orTail xs++    orTail xs (Left _)  = loop xs+    orTail _  success   = return success++siAttemps :: Monad m => (a -> m Bool) -> [a] -> m Bool+siAttemps _ []     = pure False+siAttemps f (x:xs) = f x >>= orTail+  where orTail bool = if bool then return True else siAttemps f xs
+ src/Crypto/Store/CMS/Algorithms.hs view
@@ -0,0 +1,1923 @@+-- |+-- Module      : Crypto.Store.CMS.Algorithms+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Cryptographic Message Syntax algorithms+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeFamilies #-}+module Crypto.Store.CMS.Algorithms+    ( DigestAlgorithm(..)+    , DigestProxy(..)+    , digest+    , MessageAuthenticationCode+    , MACAlgorithm(..)+    , mac+    , HasKeySize(..)+    , getMaximumKeySize+    , validateKeySize+    , generateKey+    , ContentEncryptionCipher(..)+    , ContentEncryptionAlg(..)+    , ContentEncryptionParams(..)+    , generateEncryptionParams+    , generateRC2EncryptionParams+    , getContentEncryptionAlg+    , proxyBlockSize+    , contentEncrypt+    , contentDecrypt+    , AuthContentEncryptionAlg(..)+    , AuthContentEncryptionParams+    , generateAuthEnc128Params+    , generateAuthEnc256Params+    , generateChaChaPoly1305Params+    , generateCCMParams+    , generateGCMParams+    , authContentEncrypt+    , authContentDecrypt+    , PBKDF2_PRF(..)+    , prf+    , Salt+    , generateSalt+    , KeyDerivationFunc(..)+    , kdfKeyLength+    , kdfDerive+    , KeyEncryptionParams(..)+    , keyEncrypt+    , keyDecrypt+    , OAEPParams(..)+    , KeyTransportParams(..)+    , transportEncrypt+    , transportDecrypt+    , KeyAgreementParams(..)+    , ecdhGenerate+    , ecdhEncrypt+    , ecdhDecrypt+    , MaskGenerationFunc(..)+    , mgf+    , SignatureValue+    , PSSParams(..)+    , SignatureAlg(..)+    , signatureResolveHash+    , signatureCheckHash+    , signatureGenerate+    , signatureVerify+    ) where++import Control.Applicative+import Control.Monad (guard, when)++import           Data.ASN1.BinaryEncoding+import           Data.ASN1.OID+import           Data.ASN1.Encoding+import           Data.ASN1.Types+import           Data.Bits+import           Data.ByteArray (ByteArray, ByteArrayAccess)+import qualified Data.ByteArray as B+import           Data.ByteString (ByteString)+import           Data.Maybe (fromMaybe)+import           Data.Word+import qualified Data.X509 as X509+import           Data.X509.EC++import qualified Crypto.Cipher.AES as Cipher+import qualified Crypto.Cipher.CAST5 as Cipher+import qualified Crypto.Cipher.Camellia as Cipher+import qualified Crypto.Cipher.ChaChaPoly1305 as ChaChaPoly1305+import qualified Crypto.Cipher.DES as Cipher+import qualified Crypto.Cipher.TripleDES as Cipher+import           Crypto.Cipher.Types+import           Crypto.Data.Padding+import           Crypto.Error+import qualified Crypto.Hash as Hash+import qualified Crypto.KDF.PBKDF2 as PBKDF2+import qualified Crypto.KDF.Scrypt as Scrypt+import qualified Crypto.MAC.HMAC as HMAC+import qualified Crypto.MAC.Poly1305 as Poly1305+import           Crypto.Number.Serialize+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.MaskGenFunction as MGF+import qualified Crypto.PubKey.RSA.PSS as RSAPSS+import qualified Crypto.PubKey.RSA.OAEP as RSAOAEP+import qualified Crypto.PubKey.RSA.PKCS15 as RSA+import           Crypto.Random++import Foreign.Ptr (Ptr)+import Foreign.Storable++import           Crypto.Store.ASN1.Generate+import           Crypto.Store.ASN1.Parse+import           Crypto.Store.CMS.Util+import           Crypto.Store.Cipher.RC2+import           Crypto.Store.Error+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.Util+++-- Hash functions++-- | CMS digest proxy.  Acts like 'Data.Proxy.Proxy', i.e. provides a hash+-- algorithm as type parameter.  The GADT constructors map to known algorithms.+data DigestProxy hashAlg where+    -- | MD2+    MD2    :: DigestProxy Hash.MD2+    -- | MD4+    MD4    :: DigestProxy Hash.MD4+    -- | MD5+    MD5    :: DigestProxy Hash.MD5+    -- | SHA-1+    SHA1   :: DigestProxy Hash.SHA1+    -- | SHA-224+    SHA224 :: DigestProxy Hash.SHA224+    -- | SHA-256+    SHA256 :: DigestProxy Hash.SHA256+    -- | SHA-384+    SHA384 :: DigestProxy Hash.SHA384+    -- | SHA-512+    SHA512 :: DigestProxy Hash.SHA512++deriving instance Show (DigestProxy hashAlg)+deriving instance Eq (DigestProxy hashAlg)++-- | CMS digest algorithm.+data DigestAlgorithm =+    forall hashAlg . Hash.HashAlgorithm hashAlg+        => DigestAlgorithm (DigestProxy hashAlg)++instance Show DigestAlgorithm where+    show (DigestAlgorithm a) = show a++instance Eq DigestAlgorithm where+    DigestAlgorithm MD2          == DigestAlgorithm MD2          = True+    DigestAlgorithm MD4          == DigestAlgorithm MD4          = True+    DigestAlgorithm MD5          == DigestAlgorithm MD5          = True+    DigestAlgorithm SHA1         == DigestAlgorithm SHA1         = True+    DigestAlgorithm SHA224       == DigestAlgorithm SHA224       = True+    DigestAlgorithm SHA256       == DigestAlgorithm SHA256       = True+    DigestAlgorithm SHA384       == DigestAlgorithm SHA384       = True+    DigestAlgorithm SHA512       == DigestAlgorithm SHA512       = True+    _                            == _                            = False++instance Enumerable DigestAlgorithm where+    values = [ DigestAlgorithm MD2+             , DigestAlgorithm MD4+             , DigestAlgorithm MD5+             , DigestAlgorithm SHA1+             , DigestAlgorithm SHA224+             , DigestAlgorithm SHA256+             , DigestAlgorithm SHA384+             , DigestAlgorithm SHA512+             ]++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 OIDNameable DigestAlgorithm where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++instance AlgorithmId DigestAlgorithm where+    type AlgorithmType DigestAlgorithm = DigestAlgorithm+    algorithmName _  = "digest algorithm"+    algorithmType    = id++    -- MD5 has NULL parameter, other algorithms have no parameter+    parameterASN1S (DigestAlgorithm MD5) = gNull+    parameterASN1S _                     = id++    parseParameter p = getNextMaybe nullOrNothing >> return p++-- | Compute the digest of a message.+digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString+digest (DigestAlgorithm hashAlg) message = B.convert (doHash hashAlg message)++doHash :: (Hash.HashAlgorithm hashAlg, ByteArrayAccess ba)+       => proxy hashAlg -> ba -> Hash.Digest hashAlg+doHash _ = Hash.hash++hashFromProxy :: proxy a -> a+hashFromProxy _ = undefined+++-- Cipher-like things++-- | Algorithms that are based on a secret key.  This includes ciphers but also+-- MAC algorithms.+class HasKeySize params where+    -- | Get a specification of the key sizes allowed by the algorithm.+    getKeySizeSpecifier :: params -> KeySizeSpecifier++-- | Return the maximum key size for the specified algorithm.+getMaximumKeySize :: HasKeySize params => params -> Int+getMaximumKeySize params =+    case getKeySizeSpecifier params of+        KeySizeRange _ n -> n+        KeySizeEnum  l   -> maximum l+        KeySizeFixed n   -> n++-- | Return 'True' if the specified key size is valid for the specified+-- algorithm.+validateKeySize :: HasKeySize params => params -> Int -> Bool+validateKeySize params len =+    case getKeySizeSpecifier params of+        KeySizeRange a b -> a <= len && len <= b+        KeySizeEnum  l   -> len `elem` l+        KeySizeFixed n   -> len == n++-- | Generate a random key suitable for the specified algorithm.  This uses the+-- maximum size allowed by the parameters.+generateKey :: (HasKeySize params, MonadRandom m, ByteArray key)+            => params -> m key+generateKey params = getRandomBytes (getMaximumKeySize params)+++-- MAC++-- | Message authentication code.  Equality is time constant.+type MessageAuthenticationCode = AuthTag++-- | Message Authentication Code (MAC) Algorithm.+data MACAlgorithm+    = forall hashAlg . Hash.HashAlgorithm hashAlg+        => HMAC (DigestProxy hashAlg)++deriving instance Show MACAlgorithm++instance Eq MACAlgorithm where+    HMAC a1 == HMAC a2 = DigestAlgorithm a1 == DigestAlgorithm a2++instance Enumerable MACAlgorithm where+    values = [ HMAC MD5+             , HMAC SHA1+             , HMAC SHA224+             , HMAC SHA256+             , HMAC SHA384+             , HMAC SHA512+             ]++instance OIDable MACAlgorithm where+    getObjectID (HMAC MD5)    = [1,3,6,1,5,5,8,1,1]+    getObjectID (HMAC SHA1)   = [1,3,6,1,5,5,8,1,2]+    getObjectID (HMAC SHA224) = [1,2,840,113549,2,8]+    getObjectID (HMAC SHA256) = [1,2,840,113549,2,9]+    getObjectID (HMAC SHA384) = [1,2,840,113549,2,10]+    getObjectID (HMAC SHA512) = [1,2,840,113549,2,11]++    getObjectID ty = error ("Unsupported MACAlgorithm: " ++ show ty)++instance OIDNameable MACAlgorithm where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++instance AlgorithmId MACAlgorithm where+    type AlgorithmType MACAlgorithm = MACAlgorithm+    algorithmName _  = "mac algorithm"+    algorithmType    = id+    parameterASN1S _ = id+    parseParameter p = getNextMaybe nullOrNothing >> return p++instance HasKeySize MACAlgorithm where+    getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)+      where digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy++-- | Invoke the MAC function.+mac :: (ByteArrayAccess key, ByteArrayAccess message)+     => MACAlgorithm -> key -> message -> MessageAuthenticationCode+mac (HMAC alg) = hmacWith alg+  where+    hmacWith p key = AuthTag . B.convert . runHMAC p key++    runHMAC :: (Hash.HashAlgorithm a, ByteArrayAccess k, ByteArrayAccess m)+        => proxy a -> k -> m -> HMAC.HMAC a+    runHMAC _ = HMAC.hmac+++-- Content encryption++-- | CMS content encryption cipher.+data ContentEncryptionCipher cipher where+    -- | DES+    DES         :: ContentEncryptionCipher Cipher.DES+    -- | Triple-DES with 2 keys used in alternative direction+    DES_EDE2    :: ContentEncryptionCipher Cipher.DES_EDE2+    -- | Triple-DES with 3 keys used in alternative direction+    DES_EDE3    :: ContentEncryptionCipher Cipher.DES_EDE3+    -- | AES with 128-bit key+    AES128      :: ContentEncryptionCipher Cipher.AES128+    -- | AES with 192-bit key+    AES192      :: ContentEncryptionCipher Cipher.AES192+    -- | AES with 256-bit key+    AES256      :: ContentEncryptionCipher Cipher.AES256+    -- | CAST5 (aka CAST-128) with key between 40 and 128 bits+    CAST5       :: ContentEncryptionCipher Cipher.CAST5+    -- | Camellia with 128-bit key+    Camellia128 :: ContentEncryptionCipher Cipher.Camellia128++deriving instance Show (ContentEncryptionCipher cipher)+deriving instance Eq (ContentEncryptionCipher cipher)++cecI :: ContentEncryptionCipher c -> Int+cecI DES         = 0+cecI DES_EDE2    = 1+cecI DES_EDE3    = 2+cecI AES128      = 3+cecI AES192      = 4+cecI AES256      = 5+cecI CAST5       = 6+cecI Camellia128 = 7++getCipherKeySizeSpecifier :: Cipher cipher => proxy cipher -> KeySizeSpecifier+getCipherKeySizeSpecifier = cipherKeySize . cipherFromProxy++-- | Cipher and mode of operation for content encryption.+data ContentEncryptionAlg+    = forall c . BlockCipher c => ECB (ContentEncryptionCipher c)+      -- ^ Electronic Codebook+    | forall c . BlockCipher c => CBC (ContentEncryptionCipher c)+      -- ^ Cipher Block Chaining+    | CBC_RC2+      -- ^ RC2 in CBC mode+    | forall c . BlockCipher c => CFB (ContentEncryptionCipher c)+      -- ^ Cipher Feedback+    | forall c . BlockCipher c => CTR (ContentEncryptionCipher c)+      -- ^ Counter++instance Show ContentEncryptionAlg where+    show (ECB c) = shows c "_ECB"+    show (CBC c) = shows c "_CBC"+    show CBC_RC2 = "RC2_CBC"+    show (CFB c) = shows c "_CFB"+    show (CTR c) = shows c "_CTR"++instance Enumerable ContentEncryptionAlg where+    values = [ CBC DES+             , CBC DES_EDE3+             , CBC AES128+             , CBC AES192+             , CBC AES256+             , CBC CAST5+             , CBC Camellia128+             , CBC_RC2++             , ECB DES+             , ECB AES128+             , ECB AES192+             , ECB AES256+             , ECB Camellia128++             , CFB DES+             , CFB AES128+             , CFB AES192+             , CFB AES256+             , CFB Camellia128++             , CTR Camellia128+             ]++instance OIDable ContentEncryptionAlg where+    getObjectID (CBC DES)          = [1,3,14,3,2,7]+    getObjectID (CBC DES_EDE3)     = [1,2,840,113549,3,7]+    getObjectID (CBC AES128)       = [2,16,840,1,101,3,4,1,2]+    getObjectID (CBC AES192)       = [2,16,840,1,101,3,4,1,22]+    getObjectID (CBC AES256)       = [2,16,840,1,101,3,4,1,42]+    getObjectID (CBC CAST5)        = [1,2,840,113533,7,66,10]+    getObjectID (CBC Camellia128)  = [1,2,392,200011,61,1,1,1,2]+    getObjectID CBC_RC2            = [1,2,840,113549,3,2]++    getObjectID (ECB DES)          = [1,3,14,3,2,6]+    getObjectID (ECB AES128)       = [2,16,840,1,101,3,4,1,1]+    getObjectID (ECB AES192)       = [2,16,840,1,101,3,4,1,21]+    getObjectID (ECB AES256)       = [2,16,840,1,101,3,4,1,41]+    getObjectID (ECB Camellia128)  = [0,3,4401,5,3,1,9,1]++    getObjectID (CFB DES)          = [1,3,14,3,2,9]+    getObjectID (CFB AES128)       = [2,16,840,1,101,3,4,1,4]+    getObjectID (CFB AES192)       = [2,16,840,1,101,3,4,1,24]+    getObjectID (CFB AES256)       = [2,16,840,1,101,3,4,1,44]+    getObjectID (CFB Camellia128)  = [0,3,4401,5,3,1,9,4]++    getObjectID (CTR Camellia128)  = [0,3,4401,5,3,1,9,9]++    getObjectID ty = error ("Unsupported ContentEncryptionAlg: " ++ show ty)++instance OIDNameable ContentEncryptionAlg where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Content encryption algorithm with associated parameters (i.e. the+-- initialization vector).+--+-- A value can be generated with 'generateEncryptionParams'.+data ContentEncryptionParams+    = forall c . BlockCipher c => ParamsECB (ContentEncryptionCipher c)+      -- ^ Electronic Codebook+    | forall c . BlockCipher c => ParamsCBC (ContentEncryptionCipher c) (IV c)+      -- ^ Cipher Block Chaining+    | ParamsCBCRC2 Int (IV RC2)+      -- ^ RC2 in CBC mode+    | forall c . BlockCipher c => ParamsCFB (ContentEncryptionCipher c) (IV c)+      -- ^ Cipher Feedback+    | forall c . BlockCipher c => ParamsCTR (ContentEncryptionCipher c) (IV c)+      -- ^ Counter++instance Show ContentEncryptionParams where+    show = show . getContentEncryptionAlg++instance Eq ContentEncryptionParams where+    ParamsECB c1        == ParamsECB c2        = cecI c1 == cecI c2+    ParamsCBC c1 iv1    == ParamsCBC c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2+    ParamsCBCRC2 i1 iv1 == ParamsCBCRC2 i2 iv2 = i1 == i2 && iv1 `B.eq` iv2+    ParamsCFB c1 iv1    == ParamsCFB c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2+    ParamsCTR c1 iv1    == ParamsCTR c2 iv2    = cecI c1 == cecI c2 && iv1 `B.eq` iv2+    _                   == _                   = False++instance HasKeySize ContentEncryptionParams where+    getKeySizeSpecifier (ParamsECB c)      = getCipherKeySizeSpecifier c+    getKeySizeSpecifier (ParamsCBC c _)    = getCipherKeySizeSpecifier c+    getKeySizeSpecifier (ParamsCBCRC2 i _) = KeySizeFixed $ (i + 7) `div` 8+    getKeySizeSpecifier (ParamsCFB c _)    = getCipherKeySizeSpecifier c+    getKeySizeSpecifier (ParamsCTR c _)    = getCipherKeySizeSpecifier c++instance ASN1Elem e => ProduceASN1Object e ContentEncryptionParams where+    asn1s param =+        asn1Container Sequence (oid . params)+      where+        oid    = gOID (getObjectID $ getContentEncryptionAlg param)+        params = ceParameterASN1S param++instance Monoid e => ParseASN1Object e ContentEncryptionParams where+    parse = onNextContainer Sequence $ do+        OID oid <- getNext+        withObjectID "content encryption algorithm" oid parseCEParameter++ceParameterASN1S :: ASN1Elem e => ContentEncryptionParams -> ASN1Stream e+ceParameterASN1S (ParamsECB _)         = id+ceParameterASN1S (ParamsCBC _ iv)      = gOctetString (B.convert iv)+ceParameterASN1S (ParamsCBCRC2 len iv) = rc2ParameterASN1S len iv+ceParameterASN1S (ParamsCFB _ iv)      = gOctetString (B.convert iv)+ceParameterASN1S (ParamsCTR _ iv)      = gOctetString (B.convert iv)++parseCEParameter :: Monoid e+                 => ContentEncryptionAlg -> ParseASN1 e ContentEncryptionParams+parseCEParameter (ECB c) = getMany getNext >> return (ParamsECB c)+parseCEParameter (CBC c) = ParamsCBC c <$> (getNext >>= getIV)+parseCEParameter CBC_RC2 = parseRC2Parameter+parseCEParameter (CFB c) = ParamsCFB c <$> (getNext >>= getIV)+parseCEParameter (CTR c) = ParamsCTR c <$> (getNext >>= getIV)++getIV :: BlockCipher cipher => ASN1 -> ParseASN1 e (IV cipher)+getIV (OctetString ivBs) =+    case makeIV ivBs of+        Nothing -> throwParseError "Bad IV in parsed parameters"+        Just v  -> return v+getIV _ = throwParseError "No IV in parsed parameter or incorrect format"++rc2ParameterASN1S :: ASN1Elem e => Int -> IV RC2 -> ASN1Stream e+rc2ParameterASN1S len iv+    | len == 32 = gIV+    | otherwise = asn1Container Sequence (rc2VersionASN1 len . gIV)+  where gIV = gOctetString (B.convert iv)++parseRC2Parameter :: Monoid e => ParseASN1 e ContentEncryptionParams+parseRC2Parameter = parseOnlyIV 32 <|> parseFullParams+  where+    parseOnlyIV len = ParamsCBCRC2 len <$> (getNext >>= getIV)+    parseFullParams = onNextContainer Sequence $+        parseRC2Version >>= parseOnlyIV++-- | Get the content encryption algorithm.+getContentEncryptionAlg :: ContentEncryptionParams -> ContentEncryptionAlg+getContentEncryptionAlg (ParamsECB c)      = ECB c+getContentEncryptionAlg (ParamsCBC c _)    = CBC c+getContentEncryptionAlg (ParamsCBCRC2 _ _) = CBC_RC2+getContentEncryptionAlg (ParamsCFB c _)    = CFB c+getContentEncryptionAlg (ParamsCTR c _)    = CTR c++-- | Generate random parameters for the specified content encryption algorithm.+generateEncryptionParams :: MonadRandom m+                         => ContentEncryptionAlg -> m ContentEncryptionParams+generateEncryptionParams (ECB c) = return (ParamsECB c)+generateEncryptionParams (CBC c) = ParamsCBC c <$> ivGenerate undefined+generateEncryptionParams CBC_RC2 = ParamsCBCRC2 128 <$> ivGenerate undefined+generateEncryptionParams (CFB c) = ParamsCFB c <$> ivGenerate undefined+generateEncryptionParams (CTR c) = ParamsCTR c <$> ivGenerate undefined++-- | Generate random RC2 parameters with the specified effective key length (in+-- bits).+generateRC2EncryptionParams :: MonadRandom m+                            => Int -> m ContentEncryptionParams+generateRC2EncryptionParams len = ParamsCBCRC2 len <$> ivGenerate undefined++-- | Encrypt a bytearray with the specified content encryption key and+-- algorithm.+contentEncrypt :: (ByteArray cek, ByteArray ba)+               => cek+               -> ContentEncryptionParams+               -> ba -> Either StoreError ba+contentEncrypt key params bs =+    case params of+        ParamsECB cipher    -> getCipher cipher key >>= (\c -> force $ ecbEncrypt c    $ padded c bs)+        ParamsCBC cipher iv -> getCipher cipher key >>= (\c -> force $ cbcEncrypt c iv $ padded c bs)+        ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> force $ cbcEncrypt c iv $ padded c bs)+        ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> force $ cfbEncrypt c iv $ padded c bs)+        ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> force $ ctrCombine c iv $ padded c bs)+  where+    force x  = x `seq` Right x+    padded c = pad (PKCS7 $ blockSize c)++-- | Decrypt a bytearray with the specified content encryption key and+-- algorithm.+contentDecrypt :: (ByteArray cek, ByteArray ba)+               => cek+               -> ContentEncryptionParams+               -> ba -> Either StoreError ba+contentDecrypt key params bs =+    case params of+        ParamsECB cipher    -> getCipher cipher key >>= (\c -> unpadded c (ecbDecrypt c    bs))+        ParamsCBC cipher iv -> getCipher cipher key >>= (\c -> unpadded c (cbcDecrypt c iv bs))+        ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> unpadded c (cbcDecrypt c iv bs))+        ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> unpadded c (cfbDecrypt c iv bs))+        ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> unpadded c (ctrCombine c iv bs))+  where+    unpadded c decrypted =+        case unpad (PKCS7 $ blockSize c) decrypted of+            Nothing  -> Left DecryptionFailed+            Just out -> Right out++-- from RFC 2268 section 6+rc2Table :: [Word8]+rc2Table =+    [ 0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c, 0x1b, 0x33, 0xfd, 0xd0+    , 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b, 0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a+    , 0x41, 0x9f, 0xe1, 0xd9, 0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36+    , 0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0, 0xa6, 0x3f, 0xd8, 0x0c+    , 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17, 0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60+    , 0x48, 0xe6, 0x1e, 0x53, 0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa+    , 0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12, 0xba, 0x3c, 0x06, 0x4e+    , 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b, 0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf+    , 0x3a, 0xde, 0x96, 0x0e, 0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6+    , 0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02, 0x75, 0xd5, 0x61, 0xe3+    , 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e, 0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c+    , 0xb4, 0xc5, 0xcc, 0x70, 0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2+    , 0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b, 0x82, 0xf9, 0x40, 0xb5+    , 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97, 0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5+    , 0x64, 0x6d, 0x7a, 0xd4, 0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f+    , 0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9, 0x4c, 0xff, 0x43, 0xab+    ]++rc2Forward :: B.Bytes+rc2Forward = B.pack rc2Table++rc2Reverse :: B.Bytes+rc2Reverse = B.allocAndFreeze (length rc2Table) (loop $ zip [0..] rc2Table)+  where+    loop :: [(Word8, Word8)] -> Ptr Word8 -> IO ()+    loop []         _ = return ()+    loop ((a,b):ts) p = pokeElemOff p (fromIntegral b) a >> loop ts p++rc2VersionASN1 :: ASN1Elem e => Int -> ASN1Stream e+rc2VersionASN1 len = gIntVal v+  where+    v | len < 0    = error "invalid RC2 effective key length"+      | len >= 256 = fromIntegral len+      | otherwise  = fromIntegral (B.index rc2Forward len)++parseRC2Version :: Monoid e => ParseASN1 e Int+parseRC2Version = do+    IntVal i <- getNext+    when (i < 0) $ throwParseError "Parsed invalid RC2 effective key length"+    let j = fromIntegral i+    return $ if i >= 256 then j else fromIntegral (B.index rc2Reverse j)+++-- Authenticated-content encryption++-- | Cipher and mode of operation for authenticated-content encryption.+data AuthContentEncryptionAlg+    = AUTH_ENC_128+      -- ^ authEnc with 128-bit key+    | AUTH_ENC_256+      -- ^ authEnc with 256-bit key+    | CHACHA20_POLY1305+      -- ^ ChaCha20-Poly1305 Authenticated Encryption+    | forall c . BlockCipher c => CCM (ContentEncryptionCipher c)+      -- ^ Counter with CBC-MAC+    | forall c . BlockCipher c => GCM (ContentEncryptionCipher c)+      -- ^ Galois Counter Mode++instance Show AuthContentEncryptionAlg where+    show AUTH_ENC_128 = "AUTH_ENC_128"+    show AUTH_ENC_256 = "AUTH_ENC_256"+    show CHACHA20_POLY1305 = "CHACHA20_POLY1305"+    show (CCM c)      = shows c "_CCM"+    show (GCM c)      = shows c "_GCM"++instance Enumerable AuthContentEncryptionAlg where+    values = [ AUTH_ENC_128+             , AUTH_ENC_256+             , CHACHA20_POLY1305++             , CCM AES128+             , CCM AES192+             , CCM AES256++             , GCM AES128+             , GCM AES192+             , GCM AES256+             ]++instance OIDable AuthContentEncryptionAlg where+    getObjectID AUTH_ENC_128       = [1,2,840,113549,1,9,16,3,15]+    getObjectID AUTH_ENC_256       = [1,2,840,113549,1,9,16,3,16]+    getObjectID CHACHA20_POLY1305  = [1,2,840,113549,1,9,16,3,18]++    getObjectID (CCM AES128)       = [2,16,840,1,101,3,4,1,7]+    getObjectID (CCM AES192)       = [2,16,840,1,101,3,4,1,27]+    getObjectID (CCM AES256)       = [2,16,840,1,101,3,4,1,47]++    getObjectID (GCM AES128)       = [2,16,840,1,101,3,4,1,6]+    getObjectID (GCM AES192)       = [2,16,840,1,101,3,4,1,26]+    getObjectID (GCM AES256)       = [2,16,840,1,101,3,4,1,46]++    getObjectID ty = error ("Unsupported AuthContentEncryptionAlg: " ++ show ty)++instance OIDNameable AuthContentEncryptionAlg where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++data AuthEncParams = AuthEncParams+    { prfAlgorithm :: PBKDF2_PRF+    , encAlgorithm :: ContentEncryptionParams+    , macAlgorithm :: MACAlgorithm+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e AuthEncParams where+    asn1s AuthEncParams{..} = asn1Container Sequence (kdf . encAlg . macAlg)+      where+        kdf    = algorithmASN1S (Container Context 0) (asKDF prfAlgorithm)+        encAlg = asn1s encAlgorithm+        macAlg = algorithmASN1S Sequence macAlgorithm++        asKDF algPrf = PBKDF2 { pbkdf2Salt = B.empty+                              , pbkdf2IterationCount = 1+                              , pbkdf2KeyLength = Nothing+                              , pbkdf2Prf = algPrf+                              }++instance Monoid e => ParseASN1Object e AuthEncParams where+    parse = onNextContainer Sequence $ do+        kdf    <- parseAlgorithmMaybe (Container Context 0)+        encAlg <- parse+        macAlg <- parseAlgorithm Sequence+        prfAlg <-+            case kdf of+                Nothing               -> return PBKDF2_SHA1+                Just (PBKDF2 _ _ _ a) -> return a+                Just other            -> throwParseError+                    ("Unable to use " ++ show other ++ " in AuthEncParams")+        return AuthEncParams { prfAlgorithm = prfAlg+                             , encAlgorithm = encAlg+                             , macAlgorithm = macAlg+                             }++-- | Authenticated-content encryption algorithm with associated parameters+-- (i.e. the nonce).+--+-- A value can be generated with functions 'generateAuthEnc128Params',+-- 'generateAuthEnc256Params', 'generateChaChaPoly1305Params',+-- 'generateCCMParams' and 'generateGCMParams'.+data AuthContentEncryptionParams+    = Params_AUTH_ENC_128 AuthEncParams+      -- ^ authEnc with 128-bit keying material+    | Params_AUTH_ENC_256 AuthEncParams+      -- ^ authEnc with 256-bit keying material+    | Params_CHACHA20_POLY1305 ChaChaPoly1305.Nonce+      -- ^ ChaCha20-Poly1305 Authenticated Encryption+    | forall c . BlockCipher c => ParamsCCM (ContentEncryptionCipher c) B.Bytes CCM_M CCM_L+      -- ^ Counter with CBC-MAC+    | forall c . BlockCipher c => ParamsGCM (ContentEncryptionCipher c) B.Bytes Int+      -- ^ Galois Counter Mode++instance Show AuthContentEncryptionParams where+    show = show . getAuthContentEncryptionAlg++instance Eq AuthContentEncryptionParams where+    Params_AUTH_ENC_128 p1 == Params_AUTH_ENC_128 p2 = p1 == p2+    Params_AUTH_ENC_256 p1 == Params_AUTH_ENC_256 p2 = p1 == p2+    Params_CHACHA20_POLY1305 iv1 == Params_CHACHA20_POLY1305 iv2 =+        iv1 `B.eq` iv2++    ParamsCCM c1 iv1 m1 l1 == ParamsCCM c2 iv2 m2 l2 =+        cecI c1 == cecI c2 && iv1 == iv2 && (m1, l1) == (m2, l2)+    ParamsGCM c1 iv1 len1  == ParamsGCM c2 iv2 len2  =+        cecI c1 == cecI c2 && iv1 == iv2 && len1 == len2+    _               == _               = False++instance HasKeySize AuthContentEncryptionParams where+    getKeySizeSpecifier (Params_AUTH_ENC_128 _) = KeySizeFixed 16+    getKeySizeSpecifier (Params_AUTH_ENC_256 _) = KeySizeFixed 32+    getKeySizeSpecifier (Params_CHACHA20_POLY1305 _) = KeySizeFixed 32+    getKeySizeSpecifier (ParamsCCM c _ _ _)     = getCipherKeySizeSpecifier c+    getKeySizeSpecifier (ParamsGCM c _ _)       = getCipherKeySizeSpecifier c++instance ASN1Elem e => ProduceASN1Object e AuthContentEncryptionParams where+    asn1s param =+        asn1Container Sequence (oid . params)+      where+        oid    = gOID (getObjectID $ getAuthContentEncryptionAlg param)+        params = aceParameterASN1S param++instance Monoid e => ParseASN1Object e AuthContentEncryptionParams where+    parse = onNextContainer Sequence $ do+        OID oid <- getNext+        withObjectID "authenticated-content encryption algorithm" oid+            parseACEParameter++aceParameterASN1S :: ASN1Elem e => AuthContentEncryptionParams -> ASN1Stream e+aceParameterASN1S (Params_AUTH_ENC_128 p) = asn1s p+aceParameterASN1S (Params_AUTH_ENC_256 p) = asn1s p+aceParameterASN1S (Params_CHACHA20_POLY1305 iv) = gOctetString (B.convert iv)+aceParameterASN1S (ParamsCCM _ iv m _) =+    asn1Container Sequence (nonce . icvlen)+  where+    nonce  = gOctetString (B.convert iv)+    icvlen = gIntVal (fromIntegral $ getM m)+aceParameterASN1S (ParamsGCM _ iv len) =+    asn1Container Sequence (nonce . icvlen)+  where+    nonce  = gOctetString (B.convert iv)+    icvlen = gIntVal (fromIntegral len)++parseACEParameter :: Monoid e+                  => AuthContentEncryptionAlg+                  -> ParseASN1 e AuthContentEncryptionParams+parseACEParameter AUTH_ENC_128 = Params_AUTH_ENC_128 <$> parse+parseACEParameter AUTH_ENC_256 = Params_AUTH_ENC_256 <$> parse+parseACEParameter CHACHA20_POLY1305 = do+    OctetString bs <- getNext+    case ChaChaPoly1305.nonce12 bs of+        CryptoPassed iv -> return (Params_CHACHA20_POLY1305 iv)+        CryptoFailed e  ->+            throwParseError $ "Parsed invalid ChaChaPoly1305 nonce: " ++ show e+parseACEParameter (CCM c)      = onNextContainer Sequence $ do+    OctetString iv <- getNext+    let ivlen = B.length iv+    when (ivlen < 7 || ivlen > 13) $+        throwParseError $ "Parsed invalid CCM nonce length: " ++ show ivlen+    let Just l = fromL (15 - ivlen)+    m <- parseM+    return (ParamsCCM c (B.convert iv) m l)+parseACEParameter (GCM c)      = onNextContainer Sequence $ do+    OctetString iv <- getNext+    when (B.null iv) $+        throwParseError "Parsed empty GCM nonce"+    icvlen <- fromMaybe 12 <$> getNextMaybe intOrNothing+    when (icvlen < 12 || icvlen > 16) $+        throwParseError $ "Parsed invalid GCM ICV length: " ++ show icvlen+    return (ParamsGCM c (B.convert iv) $ fromIntegral icvlen)++-- | Get the authenticated-content encryption algorithm.+getAuthContentEncryptionAlg :: AuthContentEncryptionParams+                            -> AuthContentEncryptionAlg+getAuthContentEncryptionAlg (Params_AUTH_ENC_128 _) = AUTH_ENC_128+getAuthContentEncryptionAlg (Params_AUTH_ENC_256 _) = AUTH_ENC_256+getAuthContentEncryptionAlg (Params_CHACHA20_POLY1305 _) = CHACHA20_POLY1305+getAuthContentEncryptionAlg (ParamsCCM c _ _ _)     = CCM c+getAuthContentEncryptionAlg (ParamsGCM c _ _)       = GCM c++-- | Generate random 'AUTH_ENC_128' parameters with the specified algorithms.+generateAuthEnc128Params :: MonadRandom m+                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm+                         -> m AuthContentEncryptionParams+generateAuthEnc128Params prfAlg cea macAlg = do+    params <- generateEncryptionParams cea+    return $ Params_AUTH_ENC_128 $+        AuthEncParams { prfAlgorithm = prfAlg+                      , encAlgorithm = params+                      , macAlgorithm = macAlg+                      }++-- | Generate random 'AUTH_ENC_256' parameters with the specified algorithms.+generateAuthEnc256Params :: MonadRandom m+                         => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm+                         -> m AuthContentEncryptionParams+generateAuthEnc256Params prfAlg cea macAlg = do+    params <- generateEncryptionParams cea+    return $ Params_AUTH_ENC_256 $+        AuthEncParams { prfAlgorithm = prfAlg+                      , encAlgorithm = params+                      , macAlgorithm = macAlg+                      }++-- | Generate random 'CHACHA20_POLY1305' parameters.+generateChaChaPoly1305Params :: MonadRandom m => m AuthContentEncryptionParams+generateChaChaPoly1305Params = do+    bs <- nonceGenerate 12+    let iv = throwCryptoError (ChaChaPoly1305.nonce12 bs)+    return (Params_CHACHA20_POLY1305 iv)++-- | Generate random 'CCM' parameters for the specified cipher.+generateCCMParams :: (MonadRandom m, BlockCipher c)+                  => ContentEncryptionCipher c -> CCM_M -> CCM_L+                  -> m AuthContentEncryptionParams+generateCCMParams c m l = do+    iv <- nonceGenerate (15 - getL l)+    return (ParamsCCM c iv m l)++-- | Generate random 'GCM' parameters for the specified cipher.+generateGCMParams :: (MonadRandom m, BlockCipher c)+                  => ContentEncryptionCipher c -> Int+                  -> m AuthContentEncryptionParams+generateGCMParams c l = do+    iv <- nonceGenerate 12+    return (ParamsGCM c iv l)++-- | Encrypt a bytearray with the specified authenticated-content encryption+-- key and algorithm.+authContentEncrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)+                   => cek+                   -> AuthContentEncryptionParams -> ba+                   -> aad -> ba -> Either StoreError (AuthTag, ba)+authContentEncrypt key params paramsRaw aad bs =+    case params of+        Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authEncrypt p+        Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authEncrypt p+        Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpEncrypt+        ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= encrypt (getM m)+        ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= encrypt len+  where+    msglen  = B.length bs+    force x = x `seq` Right x++    encrypt :: Int -> AEAD a -> Either StoreError (AuthTag, ba)+    encrypt len aead = force $ aeadSimpleEncrypt aead aad bs len++    ccpEncrypt :: ChaChaPoly1305.State -> Either a (AuthTag, ba)+    ccpEncrypt state = force (found, encrypted)+      where+        (encrypted, state') = ChaChaPoly1305.encrypt bs state+        found = ccpTag (ChaChaPoly1305.finalize state')++    authEncrypt :: AuthEncParams -> Either StoreError (AuthTag, ba)+    authEncrypt p@AuthEncParams{..} = do+        let (encKey, macKey) = authKeys key p+        encrypted <- contentEncrypt encKey encAlgorithm bs+        let macMsg = paramsRaw `B.append` encrypted `B.append` B.convert aad+            found  = mac macAlgorithm macKey macMsg+        return (found, encrypted)++-- | Decrypt a bytearray with the specified authenticated-content encryption key+-- and algorithm.+authContentDecrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba)+                   => cek+                   -> AuthContentEncryptionParams -> ba+                   -> aad -> ba -> AuthTag -> Either StoreError ba+authContentDecrypt key params paramsRaw aad bs expected =+    case params of+        Params_AUTH_ENC_128 p   -> checkAuthKey 16 key >> authDecrypt p+        Params_AUTH_ENC_256 p   -> checkAuthKey 32 key >> authDecrypt p+        Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpDecrypt+        ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= decrypt+        ParamsGCM cipher iv _   -> getAEAD cipher key AEAD_GCM iv >>= decrypt+  where+    msglen  = B.length bs+    badMac  = Left BadContentMAC++    decrypt :: AEAD a -> Either StoreError ba+    decrypt aead = maybe badMac Right (aeadSimpleDecrypt aead aad bs expected)++    ccpDecrypt :: ChaChaPoly1305.State -> Either StoreError ba+    ccpDecrypt state+        | found == expected = Right decrypted+        | otherwise         = badMac+      where+        (decrypted, state') = ChaChaPoly1305.decrypt bs state+        found = ccpTag (ChaChaPoly1305.finalize state')++    authDecrypt :: AuthEncParams -> Either StoreError ba+    authDecrypt p@AuthEncParams{..}+        | found == expected = contentDecrypt encKey encAlgorithm bs+        | otherwise         = badMac+      where+        (encKey, macKey) = authKeys key p+        macMsg = paramsRaw `B.append` bs `B.append` B.convert aad+        found  = mac macAlgorithm macKey macMsg++getAEAD :: (BlockCipher cipher, ByteArray key, ByteArrayAccess iv)+        => proxy cipher -> key -> AEADMode -> iv -> Either StoreError (AEAD cipher)+getAEAD cipher key mode iv = do+    c <- getCipher cipher key+    fromCryptoFailable $ aeadInit mode c iv++authKeys :: ByteArrayAccess password+         => password -> AuthEncParams+         -> (B.ScrubbedBytes, B.ScrubbedBytes)+authKeys key AuthEncParams{..} = (encKey, macKey)+  where+    encKDF = PBKDF2 "encryption" 1 Nothing prfAlgorithm+    encLen = getMaximumKeySize encAlgorithm+    encKey = kdfDerive encKDF encLen key++    macKDF = PBKDF2 "authentication" 1 Nothing prfAlgorithm+    macKey = kdfDerive macKDF macLen key++    -- RFC 6476 section 4.2: "Specifying a MAC key size gets a bit tricky"+    -- TODO: this is a hack but allows both test vectors to pass+    macLen | encLen == 24 = 16+           | otherwise    = getMaximumKeySize macAlgorithm++checkAuthKey :: ByteArrayAccess cek => Int -> cek -> Either StoreError ()+checkAuthKey sz key+    | actual == sz = Right ()+    | otherwise    = Left (CryptoError CryptoError_KeySizeInvalid)+  where actual = B.length key++ccpInit :: (ByteArrayAccess key, ByteArrayAccess aad)+        => key+        -> ChaChaPoly1305.Nonce+        -> aad+        -> Either StoreError ChaChaPoly1305.State+ccpInit key nonce aad = case ChaChaPoly1305.initialize key nonce of+    CryptoPassed s -> return (addAAD s)+    CryptoFailed e -> Left (CryptoError e)+  where addAAD = ChaChaPoly1305.finalizeAAD . ChaChaPoly1305.appendAAD aad++ccpTag :: Poly1305.Auth -> AuthTag+ccpTag (Poly1305.Auth bs) = AuthTag bs++-- PRF++-- | Pseudorandom function used for PBKDF2.+data PBKDF2_PRF = PBKDF2_SHA1   -- ^ hmacWithSHA1+                | PBKDF2_SHA256 -- ^ hmacWithSHA256+                | PBKDF2_SHA512 -- ^ hmacWithSHA512+                deriving (Show,Eq)++instance Enumerable PBKDF2_PRF where+    values = [ PBKDF2_SHA1+             , PBKDF2_SHA256+             , PBKDF2_SHA512+             ]++instance OIDable PBKDF2_PRF where+    getObjectID PBKDF2_SHA1   = [1,2,840,113549,2,7]+    getObjectID PBKDF2_SHA256 = [1,2,840,113549,2,9]+    getObjectID PBKDF2_SHA512 = [1,2,840,113549,2,11]++instance OIDNameable PBKDF2_PRF where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++instance AlgorithmId PBKDF2_PRF where+    type AlgorithmType PBKDF2_PRF = PBKDF2_PRF+    algorithmName _  = "PBKDF2 PRF"+    algorithmType    = id+    parameterASN1S _ = id+    parseParameter p = getNextMaybe nullOrNothing >> return p++-- | Invoke the pseudorandom function.+prf :: (ByteArrayAccess salt, ByteArrayAccess password, ByteArray out)+    => PBKDF2_PRF -> PBKDF2.Parameters -> password -> salt -> out+prf PBKDF2_SHA1   = PBKDF2.fastPBKDF2_SHA1+prf PBKDF2_SHA256 = PBKDF2.fastPBKDF2_SHA256+prf PBKDF2_SHA512 = PBKDF2.fastPBKDF2_SHA512+++-- Key derivation++-- | Salt value used for key derivation.+type Salt = ByteString++-- | Key derivation algorithm.+data KeyDerivationAlgorithm = TypePBKDF2 | TypeScrypt++instance Enumerable KeyDerivationAlgorithm where+    values = [ TypePBKDF2+             , TypeScrypt+             ]++instance OIDable KeyDerivationAlgorithm where+    getObjectID TypePBKDF2 = [1,2,840,113549,1,5,12]+    getObjectID TypeScrypt = [1,3,6,1,4,1,11591,4,11]++instance OIDNameable KeyDerivationAlgorithm where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key derivation algorithm and associated parameters.+data KeyDerivationFunc =+      -- | Key derivation with PBKDF2+      PBKDF2 { pbkdf2Salt           :: Salt       -- ^ Salt value+             , pbkdf2IterationCount :: Int        -- ^ Iteration count+             , pbkdf2KeyLength      :: Maybe Int  -- ^ Optional key length+             , pbkdf2Prf            :: PBKDF2_PRF -- ^ Pseudorandom function+             }+      -- | Key derivation with Scrypt+    | Scrypt { scryptSalt      :: Salt       -- ^ Salt value+             , scryptN         :: Word64     -- ^ N value+             , scryptR         :: Int        -- ^ R value+             , scryptP         :: Int        -- ^ P value+             , scryptKeyLength :: Maybe Int  -- ^ Optional key length+             }+    deriving (Show,Eq)++instance AlgorithmId KeyDerivationFunc where+    type AlgorithmType KeyDerivationFunc = KeyDerivationAlgorithm++    algorithmName _ = "key derivation algorithm"+    algorithmType PBKDF2{..} = TypePBKDF2+    algorithmType Scrypt{..} = TypeScrypt++    parameterASN1S PBKDF2{..} =+        asn1Container Sequence (salt . iters . keyLen . mprf)+      where+        salt   = gOctetString pbkdf2Salt+        iters  = gIntVal (toInteger pbkdf2IterationCount)+        keyLen = maybe id (gIntVal . toInteger) pbkdf2KeyLength+        mprf   = if pbkdf2Prf == PBKDF2_SHA1 then id else algorithmASN1S Sequence pbkdf2Prf++    parameterASN1S Scrypt{..} =+        asn1Container Sequence (salt . n . r . p . keyLen)+      where+        salt   = gOctetString scryptSalt+        n      = gIntVal (toInteger scryptN)+        r      = gIntVal (toInteger scryptR)+        p      = gIntVal (toInteger scryptP)+        keyLen = maybe id (gIntVal . toInteger) scryptKeyLength++    parseParameter TypePBKDF2 = onNextContainer Sequence $ do+        OctetString salt <- getNext+        IntVal iters <- getNext+        keyLen <- getNextMaybe intOrNothing+        b <- hasNext+        mprf <- if b then parseAlgorithm Sequence else return PBKDF2_SHA1+        return PBKDF2 { pbkdf2Salt           = salt+                      , pbkdf2IterationCount = fromInteger iters+                      , pbkdf2KeyLength      = fromInteger <$> keyLen+                      , pbkdf2Prf            = mprf+                      }++    parseParameter TypeScrypt = onNextContainer Sequence $ do+        OctetString salt <- getNext+        IntVal n <- getNext+        IntVal r <- getNext+        IntVal p <- getNext+        keyLen <- getNextMaybe intOrNothing+        return Scrypt { scryptSalt      = salt+                      , scryptN         = fromInteger n+                      , scryptR         = fromInteger r+                      , scryptP         = fromInteger p+                      , scryptKeyLength = fromInteger <$> keyLen+                      }++-- | Return the optional key length stored in the KDF parameters.+kdfKeyLength :: KeyDerivationFunc -> Maybe Int+kdfKeyLength PBKDF2{..} = pbkdf2KeyLength+kdfKeyLength Scrypt{..} = scryptKeyLength++-- | Run a key derivation function to produce a result of the specified length+-- using the supplied password.+kdfDerive :: (ByteArrayAccess password, ByteArray out)+          => KeyDerivationFunc -> Int -> password -> out+kdfDerive PBKDF2{..} len pwd = prf pbkdf2Prf params pwd pbkdf2Salt+  where params = PBKDF2.Parameters pbkdf2IterationCount len+kdfDerive Scrypt{..} len pwd = Scrypt.generate params pwd scryptSalt+  where params = Scrypt.Parameters { Scrypt.n = scryptN+                                   , Scrypt.r = scryptR+                                   , Scrypt.p = scryptP+                                   , Scrypt.outputLength = len+                                   }++-- | Generate a random salt with the specified length in bytes.  To be most+-- effective, the length should be at least 8 bytes.+generateSalt :: MonadRandom m => Int -> m Salt+generateSalt = getRandomBytes+++-- Key encryption++data KeyEncryptionType = TypePWRIKEK+                       | TypeAES128_WRAP+                       | TypeAES192_WRAP+                       | TypeAES256_WRAP+                       | TypeAES128_WRAP_PAD+                       | TypeAES192_WRAP_PAD+                       | TypeAES256_WRAP_PAD+                       | TypeDES_EDE3_WRAP+                       | TypeRC2_WRAP++instance Enumerable KeyEncryptionType where+    values = [ TypePWRIKEK+             , TypeAES128_WRAP+             , TypeAES192_WRAP+             , TypeAES256_WRAP+             , TypeAES128_WRAP_PAD+             , TypeAES192_WRAP_PAD+             , TypeAES256_WRAP_PAD+             , TypeDES_EDE3_WRAP+             , TypeRC2_WRAP+             ]++instance OIDable KeyEncryptionType where+    getObjectID TypePWRIKEK         = [1,2,840,113549,1,9,16,3,9]++    getObjectID TypeAES128_WRAP     = [2,16,840,1,101,3,4,1,5]+    getObjectID TypeAES192_WRAP     = [2,16,840,1,101,3,4,1,25]+    getObjectID TypeAES256_WRAP     = [2,16,840,1,101,3,4,1,45]++    getObjectID TypeAES128_WRAP_PAD = [2,16,840,1,101,3,4,1,8]+    getObjectID TypeAES192_WRAP_PAD = [2,16,840,1,101,3,4,1,28]+    getObjectID TypeAES256_WRAP_PAD = [2,16,840,1,101,3,4,1,48]++    getObjectID TypeDES_EDE3_WRAP   = [1,2,840,113549,1,9,16,3,6]+    getObjectID TypeRC2_WRAP        = [1,2,840,113549,1,9,16,3,7]++instance OIDNameable KeyEncryptionType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key encryption algorithm with associated parameters (i.e. the underlying+-- encryption algorithm).+data KeyEncryptionParams = PWRIKEK ContentEncryptionParams  -- ^ PWRI-KEK key wrap algorithm+                         | AES128_WRAP                      -- ^ AES-128 key wrap+                         | AES192_WRAP                      -- ^ AES-192 key wrap+                         | AES256_WRAP                      -- ^ AES-256 key wrap+                         | AES128_WRAP_PAD                  -- ^ AES-128 extended key wrap+                         | AES192_WRAP_PAD                  -- ^ AES-192 extended key wrap+                         | AES256_WRAP_PAD                  -- ^ AES-256 extended key wrap+                         | DES_EDE3_WRAP                    -- ^ Triple-DES key wrap+                         | RC2_WRAP Int                     -- ^ RC2 key wrap with effective key length+                         deriving (Show,Eq)++instance AlgorithmId KeyEncryptionParams where+    type AlgorithmType KeyEncryptionParams = KeyEncryptionType+    algorithmName _ = "key encryption algorithm"++    algorithmType (PWRIKEK _)      = TypePWRIKEK+    algorithmType AES128_WRAP      = TypeAES128_WRAP+    algorithmType AES192_WRAP      = TypeAES192_WRAP+    algorithmType AES256_WRAP      = TypeAES256_WRAP+    algorithmType AES128_WRAP_PAD  = TypeAES128_WRAP_PAD+    algorithmType AES192_WRAP_PAD  = TypeAES192_WRAP_PAD+    algorithmType AES256_WRAP_PAD  = TypeAES256_WRAP_PAD+    algorithmType DES_EDE3_WRAP    = TypeDES_EDE3_WRAP+    algorithmType (RC2_WRAP _)     = TypeRC2_WRAP++    parameterASN1S (PWRIKEK cep)  = asn1s cep+    parameterASN1S DES_EDE3_WRAP  = gNull+    parameterASN1S (RC2_WRAP ekl) = rc2VersionASN1 ekl+    parameterASN1S _              = id++    parseParameter TypePWRIKEK          = PWRIKEK <$> parse+    parseParameter TypeAES128_WRAP      = return AES128_WRAP+    parseParameter TypeAES192_WRAP      = return AES192_WRAP+    parseParameter TypeAES256_WRAP      = return AES256_WRAP+    parseParameter TypeAES128_WRAP_PAD  = return AES128_WRAP_PAD+    parseParameter TypeAES192_WRAP_PAD  = return AES192_WRAP_PAD+    parseParameter TypeAES256_WRAP_PAD  = return AES256_WRAP_PAD+    parseParameter TypeDES_EDE3_WRAP    = getNextMaybe nullOrNothing >> return DES_EDE3_WRAP+    parseParameter TypeRC2_WRAP         = RC2_WRAP <$> parseRC2Version++instance HasKeySize KeyEncryptionParams where+    getKeySizeSpecifier (PWRIKEK cep)   = getKeySizeSpecifier cep+    getKeySizeSpecifier AES128_WRAP     = getCipherKeySizeSpecifier AES128+    getKeySizeSpecifier AES192_WRAP     = getCipherKeySizeSpecifier AES192+    getKeySizeSpecifier AES256_WRAP     = getCipherKeySizeSpecifier AES256+    getKeySizeSpecifier AES128_WRAP_PAD = getCipherKeySizeSpecifier AES128+    getKeySizeSpecifier AES192_WRAP_PAD = getCipherKeySizeSpecifier AES192+    getKeySizeSpecifier AES256_WRAP_PAD = getCipherKeySizeSpecifier AES256+    getKeySizeSpecifier DES_EDE3_WRAP   = getCipherKeySizeSpecifier DES_EDE3+    getKeySizeSpecifier (RC2_WRAP _)    = KeySizeFixed 16++-- | Encrypt a key with the specified key encryption key and algorithm.+keyEncrypt :: (MonadRandom m, ByteArray kek, ByteArray ba)+           => kek -> KeyEncryptionParams -> ba -> m (Either StoreError ba)+keyEncrypt key (PWRIKEK params) bs =+    case params of+        ParamsECB cipher    -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt (const . ecbEncrypt) c undefined bs) cc+        ParamsCBC cipher iv -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt cbcEncrypt c iv bs) cc+        ParamsCBCRC2 len iv -> let cc = getRC2Cipher len key in either (return . Left) (\c -> wrapEncrypt cbcEncrypt c iv bs) cc+        ParamsCFB cipher iv -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt cfbEncrypt c iv bs) cc+        ParamsCTR _ _       -> return $ Left (InvalidParameter "Unable to wrap key in CTR mode")+keyEncrypt key AES128_WRAP      bs = return (getCipher AES128 key >>= (`AES_KW.wrap` bs))+keyEncrypt key AES192_WRAP      bs = return (getCipher AES192 key >>= (`AES_KW.wrap` bs))+keyEncrypt key AES256_WRAP      bs = return (getCipher AES256 key >>= (`AES_KW.wrap` bs))+keyEncrypt key AES128_WRAP_PAD  bs = return (getCipher AES128 key >>= (`AES_KW.wrapPad` bs))+keyEncrypt key AES192_WRAP_PAD  bs = return (getCipher AES192 key >>= (`AES_KW.wrapPad` bs))+keyEncrypt key AES256_WRAP_PAD  bs = return (getCipher AES256 key >>= (`AES_KW.wrapPad` bs))+keyEncrypt key DES_EDE3_WRAP    bs = either (return . Left) (wrap3DES bs) (getCipher DES_EDE3 key)+  where wrap3DES b c = (\iv -> TripleDES_KW.wrap c iv b) <$> ivGenerate c+keyEncrypt key (RC2_WRAP ekl)   bs = either (return . Left) (wrapRC2 bs) (getRC2Cipher ekl key)+  where wrapRC2 b c = do iv <- ivGenerate c; RC2_KW.wrap c iv b++-- | Decrypt a key with the specified key encryption key and algorithm.+keyDecrypt :: (ByteArray kek, ByteArray ba)+           => kek -> KeyEncryptionParams -> ba -> Either StoreError ba+keyDecrypt key (PWRIKEK params) bs =+    case params of+        ParamsECB cipher    -> getCipher cipher key >>= (\c -> wrapDecrypt (const . ecbDecrypt) c undefined bs)+        ParamsCBC cipher iv -> getCipher cipher key >>= (\c -> wrapDecrypt cbcDecrypt c iv bs)+        ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> wrapDecrypt cbcDecrypt c iv bs)+        ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> wrapDecrypt cfbDecrypt c iv bs)+        ParamsCTR _ _       -> Left (InvalidParameter "Unable to unwrap key in CTR mode")+keyDecrypt key AES128_WRAP      bs = getCipher AES128   key >>= (`AES_KW.unwrap` bs)+keyDecrypt key AES192_WRAP      bs = getCipher AES192   key >>= (`AES_KW.unwrap` bs)+keyDecrypt key AES256_WRAP      bs = getCipher AES256   key >>= (`AES_KW.unwrap` bs)+keyDecrypt key AES128_WRAP_PAD  bs = getCipher AES128   key >>= (`AES_KW.unwrapPad` bs)+keyDecrypt key AES192_WRAP_PAD  bs = getCipher AES192   key >>= (`AES_KW.unwrapPad` bs)+keyDecrypt key AES256_WRAP_PAD  bs = getCipher AES256   key >>= (`AES_KW.unwrapPad` bs)+keyDecrypt key DES_EDE3_WRAP    bs = getCipher DES_EDE3 key >>= (`TripleDES_KW.unwrap` bs)+keyDecrypt key (RC2_WRAP ekl)   bs = getRC2Cipher ekl key >>= (`RC2_KW.unwrap` bs)++keyWrap :: (MonadRandom m, ByteArray ba)+        => Int -> ba -> m (Either StoreError ba)+keyWrap sz input+    | inLen <   3 = return $ Left (InvalidInput "keyWrap: input key too short")+    | inLen > 255 = return $ Left (InvalidInput "keyWrap: input key too long")+    | pLen == 0   = return $ Right $ B.concat [ count, check, input ]+    | otherwise   = do+        padding <- getRandomBytes pLen+        return $ Right $ B.concat [ count, check, input, padding ]+  where+    inLen = B.length input+    count = B.singleton (fromIntegral inLen)+    check = B.xor input (B.pack [255, 255, 255] :: B.Bytes)+    pLen  = sz - (inLen + 4) `mod` sz + comp+    comp  = if inLen + 4 > sz then 0 else sz++keyUnwrap :: ByteArray ba => ba -> Either StoreError ba+keyUnwrap input+    | inLen < 4         = Left (InvalidInput "keyUnwrap: invalid wrapped key")+    | valid             = Right $ B.take count (B.drop 4 input)+    | otherwise         = Left (InvalidInput "keyUnwrap: invalid wrapped key")+  where+    inLen = B.length input+    count = fromIntegral (B.index input 0)+    bytes = [ B.index input (i + 1) `xor` B.index input (i + 4) | i <- [0..2] ]+    valid = foldl1 (.&.) bytes == 0xFF &&! inLen >= count - 4++wrapEncrypt :: (MonadRandom m, BlockCipher cipher, ByteArray ba)+            => (cipher -> IV cipher -> ba -> ba)+            -> cipher -> IV cipher -> ba -> m (Either StoreError ba)+wrapEncrypt encFn cipher iv input = do+    wrapped <- keyWrap sz input+    return (fn <$> wrapped)+  where+    sz = blockSize cipher+    fn formatted =+        let firstPass = encFn cipher iv formatted+            lastBlock = B.dropView firstPass (B.length firstPass - sz)+            Just iv'  = makeIV lastBlock+         in encFn cipher iv' firstPass++wrapDecrypt :: (BlockCipher cipher, ByteArray ba)+            => (cipher -> IV cipher -> ba -> ba)+            -> cipher -> IV cipher -> ba -> Either StoreError ba+wrapDecrypt decFn cipher iv input = keyUnwrap (decFn cipher iv firstPass)+  where+    sz = blockSize cipher+    (beg, lb) = B.splitAt (B.length input - sz) input+    lastBlock = decFn cipher iv' lb+    Just iv'  = makeIV (B.dropView beg (B.length beg - sz))+    Just iv'' = makeIV lastBlock+    firstPass = decFn cipher iv'' beg `B.append` lastBlock+++-- Key transport++-- | Encryption parameters for RSAES-OAEP.+data OAEPParams = OAEPParams+    { oaepHashAlgorithm :: DigestAlgorithm       -- ^ Hash function+    , oaepMaskGenAlgorithm :: MaskGenerationFunc -- ^ Mask generation function+    }+    deriving (Show,Eq)++withOAEPParams :: forall seed output a . (ByteArrayAccess seed, ByteArray output)+               => OAEPParams+               -> (forall hash . Hash.HashAlgorithm hash => RSAOAEP.OAEPParams hash seed output -> a)+               -> a+withOAEPParams p fn =+    case oaepHashAlgorithm p of+        DigestAlgorithm hashAlg ->+            fn RSAOAEP.OAEPParams+                { RSAOAEP.oaepHash = hashFromProxy hashAlg+                , RSAOAEP.oaepMaskGenAlg = mgf (oaepMaskGenAlgorithm p)+                , RSAOAEP.oaepLabel = Nothing+                }++instance ASN1Elem e => ProduceASN1Object e OAEPParams where+    asn1s OAEPParams{..} =+        asn1Container Sequence (h . m)+      where+        sha1  = DigestAlgorithm SHA1+        tag i = asn1Container (Container Context i)++        h | oaepHashAlgorithm == sha1 = id+          | otherwise = tag 0 (algorithmASN1S Sequence oaepHashAlgorithm)++        m | oaepMaskGenAlgorithm == MGF1 sha1 = id+          | otherwise = tag 1 (algorithmASN1S Sequence oaepMaskGenAlgorithm)++instance Monoid e => ParseASN1Object e OAEPParams where+    parse = onNextContainer Sequence $ do+        h <- tag 0 (parseAlgorithm Sequence)+        m <- tag 1 (parseAlgorithm Sequence)+        _ <- tag 2 parsePSpecified+        return OAEPParams { oaepHashAlgorithm = fromMaybe sha1 h+                          , oaepMaskGenAlgorithm = fromMaybe (MGF1 sha1) m+                          }+      where+        sha1  = DigestAlgorithm SHA1+        tag i = onNextContainerMaybe (Container Context i)++        parsePSpecified = do+            OID [1,2,840,113549,1,1,9] <- getNext+            OctetString p <- getNext+            guard (B.null p)++data KeyTransportType = TypeRSAES+                      | TypeRSAESOAEP++instance Enumerable KeyTransportType where+    values = [ TypeRSAES+             , TypeRSAESOAEP+             ]++instance OIDable KeyTransportType where+    getObjectID TypeRSAES          = [1,2,840,113549,1,1,1]+    getObjectID TypeRSAESOAEP      = [1,2,840,113549,1,1,7]++instance OIDNameable KeyTransportType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key transport algorithm with associated parameters.+data KeyTransportParams = RSAES                 -- ^ RSAES-PKCS1+                        | RSAESOAEP OAEPParams  -- ^ RSAES-OAEP+                        deriving (Show,Eq)++instance AlgorithmId KeyTransportParams where+    type AlgorithmType KeyTransportParams = KeyTransportType+    algorithmName _ = "key transport algorithm"++    algorithmType RSAES              = TypeRSAES+    algorithmType (RSAESOAEP _)      = TypeRSAESOAEP++    parameterASN1S RSAES             = gNull+    parameterASN1S (RSAESOAEP p)     = asn1s p++    parseParameter TypeRSAES         = getNextMaybe nullOrNothing >> return RSAES+    parseParameter TypeRSAESOAEP     = RSAESOAEP <$> parse++-- | Encrypt the specified content with a key-transport algorithm and recipient+-- public key.+transportEncrypt :: MonadRandom m+                 => KeyTransportParams+                 -> X509.PubKey+                 -> ByteString+                 -> m (Either StoreError ByteString)+transportEncrypt RSAES         (X509.PubKeyRSA pub) bs =+    mapLeft RSAError <$> RSA.encrypt pub bs+transportEncrypt (RSAESOAEP p) (X509.PubKeyRSA pub) bs =+    withOAEPParams p $ \params ->+        mapLeft RSAError <$> RSAOAEP.encrypt params pub bs+transportEncrypt _ _ _ = return $ Left UnexpectedPublicKeyType++-- | Decrypt the specified content with a key-transport algorithm and recipient+-- private key.+transportDecrypt :: MonadRandom m+                 => KeyTransportParams+                 -> X509.PrivKey+                 -> ByteString+                 -> m (Either StoreError ByteString)+transportDecrypt RSAES         (X509.PrivKeyRSA priv) bs =+    mapLeft RSAError <$> RSA.decryptSafer priv bs+transportDecrypt (RSAESOAEP p) (X509.PrivKeyRSA priv) bs =+    withOAEPParams p $ \params ->+        mapLeft RSAError <$> RSAOAEP.decryptSafer params priv bs+transportDecrypt _ _ _ = return $ Left UnexpectedPrivateKeyType+++-- Key agreement++data KeyAgreementType = TypeStdDH DigestAlgorithm+                      | TypeCofactorDH DigestAlgorithm+                      deriving (Show,Eq)++instance Enumerable KeyAgreementType where+    values = [ TypeStdDH (DigestAlgorithm SHA1)+             , TypeStdDH (DigestAlgorithm SHA224)+             , TypeStdDH (DigestAlgorithm SHA256)+             , TypeStdDH (DigestAlgorithm SHA384)+             , TypeStdDH (DigestAlgorithm SHA512)++             , TypeCofactorDH (DigestAlgorithm SHA1)+             , TypeCofactorDH (DigestAlgorithm SHA224)+             , TypeCofactorDH (DigestAlgorithm SHA256)+             , TypeCofactorDH (DigestAlgorithm SHA384)+             , TypeCofactorDH (DigestAlgorithm SHA512)+             ]++instance OIDable KeyAgreementType where+    getObjectID (TypeStdDH (DigestAlgorithm SHA1))        = [1,3,133,16,840,63,0,2]+    getObjectID (TypeStdDH (DigestAlgorithm SHA224))      = [1,3,132,1,11,0]+    getObjectID (TypeStdDH (DigestAlgorithm SHA256))      = [1,3,132,1,11,1]+    getObjectID (TypeStdDH (DigestAlgorithm SHA384))      = [1,3,132,1,11,2]+    getObjectID (TypeStdDH (DigestAlgorithm SHA512))      = [1,3,132,1,11,3]++    getObjectID (TypeCofactorDH (DigestAlgorithm SHA1))   = [1,3,133,16,840,63,0,3]+    getObjectID (TypeCofactorDH (DigestAlgorithm SHA224)) = [1,3,132,1,14,0]+    getObjectID (TypeCofactorDH (DigestAlgorithm SHA256)) = [1,3,132,1,14,1]+    getObjectID (TypeCofactorDH (DigestAlgorithm SHA384)) = [1,3,132,1,14,2]+    getObjectID (TypeCofactorDH (DigestAlgorithm SHA512)) = [1,3,132,1,14,3]++    getObjectID ty = error ("Unsupported KeyAgreementType: " ++ show ty)++instance OIDNameable KeyAgreementType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key agreement algorithm with associated parameters.+data KeyAgreementParams = StdDH DigestAlgorithm KeyEncryptionParams+                          -- ^ 1-Pass D-H with Stardard ECDH+                        | CofactorDH DigestAlgorithm KeyEncryptionParams+                          -- ^ 1-Pass D-H with Cofactor ECDH+                        deriving (Show,Eq)++instance AlgorithmId KeyAgreementParams where+    type AlgorithmType KeyAgreementParams = KeyAgreementType+    algorithmName _ = "key agreement algorithm"++    algorithmType (StdDH d _)         = TypeStdDH d+    algorithmType (CofactorDH d _)    = TypeCofactorDH d++    parameterASN1S (StdDH _ p)        = algorithmASN1S Sequence p+    parameterASN1S (CofactorDH _ p)   = algorithmASN1S Sequence p++    parseParameter (TypeStdDH d)      = StdDH d <$> parseAlgorithm Sequence+    parseParameter (TypeCofactorDH d) = CofactorDH d <$> parseAlgorithm Sequence++ecdhKeyMaterial :: (ByteArrayAccess bin, ByteArray bout)+                => DigestAlgorithm -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout+ecdhKeyMaterial (DigestAlgorithm hashAlg) kep ukm zz+    | r == 0    = B.concat (map chunk [1..d])+    | otherwise = B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)+  where+    (d, r)   = outLen `divMod` Hash.hashDigestSize prx++    prx      = hashFromProxy hashAlg+    outLen   = getMaximumKeySize kep+    outBits  = 8 * outLen+    toWord32 = i2ospOf_ 4 . fromIntegral++    chunk     = B.convert . Hash.hashFinalize . hashCtx+    hashCtx'  = Hash.hashInitWith prx+    hashCtx i = Hash.hashUpdate (Hash.hashUpdate (Hash.hashUpdate hashCtx' zz) (toWord32 i)) otherInfo+    otherInfo =+        let ki  = algorithmASN1S Sequence kep+            eui = case ukm of+                    Nothing -> id+                    Just bs -> asn1Container (Container Context 0)+                                   (gOctetString bs)+            spi = asn1Container (Container Context 2)+                      (gOctetString $ toWord32 outBits)+         in encodeASN1S $ asn1Container Sequence (ki . eui . spi)++-- | Generate an ephemeral ECDH key.+ecdhGenerate :: MonadRandom m => X509.PubKey -> m (Either StoreError (ECC.Curve, ECC.PrivateNumber, X509.SerializedPoint))+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)+ecdhGenerate _ = return $ Left UnexpectedPublicKeyType++-- | 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++-- | 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 =+    case ecPrivKeyCurve priv of+        Nothing    -> Left UnsupportedEllipticCurve+        Just curve ->+            case unserializePoint curve 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 =+    case ecPrivKeyCurve priv of+        Nothing    -> Left UnsupportedEllipticCurve+        Just curve ->+            case unserializePoint curve pt of+                Nothing  -> Left (InvalidInput "Invalid serialized point")+                Just pub -> do+                    let h = ECC.ecc_h (ECC.common_curve curve)+                        d = X509.privkeyEC_priv priv+                        s = ECDH.getShared curve (h * d) pub+                        k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes+                    keyDecrypt k kep bs+++-- Utilities++getCipher :: (BlockCipher cipher, ByteArray key)+          => proxy cipher -> key -> Either StoreError cipher+getCipher _ key = fromCryptoFailable (cipherInit key)++getRC2Cipher :: ByteArray key => Int -> key -> Either StoreError RC2+getRC2Cipher len key = fromCryptoFailable (rc2WithEffectiveKeyLength len key)++ivGenerate :: (BlockCipher cipher, MonadRandom m) => cipher -> m (IV cipher)+ivGenerate cipher = do+    bs <- getRandomBytes (blockSize cipher)+    let Just iv = makeIV (bs :: ByteString)+    return iv++nonceGenerate :: MonadRandom m => Int -> m B.Bytes+nonceGenerate = getRandomBytes++cipherFromProxy :: proxy cipher -> cipher+cipherFromProxy _ = undefined++-- | Return the block size of the specified block cipher.+proxyBlockSize :: BlockCipher cipher => proxy cipher -> Int+proxyBlockSize = blockSize . cipherFromProxy++getL :: CCM_L -> Int+getL CCM_L2 = 2+getL CCM_L3 = 3+getL CCM_L4 = 4++getM :: CCM_M -> Int+getM CCM_M4  = 4+getM CCM_M6  = 6+getM CCM_M8  = 8+getM CCM_M10 = 10+getM CCM_M12 = 12+getM CCM_M14 = 14+getM CCM_M16 = 16++fromL :: Int -> Maybe CCM_L+fromL 2 = Just CCM_L2+fromL 3 = Just CCM_L3+fromL 4 = Just CCM_L4+fromL _ = Nothing++parseM :: Monoid e => ParseASN1 e CCM_M+parseM = do+    IntVal l <- getNext+    case l of+        4  -> return CCM_M4+        6  -> return CCM_M6+        8  -> return CCM_M8+        10 -> return CCM_M10+        12 -> return CCM_M12+        14 -> return CCM_M14+        16 -> return CCM_M16+        i -> throwParseError ("Parsed invalid CCM parameter M: " ++ show i)+++-- Mask generation functions++data MaskGenerationType = TypeMGF1+    deriving (Show,Eq)++instance Enumerable MaskGenerationType where+    values = [ TypeMGF1+             ]++instance OIDable MaskGenerationType where+    getObjectID TypeMGF1     = [1,2,840,113549,1,1,8]++instance OIDNameable MaskGenerationType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Mask Generation Functions (MGF) and associated parameters.+newtype MaskGenerationFunc = MGF1 DigestAlgorithm+    deriving (Show,Eq)++instance AlgorithmId MaskGenerationFunc where+    type AlgorithmType MaskGenerationFunc = MaskGenerationType+    algorithmName _  = "mask generation function"++    algorithmType (MGF1 _)   = TypeMGF1++    parameterASN1S (MGF1 d)  = algorithmASN1S Sequence d++    parseParameter TypeMGF1  = MGF1 <$> parseAlgorithm Sequence++-- | Generate a mask with the MGF.+mgf :: (ByteArrayAccess seed, ByteArray output)+    => MaskGenerationFunc -> seed -> Int -> output+mgf (MGF1 (DigestAlgorithm hashAlg)) = MGF.mgf1 (hashFromProxy hashAlg)+++-- Signature algorithms++-- | Signature value.+type SignatureValue = ByteString++-- | Signature parameters for RSASSA-PSS.+data PSSParams = PSSParams+    { pssHashAlgorithm :: DigestAlgorithm       -- ^ Hash function+    , pssMaskGenAlgorithm :: MaskGenerationFunc -- ^ Mask generation function+    , pssSaltLength :: Int                      -- ^ Length of the salt in bytes+    }+    deriving (Show,Eq)++withPSSParams :: forall seed output a . (ByteArrayAccess seed, ByteArray output)+              => PSSParams+              -> (forall hash . Hash.HashAlgorithm hash => RSAPSS.PSSParams hash seed output -> a)+              -> a+withPSSParams p fn =+    case pssHashAlgorithm p of+        DigestAlgorithm hashAlg ->+            fn RSAPSS.PSSParams+                { RSAPSS.pssHash = hashFromProxy hashAlg+                , RSAPSS.pssMaskGenAlg = mgf (pssMaskGenAlgorithm p)+                , RSAPSS.pssSaltLength = pssSaltLength p+                , RSAPSS.pssTrailerField = 0xbc+                }++instance ASN1Elem e => ProduceASN1Object e PSSParams where+    asn1s PSSParams{..} =+        asn1Container Sequence (h . m . s)+      where+        sha1  = DigestAlgorithm SHA1+        tag i = asn1Container (Container Context i)++        h | pssHashAlgorithm == sha1 = id+          | otherwise = tag 0 (algorithmASN1S Sequence pssHashAlgorithm)++        m | pssMaskGenAlgorithm == MGF1 sha1 = id+          | otherwise = tag 1 (algorithmASN1S Sequence pssMaskGenAlgorithm)++        s | pssSaltLength == 20 && pssHashAlgorithm == sha1 = id+          | otherwise = tag 2 (gIntVal $ fromIntegral pssSaltLength)++instance Monoid e => ParseASN1Object e PSSParams where+    parse = onNextContainer Sequence $ do+        h <- tag 0 (parseAlgorithm Sequence)+        m <- tag 1 (parseAlgorithm Sequence)+        s <- tag 2 $ do { IntVal i <- getNext; return (fromIntegral i) }+        _ <- tag 3 $ do { IntVal 1 <- getNext; return () }+        return PSSParams { pssHashAlgorithm = fromMaybe sha1 h+                         , pssMaskGenAlgorithm = fromMaybe (MGF1 sha1) m+                         , pssSaltLength = fromMaybe 20 s+                         }+      where+        sha1  = DigestAlgorithm SHA1+        tag i = onNextContainerMaybe (Container Context i)++data SignatureType = TypeRSAAnyHash+                   | TypeRSA DigestAlgorithm+                   | TypeRSAPSS+                   | TypeDSA DigestAlgorithm+                   | TypeECDSA DigestAlgorithm+    deriving (Show,Eq)++instance Enumerable SignatureType where+    values = [ TypeRSAAnyHash++             , TypeRSA (DigestAlgorithm MD2)+             , TypeRSA (DigestAlgorithm MD5)+             , TypeRSA (DigestAlgorithm SHA1)+             , TypeRSA (DigestAlgorithm SHA224)+             , TypeRSA (DigestAlgorithm SHA256)+             , TypeRSA (DigestAlgorithm SHA384)+             , TypeRSA (DigestAlgorithm SHA512)++             , TypeRSAPSS++             , TypeDSA (DigestAlgorithm SHA1)+             , TypeDSA (DigestAlgorithm SHA224)+             , TypeDSA (DigestAlgorithm SHA256)++             , TypeECDSA (DigestAlgorithm SHA1)+             , TypeECDSA (DigestAlgorithm SHA224)+             , TypeECDSA (DigestAlgorithm SHA256)+             , TypeECDSA (DigestAlgorithm SHA384)+             , TypeECDSA (DigestAlgorithm SHA512)+             ]++instance OIDable SignatureType where+    getObjectID TypeRSAAnyHash                       = [1,2,840,113549,1,1,1]++    getObjectID (TypeRSA (DigestAlgorithm MD2))      = [1,2,840,113549,1,1,2]+    getObjectID (TypeRSA (DigestAlgorithm MD5))      = [1,2,840,113549,1,1,4]+    getObjectID (TypeRSA (DigestAlgorithm SHA1))     = [1,2,840,113549,1,1,5]+    getObjectID (TypeRSA (DigestAlgorithm SHA224))   = [1,2,840,113549,1,1,14]+    getObjectID (TypeRSA (DigestAlgorithm SHA256))   = [1,2,840,113549,1,1,11]+    getObjectID (TypeRSA (DigestAlgorithm SHA384))   = [1,2,840,113549,1,1,12]+    getObjectID (TypeRSA (DigestAlgorithm SHA512))   = [1,2,840,113549,1,1,13]++    getObjectID TypeRSAPSS                           = [1,2,840,113549,1,1,10]++    getObjectID (TypeDSA (DigestAlgorithm SHA1))     = [1,2,840,10040,4,3]+    getObjectID (TypeDSA (DigestAlgorithm SHA224))   = [2,16,840,1,101,3,4,3,1]+    getObjectID (TypeDSA (DigestAlgorithm SHA256))   = [2,16,840,1,101,3,4,3,2]++    getObjectID (TypeECDSA (DigestAlgorithm SHA1))   = [1,2,840,10045,4,1]+    getObjectID (TypeECDSA (DigestAlgorithm SHA224)) = [1,2,840,10045,4,3,1]+    getObjectID (TypeECDSA (DigestAlgorithm SHA256)) = [1,2,840,10045,4,3,2]+    getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3]+    getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4]++    getObjectID ty = error ("Unsupported SignatureType: " ++ show ty)++instance OIDNameable SignatureType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | CMS signature algorithms and associated parameters.+data SignatureAlg = RSAAnyHash+                  | RSA DigestAlgorithm+                  | RSAPSS PSSParams+                  | DSA DigestAlgorithm+                  | ECDSA DigestAlgorithm+    deriving (Show,Eq)++instance AlgorithmId SignatureAlg where+    type AlgorithmType SignatureAlg = SignatureType+    algorithmName _  = "signature algorithm"++    algorithmType RSAAnyHash  = TypeRSAAnyHash+    algorithmType (RSA alg)   = TypeRSA alg+    algorithmType (RSAPSS _)  = TypeRSAPSS+    algorithmType (DSA alg)   = TypeDSA alg+    algorithmType (ECDSA alg) = TypeECDSA alg++    parameterASN1S RSAAnyHash = gNull+    parameterASN1S (RSA _)    = gNull+    parameterASN1S (RSAPSS p) = asn1s p+    parameterASN1S (DSA _)    = id+    parameterASN1S (ECDSA _)  = 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)++-- | Sign a message using the specified algorithm and private key.+signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> ByteString -> m (Either StoreError SignatureValue)+signatureGenerate RSAAnyHash _ _ =+    error "signatureGenerate: should call signatureResolveHash first"+signatureGenerate (RSA alg)   (X509.PrivKeyRSA priv) msg =+    let err = return . Left $ InvalidParameter ("Invalid hash algorithm for RSA: " ++ show alg)+     in withHashAlgorithmASN1 alg err $ \hashAlg ->+            mapLeft RSAError <$> RSA.signSafer (Just hashAlg) priv msg+signatureGenerate (RSAPSS p)  (X509.PrivKeyRSA priv) msg =+    withPSSParams p $ \params ->+        mapLeft RSAError <$> RSAPSS.signSafer params priv msg+signatureGenerate (DSA alg)   (X509.PrivKeyDSA priv) msg =+    case alg of+        DigestAlgorithm t ->+            Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg+signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv)  msg =+    case alg of+        DigestAlgorithm t ->+            case ecdsaToPrivateKey priv of+                Nothing -> return (Left UnsupportedEllipticCurve)+                Just p  ->+                    let h = hashFromProxy t+                     in Right . ecdsaFromSignature <$> ECDSA.sign p h msg+signatureGenerate _ _ _ = return (Left UnexpectedPrivateKeyType)++-- | Verify a message signature using the specified algorithm and public key.+signatureVerify :: SignatureAlg -> X509.PubKey -> ByteString -> SignatureValue -> Bool+signatureVerify RSAAnyHash _ _ _ =+    error "signatureVerify: should call signatureResolveHash first"+signatureVerify (RSA alg)   (X509.PubKeyRSA pub) msg sig =+    withHashAlgorithmASN1 alg False $ \hashAlg ->+        RSA.verify (Just hashAlg) pub msg sig+signatureVerify (RSAPSS p)  (X509.PubKeyRSA pub) msg sig =+    withPSSParams p $ \params -> RSAPSS.verify params pub msg sig+signatureVerify (DSA alg)   (X509.PubKeyDSA pub) msg sig = fromMaybe False $ do+    s <- dsaToSignature sig+    case alg of+        DigestAlgorithm t -> return $ DSA.verify (hashFromProxy t) pub s msg+signatureVerify (ECDSA alg) (X509.PubKeyEC pub)  msg sig = fromMaybe False $ do+    p <- ecdsaToPublicKey pub+    s <- ecdsaToSignature sig+    case alg of+        DigestAlgorithm t -> return $ ECDSA.verify (hashFromProxy t) p s msg+signatureVerify _                 _                    _   _   = False++withHashAlgorithmASN1 :: DigestAlgorithm+                      -> a+                      -> (forall hashAlg . RSA.HashAlgorithmASN1 hashAlg => hashAlg -> a)+                      -> a+withHashAlgorithmASN1 (DigestAlgorithm MD2)    _ f = f Hash.MD2+withHashAlgorithmASN1 (DigestAlgorithm MD5)    _ f = f Hash.MD5+withHashAlgorithmASN1 (DigestAlgorithm SHA1)   _ f = f Hash.SHA1+withHashAlgorithmASN1 (DigestAlgorithm SHA224) _ f = f Hash.SHA224+withHashAlgorithmASN1 (DigestAlgorithm SHA256) _ f = f Hash.SHA256+withHashAlgorithmASN1 (DigestAlgorithm SHA384) _ f = f Hash.SHA384+withHashAlgorithmASN1 (DigestAlgorithm SHA512) _ f = f Hash.SHA512+withHashAlgorithmASN1 _                        e _ = e++-- | 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)++-- | Check that a signature algorithm is based on the specified digest algorithm+-- and return a substitution algorithm for when a default digest algorithm is+-- required.+signatureCheckHash :: DigestAlgorithm -> SignatureAlg -> Maybe SignatureAlg+signatureCheckHash expected RSAAnyHash = Just $ RSA expected+signatureCheckHash expected alg@(RSA found)+    | expected == found = Just alg+    | otherwise         = Nothing+signatureCheckHash expected alg@(RSAPSS p)+    | expected == pssHashAlgorithm p = Just alg+    | otherwise                      = Nothing+signatureCheckHash expected alg@(DSA found)+    | expected == found = Just alg+    | otherwise         = Nothing+signatureCheckHash expected alg@(ECDSA found)+    | expected == found = Just alg+    | otherwise         = Nothing++dsaToSignature :: ByteString -> Maybe DSA.Signature+dsaToSignature b = tryDecodeAndParse b $ onNextContainer Sequence $ do+    IntVal r <- getNext+    IntVal s <- getNext+    return DSA.Signature { DSA.sign_r = r, DSA.sign_s = s }++dsaFromSignature :: DSA.Signature -> ByteString+dsaFromSignature sig = encodeASN1S $ asn1Container Sequence+    (gIntVal (DSA.sign_r sig) . gIntVal (DSA.sign_s sig))++ecdsaToSignature :: ByteString -> Maybe ECDSA.Signature+ecdsaToSignature b = tryDecodeAndParse b $ onNextContainer Sequence $ do+    IntVal r <- getNext+    IntVal s <- getNext+    return ECDSA.Signature { ECDSA.sign_r = r, ECDSA.sign_s = s }++ecdsaFromSignature :: ECDSA.Signature -> ByteString+ecdsaFromSignature sig = encodeASN1S $ asn1Container Sequence+    (gIntVal (ECDSA.sign_r sig) . gIntVal (ECDSA.sign_s sig))++ecdsaToPublicKey :: X509.PubKeyEC -> Maybe ECDSA.PublicKey+ecdsaToPublicKey key = do+    curve <- ecPubKeyCurve key+    pt <- unserializePoint curve (X509.pubkeyEC_pub key)+    return ECDSA.PublicKey { ECDSA.public_curve = curve, ECDSA.public_q = pt }++ecdsaToPrivateKey :: X509.PrivKeyEC -> Maybe ECDSA.PrivateKey+ecdsaToPrivateKey key = do+    curve <- ecPrivKeyCurve key+    let d = X509.privkeyEC_priv key+    return ECDSA.PrivateKey { ECDSA.private_curve = curve, ECDSA.private_d = d }++tryDecodeAndParse :: ByteString -> ParseASN1 () a -> Maybe a+tryDecodeAndParse b parser =+    either (const Nothing) Just $+        case decodeASN1' BER b of+            Left _     -> Left undefined -- value ignored+            Right asn1 -> runParseASN1 parser asn1
+ src/Crypto/Store/CMS/Attribute.hs view
@@ -0,0 +1,126 @@+-- |+-- Module      : Crypto.Store.CMS.Attribute+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- CMS attributes+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.CMS.Attribute+    ( Attribute(..)+    , attributesASN1S+    , parseAttributes+    -- * Generic attribute+    , findAttribute+    , setAttribute+    , filterAttributes+    -- * Implementing attributes+    , setAttributeASN1S+    , runParseAttribute+    -- * Standard attributes+    , getContentTypeAttr+    , setContentTypeAttr+    , getMessageDigestAttr+    , setMessageDigestAttr+    ) where++import Data.ASN1.Types+import Data.ByteString (ByteString)+import Data.Maybe (fromMaybe)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util++-- | An attribute extending the parent structure with arbitrary data.+data Attribute = Attribute+    { attrType   :: OID    -- ^ Attribute type+    , attrValues :: [ASN1] -- ^ Attribute values+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e Attribute where+    asn1s Attribute{..} =+        asn1Container Sequence+            (gOID attrType . asn1Container Set (gMany attrValues))++instance Monoid e => ParseASN1Object e Attribute where+    parse = onNextContainer Sequence $ do+        OID oid <- getNext+        vals <- onNextContainer Set (getMany getNext)+        return Attribute { attrType = oid, attrValues = vals }++-- | Produce the ASN.1 stream for a list of attributes.+attributesASN1S :: ASN1Elem e+                => ASN1ConstructionType -> [Attribute] -> ASN1Stream e+attributesASN1S _  []    = id+attributesASN1S ty attrs = asn1Container ty (asn1s attrs)++-- | Parse a list of attributes.+parseAttributes :: Monoid e => ASN1ConstructionType -> ParseASN1 e [Attribute]+parseAttributes ty = fromMaybe [] <$> onNextContainerMaybe ty parse++-- | Return the values for the first attribute with the specified type.+findAttribute :: OID -> [Attribute] -> Maybe [ASN1]+findAttribute oid attrs =+    case [ attrValues a | a <- attrs, attrType a == oid ] of+        []    -> Nothing+        (v:_) -> Just v++-- | Filter a list of attributes based on a predicate applied to attribute type.+filterAttributes :: (OID -> Bool) -> [Attribute] -> [Attribute]+filterAttributes p = filter (p . attrType)++-- | Add or replace an attribute in a list of attributes.+setAttribute :: OID -> [ASN1] -> [Attribute] -> [Attribute]+setAttribute oid vals = (:) attr . filterAttributes (/= oid)+  where attr = Attribute { attrType = oid, attrValues = vals }++-- | Find an attribute with the specified attribute and run a parser on the+-- attribute value when found.  'Nothing' is returned if the attribute could not+-- be found but also when the parse failed.+runParseAttribute :: OID -> [Attribute] -> ParseASN1 () a -> Maybe a+runParseAttribute oid attrs p =+    case findAttribute oid attrs of+        Nothing -> Nothing+        Just s  -> either (const Nothing) Just (runParseASN1 p s)++-- | Add or replace an attribute in a list of attributes, using 'ASN1S'.+setAttributeASN1S :: OID -> ASN1S -> [Attribute] -> [Attribute]+setAttributeASN1S oid g = setAttribute oid (g [])+++-- Content type++contentType :: OID+contentType = [1,2,840,113549,1,9,3]++-- | Return the value of the @contentType@ attribute.+getContentTypeAttr :: [Attribute] -> Maybe ContentType+getContentTypeAttr attrs = runParseAttribute contentType attrs $ do+    OID oid <- getNext+    withObjectID "content type" oid return++-- | Add or replace the @contentType@ attribute in a list of attributes.+setContentTypeAttr :: ContentType -> [Attribute] -> [Attribute]+setContentTypeAttr ct = setAttributeASN1S contentType (gOID $ getObjectID ct)+++-- Message digest++messageDigest :: OID+messageDigest = [1,2,840,113549,1,9,4]++-- | Return the value of the @messageDigest@ attribute.+getMessageDigestAttr :: [Attribute] -> Maybe ByteString+getMessageDigestAttr attrs = runParseAttribute messageDigest attrs $ do+    OctetString d <- getNext+    return d++-- | Add or replace the @messageDigest@ attribute in a list of attributes.+setMessageDigestAttr :: ByteString -> [Attribute] -> [Attribute]+setMessageDigestAttr d = setAttributeASN1S messageDigest (gOctetString d)
+ src/Crypto/Store/CMS/AuthEnveloped.hs view
@@ -0,0 +1,97 @@+-- |+-- Module      : Crypto.Store.CMS.AuthEnveloped+-- 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.AuthEnveloped+    ( AuthEnvelopedData(..)+    , encodeAuthAttrs+    ) where++import Control.Applicative+import Control.Monad++import Data.ASN1.Types+import Data.ByteArray (convert)+import Data.ByteString (ByteString)++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.Encrypted+import Crypto.Store.CMS.Enveloped+import Crypto.Store.CMS.OriginatorInfo+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util++-- | Authenticated-enveloped content information.+data AuthEnvelopedData = AuthEnvelopedData+    { aeOriginatorInfo :: OriginatorInfo+      -- ^ Optional information about the originator+    , aeRecipientInfos :: [RecipientInfo]+      -- ^ Information for recipients, allowing to decrypt the content+    , aeContentType :: ContentType+      -- ^ Inner content type+    , aeContentEncryptionParams :: ASN1ObjectExact AuthContentEncryptionParams+      -- ^ Encryption algorithm+    , aeEncryptedContent :: EncryptedContent+      -- ^ Encrypted content info+    , aeAuthAttrs :: [Attribute]+      -- ^ Optional authenticated attributes+    , aeMAC :: MessageAuthenticationCode+      -- ^ Message authentication code+    , aeUnauthAttrs :: [Attribute]+      -- ^ Optional unauthenticated attributes+    }+    deriving (Show,Eq)++instance ProduceASN1Object ASN1P AuthEnvelopedData where+    asn1s AuthEnvelopedData{..} =+        asn1Container Sequence (ver . oi . ris . eci . aa . tag . ua)+      where+        ver = gIntVal 0+        ris = asn1Container Set (asn1s aeRecipientInfos)+        eci = encryptedContentInfoASN1S+                  (aeContentType, aeContentEncryptionParams, aeEncryptedContent)+        aa  = attributesASN1S (Container Context 1) aeAuthAttrs+        tag = gOctetString (convert aeMAC)+        ua  = attributesASN1S (Container Context 2) aeUnauthAttrs++        oi | aeOriginatorInfo == mempty = id+           | otherwise = originatorInfoASN1S (Container Context 0) aeOriginatorInfo++instance ParseASN1Object [ASN1Event] AuthEnvelopedData where+    parse =+        onNextContainer Sequence $ do+            IntVal v <- getNext+            when (v /= 0) $+                throwParseError ("AuthEnvelopedData: parsed invalid version: " ++ show v)+            oi <- parseOriginatorInfo (Container Context 0) <|> return mempty+            ris <- onNextContainer Set parse+            (ct, params, ec) <- parseEncryptedContentInfo+            aAttrs <- parseAttributes (Container Context 1)+            OctetString tag <- getNext+            uAttrs <- parseAttributes (Container Context 2)+            return AuthEnvelopedData { aeOriginatorInfo = oi+                                     , aeContentType = ct+                                     , aeRecipientInfos = ris+                                     , aeContentEncryptionParams = params+                                     , aeEncryptedContent = ec+                                     , aeAuthAttrs = aAttrs+                                     , aeMAC = AuthTag $ convert tag+                                     , aeUnauthAttrs = uAttrs+                                     }++-- | Return the DER encoding of the attributes as required for AAD.+encodeAuthAttrs :: [Attribute] -> ByteString+encodeAuthAttrs [] = mempty+encodeAuthAttrs l  = encodeASN1S $ asn1Container Set (asn1s l)
+ src/Crypto/Store/CMS/Encrypted.hs view
@@ -0,0 +1,99 @@+-- |+-- Module      : Crypto.Store.CMS.Encrypted+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+--+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.CMS.Encrypted+    ( EncryptedContent+    , ContentEncryptionKey+    , EncryptedData(..)+    , encryptedContentInfoASN1S+    , parseEncryptedContentInfo+    ) where++import Control.Applicative+import Control.Monad++import           Data.ASN1.Types+import qualified Data.ByteString as B++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util++-- | Key used for content encryption.+type ContentEncryptionKey = B.ByteString++-- | Encrypted content.+type EncryptedContent = B.ByteString++-- | Encrypted content information.+data EncryptedData = EncryptedData+    { edContentType :: ContentType+      -- ^ Inner content type+    , edContentEncryptionParams :: ContentEncryptionParams+      -- ^ Encryption algorithm+    , edEncryptedContent :: EncryptedContent+      -- ^ Encrypted content info+    , edUnprotectedAttrs :: [Attribute]+      -- ^ Optional unprotected attributes+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e EncryptedData where+    asn1s EncryptedData{..} =+        asn1Container Sequence (ver . eci . ua)+      where+        ver = gIntVal (if null edUnprotectedAttrs then 0 else 2)+        eci = encryptedContentInfoASN1S+                  (edContentType, edContentEncryptionParams, edEncryptedContent)+        ua  = attributesASN1S (Container Context 1) edUnprotectedAttrs++instance Monoid e => ParseASN1Object e EncryptedData where+    parse =+        onNextContainer Sequence $ do+            IntVal v <- getNext+            when (v /= 0 && v /= 2) $+                throwParseError ("EncryptedData: parsed invalid version: " ++ show v)+            (ct, params, ec) <- parseEncryptedContentInfo+            attrs <- parseAttributes (Container Context 1)+            return EncryptedData { edContentType = ct+                                 , edContentEncryptionParams = params+                                 , edEncryptedContent = ec+                                 , edUnprotectedAttrs = attrs+                                 }++-- | Generate ASN.1 for EncryptedContentInfo.+encryptedContentInfoASN1S :: (ASN1Elem e, ProduceASN1Object e alg)+                          => (ContentType, alg, B.ByteString) -> 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)++-- | Parse EncryptedContentInfo from ASN.1.+parseEncryptedContentInfo :: ParseASN1Object e alg+                          => ParseASN1 e (ContentType, alg, B.ByteString)+parseEncryptedContentInfo = onNextContainer Sequence $ do+    OID oid <- getNext+    alg <- parse+    ec <- parseEncryptedContent+    withObjectID "content type" oid $ \ct -> return (ct, alg, ec)+  where+    parseEncryptedContent = parseWrapped <|> parsePrimitive+    parseWrapped  = onNextContainer (Container Context 0) parseOctetStrings+    parsePrimitive = do Other Context 0 bs <- getNext; return bs+    parseOctetString = do OctetString bs <- getNext; return bs+    parseOctetStrings = B.concat <$> getMany parseOctetString
+ src/Crypto/Store/CMS/Enveloped.hs view
@@ -0,0 +1,631 @@+-- |+-- Module      : Crypto.Store.CMS.Enveloped+-- 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.Enveloped+    ( EncryptedKey+    , UserKeyingMaterial+    , RecipientInfo(..)+    , EnvelopedData(..)+    , ProducerOfRI+    , ConsumerOfRI+    -- * Key Transport recipients+    , KTRecipientInfo(..)+    , RecipientIdentifier(..)+    , IssuerAndSerialNumber(..)+    , forKeyTransRecipient+    , withRecipientKeyTrans+    -- * Key Agreement recipients+    , KARecipientInfo(..)+    , OriginatorIdentifierOrKey(..)+    , OriginatorPublicKey+    , RecipientEncryptedKey(..)+    , KeyAgreeRecipientIdentifier(..)+    , forKeyAgreeRecipient+    , withRecipientKeyAgree+    -- * Key Encryption Key recipients+    , KeyEncryptionKey+    , KEKRecipientInfo(..)+    , KeyIdentifier(..)+    , OtherKeyAttribute(..)+    , forKeyRecipient+    , withRecipientKey+    -- * Password recipients+    , Password+    , PasswordRecipientInfo(..)+    , forPasswordRecipient+    , withRecipientPassword+    ) where++import Control.Applicative+import Control.Monad++import Data.ASN1.BitArray+import Data.ASN1.Types+import Data.ByteString (ByteString)+import Data.List (find)+import Data.Maybe (fromMaybe)+import Data.X509++import Time.Types++import Crypto.Random (MonadRandom)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Encrypted+import Crypto.Store.CMS.OriginatorInfo+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util+import Crypto.Store.Error+import Crypto.Store.PKCS8.EC++-- | Encrypted key.+type EncryptedKey = ByteString++-- | User keying material.+type UserKeyingMaterial = ByteString++-- | Key used for key encryption.+type KeyEncryptionKey = ByteString++-- | A password stored as a sequence of UTF-8 bytes.+--+-- Some key-derivation functions add restrictions to what characters+-- are supported.+type Password = ByteString++-- | Union type related to identification of the recipient.+data RecipientIdentifier+    = RecipientIASN IssuerAndSerialNumber  -- ^ Issuer and Serial Number+    | RecipientSKI  ByteString             -- ^ Subject Key Identifier+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e RecipientIdentifier where+    asn1s (RecipientIASN iasn) = asn1s iasn+    asn1s (RecipientSKI  ski)  = asn1Container (Container Context 0)+                                    (gOctetString ski)++instance Monoid e => ParseASN1Object e RecipientIdentifier where+    parse = parseIASN <|> parseSKI+      where parseIASN = RecipientIASN <$> parse+            parseSKI  = RecipientSKI  <$>+                onNextContainer (Container Context 0) parseBS+            parseBS = do { OctetString bs <- getNext; return bs }++getKTVersion :: RecipientIdentifier -> Integer+getKTVersion (RecipientIASN _) = 0+getKTVersion (RecipientSKI _)  = 2++-- | Identification of a certificate using the issuer DN and serial number.+data IssuerAndSerialNumber = IssuerAndSerialNumber+    { iasnIssuer :: DistinguishedName+      -- ^ Distinguished name of the certificate issuer+    , iasnSerial :: Integer+      -- ^ Issuer-specific certificate serial number+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e IssuerAndSerialNumber where+    asn1s IssuerAndSerialNumber{..} =+        asn1Container Sequence (asn1s iasnIssuer . gIntVal iasnSerial)++instance Monoid e => ParseASN1Object e IssuerAndSerialNumber where+    parse = onNextContainer Sequence $ do+        i <- parse+        IntVal s <- getNext+        return IssuerAndSerialNumber { iasnIssuer = i+                                     , iasnSerial = s+                                     }++idEcPublicKey :: OID+idEcPublicKey = [1,2,840,10045,2,1]++-- | Originator public key used for key-agreement.  Contrary to 'PubKey' the+-- domain parameters are not used and may be left empty.+data OriginatorPublicKey = OriginatorPublicKeyEC [ASN1] BitArray+    deriving (Show,Eq)++originatorPublicKeyASN1S :: ASN1Elem e+                         => ASN1ConstructionType+                         -> OriginatorPublicKey+                         -> ASN1Stream e+originatorPublicKeyASN1S ty (OriginatorPublicKeyEC asn1 ba) =+    asn1Container ty (alg . gBitString ba)+  where+    alg = asn1Container Sequence (gOID idEcPublicKey . gMany asn1)++parseOriginatorPublicKey :: Monoid e+                         => ASN1ConstructionType+                         -> ParseASN1 e OriginatorPublicKey+parseOriginatorPublicKey ty =+    onNextContainer ty $ do+        asn1 <- onNextContainer Sequence $ do+                    OID oid <- getNext+                    guard (oid == idEcPublicKey)+                    getMany getNext+        BitString ba <- getNext+        return (OriginatorPublicKeyEC asn1 ba)++-- | Union type related to identification of the originator.+data OriginatorIdentifierOrKey+    = OriginatorIASN IssuerAndSerialNumber  -- ^ Issuer and Serial Number+    | OriginatorSKI  ByteString             -- ^ Subject Key Identifier+    | OriginatorPublic OriginatorPublicKey  -- ^ Anonymous public key+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e OriginatorIdentifierOrKey where+    asn1s (OriginatorIASN iasn)   = asn1s iasn+    asn1s (OriginatorSKI  ski)    = asn1Container (Container Context 0)+                                       (gOctetString ski)+    asn1s (OriginatorPublic pub)  =+        originatorPublicKeyASN1S (Container Context 1) pub++instance Monoid e => ParseASN1Object e OriginatorIdentifierOrKey where+    parse = parseIASN <|> parseSKI <|> parsePublic+      where parseIASN = OriginatorIASN <$> parse+            parseSKI  = OriginatorSKI  <$>+                onNextContainer (Container Context 0) parseBS+            parseBS = do { OctetString bs <- getNext; return bs }+            parsePublic  = OriginatorPublic <$>+                parseOriginatorPublicKey (Container Context 1)++-- | Union type related to identification of a key-agreement recipient.+data KeyAgreeRecipientIdentifier+    = KeyAgreeRecipientIASN IssuerAndSerialNumber  -- ^ Issuer and Serial Number+    | KeyAgreeRecipientKI   KeyIdentifier          -- ^ Key identifier+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e KeyAgreeRecipientIdentifier where+    asn1s (KeyAgreeRecipientIASN iasn) = asn1s iasn+    asn1s (KeyAgreeRecipientKI   ki)   = asn1Container (Container Context 0)+                                            (asn1s ki)++instance Monoid e => ParseASN1Object e KeyAgreeRecipientIdentifier where+    parse = parseIASN <|> parseKI+      where parseIASN = KeyAgreeRecipientIASN <$> parse+            parseKI   = KeyAgreeRecipientKI   <$>+                onNextContainer (Container Context 0) parse++-- | Encrypted key for a recipient in a key-agreement RI.+data RecipientEncryptedKey = RecipientEncryptedKey+    { rekRid :: KeyAgreeRecipientIdentifier -- ^ identifier of recipient+    , rekEncryptedKey :: EncryptedKey       -- ^ encrypted content-encryption key+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e RecipientEncryptedKey where+    asn1s RecipientEncryptedKey{..} = asn1Container Sequence (rid . ek)+      where rid = asn1s rekRid+            ek  = gOctetString rekEncryptedKey++instance Monoid e => ParseASN1Object e RecipientEncryptedKey where+    parse = onNextContainer Sequence $ do+        rid <- parse+        OctetString ek <- getNext+        return RecipientEncryptedKey { rekRid = rid, rekEncryptedKey = ek }++findRecipientEncryptedKey :: SignedCertificate+                          -> [RecipientEncryptedKey]+                          -> Maybe EncryptedKey+findRecipientEncryptedKey cert list = rekEncryptedKey <$> find fn list+  where+    c = signedObject (getSigned cert)+    matchIASN iasn =+        (iasnIssuer iasn, iasnSerial iasn) == (certIssuerDN c, certSerial c)+    matchSKI ski   =+        case extensionGet (certExtensions c) of+            Just (ExtSubjectKeyId idBs) -> idBs == ski+            Nothing                     -> False+    fn rek = case rekRid rek of+                 KeyAgreeRecipientIASN iasn -> matchIASN iasn+                 KeyAgreeRecipientKI   ki   -> matchSKI (keyIdentifier ki)++-- | Additional information in a 'KeyIdentifier'.+data OtherKeyAttribute = OtherKeyAttribute+    { keyAttrId :: OID    -- ^ attribute identifier+    , keyAttr   :: [ASN1] -- ^ attribute value+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e OtherKeyAttribute where+    asn1s OtherKeyAttribute{..} = asn1Container Sequence (attrId . attr)+      where attrId = gOID keyAttrId+            attr   = gMany keyAttr++instance Monoid e => ParseASN1Object e OtherKeyAttribute where+    parse = onNextContainer Sequence $ do+        OID attrId <- getNext+        attr <- getMany getNext+        return OtherKeyAttribute { keyAttrId = attrId, keyAttr = attr }++-- | Key identifier and optional attributes.+data KeyIdentifier = KeyIdentifier+    { keyIdentifier :: ByteString         -- ^ identifier of the key+    , keyDate :: Maybe DateTime           -- ^ optional timestamp+    , keyOther :: Maybe OtherKeyAttribute -- ^ optional information+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e KeyIdentifier where+    asn1s KeyIdentifier{..} = asn1Container Sequence (keyId . date . other)+      where+        keyId = gOctetString keyIdentifier+        date  = optASN1S keyDate $ \v -> gASN1Time TimeGeneralized v Nothing+        other = optASN1S keyOther asn1s++instance Monoid e => ParseASN1Object e KeyIdentifier where+    parse = onNextContainer Sequence $ do+        OctetString keyId <- getNext+        date <- getNextMaybe dateTimeOrNothing+        b <- hasNext+        other <- if b then Just <$> parse else return Nothing+        return KeyIdentifier { keyIdentifier = keyId+                             , keyDate = date+                             , keyOther = other+                             }++-- | Recipient using key transport.+data KTRecipientInfo = KTRecipientInfo+    { ktRid :: RecipientIdentifier                 -- ^ identifier of recipient+    , ktKeyTransportParams :: KeyTransportParams   -- ^ key transport algorithm+    , ktEncryptedKey :: EncryptedKey               -- ^ encrypted content-encryption key+    }+    deriving (Show,Eq)++-- | Recipient using key agreement.+data KARecipientInfo = KARecipientInfo+    { kaOriginator :: OriginatorIdentifierOrKey           -- ^ identifier of orginator or anonymous key+    , kaUkm        :: Maybe UserKeyingMaterial            -- ^ user keying material+    , kaKeyAgreementParams :: KeyAgreementParams          -- ^ key agreement algorithm+    , kaRecipientEncryptedKeys :: [RecipientEncryptedKey] -- ^ encrypted content-encryption key for one or multiple recipients+    }+    deriving (Show,Eq)++-- | Recipient using key encryption.+data KEKRecipientInfo = KEKRecipientInfo+    { kekId :: KeyIdentifier                        -- ^ identifier of key encryption key+    , kekKeyEncryptionParams :: KeyEncryptionParams -- ^ key encryption algorithm+    , kekEncryptedKey :: EncryptedKey               -- ^ encrypted content-encryption key+    }+    deriving (Show,Eq)++-- | Recipient using password-based protection.+data PasswordRecipientInfo = PasswordRecipientInfo+    { priKeyDerivationFunc :: KeyDerivationFunc     -- ^ function to derive key+    , priKeyEncryptionParams :: KeyEncryptionParams -- ^ key encryption algorithm+    , priEncryptedKey :: EncryptedKey               -- ^ encrypted content-encryption key+    }+    deriving (Show,Eq)++-- | Information for a recipient of an 'EnvelopedData'.  An element contains+-- the content-encryption key in encrypted form.+data RecipientInfo = KTRI KTRecipientInfo+                     -- ^ Recipient using key transport+                   | KARI KARecipientInfo+                     -- ^ Recipient using key agreement+                   | KEKRI KEKRecipientInfo+                     -- ^ Recipient using key encryption+                   | PasswordRI PasswordRecipientInfo+                     -- ^ Recipient using password-based protection+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e RecipientInfo where+    asn1s (KTRI KTRecipientInfo{..}) =+        asn1Container Sequence (ver . rid . ktp . ek)+      where+        ver = gIntVal (getKTVersion ktRid)+        rid = asn1s ktRid+        ktp = algorithmASN1S Sequence ktKeyTransportParams+        ek  = gOctetString ktEncryptedKey++    asn1s (KARI KARecipientInfo{..}) =+        asn1Container (Container Context 1) (ver . ori . ukm . kap . reks)+      where+        ver  = gIntVal 3+        ori  = asn1Container (Container Context 0) (asn1s kaOriginator)+        kap  = algorithmASN1S Sequence kaKeyAgreementParams+        reks = asn1Container Sequence (asn1s kaRecipientEncryptedKeys)++        ukm = case kaUkm of+                  Nothing -> id+                  Just bs -> asn1Container (Container Context 1) (gOctetString bs)++    asn1s (KEKRI KEKRecipientInfo{..}) =+        asn1Container (Container Context 2) (ver . kid . kep . ek)+      where+        ver = gIntVal 4+        kid = asn1s kekId+        kep = algorithmASN1S Sequence kekKeyEncryptionParams+        ek  = gOctetString kekEncryptedKey++    asn1s (PasswordRI PasswordRecipientInfo{..}) =+        asn1Container (Container Context 3) (ver . kdf . kep . ek)+      where+        ver = gIntVal 0+        kdf = algorithmASN1S (Container Context 0) priKeyDerivationFunc+        kep = algorithmASN1S Sequence priKeyEncryptionParams+        ek  = gOctetString priEncryptedKey++instance Monoid e => ParseASN1Object e RecipientInfo where+    parse = do+        c <- onNextContainerMaybe Sequence parseKT+             `orElse` onNextContainerMaybe (Container Context 1) parseKA+             `orElse` onNextContainerMaybe (Container Context 2) parseKEK+             `orElse` onNextContainerMaybe (Container Context 3) parsePassword+        case c of+            Just val -> return val+            Nothing  -> throwParseError "RecipientInfo: unable to parse"+      where+        parseKT = KTRI <$> do+            IntVal v <- getNext+            when (v `notElem` [0, 2]) $+                throwParseError ("RecipientInfo: parsed invalid KT version: " ++ show v)+            rid <- parse+            ktp <- parseAlgorithm Sequence+            (OctetString ek) <- getNext+            return KTRecipientInfo { ktRid = rid+                                   , ktKeyTransportParams = ktp+                                   , ktEncryptedKey = ek+                                   }++        parseKA = KARI <$> do+            IntVal 3 <- getNext+            ori <- onNextContainer (Container Context 0) parse+            ukm <- onNextContainerMaybe (Container Context 1) $+                       do { OctetString bs <- getNext; return bs }+            kap <- parseAlgorithm Sequence+            reks <- onNextContainer Sequence parse+            return KARecipientInfo { kaOriginator = ori+                                   , kaUkm = ukm+                                   , kaKeyAgreementParams = kap+                                   , kaRecipientEncryptedKeys = reks+                                   }++        parseKEK = KEKRI <$> do+            IntVal 4 <- getNext+            kid <- parse+            kep <- parseAlgorithm Sequence+            (OctetString ek) <- getNext+            return KEKRecipientInfo { kekId = kid+                                    , kekKeyEncryptionParams = kep+                                    , kekEncryptedKey = ek+                                    }++        parsePassword = PasswordRI <$> do+            IntVal 0 <- getNext+            kdf <- parseAlgorithm (Container Context 0)+            kep <- parseAlgorithm Sequence+            (OctetString ek) <- getNext+            return PasswordRecipientInfo { priKeyDerivationFunc = kdf+                                         , priKeyEncryptionParams = kep+                                         , priEncryptedKey = ek+                                         }++isVersion0 :: RecipientInfo -> Bool+isVersion0 (KTRI x)       = getKTVersion (ktRid x) == 0+isVersion0 (KARI _)       = False      -- because version is always 3+isVersion0 (KEKRI _)      = False      -- because version is always 4+isVersion0 (PasswordRI _) = True       -- because version is always 0++isPwriOri :: RecipientInfo -> Bool+isPwriOri (KTRI _)       = False+isPwriOri (KARI _)       = False+isPwriOri (KEKRI _)      = False+isPwriOri (PasswordRI _) = True++-- | Enveloped content information.+data EnvelopedData = EnvelopedData+    { evOriginatorInfo :: OriginatorInfo+      -- ^ Optional information about the originator+    , evRecipientInfos :: [RecipientInfo]+      -- ^ Information for recipients, allowing to decrypt the content+    , evContentType :: ContentType+      -- ^ Inner content type+    , evContentEncryptionParams :: ContentEncryptionParams+      -- ^ Encryption algorithm+    , evEncryptedContent :: EncryptedContent+      -- ^ Encrypted content info+    , evUnprotectedAttrs :: [Attribute]+      -- ^ Optional unprotected attributes+    }+    deriving (Show,Eq)++instance ProduceASN1Object ASN1P EnvelopedData where+    asn1s EnvelopedData{..} =+        asn1Container Sequence (ver . oi . ris . eci . ua)+      where+        ver = gIntVal v+        ris = asn1Container Set (asn1s evRecipientInfos)+        eci = encryptedContentInfoASN1S+                  (evContentType, evContentEncryptionParams, evEncryptedContent)+        ua  = attributesASN1S (Container Context 1) evUnprotectedAttrs++        oi | evOriginatorInfo == mempty = id+           | otherwise = originatorInfoASN1S (Container Context 0) evOriginatorInfo++        v | hasChoiceOther evOriginatorInfo = 4+          | any isPwriOri evRecipientInfos  = 3+          | evOriginatorInfo /= mempty      = 2+          | not (null evUnprotectedAttrs)   = 2+          | all isVersion0 evRecipientInfos = 0+          | otherwise                       = 2++instance ParseASN1Object [ASN1Event] EnvelopedData where+    parse =+        onNextContainer Sequence $ do+            IntVal v <- getNext+            when (v > 4) $+                throwParseError ("EnvelopedData: parsed invalid version: " ++ show v)+            oi <- parseOriginatorInfo (Container Context 0) <|> return mempty+            ris <- onNextContainer Set parse+            (ct, params, ec) <- parseEncryptedContentInfo+            attrs <- parseAttributes (Container Context 1)+            return EnvelopedData { evOriginatorInfo = oi+                                 , evRecipientInfos = ris+                                 , evContentType = ct+                                 , evContentEncryptionParams = params+                                 , evEncryptedContent = ec+                                 , evUnprotectedAttrs = attrs+                                 }++-- | Function able to produce a 'RecipientInfo'.+type ProducerOfRI m = ContentEncryptionKey -> m (Either StoreError RecipientInfo)++-- | Function able to consume a 'RecipientInfo'.+type ConsumerOfRI m = RecipientInfo -> m (Either StoreError ContentEncryptionKey)++-- | Generate a Key Transport recipient from a certificate and+-- desired algorithm.  The recipient will contain certificate identifier.+--+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.+forKeyTransRecipient :: MonadRandom m+                     => SignedCertificate -> KeyTransportParams -> ProducerOfRI m+forKeyTransRecipient cert params inkey = do+    ek <- transportEncrypt params (certPubKey obj) inkey+    return (KTRI . build <$> ek)+  where+    obj = signedObject (getSigned cert)+    isn = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)++    build ek = KTRecipientInfo+                  { ktRid = RecipientIASN isn+                  , ktKeyTransportParams = params+                  , ktEncryptedKey = ek+                  }++-- | Use a Key Transport recipient, knowing the private key.+--+-- This function can be used as parameter to+-- 'Crypto.Store.CMS.openEnvelopedData'.+withRecipientKeyTrans :: MonadRandom m => PrivKey -> ConsumerOfRI m+withRecipientKeyTrans privKey (KTRI KTRecipientInfo{..}) =+    transportDecrypt ktKeyTransportParams privKey ktEncryptedKey+withRecipientKeyTrans _ _ = pure (Left RecipientTypeMismatch)++-- | Generate a Key Agreement recipient from a certificate and+-- desired algorithm.  The recipient info will contain an ephemeral public key.+--+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.+--+-- To avoid decreasing the security strength, Key Encryption parameters should+-- use a key size equal or greater than the content encryption key.+forKeyAgreeRecipient :: MonadRandom m+                     => SignedCertificate -> KeyAgreementParams -> ProducerOfRI m+forKeyAgreeRecipient cert params inkey = do+    ephemeral <- ecdhGenerate (certPubKey obj)+    case ephemeral of+        Right (curve, d, sp) -> do+            let SerializedPoint pt = getSerializedPoint curve d+                aPub = OriginatorPublicKeyEC [] (toBitArray pt 0)+            ek <- ecdhEncrypt params Nothing curve d sp inkey+            return (KARI . build aPub <$> ek)+        Left err -> return $ Left err+  where+    obj = signedObject (getSigned cert)+    isn = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)++    makeREK ek = RecipientEncryptedKey+                     { rekRid = KeyAgreeRecipientIASN isn+                     , rekEncryptedKey = ek+                     }++    build aPub ek =+        KARecipientInfo+            { kaOriginator = OriginatorPublic aPub+            , kaUkm = Nothing+            , kaKeyAgreementParams = params+            , kaRecipientEncryptedKeys = [ makeREK ek ]+            }++-- | Use a Key Agreement recipient, knowing the recipient private key.  The+-- recipient certificate is also required to locate which encrypted key to use.+--+-- 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{..}) =+    case kaOriginator of+        OriginatorPublic (OriginatorPublicKeyEC _ ba) ->+            case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of+                Nothing -> pure (Left RecipientKeyNotFound)+                Just ek ->+                    let pub = SerializedPoint (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+-- desired algorithm.  The recipient may identify the KEK that was used with+-- the supplied identifier.+--+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.+--+-- To avoid decreasing the security strength, Key Encryption parameters should+-- use a key size equal or greater than the content encryption key.+forKeyRecipient :: MonadRandom m+                => KeyEncryptionKey+                -> KeyIdentifier+                -> KeyEncryptionParams+                -> ProducerOfRI m+forKeyRecipient key kid params inkey = do+    ek <- keyEncrypt key params inkey+    return (KEKRI . build <$> ek)+  where+    build ek = KEKRecipientInfo+                   { kekId = kid+                   , kekKeyEncryptionParams = params+                   , kekEncryptedKey = ek+                   }++-- | Use a Key Encryption Key recipient, knowing the key encryption key.+--+-- This function can be used as parameter to+-- 'Crypto.Store.CMS.openEnvelopedData'.+withRecipientKey :: Applicative f => KeyEncryptionKey -> ConsumerOfRI f+withRecipientKey key (KEKRI KEKRecipientInfo{..}) =+    pure (keyDecrypt key kekKeyEncryptionParams kekEncryptedKey)+withRecipientKey _ _ = pure (Left RecipientTypeMismatch)++-- | Generate a password recipient from a password.+--+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.+forPasswordRecipient :: MonadRandom m+                     => Password+                     -> KeyDerivationFunc+                     -> KeyEncryptionParams+                     -> ProducerOfRI m+forPasswordRecipient pwd kdf params inkey = do+    ek <- keyEncrypt derived params inkey+    return (PasswordRI . build <$> ek)+  where+    derived = kdfDerive kdf len pwd :: EncryptedKey+    len = fromMaybe (getMaximumKeySize params) (kdfKeyLength kdf)+    build ek = PasswordRecipientInfo+                   { priKeyDerivationFunc = kdf+                   , priKeyEncryptionParams = params+                   , priEncryptedKey = ek+                   }++-- | Use a password recipient, knowing the password.+--+-- This function can be used as parameter to+-- 'Crypto.Store.CMS.openEnvelopedData'.+withRecipientPassword :: Applicative f => Password -> ConsumerOfRI f+withRecipientPassword pwd (PasswordRI PasswordRecipientInfo{..}) =+    pure (keyDecrypt derived priKeyEncryptionParams priEncryptedKey)+  where+    derived = kdfDerive priKeyDerivationFunc len pwd :: EncryptedKey+    len = fromMaybe (getMaximumKeySize priKeyEncryptionParams)+                    (kdfKeyLength priKeyDerivationFunc)+withRecipientPassword _ _ = pure (Left RecipientTypeMismatch)
+ src/Crypto/Store/CMS/Info.hs view
@@ -0,0 +1,353 @@+-- |+-- Module      : Crypto.Store.CMS.Info+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- 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+    ) 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 Crypto.Cipher.Types+import Crypto.Hash hiding (MD5)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.AuthEnveloped+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+getContentType (DataCI _)              = DataType+getContentType (SignedDataCI _)        = SignedDataType+getContentType (EnvelopedDataCI _)     = EnvelopedDataType+getContentType (DigestedDataCI _)      = DigestedDataType+getContentType (EncryptedDataCI _)     = EncryptedDataType+getContentType (AuthenticatedDataCI _) = AuthenticatedDataType+getContentType (AuthEnvelopedDataCI _) = AuthEnvelopedDataType+++-- 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+                 deriving (Show,Eq)++instance ProduceASN1Object ASN1P ContentInfo where+    asn1s ci = asn1Container Sequence (oid . cont)+      where oid = gOID $ getObjectID $ getContentType ci+            cont = asn1Container (Container Context 0) inner+            inner =+                case ci of+                    DataCI bs              -> dataASN1S bs+                    SignedDataCI ed        -> asn1s ed+                    EnvelopedDataCI ed     -> asn1s ed+                    DigestedDataCI dd      -> asn1s dd+                    EncryptedDataCI ed     -> asn1s ed+                    AuthenticatedDataCI ad -> asn1s ad+                    AuthEnvelopedDataCI ae -> asn1s ae++instance ParseASN1Object [ASN1Event] ContentInfo where+    parse =+        onNextContainer Sequence $ do+            OID oid <- getNext+            withObjectID "content type" oid $ \ct ->+                onNextContainer (Container Context 0) (parseInner ct)+      where+        parseInner DataType              = DataCI <$> parseData+        parseInner SignedDataType        = SignedDataCI <$> parse+        parseInner EnvelopedDataType     = EnvelopedDataCI <$> parse+        parseInner DigestedDataType      = DigestedDataCI <$> parse+        parseInner EncryptedDataType     = EncryptedDataCI <$> parse+        parseInner AuthenticatedDataType = AuthenticatedDataCI <$> parse+        parseInner AuthEnvelopedDataType = AuthEnvelopedDataCI <$> parse+++-- Data++dataASN1S :: ASN1Elem e => ByteString -> ASN1Stream e+dataASN1S = gOctetString++parseData :: Monoid e => ParseASN1 e ByteString+parseData = do+    next <- getNext+    case next of+        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+                                                }+++-- Authenticated data++-- | 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 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++        oi | adOriginatorInfo == mempty = id+           | otherwise = originatorInfoASN1S (Container Context 0) adOriginatorInfo++        v | hasChoiceOther adOriginatorInfo = 3+          | otherwise                       = 0++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+                                     }+++-- Utilities++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++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)++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++    parseContentSingle = do { OctetString bs <- getNext; return bs }+    parseContentChunks = onNextContainer (Container Universal 4) $+        B.concat <$> getMany parseContentSingle
+ src/Crypto/Store/CMS/OriginatorInfo.hs view
@@ -0,0 +1,185 @@+-- |+-- Module      : Crypto.Store.CMS.OriginatorInfo+-- 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.OriginatorInfo+    ( OriginatorInfo(..)+    , CertificateChoice(..)+    , OtherCertificateFormat(..)+    , RevocationInfoChoice(..)+    , OtherRevocationInfoFormat(..)+    , originatorInfoASN1S+    , parseOriginatorInfo+    , hasChoiceOther+    ) where++import Control.Applicative++import Data.ASN1.Types+import Data.Maybe (fromMaybe)+import Data.Semigroup+import Data.X509++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Util++-- | Data types where choice "other" is available.+class HasChoiceOther a where+    -- | Return true when choice "other" is selected.+    hasChoiceOther :: a -> Bool++instance (HasChoiceOther a, Foldable f) => HasChoiceOther (f a) where+    hasChoiceOther = any hasChoiceOther++-- | Information about the originator of the content info, to be used when+-- a key management algorithm requires this information.+data OriginatorInfo = OriginatorInfo+    { originatorCerts :: [CertificateChoice]+      -- ^ The collection of certificates+    , originatorCRLs  :: [RevocationInfoChoice]+      -- ^ The collection of CRLs+    }+    deriving (Show,Eq)++instance Semigroup OriginatorInfo where+    OriginatorInfo a b <> OriginatorInfo c d = OriginatorInfo (a <> c) (b <> d)++instance Monoid OriginatorInfo where+    mempty = OriginatorInfo [] []+    mappend (OriginatorInfo a b) (OriginatorInfo c d) =+        OriginatorInfo (mappend a c) (mappend b d)++instance HasChoiceOther OriginatorInfo where+    hasChoiceOther OriginatorInfo{..} =+        hasChoiceOther originatorCerts || hasChoiceOther originatorCRLs++instance ProduceASN1Object ASN1P OriginatorInfo where+    asn1s = originatorInfoASN1S Sequence++instance ParseASN1Object [ASN1Event] OriginatorInfo where+    parse = parseOriginatorInfo Sequence++-- | Generate ASN.1 with the specified constructed type for the originator+-- information.+originatorInfoASN1S :: ASN1ConstructionType -> OriginatorInfo -> ASN1PS+originatorInfoASN1S ty OriginatorInfo{..} =+    asn1Container ty $ gen 0 originatorCerts . gen 1 originatorCRLs+  where+    gen tag list+        | null list = id+        | otherwise = asn1Container (Container Context tag) (asn1s list)++-- | Parse originator information with the specified constructed type.+parseOriginatorInfo :: ASN1ConstructionType+                    -> ParseASN1 [ASN1Event] OriginatorInfo+parseOriginatorInfo ty = onNextContainer ty $ do+    certs <- parseOptList 0+    crls  <- parseOptList 1+    return OriginatorInfo { originatorCerts = certs+                          , originatorCRLs  = crls+                          }+  where+    parseOptList tag =+        fromMaybe [] <$> onNextContainerMaybe (Container Context tag) parse++-- | Union type related to certificate formats.+data CertificateChoice+    = CertificateCertificate SignedCertificate -- ^ X.509 certificate+    | CertificateOther OtherCertificateFormat  -- ^ Other format+    deriving (Show,Eq)++instance HasChoiceOther CertificateChoice where+    hasChoiceOther (CertificateOther _) = True+    hasChoiceOther _                    = False++instance ProduceASN1Object ASN1P CertificateChoice where+    asn1s (CertificateCertificate cert) = asn1s cert+    asn1s (CertificateOther other) =+        otherCertificateFormatASN1PS (Container Context 3) other++instance ParseASN1Object [ASN1Event] CertificateChoice where+    parse = parseMain <|> parseOther+      where parseMain  = CertificateCertificate <$> parse+            parseOther = CertificateOther <$>+                parseOtherCertificateFormat (Container Context 3)++-- | Union type related to revocation info formats.+data RevocationInfoChoice+    = RevocationInfoCRL SignedCRL+      -- ^ A CRL, ARL, Delta CRL, or an ACRL+    | RevocationInfoOther OtherRevocationInfoFormat+      -- ^ Other format+    deriving (Show,Eq)++instance HasChoiceOther RevocationInfoChoice where+    hasChoiceOther (RevocationInfoOther _) = True+    hasChoiceOther _                       = False++instance ProduceASN1Object ASN1P RevocationInfoChoice where+    asn1s (RevocationInfoCRL crl) = asn1s crl+    asn1s (RevocationInfoOther other) =+        otherRevocationInfoFormatASN1PS (Container Context 1) other++instance ParseASN1Object [ASN1Event] RevocationInfoChoice where+    parse = parseMain <|> parseOther+      where parseMain  = RevocationInfoCRL <$> parse+            parseOther = RevocationInfoOther <$>+                parseOtherRevocationInfoFormat (Container Context 1)++-- | Certificate information in a format not supported natively.+data OtherCertificateFormat = OtherCertificateFormat+    { otherCertFormat :: OID    -- ^ Format identifier+    , otherCertValues :: [ASN1] -- ^ ASN.1 values using this format+    }+    deriving (Show,Eq)++otherCertificateFormatASN1PS :: ASN1Elem e+                             => ASN1ConstructionType+                             -> OtherCertificateFormat+                             -> ASN1Stream e+otherCertificateFormatASN1PS ty OtherCertificateFormat{..} =+    asn1Container ty (f . v)+  where f = gOID otherCertFormat+        v = gMany otherCertValues++parseOtherCertificateFormat :: Monoid e+                            => ASN1ConstructionType+                            -> ParseASN1 e OtherCertificateFormat+parseOtherCertificateFormat ty = onNextContainer ty $ do+    OID f <- getNext+    v <- getMany getNext+    return OtherCertificateFormat { otherCertFormat = f+                                  , otherCertValues = v }++-- | Revocation information in a format not supported natively.+data OtherRevocationInfoFormat = OtherRevocationInfoFormat+    { otherRevInfoFormat :: OID    -- ^ Format identifier+    , otherRevInfoValues :: [ASN1] -- ^ ASN.1 values using this format+    }+    deriving (Show,Eq)++otherRevocationInfoFormatASN1PS :: ASN1Elem e+                                => ASN1ConstructionType+                                -> OtherRevocationInfoFormat+                                -> ASN1Stream e+otherRevocationInfoFormatASN1PS ty OtherRevocationInfoFormat{..} =+    asn1Container ty (f . v)+  where f = gOID otherRevInfoFormat+        v = gMany otherRevInfoValues++parseOtherRevocationInfoFormat :: Monoid e+                               => ASN1ConstructionType+                               -> ParseASN1 e OtherRevocationInfoFormat+parseOtherRevocationInfoFormat ty = onNextContainer ty $ do+    OID f <- getNext+    v <- getMany getNext+    return OtherRevocationInfoFormat { otherRevInfoFormat = f+                                     , otherRevInfoValues = v }
+ src/Crypto/Store/CMS/PEM.hs view
@@ -0,0 +1,71 @@+-- |+-- Module      : Crypto.Store.CMS.PEM+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- PEM serialization and deserialization of CMS 'ContentInfo'.+module Crypto.Store.CMS.PEM+    ( readCMSFile+    , readCMSFileFromMemory+    , pemToContentInfo+    , writeCMSFile+    , writeCMSFileToMemory+    , contentInfoToPEM+    ) where++import           Data.ASN1.BinaryEncoding+import           Data.ASN1.Encoding+import qualified Data.ByteString as B+import           Data.Maybe (catMaybes)++import Crypto.Store.CMS.Info+import Crypto.Store.CMS.Util+import Crypto.Store.PEM+++-- Reading from PEM format++-- | Read content info elements from a PEM file.+readCMSFile :: FilePath -> IO [ContentInfo]+readCMSFile path = accumulate <$> readPEMs path++-- | Read content info elements from a bytearray in PEM format.+readCMSFileFromMemory :: B.ByteString -> [ContentInfo]+readCMSFileFromMemory = either (const []) accumulate . pemParseBS++accumulate :: [PEM] -> [ContentInfo]+accumulate = catMaybes . foldr (flip pemToContentInfo) []++-- | Read a content info from a 'PEM' element and add it to the accumulator+-- list.+pemToContentInfo :: [Maybe ContentInfo] -> PEM -> [Maybe ContentInfo]+pemToContentInfo acc pem+    | pemName pem `elem` names = decode (pemContent pem)+    | otherwise                = Nothing : acc+  where+    names = [ "CMS", "PKCS7" ]+    decode bs =+        case decodeASN1Repr' BER bs of+            Left _ -> Nothing : acc+            Right asn1 ->+                case fromASN1Repr asn1 of+                    Right (info, []) -> Just info : acc+                    _                -> Nothing : acc+++-- Writing to PEM format++-- | Write content info elements to a PEM file.+writeCMSFile :: FilePath -> [ContentInfo] -> IO ()+writeCMSFile path = B.writeFile path . writeCMSFileToMemory++-- | Write content info elements to a bytearray in PEM format.+writeCMSFileToMemory :: [ContentInfo] -> B.ByteString+writeCMSFileToMemory = pemsWriteBS . map contentInfoToPEM++-- | Generate PEM for a content info.+contentInfoToPEM :: ContentInfo -> PEM+contentInfoToPEM info = PEM { pemName = "CMS", pemHeader = [], pemContent = bs}+  where bs = encodeASN1Object info
+ src/Crypto/Store/CMS/Signed.hs view
@@ -0,0 +1,240 @@+-- |+-- Module      : Crypto.Store.CMS.Signed+-- 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.Signed+    ( SignerInfo(..)+    , SignerIdentifier(..)+    , IssuerAndSerialNumber(..)+    , isVersion3+    , ProducerOfSI+    , ConsumerOfSI+    , certSigner+    , withPublicKey+    , withSignerKey+    , withSignerCertificate+    ) 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 Crypto.Random (MonadRandom)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.AuthEnveloped+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Enveloped+import Crypto.Store.CMS.OriginatorInfo+import Crypto.Store.CMS.Type+import Crypto.Store.CMS.Util+import Crypto.Store.Error++-- | Information related to a signer of a 'Crypto.Store.CMS.SignedData'.  An+-- element contains the signature material that was produced.+data SignerInfo = SignerInfo+    { siSignerId :: SignerIdentifier+      -- ^ Identifier of the signer certificate+    , siDigestAlgorithm :: DigestAlgorithm+      -- ^ Digest algorithm used for the signature+    , siSignedAttrs :: [Attribute]+      -- ^ Optional signed attributes+    , siSignatureAlg :: SignatureAlg+      -- ^ Algorithm used for signature+    , siSignature :: SignatureValue+      -- ^ The signature value+    , siUnsignedAttrs :: [Attribute]+      -- ^ Optional unsigned attributes+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e SignerInfo where+    asn1s SignerInfo{..} =+        asn1Container Sequence (ver . sid . dig . sa . alg . sig . ua)+      where+        ver = gIntVal (getVersion siSignerId)+        sid = asn1s siSignerId+        dig = algorithmASN1S Sequence siDigestAlgorithm+        sa  = attributesASN1S (Container Context 0) siSignedAttrs+        alg = algorithmASN1S Sequence siSignatureAlg+        sig = gOctetString siSignature+        ua  = attributesASN1S (Container Context 1) siUnsignedAttrs++instance Monoid e => ParseASN1Object e SignerInfo where+    parse = onNextContainer Sequence $ do+        IntVal v <- getNext+        when (v /= 1 && v /= 3) $+            throwParseError ("SignerInfo: parsed invalid version: " ++ show v)+        sid <- parse+        dig <- parseAlgorithm Sequence+        sAttrs <- parseAttributes (Container Context 0)+        alg <- parseAlgorithm Sequence+        (OctetString sig) <- getNext+        uAttrs <- parseAttributes (Container Context 1)+        return SignerInfo { siSignerId = sid+                          , siDigestAlgorithm = dig+                          , siSignedAttrs = sAttrs+                          , siSignatureAlg = alg+                          , siSignature = sig+                          , siUnsignedAttrs = uAttrs+                          }++getVersion :: SignerIdentifier -> Integer+getVersion (SignerIASN _) = 1+getVersion (SignerSKI _)  = 3++-- | Return true when the signer info has version 3.+isVersion3 :: SignerInfo -> Bool+isVersion3 = (== 3) . getVersion . siSignerId++-- | Union type related to identification of the signer certificate.+data SignerIdentifier+    = SignerIASN IssuerAndSerialNumber  -- ^ Issuer and Serial Number+    | SignerSKI  ByteString             -- ^ Subject Key Identifier+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e SignerIdentifier where+    asn1s (SignerIASN iasn) = asn1s iasn+    asn1s (SignerSKI  ski)  = asn1Container (Container Context 0)+                                  (gOctetString ski)++instance Monoid e => ParseASN1Object e SignerIdentifier where+    parse = parseIASN <|> parseSKI+      where parseIASN = SignerIASN <$> parse+            parseSKI  = SignerSKI  <$>+                onNextContainer (Container Context 0) parseBS+            parseBS = do { OctetString bs <- getNext; return bs }++-- | Try to find a certificate with the specified identifier.+findSigner :: SignerIdentifier+           -> [SignedCertificate]+           -> Maybe (SignedCertificate, [SignedCertificate])+findSigner (SignerIASN iasn) certs =+    partitionHead (matchIASN . signedObject . getSigned) certs+  where+    matchIASN c =+        (iasnIssuer iasn, iasnSerial iasn) == (certIssuerDN c, certSerial c)+findSigner (SignerSKI  ski) certs =+    partitionHead (matchSKI. signedObject . getSigned) certs+  where+    matchSKI c =+        case extensionGet (certExtensions c) of+            Just (ExtSubjectKeyId idBs) -> idBs == ski+            Nothing                     -> False++partitionHead :: (a -> Bool) -> [a] -> Maybe (a, [a])+partitionHead p l =+    case partition p l of+        (x : _, r) -> Just (x, r)+        ([]   , _)    -> Nothing++-- | Function able to produce a 'SignerInfo'.+type ProducerOfSI m = ContentType -> ByteString -> m (Either StoreError (SignerInfo, [CertificateChoice], [RevocationInfoChoice]))++-- | Function able to consume a 'SignerInfo'.+type ConsumerOfSI m = ContentType -> ByteString -> SignerInfo -> [CertificateChoice] -> [RevocationInfoChoice] -> m Bool++-- | Create a signer info with the specified signature algorithm and+-- credentials.+--+-- Two lists of optional attributes can be provided.  The attributes will be+-- part of message signature when provided in the first list.+--+-- When the first list of attributes is provided, even empty list, signature is+-- computed from a digest of the content.  When the list of attributes is+-- 'Nothing', no intermediate digest is used and the signature is computed from+-- the full message.+certSigner :: MonadRandom m+           => SignatureAlg+           -> PrivKey+           -> CertificateChain+           -> Maybe [Attribute]+           -> [Attribute]+           -> ProducerOfSI m+certSigner alg priv (CertificateChain chain) sAttrsM uAttrs ct msg =+    fmap build <$> generate+  where+    md   = digest dig msg+    def  = DigestAlgorithm Crypto.Store.CMS.Algorithms.SHA256+    cert = head chain+    obj  = signedObject (getSigned cert)+    isn  = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)++    (dig, alg') = signatureResolveHash def alg++    (sAttrs, input) =+        case sAttrsM of+            Nothing    -> ([], msg)+            Just attrs ->+                let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs+                 in (l, encodeAuthAttrs l)++    generate  = signatureGenerate alg' priv input+    build sig =+        let si = SignerInfo { siSignerId = SignerIASN isn+                            , siDigestAlgorithm = dig+                            , siSignedAttrs = sAttrs+                            , siSignatureAlg = alg+                            , siSignature = sig+                            , siUnsignedAttrs = uAttrs+                            }+         in (si, map CertificateCertificate chain, [])++-- | Verify that the signature was produced from the specified public key.+-- Ignores all certificates and CRLs contained in the signed data.+withPublicKey :: Applicative f => PubKey -> ConsumerOfSI f+withPublicKey pub ct msg SignerInfo{..} _ _ = pure $+    fromMaybe False $ do+        guard (noAttr || attrMatch)+        alg <- signatureCheckHash siDigestAlgorithm siSignatureAlg+        return (signatureVerify alg pub input siSignature)+  where+    noAttr    = null siSignedAttrs+    mdMatch   = mdAttr == Just (digest siDigestAlgorithm msg)+    attrMatch = ctAttr == Just ct && mdMatch+    mdAttr    = getMessageDigestAttr siSignedAttrs+    ctAttr    = getContentTypeAttr siSignedAttrs+    input     = if noAttr then msg else encodeAuthAttrs siSignedAttrs++-- | Verify that the signature is valid with one of the X.509 certificates+-- contained in the signed data, but does not validate that the certificates are+-- valid.  All transmitted certificates are implicitely trusted and all CRLs are+-- ignored.+withSignerKey :: Applicative f => ConsumerOfSI f+withSignerKey = withSignerCertificate (\_ -> pure True)++-- | Verify that the signature is valid with one of the X.509 certificates+-- contained in the signed data, and verify that the signer certificate is valid+-- using the validation function supplied.  All CRLs are ignored.+withSignerCertificate :: Applicative f+                      => (CertificateChain -> f Bool) -> ConsumerOfSI f+withSignerCertificate validate ct msg SignerInfo{..} certs crls =+    case getCertificateChain of+        Just chain -> validate chain+        Nothing    -> pure False+  where+    getCertificateChain = do+        (cert, others) <- findSigner siSignerId x509Certificates+        let pub = certPubKey $ signedObject $ getSigned cert+        validSignature <- withPublicKey pub ct msg SignerInfo{..} certs crls+        guard validSignature+        return $ CertificateChain (cert : others)++    x509Certificates = mapMaybe asX509 certs++    asX509 (CertificateCertificate c) = Just c+    asX509 _                          = Nothing
+ src/Crypto/Store/CMS/Type.hs view
@@ -0,0 +1,50 @@+-- |+-- Module      : Crypto.Store.CMS.Type+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- CMS content information type.+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeFamilies #-}+module Crypto.Store.CMS.Type+    ( ContentType(..)+    ) where++import Data.ASN1.OID++import Crypto.Store.CMS.Util++-- | CMS content information type.+data ContentType = DataType              -- ^ Arbitrary octet string+                 | SignedDataType        -- ^ Signed content info+                 | EnvelopedDataType     -- ^ Enveloped content info+                 | DigestedDataType      -- ^ Content info with associated digest+                 | EncryptedDataType     -- ^ Encrypted content info+                 | AuthenticatedDataType -- ^ Authenticated content info+                 | AuthEnvelopedDataType -- ^ Authenticated-enveloped content info+                 deriving (Show,Eq)++instance Enumerable ContentType where+    values = [ DataType+             , SignedDataType+             , EnvelopedDataType+             , DigestedDataType+             , EncryptedDataType+             , AuthenticatedDataType+             , AuthEnvelopedDataType+             ]++instance OIDable ContentType where+    getObjectID DataType              = [1,2,840,113549,1,7,1]+    getObjectID SignedDataType        = [1,2,840,113549,1,7,2]+    getObjectID EnvelopedDataType     = [1,2,840,113549,1,7,3]+    getObjectID DigestedDataType      = [1,2,840,113549,1,7,5]+    getObjectID EncryptedDataType     = [1,2,840,113549,1,7,6]+    getObjectID AuthenticatedDataType = [1,2,840,113549,1,9,16,1,2]+    getObjectID AuthEnvelopedDataType = [1,2,840,113549,1,9,16,1,23]++instance OIDNameable ContentType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid
+ src/Crypto/Store/CMS/Util.hs view
@@ -0,0 +1,225 @@+-- |+-- Module      : Crypto.Store.CMS.Util+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- CMS and ASN.1 utilities+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+module Crypto.Store.CMS.Util+    (+    -- * Testing ASN.1 types+      nullOrNothing+    , intOrNothing+    , dateTimeOrNothing+    -- * Object Identifiers+    , OIDTable+    , lookupOID+    , Enumerable(..)+    , OIDNameableWrapper(..)+    , withObjectID+    -- * Parsing and encoding ASN.1 objects+    , ASN1Event+    , ASN1ObjectExact(..)+    , ProduceASN1Object(..)+    , encodeASN1Object+    , ParseASN1Object(..)+    , fromASN1Repr+    -- * Algorithm Identifiers+    , AlgorithmId(..)+    , algorithmASN1S+    , algorithmMaybeASN1S+    , parseAlgorithm+    , parseAlgorithmMaybe+    -- * Miscellaneous functions+    , orElse+    ) where++import           Data.ASN1.BinaryEncoding.Raw+import           Data.ASN1.OID+import           Data.ASN1.Stream+import           Data.ASN1.Types+import           Data.ByteString (ByteString)+import           Data.List (find)+import           Data.X509++import Time.Types (DateTime)++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse++-- | Try to parse a 'Null' ASN.1 value.+nullOrNothing :: ASN1 -> Maybe ()+nullOrNothing Null = Just ()+nullOrNothing _    = Nothing++-- | Try to parse an 'IntVal' ASN.1 value.+intOrNothing :: ASN1 -> Maybe Integer+intOrNothing (IntVal i) = Just i+intOrNothing _          = Nothing++-- | Try to parse a 'DateTime' ASN.1 value.+dateTimeOrNothing :: ASN1 -> Maybe DateTime+dateTimeOrNothing (ASN1Time _ t _) = Just t+dateTimeOrNothing _                = Nothing++-- | Mapping between values and OIDs.+type OIDTable a = [(a, OID)]++-- | Find the value associated to an OID.+lookupByOID :: OIDTable a -> OID -> Maybe a+lookupByOID table oid = fst <$> find ((==) oid . snd) table++-- | Find the OID associated to a value.+lookupOID :: Eq a => OIDTable a -> a -> Maybe OID+lookupOID table a = lookup a table++-- | Types with a finite set of values.+class Enumerable a where+    -- | Return all possible values for the given type.+    values :: [a]++-- | Type used to transform a 'Enumerable' instance to an 'OIDNameable'+-- instance.+newtype OIDNameableWrapper a = OIDNW { unOIDNW :: a }+    deriving (Show,Eq)++instance (Enumerable a, OIDable a) => OIDNameable (OIDNameableWrapper a) where+    fromObjectID = lookupByOID table+      where table = [ (OIDNW val, getObjectID val) | val <- values ]++-- | Convert the specified OID and apply a parser to the result.+withObjectID :: OIDNameable a+             => String -> OID -> (a -> ParseASN1 e b) -> ParseASN1 e b+withObjectID name oid fn =+    case fromObjectID oid of+        Just val -> fn val+        Nothing  ->+            throwParseError ("Unsupported " ++ name ++ ": OID " ++ show oid)++-- | Objects that can produce an ASN.1 stream.+class ProduceASN1Object e obj where+    asn1s :: obj -> ASN1Stream e++instance ProduceASN1Object e obj => ProduceASN1Object e [obj] where+    asn1s l r = foldr asn1s r l++instance ASN1Elem e => ProduceASN1Object e DistinguishedName where+    asn1s = asn1Container Sequence . inner+      where+        inner (DistinguishedName dn) cont = foldr dnSet cont dn+        dnSet (oid, cs) =+            asn1Container Set $+                asn1Container Sequence (gOID oid . gASN1String cs)++instance (Show a, Eq a, ASN1Object a) => ProduceASN1Object ASN1P (SignedExact a) where+    asn1s = gEncoded . encodeSignedObject++-- | Encode the ASN.1 object to DER format.+encodeASN1Object :: ProduceASN1Object ASN1P obj => obj -> ByteString+encodeASN1Object = encodeASN1S . asn1s++-- | Objects that can be parsed from an ASN.1 stream.+class Monoid e => ParseASN1Object e obj where+    parse :: ParseASN1 e obj++instance ParseASN1Object e obj => ParseASN1Object e [obj] where+    parse = getMany parse++instance Monoid e => ParseASN1Object e DistinguishedName where+    parse = DistinguishedName <$> onNextContainer Sequence inner+      where+        inner = concat <$> getMany parseOne+        parseOne =+            onNextContainer Set $ getMany $+                onNextContainer Sequence $ do+                    OID oid <- getNext+                    ASN1String cs <- getNext+                    return (oid, cs)++instance (Show a, Eq a, ASN1Object a) => ParseASN1Object [ASN1Event] (SignedExact a) where+    parse = withAnnotations parseSequence >>= finish+      where+        parseSequence = onNextContainer Sequence (getMany getNext)+        finish (_, events) =+            case decodeSignedObject (toByteString events) of+                Right se -> return se+                Left err -> throwParseError ("SignedExact: " ++ err)++-- | Create an object from the ASN.1 stream.+fromASN1Repr :: ParseASN1Object [ASN1Event] obj+             => [ASN1Repr] -> Either String (obj, [ASN1Repr])+fromASN1Repr = runParseASN1State_ parse++-- | An ASN.1 object associated with the raw data it was parsed from.+data ASN1ObjectExact a = ASN1ObjectExact+    { exactObject    :: a           -- ^ The wrapped ASN.1 object+    , exactObjectRaw :: ByteString  -- ^ The raw representation of this object+    } deriving Show++instance Eq a => Eq (ASN1ObjectExact a)+    where a == b = exactObject a == exactObject b++instance ProduceASN1Object ASN1P a => ProduceASN1Object ASN1P (ASN1ObjectExact a) where+    asn1s = gEncoded . exactObjectRaw++instance ParseASN1Object [ASN1Event] a => ParseASN1Object [ASN1Event] (ASN1ObjectExact a) where+    parse = do+        (obj, events) <- withAnnotations parse+        let objRaw = toByteString events+        return ASN1ObjectExact { exactObject = obj, exactObjectRaw = objRaw }++-- | Algorithm identifier with associated parameter.+class AlgorithmId param where+    type AlgorithmType param+    algorithmName  :: param -> String+    algorithmType  :: param -> AlgorithmType param+    parameterASN1S :: ASN1Elem e => param -> ASN1Stream e+    parseParameter :: Monoid e => AlgorithmType param -> ParseASN1 e param++-- | Transform the algorithm identifier to ASN.1 stream.+algorithmASN1S :: (ASN1Elem e, AlgorithmId param, OIDable (AlgorithmType param))+               => ASN1ConstructionType -> param -> ASN1Stream e+algorithmASN1S ty p = asn1Container ty (oid . parameterASN1S p)+  where typ = algorithmType p+        oid = gOID (getObjectID typ)++-- | Transform the optional algorithm identifier to ASN.1 stream.+algorithmMaybeASN1S :: (ASN1Elem e, AlgorithmId param, OIDable (AlgorithmType param))+                    => ASN1ConstructionType -> Maybe param -> ASN1Stream e+algorithmMaybeASN1S _  Nothing  = id+algorithmMaybeASN1S ty (Just p) = algorithmASN1S ty p++-- | Parse an algorithm identifier from an ASN.1 stream.+parseAlgorithm :: forall e param . (Monoid e, AlgorithmId param, OIDNameable (AlgorithmType param))+               => ASN1ConstructionType -> ParseASN1 e param+parseAlgorithm ty = onNextContainer ty $ do+    OID oid <- getNext+    withObjectID (getName undefined) oid parseParameter+  where+    getName :: param -> String+    getName = algorithmName++-- | Parse an optional algorithm identifier from an ASN.1 stream.+parseAlgorithmMaybe :: forall e param . (Monoid e, AlgorithmId param, OIDNameable (AlgorithmType param))+                    => ASN1ConstructionType -> ParseASN1 e (Maybe param)+parseAlgorithmMaybe ty = onNextContainerMaybe ty $ do+    OID oid <- getNext+    withObjectID (getName undefined) oid parseParameter+  where+    getName :: param -> String+    getName = algorithmName++-- | Execute the second action only if the first action produced 'Nothing'.+orElse :: Monad m => m (Maybe a) -> m (Maybe a) -> m (Maybe a)+orElse pa pb = do+    va <- pa+    case va of+        Nothing -> pb+        _       -> return va
+ src/Crypto/Store/Cipher/RC2.hs view
@@ -0,0 +1,53 @@+-- |+-- Module      : Crypto.Store.Cipher.RC2+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : stable+-- Portability : good+--+-- Implementation of RC2 block cipher, a legacy algorithm providing weak+-- security.  Use only for compatibility with software requiring this cipher and+-- data which is not sensitive.+module Crypto.Store.Cipher.RC2+    ( RC2+    , rc2WithEffectiveKeyLength+    ) where++import           Data.ByteArray (ByteArrayAccess)+import qualified Data.ByteArray as B+import qualified Data.ByteArray.Mapping as B+import           Data.Maybe (fromMaybe)++import  Crypto.Error+import  Crypto.Cipher.Types++import  Crypto.Store.Cipher.RC2.Primitive++-- | RC2 block cipher.  Key is between 8 and 1024 bits.+newtype RC2 = RC2 Key++instance Cipher RC2 where+    cipherName    _ = "RC2"+    cipherKeySize _ = KeySizeRange 1 128+    cipherInit      = fmap RC2 . initRC2 Nothing++instance BlockCipher RC2 where+    blockSize _ = 8+    ecbEncrypt (RC2 k) = B.mapAsWord64 (encrypt k)+    ecbDecrypt (RC2 k) = B.mapAsWord64 (decrypt k)++-- | Build a RC2 cipher with the specified effective key length (in bits).+rc2WithEffectiveKeyLength :: ByteArrayAccess key+                          => Int -> key -> CryptoFailable RC2+rc2WithEffectiveKeyLength bits key+    | bits < 1    = CryptoFailed CryptoError_KeySizeInvalid+    | bits > 1024 = CryptoFailed CryptoError_KeySizeInvalid+    | otherwise   = RC2 <$> initRC2 (Just bits) key++initRC2 :: ByteArrayAccess key => Maybe Int -> key -> CryptoFailable Key+initRC2 mbits bs+    | len <    1 = CryptoFailed CryptoError_KeySizeInvalid+    | len <= 128 = CryptoPassed (buildKey t1 bs)+    | otherwise  = CryptoFailed CryptoError_KeySizeInvalid+  where len = B.length bs+        t1  = fromMaybe (8 * len) mbits
+ src/Crypto/Store/Cipher/RC2/Primitive.hs view
@@ -0,0 +1,238 @@+-- |+-- Module      : Crypto.Store.Cipher.RC2.Primitive+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : stable+-- Portability : good+--+{-# LANGUAGE Rank2Types #-}+module Crypto.Store.Cipher.RC2.Primitive+    ( Key+    , buildKey+    , encrypt+    , decrypt+    ) where++import Basement.Block+import Basement.Compat.IsList+import Basement.Endianness+import Basement.Types.OffsetSize++import Control.Monad (forM_)++import           Data.Bits+import           Data.ByteArray (ByteArrayAccess)+import qualified Data.ByteArray as B+import           Data.Word++import Foreign.Storable+++-- | Expanded RC2 key+newtype Key = Key (Block Word16) -- [ K[0], K[1], ..., K[63] ]++data Q = Q {-# UNPACK #-} !Word16 {-# UNPACK #-} !Word16+           {-# UNPACK #-} !Word16 {-# UNPACK #-} !Word16+++-- Utilities++decomp64 :: Word64 -> Q+decomp64 x =+    let f = unBE . toBE . fromIntegral+        a = f (x `shiftR` 48)+        b = f (x `shiftR` 32)+        c = f (x `shiftR` 16)+        d = f  x+    in Q a b c d++comp64 :: Q -> Word64+comp64 (Q a b c d) =+    (f a `shiftL` 48) .|.+    (f b `shiftL` 32) .|.+    (f c `shiftL` 16) .|.+     f d+  where f = fromIntegral . unBE . toBE++getR :: Q -> Word8 -> Word16+getR (Q a b c d) i =+    case i .&. 3 of+        0 -> a+        1 -> b+        2 -> c+        _ -> d+{-# INLINE getR #-}++setR :: Q -> Word8 -> Word16 -> Q+setR (Q a b c d) i x =+    case i .&. 3 of+        0 -> Q x b c d+        1 -> Q a x c d+        2 -> Q a b x d+        _ -> Q a b c x+{-# INLINE setR #-}++rol :: Word8 -> Word16 -> Word16+rol i =+    case i .&. 3 of+        0 -> flip rotateL 1+        1 -> flip rotateL 2+        2 -> flip rotateL 3+        _ -> flip rotateL 5+{-# INLINE rol #-}++ror :: Word8 -> Word16 -> Word16+ror i =+    case i .&. 3 of+        0 -> flip rotateR 1+        1 -> flip rotateR 2+        2 -> flip rotateR 3+        _ -> flip rotateR 5+{-# INLINE ror #-}++f5 :: (a -> a) -> a -> a+f5 f = f . f . f . f . f++f6 :: (a -> a) -> a -> a+f6 f = f . f . f . f . f . f+++-- Encryption++-- | Encrypts a block using the specified key+encrypt :: Key -> Word64 -> Word64+encrypt k = comp64 . enc k . decomp64++enc :: Key -> Q -> Q+enc k r =+    fst $ f5 (mixingRound k) $ mashingRound k+        $ f6 (mixingRound k) $ mashingRound k+        $ f5 (mixingRound k) (r, 0)++-- Decryption++-- | Decrypts a block using the specified key+decrypt :: Key -> Word64 -> Word64+decrypt k = comp64 . dec k . decomp64++dec :: Key -> Q -> Q+dec k r =+    fst $ f5 (rmixingRound k) $ rmashingRound k+        $ f6 (rmixingRound k) $ rmashingRound k+        $ f5 (rmixingRound k) (r, 63)+++-- Encryptiong rounds++mixUp :: Key -> Word8 -> (Q, Int) -> (Q, Int)+mixUp k i input@(r, j) = seq r' $ seq j' (r', j')+  where j' = j + 1+        r' = setR r i (rol i (ri + gmix k i input))+        ri = getR r i+{-# INLINE mixUp #-}++mixingRound :: Key -> (Q, Int) -> (Q, Int)+mixingRound k = mixUp k 3 . mixUp k 2 . mixUp k 1 . mixUp k 0++mash :: Key -> Word8 -> (Q, Int) -> (Q, Int)+mash = gmash (+)+{-# INLINE mash #-}++mashingRound :: Key -> (Q, Int) -> (Q, Int)+mashingRound k = mash k 3 . mash k 2 . mash k 1 . mash k 0+++-- Decryption rounds++rmixUp :: Key -> Word8 -> (Q, Int) -> (Q, Int)+rmixUp k i input@(r, j) = seq r' $ seq j' (r', j')+  where j' = j - 1+        r' = setR r i (ri - gmix k i input)+        ri = ror i (getR r i)+{-# INLINE rmixUp #-}++rmixingRound :: Key -> (Q, Int) -> (Q, Int)+rmixingRound k = rmixUp k 0 . rmixUp k 1 . rmixUp k 2 . rmixUp k 3++rmash :: Key -> Word8 -> (Q, Int) -> (Q, Int)+rmash = gmash (-)+{-# INLINE rmash #-}++rmashingRound :: Key -> (Q, Int) -> (Q, Int)+rmashingRound k = rmash k 0 . rmash k 1 . rmash k 2 . rmash k 3+++-- Generic rounds++gmix :: Key -> Word8 -> (Q, Int) -> Word16+gmix (Key k) i (r, j) = kj + (ri1 .&. ri2) + (complement ri1 .&. ri3)+  where ri1 = getR r (i - 1)+        ri2 = getR r (i - 2)+        ri3 = getR r (i - 3)+        kj  = unsafeIndex k (Offset j)+{-# INLINE gmix #-}++gmash :: (Word16 -> Word16 -> Word16)+      -> Key -> Word8 -> (Q, Int) -> (Q, Int)+gmash op (Key k) i (r, j) = seq r' $ seq j (r', j)+  where r'  = setR r i (ri `op` kp)+        ri  = getR r i+        ri1 = getR r (i - 1)+        kp  = unsafeIndex k $ Offset (fromIntegral ri1 .&. 63)+{-# INLINE gmash #-}+++-- Key expansion++-- | Perform key expansion+buildKey :: ByteArrayAccess key+         => Int    -- ^ Effective key length in bits+         -> key    -- ^ Input key between 1 and 128 bytes+         -> Key    -- ^ Expanded key+buildKey t1 key = Key $ doCast $ B.allocAndFreeze 128 $ \p -> do+    B.copyByteArrayToPtr key p++    forM_ [t .. 127] $ \i -> do+        pos <- (+) <$> peekElemOff p (i - 1) <*> peekElemOff p (i - t)+        let b = unsafeIndex piTable (fromIntegral pos)+        pokeElemOff p i b++    pos' <- (.&. tm) <$> peekElemOff p (128 - t8)+    let b' = unsafeIndex piTable (fromIntegral pos')+    pokeElemOff p (128 - t8) b'++    forM_ (Prelude.reverse [0 .. 127 - t8]) $ \i -> do+        pos <- xor <$> peekElemOff p (i + 1) <*> peekElemOff p (i + t8)+        let b = unsafeIndex piTable (fromIntegral pos)+        pokeElemOff p i b++  where t  = B.length key+        t8 = (t1 + 7) `div` 8+        tm | t1 == 8 * t8 = 255+           | otherwise    = 255 `mod` shiftL 1 (8 + t1 - 8 * t8)++        doCast :: Block Word8 -> Block Word16+        doCast = Basement.Block.map fromLE . cast+++-- PITABLE++piTable :: Block Word8+piTable = fromList+    [ 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d+    , 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2+    , 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32+    , 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82+    , 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc+    , 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26+    , 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03+    , 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7+    , 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a+    , 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec+    , 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39+    , 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31+    , 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9+    , 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9+    , 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e+    , 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad+    ]
+ src/Crypto/Store/Error.hs view
@@ -0,0 +1,65 @@+-- |+-- Module      : Crypto.Store.Error+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Error data type.+module Crypto.Store.Error+    ( StoreError(..)+    , fromCryptoFailable+    ) where++import Crypto.Error+import Crypto.PubKey.RSA.Types as RSA++import Data.ASN1.Error++-- | Error type in cryptostore.+data StoreError =+      CryptoError CryptoError+      -- ^ Wraps a cryptonite error+    | RSAError RSA.Error+      -- ^ Wraps an RSA crypto error+    | DecodingError ASN1Error+      -- ^ Error while decoding ASN.1 content+    | ParseFailure String+      -- ^ Error while parsing an ASN.1 object+    | DecryptionFailed+      -- ^ Unable to decrypt, incorrect key or password?+    | BadContentMAC+      -- ^ MAC verification failed, incorrect key or password?+    | BadChecksum+      -- ^ Checksum verification failed, incorrect key or password?+    | InvalidInput String+      -- ^ Some condition is not met about input to algorithm+    | InvalidPassword String+      -- ^ Some condition is not met about input password+    | InvalidParameter String+      -- ^ Some condition is not met about algorithm parameters+    | UnexpectedPublicKeyType+      -- ^ The algorithm expects another public key type+    | UnexpectedPrivateKeyType+      -- ^ The algorithm expects another private key type+    | RecipientTypeMismatch+      -- ^ Returned when the type of recipient info does not match the consumer+      -- function+    | RecipientKeyNotFound+      -- ^ The certificate provided does not match any encrypted key found+    | NoRecipientInfoFound+      -- ^ No recipient info is available in the enveloped data+    | NoRecipientInfoMatched+      -- ^ No recipient info could be used with the consumer function+    | UnsupportedOriginatorFormat+      -- ^ Only anonymous public key is supported+    | UnsupportedEllipticCurve+      -- ^ The elliptic curve used is not supported+    | NamedCurveRequired+      -- ^ The algorithm requires a named elliptic curve+    deriving (Show,Eq)++-- | Turn a 'CryptoFailed' into a 'StoreError'.+fromCryptoFailable ::CryptoFailable a -> Either StoreError a+fromCryptoFailable (CryptoPassed a) = Right a+fromCryptoFailable (CryptoFailed e) = Left (CryptoError e)
+ src/Crypto/Store/KeyWrap/AES.hs view
@@ -0,0 +1,140 @@+-- |+-- Module      : Crypto.Store.KeyWrap.AES+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- AES Key Wrap (<https://tools.ietf.org/html/rfc3394 RFC 3394>) and Extended+-- Key Wrap (<https://tools.ietf.org/html/rfc5649 RFC 5649>)+--+-- Should be used with a cipher from module "Crypto.Cipher.AES".+module Crypto.Store.KeyWrap.AES+    ( wrap+    , unwrap+    , wrapPad+    , unwrapPad+    ) where++import           Data.Bits+import           Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)+import qualified Data.ByteArray as B+import           Data.List+import           Data.Word++import Crypto.Cipher.Types++import Foreign.Storable++import Crypto.Store.Error+import Crypto.Store.Util++type Chunked ba = [ba]+type Pair ba = (ba, ba)++-- TODO: should use a low-level AES implementation to reduce allocations++aes' :: (BlockCipher aes, ByteArray ba) => aes -> Pair ba -> ba+aes' cipher (msb, lsb) = ecbEncrypt cipher (B.append msb lsb)++aes :: (BlockCipher aes, ByteArray ba) => aes -> Pair ba -> Pair ba+aes cipher = B.splitAt 8 . aes' cipher++aesrev' :: (BlockCipher aes, ByteArray ba) => aes -> ba -> Pair ba+aesrev' cipher = B.splitAt 8 . ecbDecrypt cipher++aesrev :: (BlockCipher aes, ByteArray ba) => aes -> Pair ba -> Pair ba+aesrev cipher (msb, lsb) = aesrev' cipher (B.append msb lsb)++wrapc :: (BlockCipher aes, ByteArray ba)+      => aes -> ba -> Chunked ba -> Chunked ba+wrapc cipher iiv list = uncurry (:) $ foldl' pass (iiv, list) [0 .. 5]+  where+    n = fromIntegral (length list)+    pass (a, l) j = mapAccumL f a $ zip [n * j + 1 ..] l+    f a (i, r) =+        let (msb, lsb) = aes cipher (a, r)+         in (xorWith msb i, lsb)++unwrapc :: (BlockCipher aes, ByteArray ba)+        => aes -> Chunked ba -> Either StoreError (ba, Chunked ba)+unwrapc _      []         = Left (InvalidInput "KeyWrap.AES: input too short")+unwrapc cipher (iv:list)  = Right (iiv, reverse out)+  where+    (iiv, out) = foldl' pass (iv, reverse list) (reverse [0 .. 5])+    n = fromIntegral (length list)+    pass (a, l) j = mapAccumL f a $ zip (reverse [n * j + 1 .. n * j + n]) l+    f a (i, r) = aesrev cipher (xorWith a i, r)++-- | Wrap a key with the specified AES cipher.+wrap :: (BlockCipher aes, ByteArray ba) => aes -> ba -> Either StoreError ba+wrap cipher bs = unchunks . wrapc cipher iiv <$> chunks bs+  where iiv = B.replicate 8 0xA6++-- | Unwrap an encrypted key with the specified AES cipher.+unwrap :: (BlockCipher aes, ByteArray ba) => aes -> ba -> Either StoreError ba+unwrap cipher bs = unchunks <$> (check =<< unwrapc cipher =<< chunks bs)+  where+    check (iiv, out)+        | constAllEq 0xA6 iiv = Right out+        | otherwise           = Left BadChecksum++chunks :: ByteArray ba => ba -> Either StoreError (Chunked ba)+chunks bs | B.null bs       = Right []+          | B.length bs < 8 = Left (InvalidInput "KeyWrap.AES: input is not multiple of 8 bytes")+          | otherwise       = let (a, b) = B.splitAt 8 bs in (a :) <$> chunks b++unchunks :: ByteArray ba => Chunked ba -> ba+unchunks = B.concat++padMask :: Bytes+padMask = B.pack [0xA6, 0x59, 0x59, 0xA6, 0x00, 0x00, 0x00, 0x00]++pad :: ByteArray ba => Int -> ba -> Either StoreError (Pair ba)+pad inlen bs | inlen  == 0 = Left (InvalidInput "KeyWrap.AES: input is empty")+             | padlen == 8 = Right (aiv, bs)+             | otherwise   = Right (aiv, bs `B.append` B.zero padlen)+  where padlen = 8 - mod inlen 8+        aiv    = xorWith padMask (fromIntegral inlen)++unpad :: ByteArray ba => Int -> Pair ba -> Either StoreError ba+unpad inlen (aiv, b)+    | badlen         = Left BadChecksum+    | constAllEq 0 p = Right bs+    | otherwise      = Left BadChecksum+  where aivlen = fromIntegral (unxor padMask aiv)+        badlen = inlen < aivlen + 8 || inlen >= aivlen + 16+        (bs, p) = B.splitAt aivlen b++-- | Pad and wrap a key with the specified AES cipher.+wrapPad :: (BlockCipher aes, ByteArray ba) => aes -> ba -> Either StoreError ba+wrapPad cipher bs = doWrap =<< pad inlen bs+  where+    inlen = B.length bs+    doWrap (aiv, b)+        | inlen <= 8 = Right $ aes' cipher (aiv, b)+        | otherwise  = unchunks . wrapc cipher aiv <$> chunks b++-- | Unwrap and unpad an encrypted key with the specified AES cipher.+unwrapPad :: (BlockCipher aes, ByteArray ba) => aes -> ba -> Either StoreError ba+unwrapPad cipher bs = unpad inlen =<< doUnwrap+  where+    inlen = B.length bs+    doUnwrap+        | inlen == 16 = let (aiv, b) = aesrev' cipher bs in Right (aiv, b)+        | otherwise   = fmap unchunks <$> (unwrapc cipher =<< chunks bs)++xorWith :: (ByteArrayAccess bin, ByteArray bout) => bin -> Word64 -> bout+xorWith bs i = B.copyAndFreeze bs $ \dst -> loop dst len i+  where len = B.length bs+        loop _ 0 _ = return ()+        loop _ _ 0 = return () -- return early (constant-time not needed)+        loop p n j = do+            b <- peekByteOff p (n - 1)+            let mask = fromIntegral j :: Word8+            pokeByteOff p (n - 1) (xor b mask)+            loop p (n - 1) (shiftR j 8)++unxor :: (ByteArrayAccess bx, ByteArrayAccess by) => bx -> by -> Word64+unxor x y = foldl' f 0 $ zipWith xor (B.unpack x) (B.unpack y)+  where f acc z = shiftL acc 8 + fromIntegral z
+ src/Crypto/Store/KeyWrap/RC2.hs view
@@ -0,0 +1,91 @@+-- |+-- Module      : Crypto.Store.KeyWrap.RC2+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- RC2 Key Wrap (<https://tools.ietf.org/html/rfc3217 RFC 3217>)+--+-- Should be used with a cipher from module "Crypto.Store.Cipher.RC2".+module Crypto.Store.KeyWrap.RC2+    ( wrap+    , wrap'+    , unwrap+    ) where++import           Data.ByteArray (ByteArray)+import qualified Data.ByteArray as B++import Crypto.Cipher.Types+import Crypto.Hash+import Crypto.Random++import Crypto.Store.Error+import Crypto.Store.Util++checksum :: ByteArray ba => ba -> ba+checksum bs = B.convert $ B.takeView (hashWith SHA1 bs) 8++iv4adda22c79e82105 :: B.Bytes+iv4adda22c79e82105 = B.pack [0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05]++-- | Wrap an RC2 key with the specified RC2 cipher.+--+-- Input must be between 0 and 255 bytes.  A fresh IV should be generated+-- randomly for each invocation.+wrap :: (MonadRandom m, BlockCipher cipher, ByteArray ba)+     => cipher -> IV cipher -> ba -> m (Either StoreError ba)+wrap = wrap' (return . Left) randomPad+  where randomPad f = fmap (Right . f) . getRandomBytes++-- | Wrap an RC2 key with the specified RC2 cipher, using the given source of+-- random padding data.+--+-- Input must be between 0 and 255 bytes.  A fresh IV should be generated+-- randomly for each invocation.+wrap' :: (ByteArray ba, BlockCipher cipher)+      => (StoreError -> result) -> ((ba -> ba) -> Int -> result)+      -> cipher -> IV cipher -> ba -> result+wrap' failure withRandomPad cipher iv cek+    | inLen < 256 = withRandomPad f padlen+    | otherwise   = failure+        (InvalidInput "KeyWrap.RC2: invalid length for content encryption key")+  where+    inLen      = B.length cek+    padlen     = (7 - inLen) `mod` 8++    f pad =+        let lcek       = B.cons (fromIntegral inLen) cek+            lcekpad    = B.append lcek pad+            lcekpadicv = B.append lcekpad (checksum lcekpad)+            temp1      = cbcEncrypt cipher iv lcekpadicv+            temp2      = B.append (B.convert iv) temp1+            temp3      = reverseBytes temp2+            Just iv'   = makeIV iv4adda22c79e82105+         in cbcEncrypt cipher iv' temp3++-- | Unwrap an encrypted RC2 key with the specified RC2 cipher.+unwrap :: (BlockCipher cipher, ByteArray ba)+       => cipher -> ba -> Either StoreError ba+unwrap cipher wrapped+    | inLen <= 16        = invalid+    | inLen `mod` 8 /= 0 = invalid+    | checksumPadValid   = Right cek+    | otherwise          = invalid+  where+    inLen            = B.length wrapped+    Just iv'         = makeIV iv4adda22c79e82105+    temp3            = cbcDecrypt cipher iv' wrapped+    temp2            = reverseBytes temp3+    (ivBs, temp1)    = B.splitAt 8 temp2+    Just iv          = makeIV ivBs+    lcekpadicv       = cbcDecrypt cipher iv temp1+    (lcekpad, icv)   = B.splitAt (inLen - 16) lcekpadicv+    Just (l, cekpad) = B.uncons lcekpad+    len              = fromIntegral l+    padlen           = inLen - 16 - len - 1+    cek              = B.take len cekpad+    invalid          = Left BadChecksum+    checksumPadValid = B.constEq icv (checksum lcekpad)+                           &&! padlen >=0 &&! padlen < 8
+ src/Crypto/Store/KeyWrap/TripleDES.hs view
@@ -0,0 +1,66 @@+-- |+-- Module      : Crypto.Store.KeyWrap.TripleDES+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Triple-DES Key Wrap (<https://tools.ietf.org/html/rfc3217 RFC 3217>)+--+-- Should be used with a cipher from module "Crypto.Cipher.TripleDES".+module Crypto.Store.KeyWrap.TripleDES+    ( wrap+    , unwrap+    ) where++import           Data.ByteArray (ByteArray)+import qualified Data.ByteArray as B++import Crypto.Cipher.Types+import Crypto.Hash++import Crypto.Store.Error+import Crypto.Store.Util++checksum :: ByteArray ba => ba -> ba+checksum bs = B.convert $ B.takeView (hashWith SHA1 bs) 8++iv4adda22c79e82105 :: B.Bytes+iv4adda22c79e82105 = B.pack [0x4a, 0xdd, 0xa2, 0x2c, 0x79, 0xe8, 0x21, 0x05]++-- | Wrap a Triple-DES key with the specified Triple-DES cipher.+--+-- Input must be 24 bytes.  A fresh IV should be generated randomly for each+-- invocation.+wrap :: (BlockCipher cipher, ByteArray ba)+     => cipher -> IV cipher -> ba -> Either StoreError ba+wrap cipher iv cek+    | inLen == 24 = Right wrapped+    | otherwise   = Left+        (InvalidInput "KeyWrap.TripleDES: invalid length for content encryption key")+  where+    inLen    = B.length cek+    Just iv' = makeIV iv4adda22c79e82105+    cekicv   = B.append cek (checksum cek)+    temp1    = cbcEncrypt cipher iv cekicv+    temp2    = B.append (B.convert iv) temp1+    temp3    = reverseBytes temp2+    wrapped  = cbcEncrypt cipher iv' temp3++-- | Unwrap an encrypted Triple-DES key with the specified Triple-DES cipher.+unwrap :: (BlockCipher cipher, ByteArray ba)+       => cipher -> ba -> Either StoreError ba+unwrap cipher wrapped+    | inLen /= 40                  = invalid+    | B.constEq icv (checksum cek) = Right cek+    | otherwise                    = invalid+  where+    inLen         = B.length wrapped+    Just iv'      = makeIV iv4adda22c79e82105+    temp3         = cbcDecrypt cipher iv' wrapped+    temp2         = reverseBytes temp3+    (ivBs, temp1) = B.splitAt 8 temp2+    Just iv       = makeIV ivBs+    cekicv        = cbcDecrypt cipher iv temp1+    (cek, icv)    = B.splitAt 24 cekicv+    invalid       = Left BadChecksum
+ src/Crypto/Store/PEM.hs view
@@ -0,0 +1,35 @@+-- |+-- Module      : Crypto.Store.PEM+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Extend module "Data.PEM".+module Crypto.Store.PEM+    ( readPEMs+    , writePEMs+    , pemsWriteBS+    , pemsWriteLBS+    , module Data.PEM+    ) where++import Data.PEM+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as L++-- | Read a PEM file from disk.+readPEMs :: FilePath -> IO [PEM]+readPEMs filepath = either error id . pemParseLBS <$> L.readFile filepath++-- | Convert a list of PEM elements to a bytestring.+pemsWriteBS :: [PEM] -> B.ByteString+pemsWriteBS = L.toStrict . pemsWriteLBS++-- | Convert a list of PEM elements to a lazy bytestring.+pemsWriteLBS :: [PEM] -> L.ByteString+pemsWriteLBS = L.concat . map pemWriteLBS++-- | Write a PEM file to disk.+writePEMs :: FilePath -> [PEM] -> IO ()+writePEMs filepath = L.writeFile filepath . pemsWriteLBS
+ src/Crypto/Store/PKCS12.hs view
@@ -0,0 +1,652 @@+-- |+-- Module      : Crypto.Store.PKCS12+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Personal Information Exchange Syntax, aka PKCS #12.+--+-- Only password integrity mode and password privacy modes are supported.+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}+module Crypto.Store.PKCS12+    ( IntegrityParams+    , readP12File+    , readP12FileFromMemory+    , writeP12File+    , writeP12FileToMemory+    , writeUnprotectedP12File+    , writeUnprotectedP12FileToMemory+    -- * PKCS #12 privacy+    , PKCS12+    , unPKCS12+    , unPKCS12'+    , unencrypted+    , encrypted+    -- * PKCS #12 contents and bags+    , SafeContents(..)+    , SafeBag+    , Bag(..)+    , SafeInfo(..)+    , CertInfo(..)+    , CRLInfo(..)+    , Attribute(..)+    , getSafeKeys+    , getAllSafeKeys+    , getSafeX509Certs+    , getAllSafeX509Certs+    , getSafeX509CRLs+    , getAllSafeX509CRLs+    -- * PKCS #12 attributes+    , findAttribute+    , setAttribute+    , filterAttributes+    , getFriendlyName+    , setFriendlyName+    , getLocalKeyId+    , setLocalKeyId+    -- * Credentials+    , fromCredential+    , toCredential+    -- * Password-based protection+    , Password+    , OptProtected(..)+    , recover+    , recoverA+    ) where++import Control.Monad++import           Data.ASN1.Types+import           Data.ASN1.BinaryEncoding+import           Data.ASN1.Encoding+import qualified Data.ByteArray as B+import qualified Data.ByteString as BS+import           Data.Maybe (fromMaybe)+import           Data.Semigroup+import qualified Data.X509 as X509++import Crypto.Cipher.Types++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Encrypted+import Crypto.Store.CMS.Util+import Crypto.Store.Error+import Crypto.Store.PKCS5+import Crypto.Store.PKCS5.PBES1+import Crypto.Store.PKCS8+++-- Decoding and parsing++-- | Read a PKCS #12 file from disk.+readP12File :: FilePath -> IO (Either StoreError (OptProtected PKCS12))+readP12File path = readP12FileFromMemory <$> BS.readFile path++-- | Read a PKCS #12 file from a bytearray in BER format.+readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptProtected PKCS12)+readP12FileFromMemory ber = decode ber >>= integrity+  where+    integrity PFX{..} =+        case macData of+            Nothing -> Unprotected <$> decode authSafeData+            Just md -> return $ Protected (verify md authSafeData)++    verify MacData{..} content pwdUTF8 =+        case digAlg of+            DigestAlgorithm d ->+                let fn key macAlg bs+                        | macValue == mac macAlg key bs = decode bs+                        | otherwise = Left BadContentMAC+                 in pkcs12mac Left fn d macParams content pwdUTF8+++-- Generating and encoding++-- | Parameters used for password integrity mode.+type IntegrityParams = (DigestAlgorithm, PBEParameter)++-- | Write a PKCS #12 file to disk.+writeP12File :: FilePath+             -> IntegrityParams -> Password+             -> PKCS12+             -> IO (Either StoreError ())+writeP12File path intp pw aSafe =+    case writeP12FileToMemory intp pw aSafe of+        Left e   -> return (Left e)+        Right bs -> Right <$> BS.writeFile path bs++-- | Write a PKCS #12 file to a bytearray in DER format.+writeP12FileToMemory :: IntegrityParams -> Password+                     -> PKCS12+                     -> Either StoreError BS.ByteString+writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =+    encode <$> protect+  where+    content   = encodeASN1Object aSafe+    encode md = encodeASN1Object PFX { authSafeData = content, macData = Just md }++    protect = pkcs12mac Left fn hashAlg pbeParam content pwdUTF8+    fn key macAlg bs = Right MacData { digAlg    = alg+                                     , macValue  = mac macAlg key bs+                                     , macParams = pbeParam+                                     }++-- | Write a PKCS #12 file without integrity protection to disk.+writeUnprotectedP12File :: FilePath -> PKCS12 -> IO ()+writeUnprotectedP12File path = BS.writeFile path . writeUnprotectedP12FileToMemory++-- | Write a PKCS #12 file without integrity protection to a bytearray in DER+-- format.+writeUnprotectedP12FileToMemory :: PKCS12 -> BS.ByteString+writeUnprotectedP12FileToMemory aSafe = encodeASN1Object pfx+  where+    content = encodeASN1Object aSafe+    pfx     = PFX { authSafeData = content, macData = Nothing }+++-- PFX and MacData++data PFX = PFX+    { authSafeData :: BS.ByteString+    , macData :: Maybe MacData+    }+    deriving (Show,Eq)++instance ProduceASN1Object ASN1P PFX where+    asn1s PFX{..} =+        asn1Container Sequence (v . a . m)+      where+        v = gIntVal 3+        a = asn1s (DataCI authSafeData)+        m = optASN1S macData asn1s++instance ParseASN1Object [ASN1Event] PFX where+    parse = onNextContainer Sequence $ do+        IntVal v <- getNext+        when (v /= 3) $+            throwParseError ("PFX: parsed invalid version: " ++ show v)+        ci <- parse+        d <- case ci of+                 DataCI bs      -> return bs+                 SignedDataCI _ -> throwParseError "PFX: public-key integrity mode is not supported"+                 _              -> throwParseError $ "PFX: invalid content type: " ++ show (getContentType ci)+        b <- hasNext+        m <- if b then Just <$> parse else pure Nothing+        return PFX { authSafeData = d, macData = m }++data MacData = MacData+    { digAlg :: DigestAlgorithm+    , macValue :: MessageAuthenticationCode+    , macParams :: PBEParameter+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e MacData where+    asn1s MacData{..} =+        asn1Container Sequence (m . s . i)+      where+        m = asn1Container Sequence (a . v)+        a = algorithmASN1S Sequence digAlg+        v = gOctetString (B.convert macValue)+        s = gOctetString (pbeSalt macParams)+        i = gIntVal (fromIntegral $ pbeIterationCount macParams)++instance Monoid e => ParseASN1Object e MacData where+    parse = onNextContainer Sequence $ do+        (a, v) <- onNextContainer Sequence $ do+            a <- parseAlgorithm Sequence+            OctetString v <- getNext+            return (a, v)+        OctetString s <- getNext+        b <- hasNext+        IntVal i <- if b then getNext else pure (IntVal 1)+        return MacData { digAlg = a+                       , macValue = AuthTag (B.convert v)+                       , macParams = PBEParameter s (fromIntegral i)+                       }+++-- AuthenticatedSafe++-- | PKCS #12 privacy wrapper, adding optional encryption to 'SafeContents'.+-- ASN.1 equivalent is @AuthenticatedSafe@.+--+-- The semigroup interface allows to combine multiple pieces encrypted+-- separately but they should all derive from the same password to be readable+-- by 'unPKCS12' and most other software.+newtype PKCS12 = PKCS12 [ASElement]+    deriving (Show,Eq)++instance Semigroup PKCS12 where+    PKCS12 a <> PKCS12 b = PKCS12 (a ++ b)++instance ProduceASN1Object ASN1P PKCS12 where+    asn1s (PKCS12 elems) = asn1Container Sequence (asn1s elems)++instance ParseASN1Object [ASN1Event] PKCS12 where+    parse = PKCS12 <$> onNextContainer Sequence parse++-- | Read the contents of a PKCS #12.  The same privacy password will be used+-- for all content elements.+--+-- This convenience function returns a 'Protected' value as soon as one element+-- at least is encrypted.  This does not mean all elements were actually+-- protected in the input.  If detailed view is required then function+-- 'unPKCS12'' is also available.+unPKCS12 :: PKCS12 -> OptProtected [SafeContents]+unPKCS12 = applySamePassword . unPKCS12'++-- | Read the contents of a PKCS #12.+unPKCS12' :: PKCS12 -> [OptProtected SafeContents]+unPKCS12' (PKCS12 elems) = map f elems+  where f (Unencrypted sc) = Unprotected sc+        f (Encrypted e)    = Protected (decrypt e >=> decode)++-- | Build a PKCS #12 without encryption.  Usage scenario is when private keys+-- are already encrypted with 'PKCS8ShroudedKeyBag'.+unencrypted :: SafeContents -> PKCS12+unencrypted = PKCS12 . (:[]) . Unencrypted++-- | Build a PKCS #12 encrypted with the specified scheme and password.+encrypted :: EncryptionScheme -> Password -> SafeContents -> Either StoreError PKCS12+encrypted alg pwd sc = PKCS12 . (:[]) . Encrypted <$> encrypt alg pwd bs+  where bs = encodeASN1Object sc++data ASElement = Unencrypted SafeContents+               | Encrypted PKCS5+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e ASElement where+    asn1s (Unencrypted sc) = asn1Container Sequence (oid . cont)+      where+        oid = gOID (getObjectID DataType)+        cont = asn1Container (Container Context 0) (gOctetString bs)+        bs = encodeASN1Object sc++    asn1s (Encrypted PKCS5{..}) = asn1Container Sequence (oid . cont)+      where+        oid = gOID (getObjectID EncryptedDataType)+        cont = asn1Container (Container Context 0) inner+        inner = asn1Container Sequence (gIntVal 0 . eci)+        eci = encryptedContentInfoASN1S+                  (DataType, encryptionAlgorithm, encryptedData)++instance Monoid e => ParseASN1Object e ASElement where+    parse = onNextContainer Sequence $ do+        OID oid <- getNext+        withObjectID "content type" oid $ \ct ->+            onNextContainer (Container Context 0) (parseInner ct)+      where+        parseInner DataType          = Unencrypted <$> parseUnencrypted+        parseInner EncryptedDataType = Encrypted <$> parseEncrypted+        parseInner EnvelopedDataType = throwParseError "PKCS12: public-key privacy mode is not supported"+        parseInner ct                = throwParseError $ "PKCS12: invalid content type: " ++ show ct++        parseUnencrypted = parseOctetStringObject "PKCS12"+        parseEncrypted = onNextContainer Sequence $ do+            IntVal 0 <- getNext+            (DataType, eScheme, ed) <- parseEncryptedContentInfo+            return PKCS5 { encryptionAlgorithm = eScheme, encryptedData = ed }+++-- Bags++-- | Polymorphic PKCS #12 bag parameterized by the payload data type.+data Bag info = Bag+    { bagInfo :: info              -- ^ bag payload+    , bagAttributes :: [Attribute] -- ^ attributes providing additional information+    }+    deriving (Show,Eq)++class BagInfo info where+    type BagType info+    bagName  :: info -> String+    bagType  :: info -> BagType info+    valueASN1S :: ASN1Elem e => info -> ASN1Stream e+    parseValue :: Monoid e => BagType info -> ParseASN1 e info++instance (ASN1Elem e, BagInfo info, OIDable (BagType info)) => ProduceASN1Object e (Bag info) where+    asn1s Bag{..} = asn1Container Sequence (oid . val . att)+      where+        typ = bagType bagInfo+        oid = gOID (getObjectID typ)+        val = asn1Container (Container Context 0) (valueASN1S bagInfo)++        att | null bagAttributes = id+            | otherwise          = asn1Container Set (asn1s bagAttributes)++instance (Monoid e, BagInfo info, OIDNameable (BagType info)) => ParseASN1Object e (Bag info) where+    parse = onNextContainer Sequence $ do+        OID oid <- getNext+        val <- withObjectID (getName undefined) oid $+                   onNextContainer (Container Context 0) . parseValue+        att <- fromMaybe [] <$> onNextContainerMaybe Set parse+        return Bag { bagInfo = val, bagAttributes = att }+      where+        getName :: info -> String+        getName = bagName++data CertType = TypeCertX509 deriving (Show,Eq)++instance Enumerable CertType where+    values = [ TypeCertX509 ]++instance OIDable CertType where+    getObjectID TypeCertX509 = [1,2,840,113549,1,9,22,1]++instance OIDNameable CertType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Certificate bags.  Only X.509 certificates are supported.+newtype CertInfo = CertX509 X509.SignedCertificate deriving (Show,Eq)++instance BagInfo CertInfo where+    type BagType CertInfo = CertType+    bagName _ = "CertBag"+    bagType (CertX509 _) = TypeCertX509+    valueASN1S (CertX509 c) = gOctetString (encodeASN1Object c)+    parseValue TypeCertX509 = CertX509 <$> parseOctetStringObject "CertBag"++data CRLType = TypeCRLX509 deriving (Show,Eq)++instance Enumerable CRLType where+    values = [ TypeCRLX509 ]++instance OIDable CRLType where+    getObjectID TypeCRLX509 = [1,2,840,113549,1,9,23,1]++instance OIDNameable CRLType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | CRL bags.  Only X.509 CRLs are supported.+newtype CRLInfo = CRLX509 X509.SignedCRL deriving (Show,Eq)++instance BagInfo CRLInfo where+    type BagType CRLInfo = CRLType+    bagName _ = "CRLBag"+    bagType (CRLX509 _) = TypeCRLX509+    valueASN1S (CRLX509 c) = gOctetString (encodeASN1Object c)+    parseValue TypeCRLX509 = CRLX509 <$> parseOctetStringObject "CRLBag"++data SafeType = TypeKey+              | TypePKCS8ShroudedKey+              | TypeCert+              | TypeCRL+              | TypeSecret+              | TypeSafeContents+    deriving (Show,Eq)++instance Enumerable SafeType where+    values = [ TypeKey+             , TypePKCS8ShroudedKey+             , TypeCert+             , TypeCRL+             , TypeSecret+             , TypeSafeContents+             ]++instance OIDable SafeType where+    getObjectID TypeKey              = [1,2,840,113549,1,12,10,1,1]+    getObjectID TypePKCS8ShroudedKey = [1,2,840,113549,1,12,10,1,2]+    getObjectID TypeCert             = [1,2,840,113549,1,12,10,1,3]+    getObjectID TypeCRL              = [1,2,840,113549,1,12,10,1,4]+    getObjectID TypeSecret           = [1,2,840,113549,1,12,10,1,5]+    getObjectID TypeSafeContents     = [1,2,840,113549,1,12,10,1,6]++instance OIDNameable SafeType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Main bag payload in PKCS #12 contents.+data SafeInfo = KeyBag (FormattedKey X509.PrivKey) -- ^ unencrypted private key+              | PKCS8ShroudedKeyBag PKCS5          -- ^ encrypted private key+              | CertBag (Bag CertInfo)             -- ^ certificate+              | CRLBag (Bag CRLInfo)               -- ^ CRL+              | SecretBag [ASN1]                   -- ^ arbitrary secret+              | SafeContentsBag SafeContents       -- ^ safe contents embeded recursively+    deriving (Show,Eq)++instance BagInfo SafeInfo where+    type BagType SafeInfo = SafeType+    bagName _ = "SafeBag"++    bagType (KeyBag _)              = TypeKey+    bagType (PKCS8ShroudedKeyBag _) = TypePKCS8ShroudedKey+    bagType (CertBag _)             = TypeCert+    bagType (CRLBag _)              = TypeCRL+    bagType (SecretBag _)           = TypeSecret+    bagType (SafeContentsBag _)     = TypeSafeContents++    valueASN1S (KeyBag k)              = asn1s k+    valueASN1S (PKCS8ShroudedKeyBag k) = asn1s k+    valueASN1S (CertBag c)             = asn1s c+    valueASN1S (CRLBag c)              = asn1s c+    valueASN1S (SecretBag s)           = gMany s+    valueASN1S (SafeContentsBag sc)    = asn1s sc++    parseValue TypeKey              = KeyBag <$> parse+    parseValue TypePKCS8ShroudedKey = PKCS8ShroudedKeyBag <$> parse+    parseValue TypeCert             = CertBag <$> parse+    parseValue TypeCRL              = CRLBag <$> parse+    parseValue TypeSecret           = SecretBag <$> getMany getNext+    parseValue TypeSafeContents     = SafeContentsBag <$> parse++-- | Main bag type in a PKCS #12.+type SafeBag = Bag SafeInfo++-- | Content objects stored in a PKCS #12.+newtype SafeContents = SafeContents { unSafeContents :: [SafeBag] }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e SafeContents where+    asn1s (SafeContents s) = asn1Container Sequence (asn1s s)++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+  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+            _                         -> loop bags++    unshroud shrouded pwd = do+        bs <- decrypt shrouded pwd+        FormattedKey _ k <- decode bs+        return k++-- | Return all private keys contained in the safe content list.  All shrouded+-- private keys must derive from the same password.+--+-- This convenience function returns a 'Protected' value as soon as one key at+-- least is encrypted.  This does not mean all keys were actually protected in+-- the input.  If detailed view is required then function 'getSafeKeys' is+-- available.+getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey]+getAllSafeKeys = applySamePassword . concatMap getSafeKeys++-- | Return all X.509 certificates contained in the safe contents.+getSafeX509Certs :: SafeContents -> [X509.SignedCertificate]+getSafeX509Certs (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+            _                            -> loop bags++-- | 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+  where+    loop []           = []+    loop (bag : bags) =+        case bagInfo bag of+            CRLBag (Bag (CRLX509 c) _) -> c : loop bags+            SafeContentsBag inner      -> getSafeX509CRLs inner ++ loop bags+            _                          -> loop bags++-- | Return all X.509 CRLs contained in the safe content list.+getAllSafeX509CRLs :: [SafeContents] -> [X509.SignedCRL]+getAllSafeX509CRLs = concatMap getSafeX509CRLs+++-- Conversion to/from credentials++getInnerCredential :: [SafeContents] -> SamePassword (Maybe (X509.CertificateChain, X509.PrivKey))+getInnerCredential l = SamePassword (fn <$> getAllSafeKeys l)+  where+    certs = getAllSafeX509Certs l++    fn []               = Nothing+    fn [k] | null certs = Nothing+           | otherwise  = Just (X509.CertificateChain certs, k)+    fn _                = Nothing++-- | Extract the private key and certificate chain from a 'PKCS12' value.  A+-- credential is returned when the structure contains exactly one private key+-- and at least one X.509 certificate.+toCredential :: PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))+toCredential p12 =+    unSamePassword (SamePassword (unPKCS12 p12) >>= getInnerCredential)++-- | Build a 'PKCS12' value containing a private key and certificate chain.+-- Distinct encryption is applied for both.  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.+fromCredential :: Maybe EncryptionScheme -- for certificates+               -> EncryptionScheme       -- for private key+               -> Password+               -> (X509.CertificateChain, X509.PrivKey)+               -> Either StoreError PKCS12+fromCredential algChain algKey pwd (X509.CertificateChain certs, key)+    | null certs = Left (InvalidInput "Empty certificate chain")+    | otherwise  = (<>) <$> pkcs12Chain <*> pkcs12Key+  where+    pkcs12Key   = unencrypted <$> scKeyOrError+    pkcs12Chain =+        case algChain of+            Just alg -> encrypted alg pwd scChain+            Nothing  -> Right (unencrypted scChain)++    scChain     = SafeContents (map toCertBag certs)+    toCertBag c = Bag (CertBag (Bag (CertX509 c) [])) []++    scKeyOrError = wrap <$> encrypt algKey pwd encodedKey++    wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) []]+    encodedKey    = encodeASN1Object (FormattedKey PKCS8Format key)+++-- Standard attributes++friendlyName :: OID+friendlyName = [1,2,840,113549,1,9,20]++-- | Return the value of the @friendlyName@ attribute.+getFriendlyName :: [Attribute] -> Maybe String+getFriendlyName attrs = runParseAttribute friendlyName attrs $ do+    ASN1String str <- getNext+    case asn1CharacterToString str of+        Nothing -> throwParseError "Invalid friendlyName value"+        Just s  -> return s++-- | Add or replace the @friendlyName@ attribute in a list of attributes.+setFriendlyName :: String -> [Attribute] -> [Attribute]+setFriendlyName name = setAttributeASN1S friendlyName (gBMPString name)++localKeyId :: OID+localKeyId = [1,2,840,113549,1,9,21]++-- | Return the value of the @localKeyId@ attribute.+getLocalKeyId :: [Attribute] -> Maybe BS.ByteString+getLocalKeyId attrs = runParseAttribute localKeyId attrs $ do+    OctetString d <- getNext+    return d++-- | Add or replace the @localKeyId@ attribute in a list of attributes.+setLocalKeyId :: BS.ByteString -> [Attribute] -> [Attribute]+setLocalKeyId d = setAttributeASN1S localKeyId (gOctetString d)+++-- Utilities++-- Internal wrapper of OptProtected providing Applicative and Monad instances.+--+-- This adds the following constraint: all values composed must derive from the+-- same encryption password.  Semantically, 'Protected' actually means+-- "requiring a password".  Otherwise composition of 'Protected' and+-- 'Unprotected' values is unsound.+newtype SamePassword a = SamePassword { unSamePassword :: OptProtected a }++instance Functor SamePassword where+    fmap f (SamePassword opt) = SamePassword (fmap f opt)++instance Applicative SamePassword where+    pure a = SamePassword (Unprotected a)++    SamePassword (Unprotected f) <*> SamePassword (Unprotected x) =+        SamePassword (Unprotected (f x))++    SamePassword (Unprotected f) <*> SamePassword (Protected x) =+        SamePassword $ Protected (fmap f . x)++    SamePassword (Protected f) <*> SamePassword (Unprotected x) =+        SamePassword $ Protected (fmap ($ x) . f)++    SamePassword (Protected f) <*> SamePassword (Protected x) =+        SamePassword $ Protected (\pwd -> f pwd <*> x pwd)++instance Monad SamePassword where+    return = pure++    SamePassword (Unprotected x)   >>= f = f x+    SamePassword (Protected inner) >>= f =+        SamePassword . Protected $ \pwd ->+            case inner pwd of+                Left err -> Left err+                Right x  -> recover pwd (unSamePassword $ f x)++applySamePassword :: [OptProtected a] -> OptProtected [a]+applySamePassword = unSamePassword . traverse SamePassword++decode :: ParseASN1Object [ASN1Event] obj => BS.ByteString -> Either StoreError obj+decode bs =+    case decodeASN1Repr' BER bs of+        Left e     -> Left (DecodingError e)+        Right asn1 ->+            case fromASN1Repr asn1 of+                Right (obj, []) -> Right obj+                Right _         -> Left (ParseFailure "Incomplete parse")+                Left e          -> Left (ParseFailure e)++parseOctetStringObject :: (Monoid e, ParseASN1Object [ASN1Event] obj)+                       => String -> ParseASN1 e obj+parseOctetStringObject name = do+    OctetString bs <- getNext+    case decode bs of+        Left e  -> throwParseError (name ++ ": " ++ show e)+        Right c -> return c
+ src/Crypto/Store/PKCS5.hs view
@@ -0,0 +1,238 @@+-- |+-- Module      : Data.Store.PKCS5+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Password-Based Cryptography, aka PKCS #5.+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE TypeFamilies #-}+module Crypto.Store.PKCS5+    ( Password+    , EncryptedContent+    -- * High-level API+    , PKCS5(..)+    , encrypt+    , decrypt+    -- * Encryption schemes+    , EncryptionScheme(..)+    , PBEParameter(..)+    , PBES2Parameter(..)+    -- * Key derivation+    , KeyDerivationFunc(..)+    , PBKDF2_PRF(..)+    , Salt+    , generateSalt+    -- * Content encryption+    , ContentEncryptionParams+    , ContentEncryptionAlg(..)+    , ContentEncryptionCipher(..)+    , generateEncryptionParams+    , getContentEncryptionAlg+    -- * Low-level API+    , pbEncrypt+    , pbDecrypt+    ) where++import           Data.ASN1.Types+import           Data.ByteArray (ByteArrayAccess)+import           Data.ByteString (ByteString)+import           Data.Maybe (fromMaybe)++import Crypto.Store.ASN1.Parse+import Crypto.Store.ASN1.Generate+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Encrypted+import Crypto.Store.CMS.Enveloped+import Crypto.Store.CMS.Util+import Crypto.Store.Error+import Crypto.Store.PKCS5.PBES1++data EncryptionSchemeType = Type_PBES2+                          | Type_PBE_MD5_DES_CBC+                          | Type_PBE_SHA1_DES_CBC+                          | Type_PBE_SHA1_RC4_128+                          | Type_PBE_SHA1_RC4_40+                          | Type_PBE_SHA1_DES_EDE3_CBC+                          | Type_PBE_SHA1_DES_EDE2_CBC+                          | Type_PBE_SHA1_RC2_128+                          | Type_PBE_SHA1_RC2_40++instance Enumerable EncryptionSchemeType where+    values = [ Type_PBES2+             , Type_PBE_MD5_DES_CBC+             , Type_PBE_SHA1_DES_CBC+             , Type_PBE_SHA1_RC4_128+             , Type_PBE_SHA1_RC4_40+             , Type_PBE_SHA1_DES_EDE3_CBC+             , Type_PBE_SHA1_DES_EDE2_CBC+             , Type_PBE_SHA1_RC2_128+             , Type_PBE_SHA1_RC2_40+             ]++instance OIDable EncryptionSchemeType where+    getObjectID Type_PBES2                 = [1,2,840,113549,1,5,13]+    getObjectID Type_PBE_MD5_DES_CBC       = [1,2,840,113549,1,5,3]+    getObjectID Type_PBE_SHA1_DES_CBC      = [1,2,840,113549,1,5,10]+    getObjectID Type_PBE_SHA1_RC4_128      = [1,2,840,113549,1,12,1,1]+    getObjectID Type_PBE_SHA1_RC4_40       = [1,2,840,113549,1,12,1,2]+    getObjectID Type_PBE_SHA1_DES_EDE3_CBC = [1,2,840,113549,1,12,1,3]+    getObjectID Type_PBE_SHA1_DES_EDE2_CBC = [1,2,840,113549,1,12,1,4]+    getObjectID Type_PBE_SHA1_RC2_128      = [1,2,840,113549,1,12,1,5]+    getObjectID Type_PBE_SHA1_RC2_40       = [1,2,840,113549,1,12,1,6]++instance OIDNameable EncryptionSchemeType where+    fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Password-Based Encryption Scheme (PBES).+data EncryptionScheme = PBES2 PBES2Parameter               -- ^ PBES2+                      | PBE_MD5_DES_CBC PBEParameter       -- ^ pbeWithMD5AndDES-CBC+                      | PBE_SHA1_DES_CBC PBEParameter      -- ^ pbeWithSHA1AndDES-CBC+                      | PBE_SHA1_RC4_128 PBEParameter      -- ^ pbeWithSHAAnd128BitRC4+                      | PBE_SHA1_RC4_40 PBEParameter       -- ^ pbeWithSHAAnd40BitRC4+                      | PBE_SHA1_DES_EDE3_CBC PBEParameter -- ^ pbeWithSHAAnd3-KeyTripleDES-CBC+                      | PBE_SHA1_DES_EDE2_CBC PBEParameter -- ^ pbeWithSHAAnd2-KeyTripleDES-CBC+                      | PBE_SHA1_RC2_128 PBEParameter      -- ^ pbeWithSHAAnd128BitRC2-CBC+                      | PBE_SHA1_RC2_40 PBEParameter       -- ^ pbewithSHAAnd40BitRC2-CBC+                      deriving (Show,Eq)++-- | PBES2 parameters.+data PBES2Parameter = PBES2Parameter+    { pbes2KDF     :: KeyDerivationFunc       -- ^ Key derivation function+    , pbes2EScheme :: ContentEncryptionParams -- ^ Underlying encryption scheme+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e PBES2Parameter where+    asn1s PBES2Parameter{..} =+        let kdFunc  = algorithmASN1S Sequence pbes2KDF+            eScheme = asn1s pbes2EScheme+         in asn1Container Sequence (kdFunc . eScheme)++instance Monoid e => ParseASN1Object e PBES2Parameter where+    parse = onNextContainer Sequence $ do+        kdFunc  <- parseAlgorithm Sequence+        eScheme <- parse+        case kdfKeyLength kdFunc of+            Nothing -> return ()+            Just sz+                | validateKeySize eScheme sz -> return ()+                | otherwise -> throwParseError "PBES2Parameter: parsed key length incompatible with encryption scheme"+        return PBES2Parameter { pbes2KDF = kdFunc, pbes2EScheme = eScheme }++instance AlgorithmId EncryptionScheme where+    type AlgorithmType EncryptionScheme = EncryptionSchemeType+    algorithmName _  = "encryption scheme"++    algorithmType (PBES2 _)                 = Type_PBES2+    algorithmType (PBE_MD5_DES_CBC _)       = Type_PBE_MD5_DES_CBC+    algorithmType (PBE_SHA1_DES_CBC _)      = Type_PBE_SHA1_DES_CBC+    algorithmType (PBE_SHA1_RC4_128 _)      = Type_PBE_SHA1_RC4_128+    algorithmType (PBE_SHA1_RC4_40 _)       = Type_PBE_SHA1_RC4_40+    algorithmType (PBE_SHA1_DES_EDE3_CBC _) = Type_PBE_SHA1_DES_EDE3_CBC+    algorithmType (PBE_SHA1_DES_EDE2_CBC _) = Type_PBE_SHA1_DES_EDE2_CBC+    algorithmType (PBE_SHA1_RC2_128 _)      = Type_PBE_SHA1_RC2_128+    algorithmType (PBE_SHA1_RC2_40 _)       = Type_PBE_SHA1_RC2_40++    parameterASN1S (PBES2 p)                 = asn1s p+    parameterASN1S (PBE_MD5_DES_CBC p)       = asn1s p+    parameterASN1S (PBE_SHA1_DES_CBC p)      = asn1s p+    parameterASN1S (PBE_SHA1_RC4_128 p)      = asn1s p+    parameterASN1S (PBE_SHA1_RC4_40 p)       = asn1s p+    parameterASN1S (PBE_SHA1_DES_EDE3_CBC p) = asn1s p+    parameterASN1S (PBE_SHA1_DES_EDE2_CBC p) = asn1s p+    parameterASN1S (PBE_SHA1_RC2_128 p)      = asn1s p+    parameterASN1S (PBE_SHA1_RC2_40 p)       = asn1s p++    parseParameter Type_PBES2                 = PBES2 <$> parse+    parseParameter Type_PBE_MD5_DES_CBC       = PBE_MD5_DES_CBC <$> parse+    parseParameter Type_PBE_SHA1_DES_CBC      = PBE_SHA1_DES_CBC <$> parse+    parseParameter Type_PBE_SHA1_RC4_128      = PBE_SHA1_RC4_128 <$> parse+    parseParameter Type_PBE_SHA1_RC4_40       = PBE_SHA1_RC4_40 <$> parse+    parseParameter Type_PBE_SHA1_DES_EDE3_CBC = PBE_SHA1_DES_EDE3_CBC <$> parse+    parseParameter Type_PBE_SHA1_DES_EDE2_CBC = PBE_SHA1_DES_EDE2_CBC <$> parse+    parseParameter Type_PBE_SHA1_RC2_128      = PBE_SHA1_RC2_128 <$> parse+    parseParameter Type_PBE_SHA1_RC2_40       = PBE_SHA1_RC2_40 <$> parse++instance ASN1Elem e => ProduceASN1Object e EncryptionScheme where+    asn1s = algorithmASN1S Sequence++instance Monoid e => ParseASN1Object e EncryptionScheme where+    parse = parseAlgorithm Sequence+++-- High-level API++-- | Content encrypted with a Password-Based Encryption Scheme (PBES).+--+-- The content will usually be the binary representation of an ASN.1 object,+-- however the transformation may be applied to any bytestring.+data PKCS5 = PKCS5+    { encryptionAlgorithm :: EncryptionScheme -- ^ Scheme used to encrypt content+    , encryptedData       :: EncryptedContent -- ^ Encrypted content+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e PKCS5 where+    asn1s PKCS5{..} = asn1Container Sequence (alg . bs)+      where alg = asn1s encryptionAlgorithm+            bs  = gOctetString encryptedData++instance Monoid e => ParseASN1Object e PKCS5 where+    parse = onNextContainer Sequence $ do+        alg <- parse+        OctetString bs <- getNext+        return PKCS5 { encryptionAlgorithm = alg, encryptedData = bs }++instance ASN1Object PKCS5 where+    toASN1   = asn1s+    fromASN1 = runParseASN1State parse++-- | Encrypt a bytestring with the specified encryption scheme and password.+encrypt :: EncryptionScheme -> Password -> ByteString -> Either StoreError PKCS5+encrypt alg pwd bs = build <$> pbEncrypt alg bs pwd+  where+    build ed = ed `seq` PKCS5 { encryptionAlgorithm = alg, encryptedData = ed }++-- | Decrypt the PKCS #5 content with the specified password.+decrypt :: PKCS5 -> Password -> Either StoreError ByteString+decrypt obj = pbDecrypt (encryptionAlgorithm obj) (encryptedData obj)+++-- Encryption Schemes++-- | Encrypt a bytestring with the specified encryption scheme and password.+pbEncrypt :: EncryptionScheme -> ByteString -> Password+          -> Either StoreError EncryptedContent+pbEncrypt (PBES2 p)                 = pbes2  contentEncrypt p+pbEncrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentEncrypt MD5  DES p+pbEncrypt (PBE_SHA1_DES_CBC p)      = pkcs5  Left contentEncrypt SHA1 DES p+pbEncrypt (PBE_SHA1_RC4_128 p)      = pkcs12stream Left rc4Combine SHA1 16 p+pbEncrypt (PBE_SHA1_RC4_40 p)       = pkcs12stream Left rc4Combine SHA1 5 p+pbEncrypt (PBE_SHA1_DES_EDE3_CBC p) = pkcs12 Left contentEncrypt SHA1 DES_EDE3 p+pbEncrypt (PBE_SHA1_DES_EDE2_CBC p) = pkcs12 Left contentEncrypt SHA1 DES_EDE2 p+pbEncrypt (PBE_SHA1_RC2_128 p)      = pkcs12rc2 Left contentEncrypt SHA1 128 p+pbEncrypt (PBE_SHA1_RC2_40 p)       = pkcs12rc2 Left contentEncrypt SHA1 40 p++-- | Decrypt an encrypted bytestring with the specified encryption scheme and+-- password.+pbDecrypt :: EncryptionScheme -> EncryptedContent -> Password -> Either StoreError ByteString+pbDecrypt (PBES2 p)                 = pbes2  contentDecrypt p+pbDecrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentDecrypt MD5  DES p+pbDecrypt (PBE_SHA1_DES_CBC p)      = pkcs5  Left contentDecrypt SHA1 DES p+pbDecrypt (PBE_SHA1_RC4_128 p)      = pkcs12stream Left rc4Combine SHA1 16 p+pbDecrypt (PBE_SHA1_RC4_40 p)       = pkcs12stream Left rc4Combine SHA1 5 p+pbDecrypt (PBE_SHA1_DES_EDE3_CBC p) = pkcs12 Left contentDecrypt SHA1 DES_EDE3 p+pbDecrypt (PBE_SHA1_DES_EDE2_CBC p) = pkcs12 Left contentDecrypt SHA1 DES_EDE2 p+pbDecrypt (PBE_SHA1_RC2_128 p)      = pkcs12rc2 Left contentDecrypt SHA1 128 p+pbDecrypt (PBE_SHA1_RC2_40 p)       = pkcs12rc2 Left contentDecrypt SHA1 40 p++pbes2 :: ByteArrayAccess password+      => (Key -> ContentEncryptionParams -> ByteString -> result)+      -> PBES2Parameter -> ByteString -> password -> result+pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs+  where key = kdfDerive pbes2KDF len pwd :: Key+        len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)
+ src/Crypto/Store/PKCS5/PBES1.hs view
@@ -0,0 +1,352 @@+-- |+-- Module      : Data.Store.PKCS5.PBES1+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Password-Based Encryption Schemes+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+module Crypto.Store.PKCS5.PBES1+    ( PBEParameter(..)+    , Key+    , pkcs5+    , pkcs12+    , pkcs12rc2+    , pkcs12stream+    , pkcs12mac+    , rc4Combine+    ) where++import           Basement.Block (Block)+import           Basement.Compat.IsList+import           Basement.Endianness+import qualified Basement.String as S++import           Crypto.Cipher.Types+import qualified Crypto.Cipher.RC4 as RC4+import qualified Crypto.Hash as Hash++import           Data.ASN1.Types+import           Data.Bits+import           Data.ByteArray (ByteArray, ByteArrayAccess)+import qualified Data.ByteArray as B+import           Data.ByteString (ByteString)+import           Data.Maybe (fromMaybe)+import           Data.Memory.PtrMethods+import           Data.Word++import           Foreign.Ptr (plusPtr)+import           Foreign.Storable++import Crypto.Store.ASN1.Parse+import Crypto.Store.ASN1.Generate+import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Util+import Crypto.Store.Error++-- | Secret key.+type Key = B.ScrubbedBytes++-- | PBES1 parameters.+data PBEParameter = PBEParameter+    { pbeSalt           :: Salt -- ^ 8-octet salt value+    , pbeIterationCount :: Int  -- ^ Iteration count+    }+    deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e PBEParameter where+    asn1s PBEParameter{..} =+        let salt  = gOctetString pbeSalt+            iters = gIntVal (toInteger pbeIterationCount)+         in asn1Container Sequence (salt . iters)++instance Monoid e => ParseASN1Object e PBEParameter where+    parse = onNextContainer Sequence $ do+        OctetString salt <- getNext+        IntVal iters <- getNext+        return PBEParameter { pbeSalt = salt+                            , pbeIterationCount = fromInteger iters }++cbcWith :: (BlockCipher cipher, ByteArrayAccess iv)+        => ContentEncryptionCipher cipher -> iv -> ContentEncryptionParams+cbcWith cipher iv = ParamsCBC cipher getIV+  where+    getIV = fromMaybe (error "PKCS5: bad initialization vector") (makeIV iv)++rc2cbcWith :: ByteArrayAccess iv => Int -> iv -> ContentEncryptionParams+rc2cbcWith len iv = ParamsCBCRC2 len getIV+  where+    getIV = fromMaybe (error "PKCS5: bad RC2 initialization vector") (makeIV iv)++-- | RC4 encryption or decryption.+rc4Combine :: (ByteArrayAccess key, ByteArray ba) => key -> ba -> Either StoreError ba+rc4Combine key = Right . snd . RC4.combine (RC4.initialize key)++-- | Conversion to UCS2 from UTF-8, ignoring non-BMP bits.+toUCS2 :: (ByteArrayAccess butf8, ByteArray bucs2) => butf8 -> Maybe bucs2+toUCS2 pwdUTF8+    | B.null r  = Just pwdUCS2+    | otherwise = Nothing+  where+    (p, _, r) = S.fromBytes S.UTF8 $ B.snoc (B.convert pwdUTF8) 0+    pwdBlock  = fromList $ map ucs2 $ toList p :: Block (BE Word16)+    pwdUCS2   = B.convert pwdBlock++    ucs2 :: Char -> BE Word16+    ucs2 = toBE . toEnum . fromEnum+++-- PBES1, RFC 8018 section 6.1.2++-- | Apply PBKDF1 on the specified password and run an encryption or decryption+-- function on some input using derived key and IV.+pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)+      => (StoreError -> result)+      -> (Key -> ContentEncryptionParams -> ByteString -> result)+      -> DigestProxy hash+      -> ContentEncryptionCipher cipher+      -> PBEParameter+      -> ByteString+      -> password+      -> result+pkcs5 failure encdec hashAlg cec pbeParam bs pwd+    | proxyBlockSize cec /= 8 = failure (InvalidParameter "Invalid cipher block size")+    | otherwise =+        case pbkdf1 hashAlg pwd pbeParam 16 of+            Left err -> failure err+            Right dk ->+                let (key, iv) = B.splitAt 8 (dk :: Key)+                 in encdec key (cbcWith cec iv) bs+++-- PBKDF1, RFC 8018 section 5.1++pbkdf1 :: (Hash.HashAlgorithm hash, ByteArrayAccess password, ByteArray out)+       => DigestProxy hash+       -> password+       -> PBEParameter+       -> Int+       -> Either StoreError out+pbkdf1 hashAlg pwd PBEParameter{..} dkLen+    | dkLen > B.length t1 = Left (InvalidParameter "Derived key too long")+    | otherwise           = Right (B.convert $ B.takeView tc dkLen)+  where+    a  = hashFromProxy hashAlg+    t1 = Hash.hashFinalize (Hash.hashUpdate (Hash.hashUpdate (Hash.hashInitWith a) pwd) pbeSalt)+    tc = iterate (Hash.hashWith a) t1 !! pred pbeIterationCount+++-- PKCS#12 encryption, RFC 7292 appendix B.2++-- | Apply PKCS #12 derivation on the specified password and run an encryption+-- or decryption function on some input using derived key and IV.+pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)+       => (StoreError -> result)+       -> (Key -> ContentEncryptionParams -> ByteString -> result)+       -> DigestProxy hash+       -> ContentEncryptionCipher cipher+       -> PBEParameter+       -> ByteString+       -> password+       -> result+pkcs12 failure encdec hashAlg cec pbeParam bs pwdUTF8 =+    case toUCS2 pwdUTF8 of+        Nothing      -> failure passwordNotUTF8+        Just pwdUCS2 ->+            let ivLen   = proxyBlockSize cec+                iv      = pkcs12Derive hashAlg pbeParam 2 pwdUCS2 ivLen :: B.Bytes+                eScheme = cbcWith cec iv+                keyLen  = getMaximumKeySize eScheme+                key     = pkcs12Derive hashAlg pbeParam 1 pwdUCS2 keyLen :: Key+            in encdec key eScheme bs++-- | Apply PKCS #12 derivation on the specified password and run an encryption+-- or decryption function on some input using derived key and IV.  This variant+-- uses an RC2 cipher with the EKL specified (effective key length).+pkcs12rc2 :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+          => (StoreError -> result)+          -> (Key -> ContentEncryptionParams -> ByteString -> result)+          -> DigestProxy hash+          -> Int+          -> PBEParameter+          -> ByteString+          -> password+          -> result+pkcs12rc2 failure encdec hashAlg len pbeParam bs pwdUTF8 =+    case toUCS2 pwdUTF8 of+        Nothing      -> failure passwordNotUTF8+        Just pwdUCS2 ->+            let ivLen   = 8+                iv      = pkcs12Derive hashAlg pbeParam 2 pwdUCS2 ivLen :: B.Bytes+                eScheme = rc2cbcWith len iv+                keyLen  = getMaximumKeySize eScheme+                key     = pkcs12Derive hashAlg pbeParam 1 pwdUCS2 keyLen :: Key+            in encdec key eScheme bs++-- | Apply PKCS #12 derivation on the specified password and run an encryption+-- or decryption function on some input using derived key.  This variant does+-- not derive any IV and is required for RC4.+pkcs12stream :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+             => (StoreError -> result)+             -> (Key -> ByteString -> result)+             -> DigestProxy hash+             -> Int+             -> PBEParameter+             -> ByteString+             -> password+             -> result+pkcs12stream failure encdec hashAlg keyLen pbeParam bs pwdUTF8 =+    case toUCS2 pwdUTF8 of+        Nothing      -> failure passwordNotUTF8+        Just pwdUCS2 ->+            let key = pkcs12Derive hashAlg pbeParam 1 pwdUCS2 keyLen :: Key+             in encdec key bs++-- | Apply PKCS #12 derivation on the specified password and run a MAC function+-- on some input using derived key.+pkcs12mac :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+          => (StoreError -> result)+          -> (Key -> MACAlgorithm -> ByteString -> result)+          -> DigestProxy hash+          -> PBEParameter+          -> ByteString+          -> password+          -> result+pkcs12mac failure macFn hashAlg pbeParam bs pwdUTF8 =+    case toUCS2 pwdUTF8 of+        Nothing      -> failure passwordNotUTF8+        Just pwdUCS2 ->+            let macAlg = HMAC hashAlg+                keyLen = getMaximumKeySize macAlg+                key    = pkcs12Derive hashAlg pbeParam 3 pwdUCS2 keyLen :: Key+            in macFn key macAlg bs++passwordNotUTF8 :: StoreError+passwordNotUTF8 = InvalidPassword "Provided password is not valid UTF-8"++pkcs12Derive :: (Hash.HashAlgorithm hash, ByteArray bout)+             => DigestProxy hash+             -> PBEParameter+             -> Word8+             -> ByteString -- password (UCS2)+             -> Int+             -> bout+pkcs12Derive hashAlg PBEParameter{..} idByte pwdUCS2 n =+    B.take n $ B.concat $ take c $ loop t (s `B.append` p)+  where+    a = hashFromProxy hashAlg+    v = getV (DigestAlgorithm hashAlg)+    u = Hash.hashDigestSize a++    c = (n + u - 1) `div` u+    d = B.replicate v idByte :: B.Bytes+    t = Hash.hashUpdate (Hash.hashInitWith a) d++    p = pwdUCS2 `extendedToMult` v+    s = pbeSalt `extendedToMult` v++    loop :: Hash.HashAlgorithm hash+         => Hash.Context hash -> ByteString -> [Hash.Digest hash]+    loop x i = let z  = Hash.hashFinalize (Hash.hashUpdate x i)+                   ai = iterate Hash.hash z !! pred pbeIterationCount+                   b  = ai `extendedTo` v+                   j  = B.concat $ map (add1 b) (chunks v i)+                in ai : loop x j++getV :: DigestAlgorithm -> Int+getV (DigestAlgorithm MD2)    = 64+getV (DigestAlgorithm MD4)    = 64+getV (DigestAlgorithm MD5)    = 64+getV (DigestAlgorithm SHA1)   = 64+getV (DigestAlgorithm SHA224) = 64+getV (DigestAlgorithm SHA256) = 64+getV (DigestAlgorithm SHA384) = 128+getV (DigestAlgorithm SHA512) = 128++hashFromProxy :: proxy a -> a+hashFromProxy _ = undefined++-- Split in chunks of size 'n'+chunks :: ByteArray ba => Int -> ba -> [ba]+chunks n bs+    | len > n   = let (c, cs) = B.splitAt n bs in c : chunks n cs+    | len > 0   = [bs]+    | otherwise = []+  where+    len = B.length bs++-- Concatenate copies of input 'bs' to create output of length 'n'+-- bytes (the final copy may be truncated)+extendedTo :: (ByteArrayAccess bin, ByteArray bout) => bin -> Int -> bout+bs `extendedTo` n =+    B.allocAndFreeze n $ \pout ->+        B.withByteArray bs $ \pin -> do+            mapM_ (\off -> memCopy (pout `plusPtr` off) pin len)+                  (enumFromThenTo 0 len (n - 1))+            memCopy (pout `plusPtr` (n - r)) pin r+  where+    len = B.length bs+    r   = n `mod` len+{-# NOINLINE extendedTo #-}++-- Concatenate copies of input 'bs' to create output whose length is a+-- multiple of 'n' bytes (the final copy may be truncated).  If input+-- is the empty string, so is the output.+extendedToMult :: ByteArray ba => ba -> Int -> ba+bs `extendedToMult` n+    | len > n   = bs `B.append` B.take (n - len `mod` n) bs+    | len == n  = bs+    | len > 0   = bs `extendedTo` n+    | otherwise = B.empty+  where+    len = B.length bs++-- Add two bytearrays (considered as big-endian integers) and increment the+-- result.  Output has size of the first bytearray.+add1 :: ByteString -> ByteString -> ByteString+add1 a b =+    B.allocAndFreeze alen $ \pc ->+        B.withByteArray a $ \pa ->+        B.withByteArray b $ \pb ->+            loop3 pa pb pc alen blen 1+  where+    alen = B.length a+    blen = B.length b++    -- main loop when both 'a' and 'b' have remaining bytes+    loop3 !pa !pb !pc !ma !mb !c+        | ma == 0   = return ()+        | mb == 0   = loop2 pa pc ma c+        | otherwise = do+            let na = pred ma+                nb = pred mb+            ba <- peekElemOff pa na+            bb <- peekElemOff pb nb+            let (cc, bc) = carryAdd3 c ba bb+            pokeElemOff pc na bc+            loop3 pa pb pc na nb cc++    -- when 'b' is smaller and bytes are exhausted we propagate+    -- carry on 'a' alone+    loop2 !pa !pc !ma !c+        | ma == 0   = return ()+        | otherwise = do+            let na = pred ma+            ba <- peekElemOff pa na+            let (cc, bc) = carryAdd2 c ba+            pokeElemOff pc na bc+            loop2 pa pc na cc++split16 :: Word16 -> (Word8, Word8)+split16 x = (fromIntegral (shiftR x 8), fromIntegral x)++carryAdd2 :: Word8 -> Word8 -> (Word8, Word8)+carryAdd2 a b = split16 (fromIntegral a + fromIntegral b)++carryAdd3 :: Word8 -> Word8 -> Word8 -> (Word8, Word8)+carryAdd3 a b c = split16 (fromIntegral a + fromIntegral b + fromIntegral c)
+ src/Crypto/Store/PKCS8.hs view
@@ -0,0 +1,600 @@+-- |+-- Module      : Crypto.Store.PKCS8+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Private-Key Information Syntax, aka PKCS #8.+--+-- Presents an API similar to "Data.X509.Memory" and "Data.X509.File" but+-- allows to write private keys and provides support for password-based+-- encryption.+--+-- Functions to read a private key return an object wrapped in the+-- 'OptProtected' data type.+--+-- Functions related to public keys, certificates and CRLs are available from+-- "Crypto.Store.X509".+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE UndecidableInstances #-}+module Crypto.Store.PKCS8+    ( readKeyFile+    , readKeyFileFromMemory+    , pemToKey+    , writeKeyFile+    , writeKeyFileToMemory+    , keyToPEM+    , writeEncryptedKeyFile+    , writeEncryptedKeyFileToMemory+    , encryptKeyToPEM+    -- * Serialization formats+    , PrivateKeyFormat(..)+    , FormattedKey(..)+    -- * Password-based protection+    , Password+    , OptProtected(..)+    , recover+    , recoverA+    -- * Reading and writing PEM files+    , readPEMs+    , writePEMs+    ) where++import Control.Applicative+import Control.Monad (void, when)++import Data.ASN1.Types+import Data.ASN1.BinaryEncoding+import Data.ASN1.BitArray+import Data.ASN1.Encoding+import Data.Maybe+import qualified Data.X509 as X509+import qualified Data.ByteString as B+import           Crypto.Number.Serialize (i2osp, i2ospOf_, os2ip)+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.ECC.ECDSA as ECDSA+import qualified Crypto.PubKey.RSA as RSA++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Attribute+import Crypto.Store.CMS.Util+import Crypto.Store.Error+import Crypto.Store.PEM+import Crypto.Store.PKCS5+import Crypto.Store.PKCS8.EC+import Crypto.Store.Util++-- | Data type for objects that are possibly protected with a password.+data OptProtected a = Unprotected a+                      -- ^ Value is unprotected+                    | Protected (Password -> Either StoreError a)+                      -- ^ Value is protected with a password++instance Functor OptProtected where+    fmap f (Unprotected x) = Unprotected (f x)+    fmap f (Protected g)   = Protected (fmap f . g)++-- | Try to recover an 'OptProtected' content using the specified password.+recover :: Password -> OptProtected a -> Either StoreError a+recover _   (Unprotected x) = Right x+recover pwd (Protected f)   = f pwd++-- | Try to recover an 'OptProtected' content in an applicative context.  The+-- applicative password is used if necessary.+--+-- > import qualified Data.ByteString as B+-- > import           Crypto.Store.PKCS8+-- >+-- > [encryptedKey] <- readKeyFile "privkey.pem"+-- > let askForPassword = putStr "Please enter password: " >> B.getLine+-- > result <- recoverA askForPassword encryptedKey+-- > case result of+-- >     Left err  -> putStrLn $ "Unable to recover key: " ++ show err+-- >     Right key -> print key+recoverA :: Applicative f+         => f Password -> OptProtected a -> f (Either StoreError a)+recoverA _   (Unprotected x) = pure (Right x)+recoverA get (Protected f)   = fmap f get+++-- Reading from PEM format++-- | Read private keys from a PEM file.+readKeyFile :: FilePath -> IO [OptProtected X509.PrivKey]+readKeyFile path = accumulate <$> readPEMs path++-- | Read private keys from a bytearray in PEM format.+readKeyFileFromMemory :: B.ByteString -> [OptProtected X509.PrivKey]+readKeyFileFromMemory = either (const []) accumulate . pemParseBS++accumulate :: [PEM] -> [OptProtected X509.PrivKey]+accumulate = catMaybes . foldr (flip pemToKey) []++-- | Read a private key from a 'PEM' element and add it to the accumulator list.+pemToKey :: [Maybe (OptProtected X509.PrivKey)] -> PEM -> [Maybe (OptProtected X509.PrivKey)]+pemToKey acc pem =+    case decodeASN1' BER (pemContent pem) of+        Left _     -> acc+        Right asn1 -> run (getParser $ pemName pem) asn1 : acc++  where+    run p = either (const Nothing) Just . runParseASN1 p++    allTypes  = unFormat <$> parse+    rsa       = X509.PrivKeyRSA . unFormat <$> parse+    dsa       = X509.PrivKeyDSA . DSA.toPrivateKey . unFormat <$> parse+    ecdsa     = X509.PrivKeyEC . unFormat <$> parse+    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 "ENCRYPTED PRIVATE KEY" = Protected   <$> encrypted+    getParser _                       = empty++    inner decfn pwd = do+        decrypted <- decfn pwd+        asn1 <- mapLeft DecodingError $ decodeASN1' BER decrypted+        case run allTypes asn1 of+            Nothing -> Left (ParseFailure "No key parsed after decryption")+            Just k  -> return k+++-- Writing to PEM format++-- | Write unencrypted private keys to a PEM file.+writeKeyFile :: PrivateKeyFormat -> FilePath -> [X509.PrivKey] -> IO ()+writeKeyFile fmt path = writePEMs path . map (keyToPEM fmt)++-- | Write unencrypted private keys to a bytearray in PEM format.+writeKeyFileToMemory :: PrivateKeyFormat -> [X509.PrivKey] -> B.ByteString+writeKeyFileToMemory fmt = pemsWriteBS . map (keyToPEM fmt)++-- | Write a PKCS #8 encrypted private key to a PEM file.+--+-- If multiple keys need to be stored in the same file, use functions+-- 'encryptKeyToPEM' and 'writePEMs'.+--+-- Fresh 'EncryptionScheme' parameters should be generated for each key to+-- encrypt.+writeEncryptedKeyFile :: FilePath+                      -> EncryptionScheme -> Password-> X509.PrivKey+                      -> IO (Either StoreError ())+writeEncryptedKeyFile path alg pwd privKey =+    let pem = encryptKeyToPEM alg pwd privKey+     in either (return . Left) (fmap Right . writePEMs path . (:[])) pem++-- | Write a PKCS #8 encrypted private key to a bytearray in PEM format.+--+-- If multiple keys need to be stored in the same bytearray, use functions+-- 'encryptKeyToPEM' and 'pemWriteBS' or 'pemWriteLBS'.+--+-- Fresh 'EncryptionScheme' parameters should be generated for each key to+-- encrypt.+writeEncryptedKeyFileToMemory :: EncryptionScheme -> Password -> X509.PrivKey+                              -> Either StoreError B.ByteString+writeEncryptedKeyFileToMemory alg pwd privKey =+    pemWriteBS <$> encryptKeyToPEM alg pwd privKey++-- | Generate an unencrypted PEM for a private key.+keyToPEM :: PrivateKeyFormat -> X509.PrivKey -> PEM+keyToPEM TraditionalFormat = keyToTraditionalPEM+keyToPEM PKCS8Format       = keyToModernPEM++keyToTraditionalPEM :: X509.PrivKey -> PEM+keyToTraditionalPEM privKey =+    mkPEM (typeTag ++ " PRIVATE KEY") (encodeASN1S asn1)+  where (typeTag, asn1) = traditionalPrivKeyASN1S privKey++traditionalPrivKeyASN1S :: ASN1Elem e => X509.PrivKey -> (String, ASN1Stream e)+traditionalPrivKeyASN1S privKey =+    case privKey of+        X509.PrivKeyRSA k -> ("RSA", traditional k)+        X509.PrivKeyDSA k -> ("DSA", traditional (dsaPrivToPair k))+        X509.PrivKeyEC  k -> ("EC",  traditional k)+  where+    traditional a = asn1s (Traditional a)++keyToModernPEM :: X509.PrivKey -> PEM+keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)+  where asn1 = modernPrivKeyASN1S [] privKey++modernPrivKeyASN1S :: ASN1Elem e => [Attribute] -> X509.PrivKey -> ASN1Stream e+modernPrivKeyASN1S attrs privKey =+    case privKey of+        X509.PrivKeyRSA k -> modern k+        X509.PrivKeyDSA k -> modern (dsaPrivToPair k)+        X509.PrivKeyEC  k -> modern k+  where+    modern a = asn1s (Modern attrs a)++-- | Generate a PKCS #8 encrypted PEM for a private key.+--+-- Fresh 'EncryptionScheme' parameters should be generated for each key to+-- encrypt.+encryptKeyToPEM :: EncryptionScheme -> Password -> X509.PrivKey+                -> Either StoreError PEM+encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs+  where bs = pemContent (keyToModernPEM privKey)+        toPEM pkcs8 = mkPEM "ENCRYPTED PRIVATE KEY" (encodeASN1Object pkcs8)++mkPEM :: String -> B.ByteString -> PEM+mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}+++-- Private key formats: traditional (SSLeay compatible) and modern (PKCS #8)++-- | Private-key serialization format.+--+-- Encryption in traditional format is not supported currently.+data PrivateKeyFormat = TraditionalFormat -- ^ SSLeay compatible+                      | PKCS8Format       -- ^ PKCS #8+                      deriving (Show,Eq)++newtype Traditional a = Traditional { unTraditional :: a }++parseTraditional :: ParseASN1Object e (Traditional a) => ParseASN1 e a+parseTraditional = unTraditional <$> parse++data Modern a = Modern [Attribute] a++instance Functor Modern where+    fmap f (Modern attrs a) = Modern attrs (f a)++parseModern :: ParseASN1Object e (Modern a) => ParseASN1 e a+parseModern = unModern <$> parse+  where unModern (Modern _ a) = a++-- | A key associated with format.  Allows to implement 'ASN1Object' instances.+data FormattedKey a = FormattedKey PrivateKeyFormat a+    deriving (Show,Eq)++instance Functor FormattedKey where+    fmap f (FormattedKey fmt a) = FormattedKey fmt (f a)++instance (ProduceASN1Object e (Traditional a), ProduceASN1Object e (Modern a)) => ProduceASN1Object e (FormattedKey a) where+    asn1s (FormattedKey TraditionalFormat k) = asn1s (Traditional k)+    asn1s (FormattedKey PKCS8Format k)       = asn1s (Modern [] k)++instance (Monoid e, ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a)) => ParseASN1Object e (FormattedKey a) where+    parse = (traditional <$> parseTraditional) <|> (modern <$> parseModern)+      where+        traditional = FormattedKey TraditionalFormat+        modern      = FormattedKey PKCS8Format++unFormat :: FormattedKey a -> a+unFormat (FormattedKey _ a) = a+++-- Private Keys++instance ASN1Object (FormattedKey X509.PrivKey) where+    toASN1   = asn1s+    fromASN1 = runParseASN1State parse++instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKey) where+    asn1s (Traditional privKey) = snd $ traditionalPrivKeyASN1S privKey++instance Monoid e => ParseASN1Object e (Traditional X509.PrivKey) where+    parse = rsa <|> dsa <|> ecdsa+      where+        rsa   = Traditional . X509.PrivKeyRSA . unTraditional <$> parse+        dsa   = Traditional . X509.PrivKeyDSA . DSA.toPrivateKey . unTraditional <$> parse+        ecdsa = Traditional . X509.PrivKeyEC . unTraditional <$> parse++instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKey) where+    asn1s (Modern attrs privKey) = modernPrivKeyASN1S attrs privKey++instance Monoid e => ParseASN1Object e (Modern X509.PrivKey) where+    parse = rsa <|> dsa <|> ecdsa+      where+        rsa   = fmap X509.PrivKeyRSA  <$> parse+        dsa   = fmap (X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse+        ecdsa = fmap X509.PrivKeyEC <$> parse++skipVersion :: Monoid e => ParseASN1 e ()+skipVersion = do+    IntVal v <- getNext+    when (v /= 0 && v /= 1) $+        throwParseError ("PKCS8: parsed invalid version: " ++ show v)++skipPublicKey :: Monoid e => ParseASN1 e ()+skipPublicKey = void (fmap Just parseTaggedPrimitive <|> return Nothing)+  where parseTaggedPrimitive = do { Other _ 1 bs <- getNext; return bs }++parseAttrKeys :: Monoid e => ParseASN1 e ([Attribute], B.ByteString)+parseAttrKeys = do+    OctetString bs <- getNext+    attrs <- parseAttributes (Container Context 0)+    skipPublicKey+    return (attrs, bs)+++-- RSA++instance ASN1Object (FormattedKey RSA.PrivateKey) where+    toASN1   = asn1s+    fromASN1 = runParseASN1State parse++instance ASN1Elem e => ProduceASN1Object e (Traditional RSA.PrivateKey) where+    asn1s (Traditional privKey) =+        asn1Container Sequence (v . n . e . d . p1 . p2 . pexp1 . pexp2 . pcoef)+      where+        pubKey = RSA.private_pub privKey++        v     = gIntVal 0+        n     = gIntVal (RSA.public_n pubKey)+        e     = gIntVal (RSA.public_e pubKey)+        d     = gIntVal (RSA.private_d privKey)+        p1    = gIntVal (RSA.private_p privKey)+        p2    = gIntVal (RSA.private_q privKey)+        pexp1 = gIntVal (RSA.private_dP privKey)+        pexp2 = gIntVal (RSA.private_dQ privKey)+        pcoef = gIntVal (RSA.private_qinv privKey)++instance Monoid e => ParseASN1Object e (Traditional RSA.PrivateKey) where+    parse = onNextContainer Sequence $ do+        IntVal 0 <- getNext+        IntVal n <- getNext+        IntVal e <- getNext+        IntVal d <- getNext+        IntVal p1 <- getNext+        IntVal p2 <- getNext+        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+                                    , RSA.public_n    = n+                                    , RSA.public_e    = e+                                    }+            privKey = RSA.PrivateKey { RSA.private_pub  = pubKey+                                    , RSA.private_d    = d+                                    , RSA.private_p    = p1+                                    , RSA.private_q    = p2+                                    , RSA.private_dP   = pexp1+                                    , RSA.private_dQ   = pexp2+                                    , RSA.private_qinv = pcoef+                                    }+        return (Traditional privKey)++instance ASN1Elem e => ProduceASN1Object e (Modern RSA.PrivateKey) where+    asn1s (Modern attrs privKey) =+        asn1Container Sequence (v . alg . bs . att)+      where+        v     = gIntVal 0+        alg   = asn1Container Sequence (oid . gNull)+        oid   = gOID [1,2,840,113549,1,1,1]+        bs    = gOctetString (encodeASN1Object $ Traditional privKey)+        att   = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern RSA.PrivateKey) where+    parse = onNextContainer Sequence $ do+        skipVersion+        Null <- onNextContainer Sequence $ do+                    OID [1,2,840,113549,1,1,1] <- getNext+                    getNext+        (attrs, bs) <- parseAttrKeys+        let inner = decodeASN1' BER bs+            strError = Left .  ("PKCS8: error decoding inner RSA: " ++) . show+        case either strError (runParseASN1 parseTraditional) inner of+             Left err -> throwParseError ("PKCS8: error parsing inner RSA: " ++ err)+             Right privKey -> return (Modern attrs privKey)+++-- DSA++instance ASN1Object (FormattedKey DSA.KeyPair) where+    toASN1   = asn1s+    fromASN1 = runParseASN1State parse++instance ASN1Elem e => ProduceASN1Object e (Traditional DSA.KeyPair) where+    asn1s (Traditional (DSA.KeyPair params pub priv)) =+        asn1Container Sequence (v . pqgASN1S params . pub' . priv')+      where+        v     = gIntVal 0+        pub'  = gIntVal pub+        priv' = gIntVal priv++instance Monoid e => ParseASN1Object e (Traditional DSA.KeyPair) where+    parse = onNextContainer Sequence $ do+        IntVal 0 <- getNext+        params <- parsePQG+        IntVal pub <- getNext+        IntVal priv <- getNext+        return (Traditional $ DSA.KeyPair params pub priv)++instance ASN1Elem e => ProduceASN1Object e (Modern DSA.KeyPair) where+    asn1s (Modern attrs (DSA.KeyPair params _ priv)) =+        asn1Container Sequence (v . alg . bs . att)+      where+        v     = gIntVal 0+        alg   = asn1Container Sequence (oid . pr)+        oid   = gOID [1,2,840,10040,4,1]+        pr    = asn1Container Sequence (pqgASN1S params)+        bs    = gOctetString (encodeASN1S $ gIntVal priv)+        att   = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern DSA.KeyPair) where+    parse = onNextContainer Sequence $ do+        skipVersion+        params <- onNextContainer Sequence $ do+                      OID [1,2,840,10040,4,1] <- getNext+                      onNextContainer Sequence parsePQG+        (attrs, bs) <- parseAttrKeys+        case decodeASN1' BER bs of+            Right [IntVal priv] ->+                let pub = DSA.calculatePublic params priv+                 in return (Modern attrs $ DSA.KeyPair params pub priv)+            Right _ -> throwParseError "PKCS8: invalid format when parsing inner DSA"+            Left  e -> throwParseError ("PKCS8: error parsing inner DSA: " ++ show e)++pqgASN1S :: ASN1Elem e => DSA.Params -> ASN1Stream e+pqgASN1S params = p . q . g+  where p = gIntVal (DSA.params_p params)+        q = gIntVal (DSA.params_q params)+        g = gIntVal (DSA.params_g params)++parsePQG :: Monoid e => ParseASN1 e DSA.Params+parsePQG = do+    IntVal p <- getNext+    IntVal q <- getNext+    IntVal g <- getNext+    return DSA.Params { DSA.params_p = p+                      , DSA.params_q = q+                      , DSA.params_g = g+                      }++dsaPrivToPair :: DSA.PrivateKey -> DSA.KeyPair+dsaPrivToPair k = DSA.KeyPair params pub x+  where pub     = DSA.calculatePublic params x+        params  = DSA.private_params k+        x       = DSA.private_x k+++-- ECDSA++instance ASN1Object (FormattedKey X509.PrivKeyEC) where+    toASN1   = asn1s+    fromASN1 = runParseASN1State parse++instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKeyEC) where+    asn1s = innerEcdsaASN1S True . unTraditional++instance Monoid e => ParseASN1Object e (Traditional X509.PrivKeyEC) where+    parse = Traditional <$> parseInnerEcdsa Nothing++instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKeyEC) where+    asn1s (Modern attrs privKey) = asn1Container Sequence (v . f . bs . att)+      where+        v     = gIntVal 0+        f     = asn1Container Sequence (oid . curveFnASN1S privKey)+        oid   = gOID [1,2,840,10045,2,1]+        bs    = gOctetString (encodeASN1S inner)+        inner = innerEcdsaASN1S False privKey+        att   = attributesASN1S (Container Context 0) attrs++instance Monoid e => ParseASN1Object e (Modern X509.PrivKeyEC) where+    parse = onNextContainer Sequence $ do+        skipVersion+        f <- onNextContainer Sequence $ do+            OID [1,2,840,10045,2,1] <- getNext+            parseCurveFn+        (attrs, bs) <- parseAttrKeys+        let inner = decodeASN1' BER bs+            strError = Left .  ("PKCS8: error decoding inner EC: " ++) . show+        case either strError (runParseASN1 $ parseInnerEcdsa $ Just f) inner of+            Left err -> throwParseError ("PKCS8: error parsing inner EC: " ++ err)+            Right privKey -> return (Modern attrs privKey)++innerEcdsaASN1S :: ASN1Elem e => Bool -> X509.PrivKeyEC -> ASN1Stream e+innerEcdsaASN1S addC k+    | addC      = asn1Container Sequence (v . ds . c0 . c1)+    | otherwise = asn1Container Sequence (v . ds . c1)+  where+    curve = fromMaybe (error "PKCS8: invalid EC parameters") (ecPrivKeyCurve k)+    bytes = curveOrderBytes curve++    v  = gIntVal 1+    ds = gOctetString (i2ospOf_ bytes (X509.privkeyEC_priv k))+    c0 = asn1Container (Container Context 0) (curveFnASN1S k)+    c1 = asn1Container (Container Context 1) pub++    pub = gBitString (toBitArray sp 0)+    X509.SerializedPoint sp = getSerializedPoint curve (X509.privkeyEC_priv k)++parseInnerEcdsa :: Monoid e+                => Maybe (ECDSA.PrivateNumber -> X509.PrivKeyEC)+                -> ParseASN1 e X509.PrivKeyEC+parseInnerEcdsa fn = onNextContainer Sequence $ do+    IntVal 1 <- getNext+    OctetString ds <- getNext+    let d = os2ip ds+    m <- onNextContainerMaybe (Container Context 0) parseCurveFn+    _ <- onNextContainerMaybe (Container Context 1) parsePK+    case fn <|> m of+        Nothing     -> throwParseError "PKCS8: no curve found in EC private key"+        Just getKey -> return (getKey d)+  where+    parsePK = do { BitString bs <- getNext; return bs }++curveFnASN1S :: ASN1Elem e => X509.PrivKeyEC -> ASN1Stream e+curveFnASN1S X509.PrivKeyEC_Named{..} = gOID (curveNameOID privkeyEC_name)+curveFnASN1S X509.PrivKeyEC_Prime{..} =+    asn1Container Sequence (v . prime . abSeed . gen . o . c)+  where+    X509.SerializedPoint generator = privkeyEC_generator+    bytes  = numBytes privkeyEC_prime++    v      = gIntVal 1++    prime  = asn1Container Sequence (oid . p)+    oid    = gOID [1,2,840,10045,1,1]+    p      = gIntVal privkeyEC_prime++    abSeed = asn1Container Sequence (a . b . seed)+    a      = gOctetString (i2ospOf_ bytes privkeyEC_a)+    b      = gOctetString (i2ospOf_ bytes privkeyEC_b)+    seed   = if privkeyEC_seed > 0+                 then gBitString (toBitArray (i2osp privkeyEC_seed) 0)+                 else id++    gen    = gOctetString generator+    o      = gIntVal privkeyEC_order+    c      = gIntVal privkeyEC_cofactor++parseCurveFn :: Monoid e => ParseASN1 e (ECDSA.PrivateNumber -> X509.PrivKeyEC)+parseCurveFn = parseNamedCurve <|> parsePrimeCurve+  where+    parseNamedCurve = do+        OID oid <- getNext+        case lookupCurveNameByOID oid of+            Just name -> return $ \d ->+                            X509.PrivKeyEC_Named+                                { X509.privkeyEC_name = name+                                , X509.privkeyEC_priv = d+                                }+            Nothing -> throwParseError ("PKCS8: unknown EC curve with OID " ++ show oid)++    parsePrimeCurve =+        onNextContainer Sequence $ do+            IntVal 1 <- getNext+            prime <- onNextContainer Sequence $ do+                OID [1,2,840,10045,1,1] <- getNext+                IntVal prime <- getNext+                return prime+            (a, b, seed) <- onNextContainer Sequence $ do+                OctetString a <- getNext+                OctetString b <- getNext+                seed <- parseOptionalSeed+                return (a, b, seed)+            OctetString generator <- getNext+            IntVal order <- getNext+            IntVal cofactor <- getNext+            return $ \d ->+                X509.PrivKeyEC_Prime+                    { X509.privkeyEC_priv      = d+                    , X509.privkeyEC_a         = os2ip a+                    , X509.privkeyEC_b         = os2ip b+                    , X509.privkeyEC_prime     = prime+                    , X509.privkeyEC_generator = X509.SerializedPoint generator+                    , X509.privkeyEC_order     = order+                    , X509.privkeyEC_cofactor  = cofactor+                    , X509.privkeyEC_seed      = seed+                    }++    parseOptionalSeed = do+        seedAvail <- hasNext+        if seedAvail+            then do BitString seed <- getNext+                    return (os2ip $ bitArrayGetData seed)+            else return 0
+ src/Crypto/Store/PKCS8/EC.hs view
@@ -0,0 +1,99 @@+-- |+-- Module      : Crypto.Store.PKCS8.EC+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Additional EC utilities.+module Crypto.Store.PKCS8.EC+    ( numBytes+    , curveSizeBytes+    , curveOrderBytes+    , curveNameOID+    , getSerializedPoint+    , module Data.X509.EC+    ) where++import           Data.ASN1.OID+import qualified Data.ByteString as B+import           Data.Maybe (fromMaybe)++import Data.X509+import Data.X509.EC++import Crypto.Number.Basic (numBits)+import Crypto.Number.Serialize (i2ospOf_)+import Crypto.PubKey.ECC.Prim+import Crypto.PubKey.ECC.Types++import Crypto.Store.CMS.Util++-- | 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+curveSizeBytes = bitsToBytes . curveSizeBits++-- | Number of bytes to serialize a scalar.+curveOrderBytes :: Curve -> Int+curveOrderBytes = bitsToBytes . numBits . ecc_n . common_curve++-- | Transform a private scalar to a point in uncompressed format.+getSerializedPoint :: Curve -> PrivateNumber -> SerializedPoint+getSerializedPoint curve d = SerializedPoint (serializePoint pt)+  where+    pt = pointBaseMul curve d+    bs = i2ospOf_ (curveSizeBytes curve)++    serializePoint PointO      = B.singleton 0+    serializePoint (Point x y) = B.cons 4 (B.append (bs x) (bs y))++-- | Return the OID associated to a curve name.+curveNameOID :: CurveName -> OID+curveNameOID name =+    fromMaybe (error $ "PKCS8: OID unknown for EC curve " ++ show name)+        (lookupOID curvesOIDTable name)++curvesOIDTable :: OIDTable CurveName+curvesOIDTable =+    [ (SEC_p112r1, [1,3,132,0,6])+    , (SEC_p112r2, [1,3,132,0,7])+    , (SEC_p128r1, [1,3,132,0,28])+    , (SEC_p128r2, [1,3,132,0,29])+    , (SEC_p160k1, [1,3,132,0,9])+    , (SEC_p160r1, [1,3,132,0,8])+    , (SEC_p160r2, [1,3,132,0,30])+    , (SEC_p192k1, [1,3,132,0,31])+    , (SEC_p192r1, [1,2,840,10045,3,1,1])+    , (SEC_p224k1, [1,3,132,0,32])+    , (SEC_p224r1, [1,3,132,0,33])+    , (SEC_p256k1, [1,3,132,0,10])+    , (SEC_p256r1, [1,2,840,10045,3,1,7])+    , (SEC_p384r1, [1,3,132,0,34])+    , (SEC_p521r1, [1,3,132,0,35])+    , (SEC_t113r1, [1,3,132,0,4])+    , (SEC_t113r2, [1,3,132,0,5])+    , (SEC_t131r1, [1,3,132,0,22])+    , (SEC_t131r2, [1,3,132,0,23])+    , (SEC_t163k1, [1,3,132,0,1])+    , (SEC_t163r1, [1,3,132,0,2])+    , (SEC_t163r2, [1,3,132,0,15])+    , (SEC_t193r1, [1,3,132,0,24])+    , (SEC_t193r2, [1,3,132,0,25])+    , (SEC_t233k1, [1,3,132,0,26])+    , (SEC_t233r1, [1,3,132,0,27])+    , (SEC_t239k1, [1,3,132,0,3])+    , (SEC_t283k1, [1,3,132,0,16])+    , (SEC_t283r1, [1,3,132,0,17])+    , (SEC_t409k1, [1,3,132,0,36])+    , (SEC_t409r1, [1,3,132,0,37])+    , (SEC_t571k1, [1,3,132,0,38])+    , (SEC_t571r1, [1,3,132,0,39])+    ]
+ src/Crypto/Store/Util.hs view
@@ -0,0 +1,45 @@+-- |+-- Module      : Crypto.Store.Util+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+--+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE MagicHash #-}+module Crypto.Store.Util+    ( (&&!)+    , reverseBytes+    , constAllEq+    , mapLeft+    ) where++import           Data.Bits+import           Data.ByteArray (ByteArray, ByteArrayAccess)+import qualified Data.ByteArray as B+import           Data.List+import           Data.Word++import GHC.Exts++-- | This is a strict version of &&.+(&&!) :: Bool -> Bool -> Bool+(&&!) x y = isTrue# (andI# (getTag# x) (getTag# y))+  where getTag# !z = dataToTag# z+infixr 3 &&!++-- | Reverse a bytearray.+reverseBytes :: (ByteArrayAccess bin, ByteArray bout) => bin -> bout+reverseBytes = B.pack . reverse . B.unpack++-- | Test if all bytes in a bytearray are equal to the value specified.  Runs in+-- constant time.+constAllEq :: ByteArrayAccess ba => Word8 -> ba -> Bool+constAllEq b = (== 0) . foldl' fn 0 . B.unpack+  where fn acc x = acc .|. xor b x++-- | Map over the left value.+mapLeft :: (a -> b) -> Either a c -> Either b c+mapLeft f (Left a)  = Left (f a)+mapLeft _ (Right c) = Right c
+ src/Crypto/Store/X509.hs view
@@ -0,0 +1,178 @@+-- |+-- Module      : Crypto.Store.X509+-- License     : BSD-style+-- Maintainer  : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability   : experimental+-- Portability : unknown+--+-- Public keys, certificates and CRLs.+--+-- Presents an API similar to "Data.X509.Memory" and "Data.X509.File" but+-- provides support for public-key files and allows to write objects.+--+-- Functions related to private keys are available from "Crypto.Store.PKCS8".+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+module Crypto.Store.X509+    ( SignedObject()+    -- * Public keys+    , readPubKeyFile+    , readPubKeyFileFromMemory+    , pemToPubKey+    , writePubKeyFile+    , writePubKeyFileToMemory+    , pubKeyToPEM+    -- * Signed objects+    , readSignedObject+    , readSignedObjectFromMemory+    , writeSignedObject+    , writeSignedObjectToMemory+    -- * Reading and writing PEM files+    , readPEMs+    , writePEMs+    ) where++import Data.ASN1.Types+import Data.ASN1.BinaryEncoding+import Data.ASN1.Encoding+import Data.Maybe+import Data.Proxy+import qualified Data.X509 as X509+import qualified Data.ByteString as B+import qualified Crypto.PubKey.RSA as RSA++import Crypto.Store.ASN1.Generate+import Crypto.Store.ASN1.Parse+import Crypto.Store.CMS.Util+import Crypto.Store.PEM+++-- | Class of signed objects convertible to PEM.+class (ASN1Object a, Eq a, Show a) => SignedObject a where+    signedObjectName :: proxy a -> String+    otherObjectNames :: proxy a -> [String]++instance SignedObject X509.Certificate where+    signedObjectName _ = "CERTIFICATE"+    otherObjectNames _ = ["X509 CERTIFICATE"]++instance SignedObject X509.CRL where+    signedObjectName _ = "X509 CRL"+    otherObjectNames _ = []++validObjectName :: SignedObject a => proxy a -> String -> Bool+validObjectName prx name =+    name == signedObjectName prx || name `elem` otherObjectNames prx+++-- Reading from PEM format++-- | Read public keys from a PEM file.+readPubKeyFile :: FilePath -> IO [X509.PubKey]+readPubKeyFile path = accumulate <$> readPEMs path++-- | Read public keys from a bytearray in PEM format.+readPubKeyFileFromMemory :: B.ByteString -> [X509.PubKey]+readPubKeyFileFromMemory = either (const []) accumulate . pemParseBS++accumulate :: [PEM] -> [X509.PubKey]+accumulate = catMaybes . foldr (flip pemToPubKey) []++-- | Read a public key from a 'PEM' element and add it to the accumulator list.+pemToPubKey :: [Maybe X509.PubKey] -> PEM -> [Maybe X509.PubKey]+pemToPubKey acc pem =+    case decodeASN1' BER (pemContent pem) of+        Left _     -> acc+        Right asn1 -> run (getParser $ pemName pem) asn1 : acc++  where+    run p asn1 =+        case p asn1 of+            Right (pubKey, []) -> Just pubKey+            _                  -> Nothing++    getParser "PUBLIC KEY"           = fromASN1+    getParser "RSA PUBLIC KEY"       = runParseASN1State rsapkParser+    getParser _                      = const (Left undefined)++    rsapkParser = (\(RSAPublicKey pub) -> X509.PubKeyRSA pub) <$> parse++-- | Read signed objects from a PEM file (only one type at a time).+readSignedObject :: SignedObject a => FilePath -> IO [X509.SignedExact a]+readSignedObject path = accumulate' <$> readPEMs path++-- | Read signed objects from a bytearray in PEM format (only one type at a+-- time).+readSignedObjectFromMemory :: SignedObject a+                           => B.ByteString+                           -> [X509.SignedExact a]+readSignedObjectFromMemory = either (const []) accumulate' . pemParseBS++accumulate' :: forall a. SignedObject a => [PEM] -> [X509.SignedExact a]+accumulate' = foldr pemToSigned []+  where+    prx = Proxy :: Proxy a++    pemToSigned pem acc+        | validObjectName prx (pemName pem) =+            case X509.decodeSignedObject $ pemContent pem of+                Left _    -> acc+                Right obj -> obj : acc+        | otherwise = acc+++-- Writing to PEM format++-- | Write public keys to a PEM file.+writePubKeyFile :: FilePath -> [X509.PubKey] -> IO ()+writePubKeyFile path = writePEMs path . map pubKeyToPEM++-- | Write public keys to a bytearray in PEM format.+writePubKeyFileToMemory :: [X509.PubKey] -> B.ByteString+writePubKeyFileToMemory = pemsWriteBS . map pubKeyToPEM++-- | Generate a PEM for a public key.+pubKeyToPEM :: X509.PubKey -> PEM+pubKeyToPEM pubKey = mkPEM "PUBLIC KEY" (encodeASN1S $ gMany asn1)+  where asn1 = toASN1 pubKey []++-- | Write signed objects to a PEM file.+writeSignedObject :: SignedObject a => FilePath -> [X509.SignedExact a] -> IO ()+writeSignedObject path = writePEMs path . map signedToPEM++-- | Write signed objects to a bytearray in PEM format.+writeSignedObjectToMemory :: SignedObject a => [X509.SignedExact a] -> B.ByteString+writeSignedObjectToMemory = pemsWriteBS . map signedToPEM++signedToPEM :: forall a. SignedObject a => X509.SignedExact a -> PEM+signedToPEM obj = mkPEM (signedObjectName prx) (X509.encodeSignedObject obj)+  where prx = Proxy :: Proxy a++mkPEM :: String -> B.ByteString -> PEM+mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}+++-- RSA public keys++newtype RSAPublicKey = RSAPublicKey RSA.PublicKey++instance ASN1Elem e => ProduceASN1Object e RSAPublicKey where+    asn1s (RSAPublicKey pub) = asn1Container Sequence (n . e)+      where+        n = gIntVal (RSA.public_n pub)+        e = gIntVal (RSA.public_e pub)++instance Monoid e => ParseASN1Object e RSAPublicKey where+    parse = onNextContainer Sequence $ do+        IntVal modulus <- getNext+        IntVal pubexp <- getNext+        let pub = RSA.PublicKey { RSA.public_size = calculate_modulus modulus 1+                                , 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)
+ tests/CMS/Instances.hs view
@@ -0,0 +1,468 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Orphan instances.+module CMS.Instances+    ( arbitraryPassword+    , arbitraryAttributes+    , arbitraryIntegrityDigest+    , arbitrarySigVer+    , arbitraryEnvDev+    ) where++import           Data.ASN1.Types+import qualified Data.ByteArray as B+import           Data.ByteString (ByteString)+import           Data.X509++import Test.Tasty.QuickCheck++import Crypto.Cipher.Types++import Crypto.Store.CMS+import Crypto.Store.Error++import X509.Instances++arbitrarySmall :: Gen ByteString+arbitrarySmall = resize 10 (B.pack <$> arbitrary)++arbitraryPassword :: Gen ByteString+arbitraryPassword = resize 16 (B.pack <$> asciiChar)+  where asciiChar = listOf $ choose (0x20,0x7f)++instance Arbitrary ContentInfo where+    arbitrary = sized $ \n ->+        if n == 0+            then DataCI <$> arbitraryMessage+            else oneof [ DataCI <$> arbitraryMessage+                       , arbitrarySignedData+                       , arbitraryEnvelopedData+                       , arbitraryDigestedData+                       , arbitraryEncryptedData+                       , arbitraryAuthenticatedData+                       , arbitraryAuthEnvelopedData+                       ]+      where+        arbitraryMessage :: Gen ByteString+        arbitraryMessage = resize 2048 (B.pack <$> arbitrary)++        arbitrarySignedData :: Gen ContentInfo+        arbitrarySignedData = do+            alg   <- arbitrary+            (sigFns, _) <- arbitrarySigVer alg+            inner <- scale (subtract $ length sigFns) arbitrary+            signData sigFns inner >>= failIfError++        arbitraryEnvelopedData :: Gen ContentInfo+        arbitraryEnvelopedData = do+            oinfo <- arbitrary+            (alg, key, attrs) <- getCommon+            (envFns, _) <- arbitraryEnvDev key+            inner <- scale (subtract $ length envFns) arbitrary+            envelopData oinfo key alg envFns attrs inner >>= failIfError++        arbitraryDigestedData :: Gen ContentInfo+        arbitraryDigestedData = do+            inner <- scale pred arbitrary+            dt <- arbitrary+            return $ digestData dt inner++        arbitraryEncryptedData :: Gen ContentInfo+        arbitraryEncryptedData = do+            (alg, key, attrs) <- getCommon+            inner <- scale pred arbitrary+            failIfError $ encryptData key alg attrs inner++        arbitraryAuthenticatedData :: Gen ContentInfo+        arbitraryAuthenticatedData = do+            (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth+            dig <- arbitrary+            inner <- scale (subtract $ length envFns) arbitrary+            generateAuthenticatedData oinfo key alg dig envFns aAttrs uAttrs inner+                >>= failIfError++        arbitraryAuthEnvelopedData :: Gen ContentInfo+        arbitraryAuthEnvelopedData = do+            (oinfo, alg, key, envFns, aAttrs, uAttrs) <- getCommonAuth+            inner <- scale (subtract $ length envFns) arbitrary+            authEnvelopData oinfo key alg envFns aAttrs uAttrs inner+                >>= failIfError++        getCommonAuth :: (HasKeySize params, Arbitrary params)+                  => Gen ( OriginatorInfo, params, ContentEncryptionKey+                         , [ProducerOfRI Gen], [Attribute], [Attribute]+                         )+        getCommonAuth = do+            oinfo <- arbitrary+            (alg, key, uAttrs) <- getCommon+            aAttrs <- arbitraryAttributes+            (envFns, _) <- arbitraryEnvDev key+            return (oinfo, alg, key, envFns, aAttrs, uAttrs)++        getCommon :: (HasKeySize params, Arbitrary params, B.ByteArray key)+                  => Gen (params, key, [Attribute])+        getCommon = do+            alg   <- arbitrary+            key   <- generateKey alg+            attrs <- arbitraryAttributes+            return (alg, key, attrs)++        failIfError :: Either StoreError a -> Gen a+        failIfError = either (fail . show) return++instance Arbitrary Attribute where+    arbitrary = do+        oid  <- arbitraryOID+        vals <- resize 3 $ listOf1 (OctetString <$> arbitrarySmall)+        return Attribute { attrType = oid, attrValues = vals }++arbitraryAttributes :: Gen [Attribute]+arbitraryAttributes = resize 3 arbitrary++instance Arbitrary DigestAlgorithm where+    arbitrary = elements+        [ DigestAlgorithm MD2+        , DigestAlgorithm MD4+        , DigestAlgorithm MD5+        , DigestAlgorithm SHA1+        , DigestAlgorithm SHA224+        , DigestAlgorithm SHA256+        , DigestAlgorithm SHA384+        , DigestAlgorithm SHA512+        ]++arbitraryIntegrityDigest :: Gen DigestAlgorithm+arbitraryIntegrityDigest = elements+    [ DigestAlgorithm MD5+    , DigestAlgorithm SHA1+    , DigestAlgorithm SHA224+    , DigestAlgorithm SHA256+    , DigestAlgorithm SHA384+    , DigestAlgorithm SHA512+    ]++instance Arbitrary MACAlgorithm where+    arbitrary = (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest++instance Arbitrary OAEPParams where+    arbitrary = do+        alg <- arbitrary+        mga <- MGF1 <$> arbitrary+        return OAEPParams { oaepHashAlgorithm = alg+                          , oaepMaskGenAlgorithm = mga+                          }++instance Arbitrary PSSParams where+    arbitrary = do+        alg <- arbitrary+        mga <- MGF1 <$> arbitrary+        len <- choose (1, 30)+        return PSSParams { pssHashAlgorithm = alg+                         , pssMaskGenAlgorithm = mga+                         , pssSaltLength = len+                         }++instance Arbitrary SignatureAlg where+    arbitrary = oneof+        [ pure RSAAnyHash++        , pure $ RSA (DigestAlgorithm MD2)+        , pure $ RSA (DigestAlgorithm MD5)+        , pure $ RSA (DigestAlgorithm SHA1)+        , pure $ RSA (DigestAlgorithm SHA224)+        , pure $ RSA (DigestAlgorithm SHA256)+        , pure $ RSA (DigestAlgorithm SHA384)+        , pure $ RSA (DigestAlgorithm SHA512)++        , RSAPSS <$> arbitrary++        , pure $ DSA (DigestAlgorithm SHA1)+        , pure $ DSA (DigestAlgorithm SHA224)+        , pure $ DSA (DigestAlgorithm SHA256)++        , pure $ ECDSA (DigestAlgorithm SHA1)+        , pure $ ECDSA (DigestAlgorithm SHA224)+        , pure $ ECDSA (DigestAlgorithm SHA256)+        , pure $ ECDSA (DigestAlgorithm SHA384)+        , pure $ ECDSA (DigestAlgorithm SHA512)+        ]++arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)+arbitraryKeyPair RSAAnyHash = do+    (pub, priv) <- arbitraryRSA+    return (PubKeyRSA pub, PrivKeyRSA priv)+arbitraryKeyPair (RSA _) = do+    (pub, priv) <- arbitraryRSA+    return (PubKeyRSA pub, PrivKeyRSA priv)+arbitraryKeyPair (RSAPSS _) = do+    (pub, priv) <- arbitraryRSA+    return (PubKeyRSA pub, PrivKeyRSA priv)+arbitraryKeyPair (DSA _) = do+    (pub, priv) <- arbitraryDSA+    return (PubKeyDSA pub, PrivKeyDSA priv)+arbitraryKeyPair (ECDSA _) = do+    (pub, priv) <- arbitraryNamedEC+    return (PubKeyEC pub, PrivKeyEC priv)++arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen)+arbitrarySigVer alg = sized $ \n -> do+    (sigFn, verFn) <- onePair+    otherPairs <- resize (min (pred n) 3) $ listOf onePair+    sigFns <- shuffle (sigFn : map fst otherPairs)+    return (sigFns, verFn)+  where+    onePair = do+        (pub, priv) <- arbitraryKeyPair alg+        chain <- arbitraryCertificateChain pub+        sAttrs <- oneof [ pure Nothing, Just <$> arbitraryAttributes ]+        uAttrs <- arbitraryAttributes+        return (certSigner alg priv chain sAttrs uAttrs, withPublicKey pub)++instance Arbitrary PBKDF2_PRF where+    arbitrary = elements+        [ PBKDF2_SHA1+        , PBKDF2_SHA256+        , PBKDF2_SHA512+        ]++instance Arbitrary ContentEncryptionAlg where+    arbitrary = elements+        [ CBC DES+        , CBC DES_EDE3+        , CBC AES128+        , CBC AES192+        , CBC AES256+        , CBC CAST5+        , CBC Camellia128+        , CBC_RC2++        , ECB DES+        , ECB AES128+        , ECB AES192+        , ECB AES256+        , ECB Camellia128++        , CFB DES+        , CFB AES128+        , CFB AES192+        , CFB AES256+        , CFB Camellia128++        , CTR Camellia128+        ]++instance Arbitrary ContentEncryptionParams where+    arbitrary = arbitrary >>= gen+      where+        gen CBC_RC2 = choose (24, 512) >>= generateRC2EncryptionParams+        gen alg     = generateEncryptionParams alg++instance Arbitrary AuthContentEncryptionAlg where+    arbitrary = elements+        [ AUTH_ENC_128+        , AUTH_ENC_256+        , CHACHA20_POLY1305++        , CCM AES128+        , CCM AES192+        , CCM AES256++        , GCM AES128+        , GCM AES192+        , GCM AES256+        ]++instance Arbitrary AuthContentEncryptionParams where+    arbitrary = do+        alg <- arbitrary+        case alg of+            AUTH_ENC_128 -> arb3 generateAuthEnc128Params+            AUTH_ENC_256 -> arb3 generateAuthEnc256Params+            CHACHA20_POLY1305 -> generateChaChaPoly1305Params+            CCM c -> do m <- arbitraryM+                        l <- arbitraryL+                        generateCCMParams c m l+            GCM c -> choose (12,16) >>= generateGCMParams c+      where arb3 fn = do+                a <- arbitrary; b <- arbitrary; c <- arbitrary+                fn a b c++arbitraryM :: Gen CCM_M+arbitraryM = elements+    [ CCM_M4+    , CCM_M6+    , CCM_M8+    , CCM_M10+    , CCM_M12+    , CCM_M14+    , CCM_M16+    ]++arbitraryL :: Gen CCM_L+arbitraryL = elements [ CCM_L2, CCM_L3, CCM_L4 ]++instance Arbitrary KeyDerivationFunc where+    arbitrary = do+        salt <- generateSalt 8+        oneof [ pbkdf2 salt , scrypt salt ]+      where+        pbkdf2 salt = do+            iters <- choose (1,512)+            pf <- arbitrary+            return PBKDF2 { pbkdf2Salt           = salt+                          , pbkdf2IterationCount = iters+                          , pbkdf2KeyLength      = Nothing+                          , pbkdf2Prf            = pf+                          }+        scrypt salt = do+            (n, r, p) <- elements [ (16, 1, 1) , (1024, 8, 16) ]+            return Scrypt { scryptSalt      = salt+                          , scryptN         = n+                          , scryptR         = r+                          , scryptP         = p+                          , scryptKeyLength = Nothing+                          }++instance Arbitrary KeyTransportParams where+    arbitrary = oneof+        [ pure RSAES+        , RSAESOAEP <$> arbitrary+        ]++instance Arbitrary KeyEncryptionParams where+    arbitrary = oneof+        [ PWRIKEK <$> arbitrary+        , return AES128_WRAP+        , return AES192_WRAP+        , return AES256_WRAP+        , return AES128_WRAP_PAD+        , return AES192_WRAP_PAD+        , return AES256_WRAP_PAD+        , return DES_EDE3_WRAP+        , RC2_WRAP <$> choose (1, 1024)+        ]++instance Arbitrary OtherKeyAttribute where+    arbitrary = do+        oid <- arbitraryOID+        vals <- resize 3 $ listOf1 (OctetString <$> arbitrarySmall)+        return OtherKeyAttribute { keyAttrId = oid, keyAttr = vals }++instance Arbitrary KeyIdentifier where+    arbitrary = do+        kid <- arbitrarySmall+        KeyIdentifier kid Nothing <$> arbitrary++arbitraryAgreeParams :: KeyEncryptionParams -> Gen KeyAgreementParams+arbitraryAgreeParams alg = oneof+    [ flip StdDH alg <$> arbitraryDigest+    , flip CofactorDH alg <$> arbitraryDigest+    ]+  where+    arbitraryDigest =+        elements+            [ DigestAlgorithm SHA1+            , DigestAlgorithm SHA224+            , DigestAlgorithm SHA256+            , DigestAlgorithm SHA384+            , DigestAlgorithm SHA512+            ]++arbitraryEnvDev :: ContentEncryptionKey+                -> Gen ([ProducerOfRI Gen], ConsumerOfRI Gen)+arbitraryEnvDev cek = sized $ \n -> do+    (envFn, devFn) <- onePair+    otherPairs <- resize (min (pred n) 3) $ listOf onePair+    envFns <- shuffle (envFn : map fst otherPairs)+    return (envFns, devFn)+  where+    len     = B.length cek+    onePair = oneof [ arbitraryKT, arbitraryKA, arbitraryKEK, arbitraryPW ]++    arbitraryKT = do+        (pub, priv) <- arbitraryLargeRSA+        cert <- arbitrarySignedCertificate (PubKeyRSA pub)+        ktp  <- arbitrary+        let envFn = forKeyTransRecipient cert ktp+            devFn = withRecipientKeyTrans (PrivKeyRSA priv)+        return (envFn, devFn)++    arbitraryKA = do+        (pub, priv) <- arbitraryNamedEC+        cert <- arbitrarySignedCertificate (PubKeyEC pub)+        kap  <- arbitraryAlg >>= arbitraryAgreeParams+        let envFn = forKeyAgreeRecipient cert kap+            devFn = withRecipientKeyAgree (PrivKeyEC priv) cert+        return (envFn, devFn)++    arbitraryKEK = do+        kid <- arbitrary+        es  <- arbitraryAlg+        key <- generateKey es+        return (forKeyRecipient key kid es, withRecipientKey key)++    arbitraryPW  = do+        pwd <- arbitraryPassword+        kdf <- arbitrary+        cea <- arbitrary `suchThat` notModeCTR+        let es = PWRIKEK cea+        return (forPasswordRecipient pwd kdf es, withRecipientPassword pwd)++    arbitraryAlg+        | len == 24      = oneof [ return AES128_WRAP+                                 , return AES192_WRAP+                                 , return AES256_WRAP+                                 , return AES128_WRAP_PAD+                                 , return AES192_WRAP_PAD+                                 , return AES256_WRAP_PAD+                                 , return DES_EDE3_WRAP+                                 , RC2_WRAP <$> choose (1, 1024)+                                 ]+        | mod len 8 == 0 = oneof [ return AES128_WRAP+                                 , return AES192_WRAP+                                 , return AES256_WRAP+                                 , return AES128_WRAP_PAD+                                 , return AES192_WRAP_PAD+                                 , return AES256_WRAP_PAD+                                 , RC2_WRAP <$> choose (1, 1024)+                                 ]+        | otherwise      = oneof [ return AES128_WRAP_PAD+                                 , return AES192_WRAP_PAD+                                 , return AES256_WRAP_PAD+                                 , RC2_WRAP <$> choose (1, 1024)+                                 ]++    -- key wrapping in PWRIKEK is incompatible with CTR mode so we must never+    -- generate this combination+    notModeCTR params =+        case getContentEncryptionAlg params of+            CTR _ -> False+            _     -> True++instance Arbitrary OriginatorInfo where+    arbitrary = OriginatorInfo <$> arbitrary <*> arbitrary++instance Arbitrary CertificateChoice where+    arbitrary = oneof [ CertificateCertificate <$> arbitrary+                      , CertificateOther <$> arbitrary+                      ]++instance Arbitrary RevocationInfoChoice where+    arbitrary = oneof [ RevocationInfoCRL <$> arbitrary+                      , RevocationInfoOther <$> arbitrary+                      ]++instance Arbitrary OtherCertificateFormat where+    arbitrary = do+        oid  <- arbitraryOID+        vals <- resize 3 $ listOf1 (OctetString <$> arbitrarySmall)+        return OtherCertificateFormat { otherCertFormat = oid+                                      , otherCertValues = vals+                                      }++instance Arbitrary OtherRevocationInfoFormat where+    arbitrary = do+        oid  <- arbitraryOID+        vals <- resize 3 $ listOf1 (OctetString <$> arbitrarySmall)+        return OtherRevocationInfoFormat { otherRevInfoFormat = oid+                                         , otherRevInfoValues = vals+                                         }
+ tests/CMS/Tests.hs view
@@ -0,0 +1,311 @@+-- | CMS tests.+module CMS.Tests (cmsTests) where++import Control.Monad++import qualified Data.ByteString as B+import           Data.String (fromString)+import           Data.Maybe (isNothing)++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Crypto.Store.CMS+import Crypto.Store.PKCS8+import Crypto.Store.X509 (readSignedObject)++import CMS.Instances+import Util++message :: B.ByteString+message = fromString "hello, world\r\n"++testKey :: Int -> B.ByteString+testKey len = B.pack [0 .. toEnum (len - 1)]++hasType :: ContentType -> ContentInfo -> Bool+hasType t ci = t == getContentType ci++verifyInnerMessage :: B.ByteString -> ContentInfo -> IO ()+verifyInnerMessage msg ci = do+    assertBool "unexpected inner type" (hasType DataType ci)+    let DataCI bs = ci+    assertEqual "inner message differs" msg bs++dataTests :: TestTree+dataTests =+    testGroup "Data"+        [ testCase "read" $ do+              cms <- readCMSFile path+              length cms @?= count+              forM_ cms (verifyInnerMessage message)+        , testCase "write" $ do+              bs <- B.readFile path+              let ciList = [DataCI message]+              writeCMSFileToMemory ciList @?= bs+        ]+  where path  = testFile "cms-data.pem"+        count = 1++signedDataTests :: TestTree+signedDataTests =+    testCaseSteps "SignedData" $ \step -> do+        cms <- readCMSFile path+        assertEqual "unexpected parse count" (length names) (length cms)++        forM_ (zip [0..] cms) $ \(index, ci) -> do+            let name = names !! index++            step ("verifying " ++ name)+            assertBool "unexpected type" (hasType SignedDataType ci)+            let SignedDataCI sd = ci+            result <- verifySignedData withSignerKey sd+            assertJust result (verifyInnerMessage message)+  where path  = testFile "cms-signed-data.pem"+        names = [ "RSA"+                , "DSA"+                , "EC (named curve)"+                , "EC (explicit prime curve)"+                , "RSA-PSS"+                ]++envelopedDataTests :: TestTree+envelopedDataTests =+    testGroup "EnvelopedData"+        [ testKT "KTRI" path3 keys1+        , testKA "KARI" path4 keys1+        , test "KEKRI" path1 keys1 withRecipientKey+        , test "PWRI" path2 keys2 (\_ -> withRecipientPassword pwd)+        ]+  where test caseName path keys f = testCaseSteps caseName $ \step -> do+            cms <- readCMSFile path+            assertEqual "unexpected parse count" (length keys) (length cms)++            forM_ (zip [0..] cms) $ \(index, ci) -> do+                let (name, key) = keys !! index++                step ("testing " ++ name)+                assertBool "unexpected type" (hasType EnvelopedDataType ci)+                let EnvelopedDataCI ev = ci+                result <- openEnvelopedData (f key) ev+                assertRight result (verifyInnerMessage message)+        testKT caseName path keys = testCaseSteps caseName $ \step -> do+            let rsaPath = testFile "rsa-unencrypted-pkcs8.pem"+            [Unprotected priv] <- readKeyFile rsaPath++            cms <- readCMSFile path+            assertEqual "unexpected parse count" (length modes * length keys) (length cms)++            let pairs = [ (c, m) | c <- map fst 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+                result <- openEnvelopedData (withRecipientKeyTrans priv) ev+                assertRight result (verifyInnerMessage message)+        testKA caseName path keys = testCaseSteps caseName $ \step -> do+            let ecdsaKeyPath  = testFile "ecdsa-p256-unencrypted-pkcs8.pem"+                ecdsaCertPath = testFile "ecdsa-p256-self-signed-cert.pem"+            [Unprotected priv] <- readKeyFile ecdsaKeyPath+            [cert] <- readSignedObject ecdsaCertPath++            cms <- readCMSFile path+            assertEqual "unexpected parse count" (length mds * length keys) (length cms)++            let pairs = [ (c, h) | c <- map fst keys, h <- mds ]+            forM_ (zip pairs cms) $ \((c, h), ci) -> do+                step ("testing " ++ c ++ " with " ++ h)+                assertBool "unexpected type" (hasType EnvelopedDataType ci)+                let EnvelopedDataCI ev = ci+                result <- openEnvelopedData (withRecipientKeyAgree priv cert) ev+                assertRight result (verifyInnerMessage message)+        path1 = testFile "cms-enveloped-kekri-data.pem"+        path2 = testFile "cms-enveloped-pwri-data.pem"+        path3 = testFile "cms-enveloped-ktri-data.pem"+        path4 = testFile "cms-enveloped-kari-data.pem"+        pwd   = fromString "dontchangeme"+        keys2 = [ ("3DES_CBC",             testKey 24)+                , ("AES128_CBC",           testKey 16)+                , ("AES192_CBC",           testKey 24)+                , ("AES256_CBC",           testKey 32)+                , ("CAST5_CBC (128 bits)", testKey 16)+                , ("Camellia128_CBC",      testKey 16)+                , ("RC2 (128 bits)",       testKey 16)+                ]+        keys1 = keys2 +++                [ ("AES128_ECB",           testKey 16)+                , ("AES192_ECB",           testKey 24)+                , ("AES256_ECB",           testKey 32)+                , ("Camellia128_ECB",      testKey 16)+                ]+        modes = [ "RSAES-PKCS1"+                , "RSAES-OAEP"+                ]+        mds   = [ "SHA1"+                , "SHA224"+                , "SHA256"+                , "SHA384"+                , "SHA512"+                ]++digestedDataTests :: TestTree+digestedDataTests =+    testCaseSteps "DigestedData" $ \step -> do+        cms <- readCMSFile path+        assertEqual "unexpected parse count" (length algs) (length cms)++        forM_ (zip [0..] cms) $ \(index, ci) -> do+            let (name, alg) = algs !! index++            step ("verifying " ++ name)+            assertBool "unexpected type" (hasType DigestedDataType ci)+            let DigestedDataCI dd = ci+                result = digestVerify dd+            assertJust result (verifyInnerMessage message)++            step ("digesting " ++ name)+            let ci' = digestData alg (DataCI message)+            ci @?= ci'+  where path  = testFile "cms-digested-data.pem"+        algs  = [ ("MD5",    DigestAlgorithm MD5)+                , ("SHA1",   DigestAlgorithm SHA1)+                , ("SHA224", DigestAlgorithm SHA224)+                , ("SHA256", DigestAlgorithm SHA256)+                , ("SHA384", DigestAlgorithm SHA384)+                , ("SHA512", DigestAlgorithm SHA512)+                ]++encryptedDataTests :: TestTree+encryptedDataTests =+    testCaseSteps "EncryptedData" $ \step -> do+        cms <- readCMSFile path+        assertEqual "unexpected parse count" (length keys) (length cms)++        forM_ (zip [0..] cms) $ \(index, ci) -> do+            let (name, key) = keys !! index++            step ("decrypting " ++ name)+            assertBool "unexpected type" (hasType EncryptedDataType ci)+            let EncryptedDataCI ed = ci+                result = decryptData key ed+            assertRight result (verifyInnerMessage message)++            step ("encrypting " ++ name)+            let params = edContentEncryptionParams ed+                ci'    = encryptData key params [] (DataCI message)+            Right ci @?= ci'+  where path  = testFile "cms-encrypted-data.pem"+        keys  = [ ("DES_CBC",              testKey  8)+                , ("3DES_CBC",             testKey 24)+                , ("AES128_CBC",           testKey 16)+                , ("AES192_CBC",           testKey 24)+                , ("AES256_CBC",           testKey 32)+                , ("CAST5_CBC (40 bits)",  testKey  5)+                , ("CAST5_CBC (128 bits)", testKey 16)+                , ("Camellia128_CBC",      testKey 16)+                , ("RC2 (40 bits)",        testKey  5)+                , ("RC2 (64 bits)",        testKey  8)+                , ("RC2 (128 bits)",       testKey 16)+                , ("DES_ECB",              testKey  8)+                , ("AES128_ECB",           testKey 16)+                , ("AES192_ECB",           testKey 24)+                , ("AES256_ECB",           testKey 32)+                , ("Camellia128_ECB",      testKey 16)+                ]++authEnvelopedDataTests :: TestTree+authEnvelopedDataTests =+    testCaseSteps "AuthEnvelopedData" $ \step -> do+        cms <- readCMSFile path+        assertEqual "unexpected parse count" count (length cms)++        forM_ (zip [0..] cms) $ \(index, ci) -> do+            step ("testing vector " ++ show (index :: Int))+            assertBool "unexpected type" (hasType AuthEnvelopedDataType ci)+            let AuthEnvelopedDataCI ae = ci+            result <- openAuthEnvelopedData (withRecipientPassword pwd) ae+            assertRight result (verifyInnerMessage msg)++            step ("testing encoded vector " ++ show index)+            let [Just ci'] = pemToContentInfo [] (contentInfoToPEM ci)+                AuthEnvelopedDataCI ae' = ci'+            result' <- openAuthEnvelopedData (withRecipientPassword pwd) ae'+            assertRight result' (verifyInnerMessage msg)+  where path  = testFile "cms-auth-enveloped-data-rfc6476.pem"+        pwd   = fromString "password"+        msg   = fromString "Some test data\NUL"+        count = 2++propertyTests :: TestTree+propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"+    [ testProperty "marshalling" $ \l ->+        let bs = writeCMSFileToMemory l+        in label (sizeRange bs) $ l === readCMSFileFromMemory bs+    , testProperty "signing" $ \alg ci ->+        collect alg $ do+            (sigFns, verFn) <- scale succ (arbitrarySigVer alg)+            r <- signData sigFns ci+            let Right (SignedDataCI sd) = r+            r' <- verifySignedData verFn sd+            return (Just 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+            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+    , testProperty "encrypting" $ \alg ci ->+        collect alg $ do+            key <- generateKey alg+            attrs <- arbitraryAttributes+            let Right (EncryptedDataCI 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+            r' <- verifyAuthenticatedData devFn ad+            return (Right ci === r')+    , testProperty "enveloping with authentication" $ \alg ci ->+        collect alg $ do+            (oinfo, key, envFns, devFn, uAttrs) <- getCommon alg+            aAttrs <- arbitraryAttributes+            r <- authEnvelopData oinfo key alg envFns aAttrs uAttrs ci+            let Right (AuthEnvelopedDataCI ae) = r+            r' <- openAuthEnvelopedData devFn ae+            return (Right ci === r')+    ]+  where+    sizeRange bs =+        let n = B.length bs `div` 1024+         in show n ++ " .. " ++ show (n + 1) ++ " KB"++getCommon :: HasKeySize params+          => params+          -> Gen (OriginatorInfo, B.ByteString, [ProducerOfRI Gen], ConsumerOfRI Gen, [Attribute])+getCommon alg = do+    oinfo <- arbitrary+    key <- generateKey alg+    (envFns, devFn) <- scale succ (arbitraryEnvDev key)+    attrs <- arbitraryAttributes+    return (oinfo, key, envFns, devFn, attrs)++cmsTests :: TestTree+cmsTests =+    testGroup "CMS"+        [ dataTests+        , signedDataTests+        , envelopedDataTests+        , digestedDataTests+        , encryptedDataTests+        , authEnvelopedDataTests+        , propertyTests+        ]
+ tests/Cipher/RC2.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test vectors from RFC 2268.+module Cipher.RC2 (rc2Tests) where++import Data.ByteString (ByteString, pack)++import Crypto.Cipher.Types+import Crypto.Error++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Crypto.Store.Cipher.RC2++import Util++newtype Message = Message ByteString deriving (Show, Eq)++instance Arbitrary Message where+    arbitrary = sized $ \n -> Message . pack <$> vector (8 * n)++data Vector = Vector+    { vecEffectiveKeyLength :: Int+    , vecKey                :: ByteString+    , vecPlaintext          :: ByteString+    , vecCiphertext         :: ByteString+    }++vectors :: [Vector]+vectors =+    [ Vector+        { vecEffectiveKeyLength = 63+        , vecKey                = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\xeb\xb7\x73\xf9\x93\x27\x8e\xff"+        }+    , Vector+        { vecEffectiveKeyLength = 64+        , vecKey                = "\xff\xff\xff\xff\xff\xff\xff\xff"+        , vecPlaintext          = "\xff\xff\xff\xff\xff\xff\xff\xff"+        , vecCiphertext         = "\x27\x8b\x27\xe4\x2e\x2f\x0d\x49"+        }+    , Vector+        { vecEffectiveKeyLength = 64+        , vecKey                = "\x30\x00\x00\x00\x00\x00\x00\x00"+        , vecPlaintext          = "\x10\x00\x00\x00\x00\x00\x00\x01"+        , vecCiphertext         = "\x30\x64\x9e\xdf\x9b\xe7\xd2\xc2"+        }+    , Vector+        { vecEffectiveKeyLength = 64+        , vecKey                = "\x88"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\x61\xa8\xa2\x44\xad\xac\xcc\xf0"+        }+    , Vector+        { vecEffectiveKeyLength = 64+        , vecKey                = "\x88\xbc\xa9\x0e\x90\x87\x5a"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\x6c\xcf\x43\x08\x97\x4c\x26\x7f"+        }+    , Vector+        { vecEffectiveKeyLength = 64+        , vecKey                = "\x88\xbc\xa9\x0e\x90\x87\x5a\x7f\x0f\x79\xc3\x84\x62\x7b\xaf\xb2"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\x1a\x80\x7d\x27\x2b\xbe\x5d\xb1"+        }+    , Vector+        { vecEffectiveKeyLength = 128+        , vecKey                = "\x88\xbc\xa9\x0e\x90\x87\x5a\x7f\x0f\x79\xc3\x84\x62\x7b\xaf\xb2"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\x22\x69\x55\x2a\xb0\xf8\x5c\xa6"+        }+    , Vector+        { vecEffectiveKeyLength = 129+        , vecKey                = "\x88\xbc\xa9\x0e\x90\x87\x5a\x7f\x0f\x79\xc3\x84\x62\x7b\xaf\xb2\x16\xf8\x0a\x6f\x85\x92\x05\x84\xc4\x2f\xce\xb0\xbe\x25\x5d\xaf\x1e"+        , vecPlaintext          = "\x00\x00\x00\x00\x00\x00\x00\x00"+        , vecCiphertext         = "\x5b\x78\xd3\xa4\x3d\xff\xf1\xf1"+        }+    ]++testCipher :: forall cipher . BlockCipher cipher+           => String -> [Vector] -> cipher -> TestTree+testCipher name vec _cipher =+    testGroup name+        [ testGroup "properties"+            [ testProperty "decrypt . encrypt == id" encryptDecryptProperty+            ]+        , testGroup "vectors" $ zipWith makeTest [1..] vec+        ]+  where+    initCipher :: BlockCipher cipher => ByteString -> cipher+    initCipher k = throwCryptoError (cipherInit k)++    encryptDecryptProperty :: TestKey cipher -> Message -> Property+    encryptDecryptProperty (Key key) (Message msg) =+        ecbDecrypt ctx (ecbEncrypt ctx msg) === msg+      where ctx = initCipher key++    makeTest :: Integer -> Vector -> TestTree+    makeTest i Vector{..} =+        testGroup (show i)+            [ testCase "Encrypt" (ecbEncrypt ctx vecPlaintext @?= vecCiphertext)+            , testCase "Decrypt" (ecbDecrypt ctx vecCiphertext @?= vecPlaintext)+            ]+      where+        ctx = throwCryptoError $+            rc2WithEffectiveKeyLength vecEffectiveKeyLength vecKey++rc2Tests :: TestTree+rc2Tests = testCipher "Cipher.RC2" vectors (undefined :: RC2)
+ tests/KeyWrap/AES.hs view
@@ -0,0 +1,145 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test vectors from RFC 3394 and RFC 5649.+module KeyWrap.AES (aeskwTests) where++import Data.ByteString (ByteString, pack)++import Crypto.Cipher.AES+import Crypto.Cipher.Types+import Crypto.Error++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Crypto.Store.KeyWrap.AES++import Util++newtype Message = Message ByteString deriving (Show, Eq)++instance Arbitrary Message where+    arbitrary = sized $ \n -> Message . pack <$> vector (8 * n)++newtype MessageP = MessageP ByteString deriving (Show, Eq)++instance Arbitrary MessageP where+    arbitrary = MessageP . pack <$> listOf1 arbitrary++data Vector = Vector+    { vecKey        :: ByteString+    , vecPlaintext  :: ByteString+    , vecCiphertext :: ByteString+    }++vectors128 :: [Vector]+vectors128 =+    [ Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"+        , vecCiphertext = "\x1F\xA6\x8B\x0A\x81\x12\xB4\x47\xAE\xF3\x4B\xD8\xFB\x5A\x7B\x82\x9D\x3E\x86\x23\x71\xD2\xCF\xE5"+        }+    ]++vectors128P :: [Vector]+vectors128P = []++vectors192 :: [Vector]+vectors192 =+    [ Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"+        , vecCiphertext = "\x96\x77\x8B\x25\xAE\x6C\xA4\x35\xF9\x2B\x5B\x97\xC0\x50\xAE\xD2\x46\x8A\xB8\xA1\x7A\xD8\x4E\x5D"+        }+    , Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"+        , vecCiphertext = "\x03\x1D\x33\x26\x4E\x15\xD3\x32\x68\xF2\x4E\xC2\x60\x74\x3E\xDC\xE1\xC6\xC7\xDD\xEE\x72\x5A\x93\x6B\xA8\x14\x91\x5C\x67\x62\xD2"+        }+    ]++vectors192P :: [Vector]+vectors192P =+    [ Vector+        { vecKey        = "\x58\x40\xdf\x6e\x29\xb0\x2a\xf1\xab\x49\x3b\x70\x5b\xf1\x6e\xa1\xae\x83\x38\xf4\xdc\xc1\x76\xa8"+        , vecPlaintext  = "\xc3\x7b\x7e\x64\x92\x58\x43\x40\xbe\xd1\x22\x07\x80\x89\x41\x15\x50\x68\xf7\x38"+        , vecCiphertext = "\x13\x8b\xde\xaa\x9b\x8f\xa7\xfc\x61\xf9\x77\x42\xe7\x22\x48\xee\x5a\xe6\xae\x53\x60\xd1\xae\x6a\x5f\x54\xf3\x73\xfa\x54\x3b\x6a"+        }+    , Vector+        { vecKey        = "\x58\x40\xdf\x6e\x29\xb0\x2a\xf1\xab\x49\x3b\x70\x5b\xf1\x6e\xa1\xae\x83\x38\xf4\xdc\xc1\x76\xa8"+        , vecPlaintext  = "\x46\x6f\x72\x50\x61\x73\x69"+        , vecCiphertext = "\xaf\xbe\xb0\xf0\x7d\xfb\xf5\x41\x92\x00\xf2\xcc\xb5\x0b\xb2\x4f"+        }+    ]++vectors256 :: [Vector]+vectors256 =+    [ Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF"+        , vecCiphertext = "\x64\xE8\xC3\xF9\xCE\x0F\x5B\xA2\x63\xE9\x77\x79\x05\x81\x8A\x2A\x93\xC8\x19\x1E\x7D\x6E\x8A\xE7"+        }+    , Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07"+        , vecCiphertext = "\xA8\xF9\xBC\x16\x12\xC6\x8B\x3F\xF6\xE6\xF4\xFB\xE3\x0E\x71\xE4\x76\x9C\x8B\x80\xA3\x2C\xB8\x95\x8C\xD5\xD1\x7D\x6B\x25\x4D\xA1"+        }+    , Vector+        { vecKey        = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F"+        , vecPlaintext  = "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xAA\xBB\xCC\xDD\xEE\xFF\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F"+        , vecCiphertext = "\x28\xC9\xF4\x04\xC4\xB8\x10\xF4\xCB\xCC\xB3\x5C\xFB\x87\xF8\x26\x3F\x57\x86\xE2\xD8\x0E\xD3\x26\xCB\xC7\xF0\xE7\x1A\x99\xF4\x3B\xFB\x98\x8B\x9B\x7A\x02\xDD\x21"+        }+    ]++vectors256P :: [Vector]+vectors256P = []++testCipher :: forall cipher . BlockCipher cipher+           => [Vector] -> [Vector] -> cipher -> TestTree+testCipher vec vecP cipher =+    testGroup (cipherName cipher)+        [ testGroup "properties"+            [ testProperty "unwrap . wrap == id" wrapUnwrapProperty+            , testProperty "unwrapPad . wrapPad == id" wrapUnwrapPadProperty+            ]+        , testGroup "vectors" $+            zipWith makeTest  [1..] vec ++ zipWith makeTestP [1..] vecP+        ]+  where+    initCipher :: BlockCipher cipher => ByteString -> cipher+    initCipher k = throwCryptoError (cipherInit k)++    wrapUnwrapProperty :: TestKey cipher -> Message -> Property+    wrapUnwrapProperty (Key key) (Message msg) =+        (wrap ctx msg >>= unwrap ctx) === Right msg+      where ctx = initCipher key++    makeTest :: Integer -> Vector -> TestTree+    makeTest i Vector{..} =+        testGroup (show i)+            [ testCase "Wrap"   (wrap ctx vecPlaintext @?= Right vecCiphertext)+            , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)+            ]+      where ctx = initCipher vecKey++    wrapUnwrapPadProperty :: TestKey cipher -> MessageP -> Property+    wrapUnwrapPadProperty (Key key) (MessageP msg) =+        (wrapPad ctx msg >>= unwrapPad ctx) === Right msg+      where ctx = initCipher key++    makeTestP :: Integer -> Vector -> TestTree+    makeTestP i Vector{..} =+        testGroup ("Pad" ++ show i)+            [ testCase "Wrap"   (wrapPad ctx vecPlaintext @?= Right vecCiphertext)+            , testCase "Unwrap" (unwrapPad ctx vecCiphertext @?= Right vecPlaintext)+            ]+      where ctx = initCipher vecKey++aeskwTests :: TestTree+aeskwTests = testGroup "KeyWrap.AES"+    [ testCipher vectors128 vectors128P (undefined :: AES128)+    , testCipher vectors192 vectors192P (undefined :: AES192)+    , testCipher vectors256 vectors256P (undefined :: AES256)+    ]
+ tests/KeyWrap/RC2.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test vectors from RFC 3217.+module KeyWrap.RC2 (rc2kwTests) where++import           Data.ByteString (ByteString)+import qualified Data.ByteString as B++import Crypto.Cipher.Types+import Crypto.Error++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Crypto.Store.Cipher.RC2+import Crypto.Store.Error+import Crypto.Store.KeyWrap.RC2++import X509.Instances () -- for instance MonadRandom Gen+import Util++newtype Message = Message ByteString deriving (Show, Eq)++instance Arbitrary Message where+    arbitrary = Message . B.pack <$> (choose (0, 255) >>= vector)++data Vector = Vector+    { vecEKL        :: Int+    , vecKey        :: ByteString+    , vecPad        :: ByteString+    , vecIV         :: ByteString+    , vecPlaintext  :: ByteString+    , vecCiphertext :: ByteString+    }++vectors :: [Vector]+vectors =+    [ Vector+        { vecEKL        = 40+        , vecKey        = "\xfd\x04\xfd\x08\x06\x07\x07\xfb\x00\x03\xfe\xff\xfd\x02\xfe\x05"+        , vecPad        = "\x48\x45\xcc\xe7\xfd\x12\x50"+        , vecIV         = "\xc7\xd9\x00\x59\xb2\x9e\x97\xf7"+        , vecPlaintext  = "\xb7\x0a\x25\xfb\xc9\xd8\x6a\x86\x05\x0c\xe0\xd7\x11\xea\xd4\xd9"+        , vecCiphertext = "\x70\xe6\x99\xfb\x57\x01\xf7\x83\x33\x30\xfb\x71\xe8\x7c\x85\xa4\x20\xbd\xc9\x9a\xf0\x5d\x22\xaf\x5a\x0e\x48\xd3\x5f\x31\x38\x98\x6c\xba\xaf\xb4\xb2\x8d\x4f\x35"+        }+    , Vector+        { vecEKL        = 128 -- from RFC Errata+        , vecKey        = "\xfd\x04\xfd\x08\x06\x07\x07\xfb\x00\x03\xfe\xff\xfd\x02\xfe\x05"+        , vecPad        = "\x48\x45\xcc\xe7\xfd\x12\x50"+        , vecIV         = "\xc7\xd9\x00\x59\xb2\x9e\x97\xf7"+        , vecPlaintext  = "\xb7\x0a\x25\xfb\xc9\xd8\x6a\x86\x05\x0c\xe0\xd7\x11\xea\xd4\xd9"+        , vecCiphertext = "\xf4\xd8\x02\x1c\x1e\xa4\x63\xd2\x17\xa9\xeb\x69\x29\xff\xa5\x77\x36\xd3\xe2\x03\x86\xc9\x09\x93\x83\x5b\x4b\xe4\xad\x8d\x8a\x1b\xc6\x3b\x25\xde\x2b\xf7\x79\x93"+        }+    ]++rc2kwTests :: TestTree+rc2kwTests =+    testGroup "KeyWrap.RC2"+        [ testGroup "properties"+            [ testProperty "unwrap . wrap == id" wrapUnwrapProperty+            ]+        , testGroup "vectors" (zipWith makeTest [1..] vectors)+        ]+  where+    initCipher :: Int -> ByteString -> RC2+    initCipher ekl k = throwCryptoError (rc2WithEffectiveKeyLength ekl k)++    wrapUnwrapProperty :: TestKey RC2 -> TestIV RC2 -> Message -> Gen Property+    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) = do+        ekl <- choose (1, 1024)+        let ctx = initCipher ekl key+        wrapped <- wrap ctx iv msg+        return $ (wrapped >>= unwrap ctx) === Right msg+      where Just iv = makeIV ivBs++    makeTest :: Integer -> Vector -> TestTree+    makeTest i Vector{..} =+        testGroup (show i)+            [ testCase "Wrap" (doWrap ctx iv vecPlaintext @?= Right vecCiphertext)+            , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)+            ]+      where ctx = initCipher vecEKL vecKey+            Just iv = makeIV vecIV+            doWrap = wrap' Left withRandomPad+            withRandomPad f len+                | B.length vecPad /= len = Left (InvalidInput "unexpected length")+                | otherwise              = Right (f vecPad)
+ tests/KeyWrap/TripleDES.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test vectors from RFC 3217.+module KeyWrap.TripleDES (tripledeskwTests) where++import Data.ByteString (ByteString, pack)++import Crypto.Cipher.TripleDES+import Crypto.Cipher.Types+import Crypto.Error++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Crypto.Store.KeyWrap.TripleDES++import Util++newtype Message = Message ByteString deriving (Show, Eq)++instance Arbitrary Message where+    arbitrary = Message . pack <$> vector 24++data Vector = Vector+    { vecKey        :: ByteString+    , vecIV         :: ByteString+    , vecPlaintext  :: ByteString+    , vecCiphertext :: ByteString+    }++vectorsEDE3 :: [Vector]+vectorsEDE3 =+    [ Vector+        { vecKey        = "\x25\x5e\x0d\x1c\x07\xb6\x46\xdf\xb3\x13\x4c\xc8\x43\xba\x8a\xa7\x1f\x02\x5b\x7c\x08\x38\x25\x1f"+        , vecIV         = "\x5d\xd4\xcb\xfc\x96\xf5\x45\x3b"+        , vecPlaintext  = "\x29\x23\xbf\x85\xe0\x6d\xd6\xae\x52\x91\x49\xf1\xf1\xba\xe9\xea\xb3\xa7\xda\x3d\x86\x0d\x3e\x98"+        , vecCiphertext = "\x69\x01\x07\x61\x8e\xf0\x92\xb3\xb4\x8c\xa1\x79\x6b\x23\x4a\xe9\xfa\x33\xeb\xb4\x15\x96\x04\x03\x7d\xb5\xd6\xa8\x4e\xb3\xaa\xc2\x76\x8c\x63\x27\x75\xa4\x67\xd4"+        }+    ]++vectorsEEE3 :: [Vector]+vectorsEEE3 = []++vectorsEDE2 :: [Vector]+vectorsEDE2 = []++vectorsEEE2 :: [Vector]+vectorsEEE2 = []++testCipher :: forall cipher . BlockCipher cipher => [Vector] -> cipher -> TestTree+testCipher vectors cipher =+    testGroup (cipherName cipher)+        [ localOption (QuickCheckTests 10) $ testGroup "properties"+            [ testProperty "unwrap . wrap == id" wrapUnwrapProperty+            ]+        , testGroup "vectors" (zipWith makeTest [1..] vectors)+        ]+  where+    initCipher :: BlockCipher cipher => ByteString -> cipher+    initCipher k = throwCryptoError (cipherInit k)++    wrapUnwrapProperty :: TestKey cipher -> TestIV cipher -> Message -> Property+    wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) =+        (wrap ctx iv msg >>= unwrap ctx) === Right msg+      where ctx = initCipher key+            Just iv = makeIV ivBs++    makeTest :: Integer -> Vector -> TestTree+    makeTest i Vector{..} =+        testGroup (show i)+            [ testCase "Wrap"   (wrap ctx iv vecPlaintext @?= Right vecCiphertext)+            , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext)+            ]+      where ctx = initCipher vecKey+            Just iv = makeIV vecIV++tripledeskwTests :: TestTree+tripledeskwTests = testGroup "KeyWrap.TripleDES"+    [ testCipher vectorsEDE3 (undefined :: DES_EDE3)+    , testCipher vectorsEEE3 (undefined :: DES_EEE3)+    , testCipher vectorsEDE2 (undefined :: DES_EDE2)+    , testCipher vectorsEEE2 (undefined :: DES_EEE2)+    ]
+ tests/Main.hs view
@@ -0,0 +1,26 @@+-- | cryptostore test suite.+module Main (main) where++import Test.Tasty++import KeyWrap.AES+import KeyWrap.TripleDES+import KeyWrap.RC2+import Cipher.RC2+import CMS.Tests+import PKCS12.Tests+import PKCS8.Tests+import X509.Tests++-- | Run the test suite.+main :: IO ()+main = defaultMain $ testGroup "cryptostore"+    [ aeskwTests+    , tripledeskwTests+    , rc2kwTests+    , rc2Tests+    , cmsTests+    , x509Tests+    , pkcs8Tests+    , pkcs12Tests+    ]
+ tests/PKCS12/Instances.hs view
@@ -0,0 +1,71 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Orphan instances.+module PKCS12.Instances+    ( arbitraryPassword+    , arbitraryIntegrityParams+    , arbitraryPKCS12+    ) where++import qualified Data.ByteArray as B+import           Data.ByteString (ByteString)+import           Data.Semigroup++import Test.Tasty.QuickCheck++import Crypto.Store.PKCS12+import Crypto.Store.PKCS5++import CMS.Instances+import PKCS8.Instances ()++arbitrarySmall :: Gen ByteString+arbitrarySmall = resize 10 (B.pack <$> arbitrary)++arbitraryIntegrityParams :: Gen IntegrityParams+arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary++arbitraryPKCS12 :: Password -> Gen PKCS12+arbitraryPKCS12 pwd = do+    p <- one+    ps <- listOf one+    return (foldr (<>) p ps)+  where+    one = oneof [ unencrypted <$> arbitrary+                , arbitrary >>= arbitraryEncrypted+                ]++    arbitraryEncrypted sc = do+        alg <- arbitrary+        case encrypted alg pwd sc of+            Left e -> fail (show e)+            Right aSafe -> return aSafe++instance Arbitrary SafeContents where+    arbitrary = SafeContents <$> arbitrary++instance Arbitrary info => Arbitrary (Bag info) where+    arbitrary = do+        info <- arbitrary+        attrs <- arbitraryAttributes+        return Bag { bagInfo = info, bagAttributes = attrs }++instance Arbitrary CertInfo where+    arbitrary = CertX509 <$> arbitrary++instance Arbitrary CRLInfo where+    arbitrary = CRLX509 <$> arbitrary++instance Arbitrary SafeInfo where+    arbitrary = oneof [ KeyBag <$> arbitrary+                      , PKCS8ShroudedKeyBag <$> arbitraryShrouded+                      , CertBag <$> arbitrary+                      , CRLBag <$> arbitrary+                      --, SecretBag <$> arbitrary+                      , SafeContentsBag <$> arbitrary+                      ]++arbitraryShrouded :: Gen PKCS5+arbitraryShrouded = do+    alg <- arbitrary+    bs <- arbitrarySmall -- fake content, tested with PKCS8+    return PKCS5 { encryptionAlgorithm = alg, encryptedData = bs }
+ tests/PKCS12/Tests.hs view
@@ -0,0 +1,99 @@+-- | PKCS #12 tests.+module PKCS12.Tests (pkcs12Tests) where++import Control.Monad (forM_)++import Data.PEM (pemContent)+import Data.String (fromString)++import Crypto.Store.PKCS12+import Crypto.Store.PKCS8+import Crypto.Store.X509 (readSignedObject)++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Util+import PKCS12.Instances+import X509.Instances++testType :: TestName -> String -> TestTree+testType caseName prefix = testCaseSteps caseName $ \step -> do+    let fKey  = testFile (prefix ++ "-unencrypted-pkcs8.pem")+        fCert = testFile (prefix ++ "-self-signed-cert.pem")+        p12   = testFile (prefix ++ "-pkcs12.pem")++    step "Reading PKCS #12 files"+    pems <- readPEMs p12+    length pems @?= length names++    step "Reading private key"+    [Unprotected key] <- readKeyFile fKey++    step "Reading certificate"+    certs <- readSignedObject fCert++    forM_ (zip names pems) $ \(name, pem) -> do+        let r = readP12FileFromMemory (pemContent pem)++        assertRight r $ \integrity ->+            assertRight (recover pwd integrity) $ \privacy ->+                assertRight (recover pwd $ unPKCS12 privacy) $ \scs -> do+                    step ("Testing " ++ name)+                    recover pwd (getAllSafeKeys scs) @?= Right [key]+                    getAllSafeX509Certs scs @?= certs+  where+    pwd :: Password+    pwd = fromString "dontchangeme"++    nameIntegrity n = "integrity with " ++ n+    namePrivacy t n = t ++ " privacy with " ++ n++    integrityNames = map nameIntegrity integrityModes+    privacyNames t = ("without " ++ t ++ " privacy") :+                     map (namePrivacy t) privacyModes++    names = [ "without integrity" ] ++ integrityNames +++            privacyNames "certificate" ++ privacyNames "private-key"++    integrityModes = [ "SHA-1"+                     , "SHA-256"+                     , "SHA-384"+                     ]++    privacyModes   = [ "aes-128-cbc"+                     , "PBE-SHA1-RC2-128"+                     , "PBE-SHA1-RC2-40"+                     ]++propertyTests :: TestTree+propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"+    [ testProperty "marshalling" $ do+        pE <- arbitraryPassword+        c <- arbitraryPKCS12 pE+        let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c+        return $ Right (Right c) === (recover (fromString "not-used") <$> r)+    , testProperty "marshalling with authentication" $ do+        params <- arbitraryIntegrityParams+        c <- arbitraryPassword >>= arbitraryPKCS12+        pI <- arbitraryPassword+        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 -> 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+    ]++pkcs12Tests :: TestTree+pkcs12Tests =+    testGroup "PKCS12"+        [ testType "RSA"                        "rsa"+        , testType "EC (named curve)"           "ecdsa-p256"+        , propertyTests+        ]
+ tests/PKCS8/Instances.hs view
@@ -0,0 +1,39 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Orphan instances.+module PKCS8.Instances+    ( arbitraryPassword+    ) where++import Test.Tasty.QuickCheck++import Crypto.Store.CMS+import Crypto.Store.PKCS5+import Crypto.Store.PKCS8++import CMS.Instances++instance Arbitrary PBEParameter where+    arbitrary = do+        salt <- generateSalt 8+        PBEParameter salt <$> choose (1,512)++instance Arbitrary PBES2Parameter where+    arbitrary = PBES2Parameter <$> arbitrary <*> arbitrary++instance Arbitrary EncryptionScheme where+    arbitrary = oneof [ PBES2 <$> arbitrary+                      , PBE_MD5_DES_CBC <$> arbitrary+                      , PBE_SHA1_DES_CBC <$> arbitrary+                      , PBE_SHA1_RC4_128 <$> arbitrary+                      , PBE_SHA1_RC4_40 <$> arbitrary+                      , PBE_SHA1_DES_EDE3_CBC <$> arbitrary+                      , PBE_SHA1_DES_EDE2_CBC <$> arbitrary+                      , PBE_SHA1_RC2_128 <$> arbitrary+                      , PBE_SHA1_RC2_40 <$> arbitrary+                      ]++instance Arbitrary PrivateKeyFormat where+    arbitrary = elements [ TraditionalFormat, PKCS8Format ]++instance Arbitrary a => Arbitrary (FormattedKey a) where+    arbitrary = FormattedKey <$> arbitrary <*> arbitrary
+ tests/PKCS8/Tests.hs view
@@ -0,0 +1,101 @@+-- | PKCS #8 tests.+module PKCS8.Tests (pkcs8Tests) where++import qualified Data.ByteString as B+import           Data.String (fromString)++import Crypto.Store.PKCS8++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Util+import PKCS8.Instances++keyTests :: String -> TestTree+keyTests prefix =+    testGroup "PrivateKey"+        [ testCase "read outer" $ do+              kOuter <- readKeyFile fOuter+              length kOuter @?= 1+        , testCase "read inner" $ do+              kInner <- readKeyFile fInner+              length kInner @?= 1+        , testCase "same key"   $ do+              kInner <- readKeyFile fInner+              kOuter <- readKeyFile fOuter+              assertBool "keys differ" $+                  let [Unprotected kI] = kInner+                      [Unprotected kO] = kOuter+                   in kI == kO+        , testCase "write outer" $ do+              bs <- B.readFile fOuter+              let kOuter = readKeyFileFromMemory bs+                  [Unprotected kO] = kOuter+              writeKeyFileToMemory PKCS8Format [kO] @?= bs+        , testCase "write inner" $ do+              bs <- B.readFile fInner+              let kInner = readKeyFileFromMemory bs+                  [Unprotected kI] = kInner+              writeKeyFileToMemory TraditionalFormat [kI] @?= bs+        ]+  where+    fInner = testFile (prefix ++ "-unencrypted-trad.pem")+    fOuter = testFile (prefix ++ "-unencrypted-pkcs8.pem")++encryptedKeyTests :: String -> TestTree+encryptedKeyTests prefix =+    testGroup "EncryptedPrivateKey"+        [ keyTest "PBES1"  "pbes1"  8+        , keyTest "PBKDF2" "pbkdf2" 7+        , keyTest "Scrypt" "scrypt" 3+        ]+  where+    pwd = fromString "dontchangeme"++    keyTest name suffix count =+        let fE = testFile (prefix ++ "-encrypted-" ++ suffix ++ ".pem")+            fU = testFile (prefix ++ "-unencrypted-pkcs8.pem")+         in testGroup name+                [ testCase "read unencrypted" $ do+                      kU <- readKeyFile fU+                      length kU @?= 1+                , testCase "read encrypted"   $ do+                      kE <- readKeyFile fE+                      length kE @?= count+                , testCase "same keys"        $ do+                      kE <- readKeyFile fE+                      kU <- readKeyFile fU+                      assertBool "some keys differ" $+                          let [Unprotected key] = kU+                           in all (\(Protected getKey) -> getKey pwd == Right key) kE+                ]++testType :: TestName -> String -> TestTree+testType name prefix =+    testGroup name+        [ keyTests prefix+        , encryptedKeyTests prefix+        ]++propertyTests :: TestTree+propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"+    [ testProperty "marshalling" $ \fmt l ->+        let r = readKeyFileFromMemory $ writeKeyFileToMemory fmt l+        in map Right l === map (recover $ fromString "not-used") r+    , testProperty "marshalling with encryption" $ \es k -> do+        p <- arbitraryPassword+        let r = readKeyFileFromMemory <$> writeEncryptedKeyFileToMemory es p k+        return $ Right [Right k] === (map (recover p) <$> r)+    ]++pkcs8Tests :: TestTree+pkcs8Tests =+    testGroup "PKCS8"+        [ testType "RSA"                        "rsa"+        , testType "DSA"                        "dsa"+        , testType "EC (named curve)"           "ecdsa-p256"+        , testType "EC (explicit prime curve)"  "ecdsa-epc"+        , propertyTests+        ]
+ tests/Util.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE ScopedTypeVariables #-}+-- | Test utilities.+module Util+    ( assertJust+    , assertRight+    , testFile+    , TestKey(..)+    , TestIV(..)+    ) where++import Data.ByteString (ByteString, pack)++import Crypto.Cipher.Types++import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++assertJust :: Maybe a -> (a -> Assertion) -> Assertion+assertJust (Just a) f = f a+assertJust Nothing  _ = assertFailure "expecting Just but got Nothing"++assertRight :: Show a => Either a b -> (b -> Assertion) -> Assertion+assertRight (Right b)  f = f b+assertRight (Left val) _ =+    assertFailure ("expecting Right but got: Left " ++ show val)++testFile :: String -> FilePath+testFile name = "tests/files/" ++ name++newtype TestKey cipher = Key ByteString deriving (Show, Eq)++instance Cipher cipher => Arbitrary (TestKey cipher) where+    arbitrary = Key . pack <$>+        case cipherKeySize cipher of+            KeySizeFixed len -> vector len+            KeySizeRange a b -> choose (a, b) >>= vector+            KeySizeEnum list -> elements list >>= vector+      where cipher = undefined :: cipher++newtype TestIV cipher = IV ByteString deriving (Show, Eq)++instance BlockCipher cipher => Arbitrary (TestIV cipher) where+    arbitrary = IV . pack <$> vector (blockSize cipher)+      where cipher = undefined :: cipher
+ tests/X509/Instances.hs view
@@ -0,0 +1,267 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+-- | Orphan instances.+module X509.Instances+    ( arbitraryOID+    , arbitraryRSA+    , arbitraryLargeRSA+    , arbitraryDSA+    , arbitraryNamedEC+    , arbitrarySignedCertificate+    , arbitraryCertificateChain+    ) where++import           Data.ASN1.Types+import qualified Data.ByteArray as B+import           Data.Hourglass+import           Data.X509++import Test.Tasty.QuickCheck++import           Crypto.Number.Serialize (i2ospOf_)+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.RSA as RSA+import           Crypto.Random++-- Warning: not a cryptographic implementation, used for tests only+instance MonadRandom Gen where+    getRandomBytes n = B.pack <$> vector n++arbitraryOID :: Gen [Integer]+arbitraryOID = do+    o1 <- choose (0,6)+    o2 <- choose (0,15)+    os <- resize 5 $ listOf (getPositive <$> arbitrary)+    return (o1 : o2 : os)++arbitraryDN :: Gen DistinguishedName+arbitraryDN = DistinguishedName <$> resize 5 (listOf1 arbitraryDE)+  where+    arbitrarySE = elements [IA5, UTF8]+    arbitraryDE = (,) <$> arbitraryOID <*> arbitraryCS+    arbitraryCS = ASN1CharacterString <$> arbitrarySE <*> arbitraryBS+    arbitraryBS = resize 16 (B.pack <$> listOf1 arbitrary)++instance Arbitrary PubKey where+    arbitrary = oneof [ PubKeyRSA . fst <$> arbitraryRSA+                      , PubKeyDSA . fst <$> arbitraryDSA+                      , PubKeyEC . fst  <$> arbitraryNamedEC+                      --, PubKeyEC . fst  <$> arbitraryExplicitPrimeCurve+                      ]++instance Arbitrary PrivKey where+    arbitrary = oneof [ PrivKeyRSA . snd <$> arbitraryRSA+                      , PrivKeyDSA . snd <$> arbitraryDSA+                      , PrivKeyEC . snd  <$> arbitraryNamedEC+                      , PrivKeyEC . snd  <$> arbitraryExplicitPrimeCurve+                      ]++arbitraryRSA :: Gen (RSA.PublicKey, RSA.PrivateKey)+arbitraryRSA = do+    n <- elements [ 768, 1024 ]     -- enough bits to sign with SHA-512+    e <- elements [ 3, 0x10001 ]+    RSA.generate (n `div` 8) e++arbitraryLargeRSA :: Gen (RSA.PublicKey, RSA.PrivateKey)+arbitraryLargeRSA = do+    n <- elements [ 1792, 2048 ]    -- enough bits for RSA-OAEP with SHA-512+    e <- elements [ 3, 0x10001 ]+    RSA.generate (n `div` 8) e++arbitraryDSA :: Gen (DSA.PublicKey, DSA.PrivateKey)+arbitraryDSA = do+    x <- DSA.generatePrivate params+    let y = DSA.calculatePublic params x+        priv = DSA.PrivateKey { DSA.private_params = params, DSA.private_x = x }+        pub = DSA.PublicKey { DSA.public_params = params, DSA.public_y = y }+    return (pub, priv)+  where+    -- DSA parameters were generated using 'openssl dsaparam -C 2048'+    params = DSA.Params+        { DSA.params_p = 0x9994B9B1FC22EC3A5F607B5130D314F35FC8D387015A6D8FA2B56D3CC1F13FE330A631DBC765CEFFD6986BDEB8512580BBAD93D56EE7A8997DB9C65C29313FBC5077DB6F1E9D9E6D3499F997F09C8CF8ECC9E5F38DC34C3D656CFDF463893DDF9E246E223D7E5C4E86F54426DDA5DE112FCEDBFB5B6D6F7C76ED190EA1A7761CA561E8E5803F9D616DAFF25E2CCD4011A6D78D5CE8ED28CC2D865C7EC01508BA96FBD1F8BB5E517B6A5208A90AC2D3DCAE50281C02510B86C16D449465CD4B3754FD91AA19031282122A25C68292F033091FCB9DEBDE0D220F81F7EE4AB6581D24BE48204AF3DA52BDB944DA53B76148055395B30954735DC911574D360C953B+        , DSA.params_g = 0x10E51AEA37880C5E52DD477ED599D55050C47012D038B9E4B3199C9DE9A5B873B1ABC8B954F26AFEA6C028BCE1783CFE19A88C64E4ED6BFD638802A78457A5C25ABEA98BE9C6EF18A95504C324315EABE7C1EA50E754591E3EFD3D33D4AE47F82F8978ABC871C135133767ACC60683F065430C749C43893D73596B12D5835A78778D0140B2F63B32A5658308DD5BA6BBC49CF6692929FA6A966419404F9A2C216860E3F339EDDB49AD32C294BDB4C9C6BB0D1CC7B691C65968C3A0A5106291CD3810147C8A16B4BFE22968AD9D3890733F4AA9ACD8687A5B981653A4B1824004639956E8C1EDAF31A8224191E8ABD645D2901F5B164B4B93F98039A6EAEC6088+        , DSA.params_q = 0xE1FDFADD32F46B5035EEB3DB81F9974FBCA69BE2223E62FCA8C77989B2AACDF7+        }++arbitraryNamedEC :: Gen (PubKeyEC, PrivKeyEC)+arbitraryNamedEC = do+    name <- arbitraryCurveName+    let curve = ECC.getCurveByName name+    pair <- ECC.generate curve+    let d = ECDSA.private_d (snd pair)+        priv = PrivKeyEC_Named { privkeyEC_name = name, privkeyEC_priv = d }+        q = ECDSA.public_q (fst pair)+        pt = getSerializedPoint curve q+        pub = PubKeyEC_Named { pubkeyEC_name = name, pubkeyEC_pub = pt }+    return (pub, priv)++arbitraryExplicitPrimeCurve :: Gen (PubKeyEC, PrivKeyEC)+arbitraryExplicitPrimeCurve = do+    curve <- arbitraryPrimeCurve+    pair <- ECC.generate curve+    let cc   = ECC.common_curve curve+        c    = fp curve+        gen  = getSerializedPoint curve (ECC.ecc_g cc)+        d    = ECDSA.private_d (snd pair)+        priv =+            PrivKeyEC_Prime+                { privkeyEC_priv      = d+                , privkeyEC_a         = ECC.ecc_a cc+                , privkeyEC_b         = ECC.ecc_b cc+                , privkeyEC_prime     = ECC.ecc_p c+                , privkeyEC_generator = gen+                , privkeyEC_order     = ECC.ecc_n cc+                , privkeyEC_cofactor  = ECC.ecc_h cc+                , privkeyEC_seed      = 0+                }+        q    = ECDSA.public_q (fst pair)+        pt   = getSerializedPoint curve q+        pub  =+            PubKeyEC_Prime+                { pubkeyEC_pub        = pt+                , pubkeyEC_a          = ECC.ecc_a cc+                , pubkeyEC_b          = ECC.ecc_b cc+                , pubkeyEC_prime      = ECC.ecc_p c+                , pubkeyEC_generator  = gen+                , pubkeyEC_order      = ECC.ecc_n cc+                , pubkeyEC_cofactor   = ECC.ecc_h cc+                , pubkeyEC_seed       = 0+                }+    return (pub, priv)+  where+    fp (ECC.CurveFP c) = c+    fp _               = error "arbitraryExplicitPrimeCurve: assumption failed"++arbitraryCurveName :: Gen ECC.CurveName+arbitraryCurveName = elements allCurveNames++allCurveNames :: [ECC.CurveName]+allCurveNames =+    [ ECC.SEC_p112r1+    , ECC.SEC_p112r2+    , ECC.SEC_p128r1+    , ECC.SEC_p128r2+    , ECC.SEC_p160k1+    , ECC.SEC_p160r1+    , ECC.SEC_p160r2+    , ECC.SEC_p192k1+    , ECC.SEC_p192r1+    , ECC.SEC_p224k1+    , ECC.SEC_p224r1+    , ECC.SEC_p256k1+    , ECC.SEC_p256r1+    , ECC.SEC_p384r1+    , ECC.SEC_p521r1+    , ECC.SEC_t113r1+    , ECC.SEC_t113r2+    , ECC.SEC_t131r1+    , ECC.SEC_t131r2+    , ECC.SEC_t163k1+    , ECC.SEC_t163r1+    , ECC.SEC_t163r2+    , ECC.SEC_t193r1+    , ECC.SEC_t193r2+    , ECC.SEC_t233k1+    , ECC.SEC_t233r1+    , ECC.SEC_t239k1+    , ECC.SEC_t283k1+    , ECC.SEC_t283r1+    , ECC.SEC_t409k1+    , ECC.SEC_t409r1+    , ECC.SEC_t571k1+    , ECC.SEC_t571r1+    ]++primeCurves :: [ECC.Curve]+primeCurves = filter isPrimeCurve $ map ECC.getCurveByName allCurveNames+  where isPrimeCurve (ECC.CurveFP _) = True+        isPrimeCurve _               = False++arbitraryPrimeCurve :: Gen ECC.Curve+arbitraryPrimeCurve = elements primeCurves++getSerializedPoint :: ECC.Curve -> ECC.Point -> SerializedPoint+getSerializedPoint curve pt = SerializedPoint (serializePoint pt)+  where+    bs = i2ospOf_ (curveSizeBytes curve)++    serializePoint ECC.PointO      = B.singleton 0+    serializePoint (ECC.Point x y) = B.cons 4 (B.append (bs x) (bs y))++curveSizeBytes :: ECC.Curve -> Int+curveSizeBytes curve = (ECC.curveSizeBits curve + 7) `div` 8++instance Arbitrary SignatureALG where+    arbitrary = elements+        [ SignatureALG HashSHA1   PubKeyALG_RSA+        , SignatureALG HashMD5    PubKeyALG_RSA+        , SignatureALG HashMD2    PubKeyALG_RSA+        , SignatureALG HashSHA256 PubKeyALG_RSA+        , SignatureALG HashSHA384 PubKeyALG_RSA+        , SignatureALG HashSHA512 PubKeyALG_RSA+        , SignatureALG HashSHA224 PubKeyALG_RSA++        , SignatureALG HashSHA1   PubKeyALG_DSA+        , SignatureALG HashSHA224 PubKeyALG_DSA+        , SignatureALG HashSHA256 PubKeyALG_DSA++        , SignatureALG HashSHA224 PubKeyALG_EC+        , SignatureALG HashSHA256 PubKeyALG_EC+        , SignatureALG HashSHA384 PubKeyALG_EC+        , SignatureALG HashSHA512 PubKeyALG_EC+        ]++instance Arbitrary DateTime where+    arbitrary =+        let arbitraryElapsed = Elapsed . Seconds <$> choose (1, 100000000)+         in timeConvert <$> arbitraryElapsed++arbitraryCertificate :: PubKey -> Gen Certificate+arbitraryCertificate pubKey =+    Certificate <$> pure 2+                <*> arbitrary+                <*> arbitrary+                <*> arbitraryDN+                <*> arbitrary+                <*> arbitraryDN+                <*> pure pubKey+                <*> pure (Extensions Nothing)++instance Arbitrary Certificate where+    arbitrary = arbitrary >>= arbitraryCertificate++instance Arbitrary RevokedCertificate where+    arbitrary = RevokedCertificate <$> arbitrary+                                   <*> arbitrary+                                   <*> pure (Extensions Nothing)++instance Arbitrary CRL where+    arbitrary = CRL <$> pure 1+                    <*> arbitrary+                    <*> arbitraryDN+                    <*> arbitrary+                    <*> arbitrary+                    <*> arbitrary+                    <*> pure (Extensions Nothing)++arbitrarySignedExact :: (Show a, Eq a, ASN1Object a)+                     => a -> Gen (SignedExact a)+arbitrarySignedExact = objectToSignedExactF doSign+  where+    doSign _ = (,) <$> arbitrarySig <*> arbitrary+    arbitrarySig = B.pack <$> vector 16++arbitrarySignedCertificate :: PubKey -> Gen SignedCertificate+arbitrarySignedCertificate pubKey =+    arbitraryCertificate pubKey >>= arbitrarySignedExact++instance (Show a, Eq a, ASN1Object a, Arbitrary a) => Arbitrary (SignedExact a) where+    arbitrary = arbitrary >>= arbitrarySignedExact++arbitraryCertificateChain :: PubKey -> Gen CertificateChain+arbitraryCertificateChain pubKey = do+    leaf <- arbitrarySignedCertificate pubKey+    others <- resize 3 $ listOf (arbitrary >>= arbitrarySignedCertificate)+    return $ CertificateChain (leaf:others)
+ tests/X509/Tests.hs view
@@ -0,0 +1,70 @@+-- | X.509 tests.+module X509.Tests (x509Tests) where++import qualified Data.ByteString as B+import           Data.X509++import Crypto.Store.X509++import Test.Tasty+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck++import Util+import X509.Instances ()++keyTests :: TestName -> String -> Int -> TestTree+keyTests name prefix count =+    testGroup name+        [ testCase "read public key" $ do+              keys <- readPubKeyFile fKey+              length keys @?= count+        , testCase "read certificate" $ do+              cert <- readSignedObject fCert :: IO [SignedCertificate]+              length cert @?= 1+        , testCase "same key" $ do+              cert <- readSignedObject fCert :: IO [SignedCertificate]+              keys <- readPubKeyFile fKey+              assertBool "keys differ" $+                  let [c] = cert+                      key = certPubKey (signedObject (getSigned c))+                   in all (== key) keys+        , testCase "write certificate" $ do+              bs <- B.readFile fCert+              let objs = readSignedObjectFromMemory bs :: [SignedCertificate]+              writeSignedObjectToMemory objs @?= bs+        , testCase "write public key" $ do+              bs <- B.readFile fKey+              let key = head (readPubKeyFileFromMemory bs)+              assertBool "first key differs" $+                  writePubKeyFileToMemory [key] `B.isPrefixOf` bs+        ]+  where+    fCert = testFile (prefix ++ "-self-signed-cert.pem")+    fKey  = testFile (prefix ++ "-public.pem")++propertyTests :: TestTree+propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"+    [ testProperty "marshalling public keys" $ \keys ->+          keys === readPubKeyFileFromMemory (writePubKeyFileToMemory keys)+    , testProperty "marshalling certificates" $ \objs ->+          asCerts objs === writeReadObjs objs+    , testProperty "marshalling CRLs" $ \objs ->+          asCRLs objs === writeReadObjs objs+    ]+  where+    writeReadObjs :: SignedObject a => [SignedExact a] -> [SignedExact a]+    writeReadObjs = readSignedObjectFromMemory . writeSignedObjectToMemory++    asCerts = id :: [SignedCertificate] -> [SignedCertificate]+    asCRLs  = id :: [SignedCRL] -> [SignedCRL]++x509Tests :: TestTree+x509Tests =+    testGroup "X509"+        [ keyTests "RSA"                        "rsa"        2+        , keyTests "DSA"                        "dsa"        1+        , keyTests "EC (named curve)"           "ecdsa-p256" 1+--        , keyTests "EC (explicit prime curve)"  "ecdsa-epc"  1+        , propertyTests+        ]
+ tests/files/cms-auth-enveloped-data-rfc6476.pem view
@@ -0,0 +1,15 @@+-----BEGIN PKCS7-----+MIHjBgsqhkiG9w0BCRABF6CB0zCB0AIBADFho18CAQCgGwYJKoZIhvcNAQUMMA4E+CLfrI6dr0gUWAgITiDAjBgsqhkiG9w0BCRADCTAUBggqhkiG9w0DBwQIZpECRWtz+u5kEGDCjerXY8odQ7EEEromZJvAurk/j81IrozBSBgkqhkiG9w0BBwEwMwYLKoZI+hvcNAQkQAw8wJDAUBggqhkiG9w0DBwQI0tCBcU09nxEwDAYIKwYBBQUIAQIFAIAQ+OsYGYUFdAH0RNc1p4VbKEAQUM2Xo8PMHBoYdqEcsbTodlCFAZH4=+-----END PKCS7-----+-----BEGIN PKCS7-----+MIH9BgsqhkiG9w0BCRABF6CB7TCB6gIBADFyo3ACAQCgGwYJKoZIhvcNAQUMMA4E+COe3h9+CHRLMAgITiDAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEAQIEEBHZXFIK+Or8isjBw7/R9bvYEIBg5IifDwiwqpp8qsHckdarYWJzNu0yu0w3Cyx2DlGw3MFsG+CSqGSIb3DQEHATA8BgsqhkiG9w0BCRADDzAtMB0GCWCGSAFlAwQBAgQQtyUCdoQ8+WBulMOJAJ+7DBjAMBggrBgEFBQgBAgUAgBCYNg8MeWI2tS0tnhxihR4QBBSIpMGy+ungbyvkUsOX80Y34AuKyng==+-----END PKCS7-----
+ tests/files/cms-data.pem view
@@ -0,0 +1,3 @@+-----BEGIN CMS-----+MB0GCSqGSIb3DQEHAaAQBA5oZWxsbywgd29ybGQNCg==+-----END CMS-----
+ tests/files/cms-digested-data.pem view
@@ -0,0 +1,27 @@+-----BEGIN CMS-----+MFEGCSqGSIb3DQEHBaBEMEICAQAwDAYIKoZIhvcNAgUFADAdBgkqhkiG9w0BBwGg+EAQOaGVsbG8sIHdvcmxkDQoEEM1RYEXDSwgRKZGd+Ja3VVo=+-----END CMS-----+-----BEGIN CMS-----+MFAGCSqGSIb3DQEHBaBDMEECAQAwBwYFKw4DAhowHQYJKoZIhvcNAQcBoBAEDmhl+bGxvLCB3b3JsZA0KBBSCJcpSBohk9ZFjXX73CaruHbNZqA==+-----END CMS-----+-----BEGIN CMS-----+MFwGCSqGSIb3DQEHBaBPME0CAQAwCwYJYIZIAWUDBAIEMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQczcRQGqOTlmA3SbLea8IWbnraq6CgYvJB35ds7A==+-----END CMS-----+-----BEGIN CMS-----+MGAGCSqGSIb3DQEHBaBTMFECAQAwCwYJYIZIAWUDBAIBMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQgu747Zx6FPf4woOYFlDZvJPAvMeok/0ZRdD/WDHPN+aCI=+-----END CMS-----+-----BEGIN CMS-----+MHAGCSqGSIb3DQEHBaBjMGECAQAwCwYJYIZIAWUDBAICMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQwppJgPaHRdFOAXmaxqE4LqWf8xnTAsB2M/HiQTA/2+QQDpK7cvaFmjVu6jb8iCj5RL+-----END CMS-----+-----BEGIN CMS-----+MIGABgkqhkiG9w0BBwWgczBxAgEAMAsGCWCGSAFlAwQCAzAdBgkqhkiG9w0BBwGg+EAQOaGVsbG8sIHdvcmxkDQoEQNmRYGENk6X3fR9Sk9k++QSKUPYBvF+nGKHL4FXN+QVoNz4kKv9BI4MPnsV77ikuw7kWGR3OFLWO8waCyFK+EEu0=+-----END CMS-----
+ tests/files/cms-encrypted-data.pem view
@@ -0,0 +1,64 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBEGBSsOAwIHBAikeD2B+358Jh6CABAjJSfpa1LuLyAQIY6cX6W8/IhwAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBQGCCqGSIb3DQMHBAiC+6+NMJ44OmqCABAjW9hA48kkK3AQIh+a+VHqpvWMAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBAgQQ+mkkKJDu2aiEgb0/ZxJBsYKCABBDcIZ09lh+9CsLaYdR1SgDOAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBFgQQ+j6MESspPo2oHNS89EDG1aKCABBATA2wR43qFEySZz42vGzGKAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQBKgQQ+UVdEll0/Q4TaK20aQF9g2qCABBDEiZdmwk+evavQgmw1sComAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBUGCSqGSIb2fQdCCgQI+GRIlaIuvmQ+ggAQILWj2kKDN1s0ECEiJbc0I7i2cAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBUGCSqGSIb2fQdCCgQI+WDzAE/e9EsSggAQILkJl9q9FijEECNIDSgZF99sjAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMB8GCyqDCIyaSz0BAQEC+BBA2iD8nnUhmlqyjEW24xUTFoIAEEDgF21lihlEHVa1D4g17tDYAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBoGCCqGSIb3DQMCMA4C+AgCgBAjrdywymrZ6FKCABAj2In9qh8hjGAQI6wNZVOTYKTkAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBkGCCqGSIb3DQMCMA0C+AXgECFYB6AfzFjbOoIAECBJ3vXMNAn34BAjOBiCsA8vj7AAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMBkGCCqGSIb3DQMCMA0C+AToECFxl2R8EmepkoIAECFnHCd1/a+BdBAj2gVsc+lQZxAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMAkGBSsOAwIGBACggAQI+wVvzZIHhjREECBpzXeoXbghhAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMA0GCWCGSAFlAwQBAQQA+oIAEEPq7Jdg9K+WiMDzclI2E3ZUAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMA0GCWCGSAFlAwQBFQQA+oIAEEF9g2Xr4VpbfRXzmI6Trt6EAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMA0GCWCGSAFlAwQBKQQA+oIAEEB6dQRYioWisrBYHjAebWgwAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHBqCAMIACAQAwgAYJKoZIhvcNAQcBMAwGCAOiMQUDAQkBBACg+gAQQ6RwXVWk9R1eE7Jk7CUyMRAAAAAAAAAAAAAA=+-----END CMS-----
+ tests/files/cms-enveloped-kari-data.pem view
@@ -0,0 +1,462 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdWhgdICAQOgUaFPMAkGByqGSM49AgEDQgAE+Ed6o5AhsIHUjW+EUXgK6gcsSpY5/tAXgBs9j1h9bNhvBquwRih2dDUmJwJRsjNcU+UvhBljKPEy2ib6WeQnEsojAcBgkrgQUQhkg/AAIwDwYLKoZIhvcNAQkQAwYFADBc+MFowLjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAl/pm8dLg+PgAEKPMvSLFKDI5Pc2NAr0tt7Jrhi82TZo6auIhDBjV4R2PCVe+I680iwiowgAYJ+KoZIhvcNAQcBMBQGCCqGSIb3DQMHBAi9UZzbbgEaHKCABAj/zb76X6Se/gQIrc14+qkjoFYMAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdKhgc8CAQOgUaFPMAkGByqGSM49AgEDQgAE+2Y+zAHO5ulufpaH44mk3kGBgfd9vWzq5HkRae8dlaezpaSmc+oKtZlGZXDuC7bEP+5eluiynuYYfVVgxETZQ7TDAZBgYrgQQBCwAwDwYLKoZIhvcNAQkQAwYFADBcMFow+LjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAl/pm8dLgPgAE+KB36kd95/gx1SjDa4+UL4qWXd1RDCqPFhcSLyCJyAIF19WkCc0U3o8QwgAYJKoZI+hvcNAQcBMBQGCCqGSIb3DQMHBAh5J5a0tWhj+6CABAhq554Xa7EUdwQIgxGzaXwT+nX4AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdKhgc8CAQOgUaFPMAkGByqGSM49AgEDQgAE+XFCZOCO50366MGOprn5RzFqhDK/VHN4zCAripWsSE/MJafxfNd8ov7liRj5e/Rwp+PtA5wysulypS8sL/8RlwUDAZBgYrgQQBCwEwDwYLKoZIhvcNAQkQAwYFADBcMFow+LjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAl/pm8dLgPgAE+KEhcaAC5MPm8Xf5YX7kmTTSoO4tG0rRhYqYLwb0pQFVVQlCSbi2JfqMwgAYJKoZI+hvcNAQcBMBQGCCqGSIb3DQMHBAhe7ZmMpONL5qCABAgcSGlAlLskmAQIZt9VALxf+dRwAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdKhgc8CAQOgUaFPMAkGByqGSM49AgEDQgAE+YnNf8skxVk51tvLHYy3hELWfOug40zlob+10nWeYxOBRBBcNmzLiVJ4VcwEDAvQH+gSG/ftWR/cu8d7npjh/fTDAZBgYrgQQBCwIwDwYLKoZIhvcNAQkQAwYFADBcMFow+LjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAl/pm8dLgPgAE+KPB7npEdNX96iMm3saaTkmkYBLwX1IVEgZU2NHqMNm2wvnBpeBouzq0wgAYJKoZI+hvcNAQcBMBQGCCqGSIb3DQMHBAivrGaWndwuIaCABAisrCnBVtd/0AQI/h/EdI4d+JHEAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdKhgc8CAQOgUaFPMAkGByqGSM49AgEDQgAE+WdNVTNdYqeSR5NCdy0s10ipnhzuQEj3/BoGyL2FL8M3fCrQdP36qacziFOoDjbNZ+8so2s33aXcCglhZ9/AyULTAZBgYrgQQBCwMwDwYLKoZIhvcNAQkQAwYFADBcMFow+LjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAl/pm8dLgPgAE+KJPYTTgAItxMFq0Hqx16UDgGM+ZpkCrPyQARYs3XBZcrzqyDzdQY+XUwgAYJKoZI+hvcNAQcBMBQGCCqGSIb3DQMHBAidhr3XY7svRKCABAgE1skQLauNDQQIvbre4W/d+nX8AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+NkH2menyiIOiyJMAsSWmBPPU49NrakPUDywzNitZa0Yu00DOTvmggkQP1RR+QSrk+JEbx6xaCYxFaJNF/2ZgLBDAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+z1bGx7ABf5OxxKDHaoBXkho4PL2+0S5QMIAGCSqGSIb3DQEHATAdBglghkgBZQME+AQIEEMD/gqzuyP3V8zydroS0AwiggAQQ6yjZFdDXrEn69YEKqIoXywAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+t6fN7Ia3+ySsvomfy1FsAklLIEERQTKoknia/cMMsks/N+G9nijba7W3La6vUGCt+eAiaYLp3vd0oUmgDKTgawTAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYH3BR+OSPFufyVQePGfvngsDBMtp/FmgNxMIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIE+ENhrWc7aJlKi6KxbEX8vb8iggAQQNauiYNoLDzYsWl8mIvfYHQAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+oIjIxcHzuen6Vrr5qU8Qnb3l1MbCs/F6JK0uSRDOhgDlcCxF3R3LkqQ/eyyM4AjE+KLY6aei6j/F6hy5rSvLFoDAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYxdAR+oqPDS7zSUuZny4vrAUsqJt8jcR3MMIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIE+EMuSG2Cb05eLWCdAGIu3BcmggAQQE7wDkRaV9w14sPR2iXsaMAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+bXzt6x2ihVjuL4djVxsWWLt60vLl7bsHAx23Zf7UfSIYL6grI2V6iTtFEV4YppnT+jHq07oU+wsasCYBnrhGrBTAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYy7XB+WFrsqm7Qs0Gi9jDwbceZQWZvOsH6MIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIE+EHERwOSL8JBMOaPAKywIL9WggAQQQHRSxU2L/5pOk9k+1MChQgAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+2pxcjKYQaCqYHJ7NrJkbTdutonvhJ1ZHlayPhTJecw0OtTK5fAx2rwwUsRQaMYeT+BuWWGCEvkwQkbMIYWhrhUzAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYa3Qe+EwS1T2VRYvbOaN/tQRCwQfy+066gMIAGCSqGSIb3DQEHATAdBglghkgBZQMEAQIE+EB4Es/GYPUJ2f3F6BZWL1caggAQQ/0qaUyUkb0cnu0mAzH/IzAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcmhgcYCAQOgUaFPMAkGByqGSM49AgEDQgAE+Ok8M+RlivdV5+eJMInGlk3bHDp+lCY75r3WG21etOh5zve1MjlxIWW14MZJt7k0E+jCl1QbeyzX1EB+hZOvxTvzAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEZMFQwUjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQg+TgAwCMxitIJmcLt07j37L1QzqlGlzSxGJM4Qk11iQ8IwgAYJKoZIhvcNAQcBMB0G+CWCGSAFlAwQBFgQQg/zLqFBHcCRnkyr9u3F+kqCABBA68WRoFD0KTi05iwYtRoZ1+AAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+w09hnWB1xWff+NM3YGEioZc9d0bZdLbsj5i5DGRqp7YGmucLntyjXYvSyyaoKZYI+X3/mgwjkOlpeoPeaEUjngTAVBgYrgQQBCwAwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQg8WuO+nYYrxosyAxA4FdwxBWuCCN4DWm7QW1v3ohgo7JswgAYJKoZIhvcNAQcBMB0GCWCG+SAFlAwQBFgQQr2Lf2U4dBL5fMJ3yRiL7eKCABBBxV6zUhoCpDylAiZf7FtWRAAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+RkIgB0n1JaTuswpI3CvbFo7HM9esPnajZpjVNEbkIWncwq203luV2poABI1yd3f0+8ID1LlhSgzVBh4jLIheAOzAVBgYrgQQBCwEwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgHezE+008dCR0GpVxBjcHOLALlpcBiJYemn5/Gd0Q8hZIwgAYJKoZIhvcNAQcBMB0GCWCG+SAFlAwQBFgQQwHGxX3MzxEAb7tF4q5aZZaCABBArUt/vthlUC+CiE5ZF5n17AAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+TM9a8AkCWY+1PPUiv8YRTDP8CkM8TVguegkv37otYosgoLj5S0IJ5qLuVDmIsrEh+JV/1M7k3jVghCh8Fk4N74zAVBgYrgQQBCwIwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgTUP++ohzITwtreuhRAYCS3xgKjC224WJWNWl71e12tdgwgAYJKoZIhvcNAQcBMB0GCWCG+SAFlAwQBFgQQcToIk1W67hLKdYiS0Rv0sqCABBDS/GmIldQoA+y0dpNdb1NiAAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+l87IK6nNVNDGFqQ7aEqqfIDhukqCFWubjY4V1Na8GIiaAZ9mv9Flk8bqqEaVI4Z3+eZiVZ70fYeeXDQK6IP/HSjAVBgYrgQQBCwMwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgst2++ObTvFwlaVdcaVUOGT5dNljVOgFss2P/7HdoxzxkwgAYJKoZIhvcNAQcBMB0GCWCG+SAFlAwQBFgQQ/YZVdo7qn1Ur5IvS8krveaCABBCXs1En2ICNad4m9RUv95aYAAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdGhgc4CAQOgUaFPMAkGByqGSM49AgEDQgAE+CVsIWAAIOjLr6akT92E3GnGkuGu/uiV/O5CORs0gieMXoIg00XoVXFvF97w3lIR5+LSeReKZfeCeqg3sHQ5B0YTAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEtMFwwWjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQo+aNVpE6iz37lKYrP5eFAktgCDq1874/V23eeiVLz8EeUIu4LLL+zkdzCABgkqhkiG+9w0BBwEwHQYJYIZIAWUDBAEqBBCldPVP1En+B7twjFmIf9gCoIAEEBQeL+Df7Z0V+01XPBAXNQAkAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+2acvj/2X41n/UnA+AbfP9FdqBsmPKhzSSJWoqjBc+LvvrEFyv/kvRzF5BVpU9lcq+OoUA899tXIMMqHezDzN/wDAVBgYrgQQBCwAwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQoRj7S+0EOP5XoG8DQWywlF3Iys25+1UTQq4KZddk4EvvxuUZ5hbw40+TCABgkqhkiG9w0B+BwEwHQYJYIZIAWUDBAEqBBDy3jMIbY+L6/nhjGQRmpBDoIAEEBSpJEtVjBwaeZuQ+GPdO3B8AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+BherrKZIQMHjt/tf6gpZ0LI7MkBGoDMPFY9VvgrJ9yn8Fy4NAaXBLL3q2V+maZtY+HjTVMq+nCTgNU6TRtk5eOzAVBgYrgQQBCwEwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQotN9/+b6c9aJnElELnDoL8Vl/bD+GbxARw9r61Mffaje2DwvMurVk9ljCABgkqhkiG9w0B+BwEwHQYJYIZIAWUDBAEqBBBadkWusaPBLljDA2uaOJvioIAEEMK6XkBQQRUfjkzF+zFyWDVsAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+OCdlCnQEZi1ty887EKww+iZwZUjLZ3IdS+6AAnDLb4D/OpoRobmYD0TT1ihcCYdO+Hfs2y7WUskWWEpItGdkpDzAVBgYrgQQBCwIwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQoRlHj+cDX/7zCtOM+ZdjGFepFWH9bfrXopmSINNL9CCH/KHemYSr2ByDCABgkqhkiG9w0B+BwEwHQYJYIZIAWUDBAEqBBBFdMNJ+RS/WQnalqWS0KG8oIAEEBvHcKhUPjh+JPcq+Kr6LxYUAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+tL64aZ6HovPynNbmtlIokrDIicvGdomhqjqG+Tv6Ji9Wcr/1PIH/KMjxxaIMV/Ou+RHR31qHADGry+ZZ/mXizITAVBgYrgQQBCwMwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQomSyh+09xxkpMeODlDWDLi3jrq4NaVOeQVB/E7JnwN4MJuTHDu40fxDDCABgkqhkiG9w0B+BwEwHQYJYIZIAWUDBAEqBBD+Ca0nBbH4qbQL+tgVIx8zoIAEEJ5SxJ+Aw8QtKUQG+DGcE0CAAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+gZKsGO/M7D9GqyaISh/qOOYm3uM8GD/2u9E864FiDdAE+SHPOYG8xgx0kQxxTzfA+6EvnXbfhWOT4hmiEL++bTjAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+NYzMGrZJ76aNAOtU7dsLIJ0Itg5SbSI3MIAGCSqGSIb3DQEHATAVBgkqhkiG9n0H+QgoECI7LjKaujpDRoIAECBjJbDivGx2IBAjiOnGDfU6u/gAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+IBqf10T8dNW1hxj9HMSoNIaaZtl8RPZfloQfgihKvYg9aXuE+Q5RdC4GV/f1pLxd+JgESlcl/3mOBKohqAvMMmjAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYNUnq+GaHHtDTCdltY4G7FOyTM1oiWP9qoMIAGCSqGSIb3DQEHATAVBgkqhkiG9n0HQgoE+CBjyxYOtxWvZoIAECLyMrarzIhWlBAh72cydBQhIyAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+rP25FELCbEM5jBWeeU2rzq0sN3aYACNAznb2u9QA+8FYKUeifEPXN4FAmHZWO0BT+Lb8XF9w84Ra0lA7INV1RmzAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYXzBl+/qAahtKaYTCUeb3AYTRyBGnk8RVNMIAGCSqGSIb3DQEHATAVBgkqhkiG9n0HQgoE+CGEmLKtt35HqoIAECAKttXW1125kBAh2WTqti5Y+mAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+rwn4M/Ame+7UEZmJk3NWyLzfnMvt8CyvRDk3/lILCKRGv8yFi+NMxY0Pnkl/w3lJ+jmtBSTzyl0FH86yVujnbmTAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY4fPV+R/Fa5/ielrY0nFK3UbU/+FwdKIsHMIAGCSqGSIb3DQEHATAVBgkqhkiG9n0HQgoE+CA5LtVn2hR9FoIAECAUAcOqFIJEQBAhb3SxGXkA89wAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+vNqu+oATVqFdAiZIOmdcprvBVhSplrBCBvhPkP25O/Z9o+O9xsnpVZE2vAgftwSA+KjPw8sqERpPiYUGOxLxdmjAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYxYV9+jFhY6lIpkXN8AWQJQkNiTDXK9XWSMIAGCSqGSIb3DQEHATAVBgkqhkiG9n0HQgoE+CP4ABapU/5IzoIAECLUree4PSI1DBAgyAi+gSInDhAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+d4LCQ76At7ug9FqIfvw+Kg2IzG3K8jYD53kqVR0awkV3eRIV/CY+nkyI/x68Gc2y+UOBeE3BykJ9yaARY9ZuQzzAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+AELGlK0LQ4aMsSoIKeMfZcxcGnqwzLXpMIAGCSqGSIb3DQEHATAfBgsqgwiMmks9+AQEBAgQQs6uUmpJ+F2+POVFx59Gbz6CABBASwae11oKDmhaMFreD14ehAAAAAAAA+AAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+6r53ZnLlnO/GpWmxUOvb6FMAyVL99UzfwaPd6jGIkV4/D3/NJLoUAEPjwJwUey6C+NIYyCTlMFertMzhffcgBMzAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYsSSC+Png7v6TOhSlBD5yjAJLv/sLQTMLkMIAGCSqGSIb3DQEHATAfBgsqgwiMmks9AQEB+AgQQ6U90bpZgD/3RS5cOJ1YcNqCABBCFcGfBZv8eoqkxM5j3D6zVAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+Cce5H6wStNqCrn17cOPxAP41QTpUufb5aC9/sWUDkDNgpmm4SNl3S5DX0Ouut+EP+YhBo9YcN9H4jvLUIjWCYpDAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYjI3A+1ntItCnh+iM/f7sBdkjkcsZSUG0FMIAGCSqGSIb3DQEHATAfBgsqgwiMmks9AQEB+AgQQxE+F/5gZnVd42/D7Aedt/qCABBBkwbOOfBBol40r2KyPQjNYAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+sjMsUNnWUgIDCJrffiDuZwu9fpq7A6X5qUIJojvFQ4352D3aCXEenPRQA4g1lueU+clTZ4AEEHsBFK9Kf9OOuFjAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYCd15+aJPiSbeZi0tpLI4bQcQLO1YnO2ihMIAGCSqGSIb3DQEHATAfBgsqgwiMmks9AQEB+AgQQ3T5S3rr68icu+5ADc9ybuKCABBALA6cpuh3nLIQ4Vk1VD7JdAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+ng41pqn3itTDqqaQ/6J/owpC8pEUG46nDlZuRe1Kf4dehXB4KXxYilDPx/30iJCr+wwzOMEqRtCSY3I9nTWYt6TAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY6qCH+eLq93ENMbdTZ6MnFf+6BZSEDyF0CMIAGCSqGSIb3DQEHATAfBgsqgwiMmks9AQEB+AgQQmG/EqB4VbniISb63N/G5U6CABBBab0am0A2aCM7JL8XHHg2tAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+OdnaWWIsuEaRaXJQPvhGSbUuQqJjFcEFusalzcbMIidDhpeqQg4mWv72SjzG58vU+8iH0V8Kjson8PJUNsRRfTDAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+LH4G/It9nl67Zl+enoPNBPZJerfVCMogMIAGCSqGSIb3DQEHATAZBggqhkiG9w0D+AjANAgE6BAgh8q9PcJRPNKCABAi3G7yWwrgscwQIY7upGF32NTUAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+e1+bTztfsOpSYLn+adO9a1Ooem6HFGvV8bBppQwi9F+kGu1rTVCQx9qUzgmHETDK+54R1V1V5a2TOUR8xdu1g9TAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYovcD+YG2K0JYETFJorRoHtgYjKkSi7egYMIAGCSqGSIb3DQEHATAZBggqhkiG9w0DAjAN+AgE6BAjUb4iafUjk8aCABAhRNyVhrpHzmwQI8XDonroDuXYAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+FoMnzbf1oXZjFOTtqUDsSWymBrNmHzO3gL+m01VIGnkiFj8EejvCxrPNjabce6TC+chKlhlDLxdhmb8Mtt52DIjAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYkDI++rlPoNxu7WoPB9Sgsejhw94pY7oapMIAGCSqGSIb3DQEHATAZBggqhkiG9w0DAjAN+AgE6BAiwmRI7w+U7w6CABAhqrIOiZHWGUAQIzOlO4nvZn5gAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE++R9I7CJUNznNxDGiC8cR9qIXZyVK/0clHFtZctKwMQeJ8BEzsFtmAiA97sf9H6E3+4A8YwgrzeQod4TVDJ0yw3jAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY8/8O+JKxrUwuqe0mvpJewQk5BCmd/TTsnMIAGCSqGSIb3DQEHATAZBggqhkiG9w0DAjAN+AgE6BAiHKjbBK3DWiaCABAhCm9TeCCBdowQIbAtKUdN4fn4AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+Mhh6OfMp/kG2xWP4P5p443DBYkcgodaH5+P7eaUuKzb+BCfrXuHhVapcWSEWBpYf+IXUYk6NO8atarZ2ruCaznjAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYIQek+muKfqohyH3VwQgfnQWBDEgHt8VKnMIAGCSqGSIb3DQEHATAZBggqhkiG9w0DAjAN+AgE6BAhBAkTfDryDRKCABAicY2DaqZeobAQIpdbjoyUY04cAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+FKrA/1HA1yvJiDynCweQ1qr8BohYtxNa3KjCGZ+fwbzEMB+FP4aqVB9WbRIaE6MO+cAPnbew9gsoj4aZyJ7Ik5zAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+xEEeQbm5Q2NfSU21+fPj8vsSEG/vq3EAMIAGCSqGSIb3DQEHATANBglghkgBZQME+AQEEAKCABBDUnRs2t79NIjljXEwG/sAMAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+1EMpS8ICvJ7JKEnf5i9vTmMfK5yA2urR2+Ta3x6rG/flWRU4p8taH7niLkbMkITK+ADwwoYTidQpBGuozWCayrDAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYM21g+lr4ePPZtMp5Zy3NtbA73j+idtew6MIAGCSqGSIb3DQEHATANBglghkgBZQMEAQEE+AKCABBCfRV7RN4fpIH3Yg+NgyCTzAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+98WX7RssBk6i+iQ2TFGU7bf5JzCOrs413QXgWn8ehHpyPYbqECR8sO3yb/ThrpHO+ypQcFTyAJSha2BHbmXGnCjAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYpeNN+d4eEr09mBNEFQsBwXPlDV52OW5ybMIAGCSqGSIb3DQEHATANBglghkgBZQMEAQEE+AKCABBDEYG1WVg9zU+GtID6Z62MVAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+SdXuTrXQjAV8G+iLGPSGUc7J7/aCLT/w+ySjN3c+ps1bTgU9nDnrvZ0GyaJpMNGl+gqmkDg2Ju4BD6+qoLX4LZzAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYyew3+isNLa6VVuOLExAbh6/fneOj2HCamMIAGCSqGSIb3DQEHATANBglghkgBZQMEAQEE+AKCABBDSe/7UBhgGj8KRH39gK5xWAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+RLdf7BdnFnkVG+Q7V9kfmR/FlJTDBd9equqyvQIPeY4/+d6KIkwrjZ1uGRGnOsH++L5+uawKIXwqzCDzIF8Nj8jAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYaWsb+/pW8CgyafVs7KfkXryp33BN7XRmaMIAGCSqGSIb3DQEHATANBglghkgBZQMEAQEE+AKCABBAncG9TyQN/m4tJYLSBo3qAAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcmhgcYCAQOgUaFPMAkGByqGSM49AgEDQgAE+Zw0Y6ktKR60a3WTOG72NTQc1IKKaMQTK71GYgOjOq5uVW1lZHglu4GPBrIM9hGt2+SsFIP5KvoD9ts7Btyt5wEDAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEZMFQwUjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQg+31p1xsISU0KVexH6MSvU4u+MDhtuYz6ooa/Cky8crDgwgAYJKoZIhvcNAQcBMA0G+CWCGSAFlAwQBFQQAoIAEEMan0t82ejdJdRnOLHPvi44AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+IVubQBzSV1SSvJk2ab3mxJ7AkxR2UgilIvujEWK4iGBE2iODGULyg5VKRZArZbyK+OEXOZEBr29QIHjffEuAHLTAVBgYrgQQBCwAwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgxJ7R+HSSWI3wgOljuIywmFA4SSxOrqa7URLF8az6cvmwwgAYJKoZIhvcNAQcBMA0GCWCG+SAFlAwQBFQQAoIAEEDjiS0Z7OhplQiXkzZD4DGAAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+6FLvzaA0WyjIgi7epe2VkiCoTVb4ky2E23AG6u4NkDjbvlG/B+MF0TEmtW5SxAJD+qUI4EghJcGX2BQ4+UgBi3jAVBgYrgQQBCwEwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgfLT2+rgPa4JlW8TL8YW30M8m+wxACPAMeAT8TDDYO0WAwgAYJKoZIhvcNAQcBMA0GCWCG+SAFlAwQBFQQAoIAEEG/qcW4KawEUK0HeOpq4ZdEAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+DVwSKP2BsrinZaF4tlt7ZjRq59l05bF6PqY+9Z0fYJ03dXAM8VsSyEAVqTxbHNTj+nFWE2C8miuA7r7c8M5JuhjAVBgYrgQQBCwIwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgQ134+rLBOz3ljqJYBuKTzB2BHi2WdJgR3viPkooC/998wgAYJKoZIhvcNAQcBMA0GCWCG+SAFlAwQBFQQAoIAEEOMBdbXo7mwTCJLyOU6MvtwAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcahgcMCAQOgUaFPMAkGByqGSM49AgEDQgAE+xGLdCxmNHwUvIU7BICPdFvIo0+owMOU05HygkoMMIQZxvOVTpzS/2GY1wc7SRyeN+5+fOiZCN7v8E9CSCKsTJDzAVBgYrgQQBCwMwCwYJYIZIAWUDBAEZMFQwUjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQgRoQC+VMoHH5IbQzg3vS9Aw3mCGQ36uKpWrs6EjSryfxkwgAYJKoZIhvcNAQcBMA0GCWCG+SAFlAwQBFQQAoIAEEB6FNYftgZazlVG+9VmWGlgAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgdGhgc4CAQOgUaFPMAkGByqGSM49AgEDQgAE+Mf4KLGdDuedxTSZmmF4J9+2pQh8AOrQJnR4TVmiXLCddrlj2fJPwB4n74fHNmXYe+T1A35DVGtXUWYZaFbHfh0TAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEtMFwwWjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQo+aJolf029cD3lfGip8jgUWQoK+FbU1Wh7Ir6Vn6+PDLZVFHch+RsgIjCABgkqhkiG+9w0BBwEwDQYJYIZIAWUDBAEpBACggAQQZB1VdS5wydzSW/Oow/7MvwAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+zLajMBAMGJiYDQIoeCr+5Pf1m4rVvZPStzJteLEolwrapZXPes5IlBZQ03k/9fab+vAmcnWjP6murHML2UjKsLTAVBgYrgQQBCwAwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQoLxA2+jh7WPxVl57x8E/k0DgyWzErCeCuQgNIwS48DtIBoYos5A98KITCABgkqhkiG9w0B+BwEwDQYJYIZIAWUDBAEpBACggAQQ6a85Gcfmaz8GUP3Vs15n3AAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+MfV71Ca9sLSoOaL/UGNPH8+4r8AG2tGkOtQ0S7g09BCAuhqoztWPThZkcPoFJTkD+DEojhHUvlRorZFvjFw7NVTAVBgYrgQQBCwEwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQopfRd+PBICOge2lZsJZPJsxoldBjUCsBOHuEBEPERic0NmCbWWRO8YOjCABgkqhkiG9w0B+BwEwDQYJYIZIAWUDBAEpBACggAQQkAAwrvxK9Mb9R/MuXaJkYQAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+63G8cn63oUtF1QgvckQ7DdPtD8oRPAYcymF7avKPqzpKbIHm6mFNOparhbNNhaLn+zJB+w0oQVJe85AL09eEOYzAVBgYrgQQBCwIwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQox/vj+Z7M5hgXcSmGsqoW8aOCUX8GdPZRFpUhitVnjZ1Q1GDoLZwwM0zCABgkqhkiG9w0B+BwEwDQYJYIZIAWUDBAEpBACggAQQcX4IWuRvjN7CpVcGkj3izgAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgc6hgcsCAQOgUaFPMAkGByqGSM49AgEDQgAE+efrLjBFWs/12kCjSqL03WEaPlRtpgTyLpCbPDc29nFBFcIiIR62cRUpZGuo64rAE+n69T0nmkW1f0gPVeKvbMNTAVBgYrgQQBCwMwCwYJYIZIAWUDBAEtMFwwWjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQo3qaN+UAxakRXZmNQR0PIxxQg56V5MrhxKtMAVIyQccYPNhsM/Lap74jCABgkqhkiG9w0B+BwEwDQYJYIZIAWUDBAEpBACggAQQK8er40y4+VxfVyAFHD+eqAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgcGhgb4CAQOgUaFPMAkGByqGSM49AgEDQgAE+X1veoS45kW4a0IppN/WS/hnK5xPY91Vy2jIw+edev/HiAVaebyN9BapW4YeELHxt+VYcIXZZ/o4rW/jDcAXUFSzAYBgkrgQUQhkg/AAIwCwYJYIZIAWUDBAEFMEwwSjAu+MCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY+hyqSIsSGvtV3T0H9AXWRdlhJI6Ri5eZFMIAGCSqGSIb3DQEHATAMBggDojEFAwEJ+AQQAoIAEECAF2uY94Anbr0lhM4tYzdoAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+8zN8f1hdBCp7Tk1xVJWEw5WmsdQ+AY0hpnQL9FMvu3/BUKTx59SfTyi+VI8q7wtK+W+OqqFhY4qb7mSY0qFdttTAVBgYrgQQBCwAwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYSWYc+zbbIQhLX251LeuCLfHPa9/r+pobmMIAGCSqGSIb3DQEHATAMBggDojEFAwEJAQQA+oIAEEGMid1kywhmcLeRfdeSIXIMAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+47qJqR+LpLp69XPlUC//5b/pQOrMadqMPJvd7siWpZvRukn5BuC103f2g9BKGt1z+o/WlNfQkDC/XuyuUYEFlMTAVBgYrgQQBCwEwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYcCIH+Mrt5GhLAOqbBiLh0LzSBn1okQT72MIAGCSqGSIb3DQEHATAMBggDojEFAwEJAQQA+oIAEEDyfyYVCDlUQLaWR5WdNwt0AAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+G2RJOMvnDAjbk1QvmNXcXDWus++MXwfu7Rsvp4YaFzLh9Z4gVpqrJjp114VYKfou+G+AnSSu7vkPyU7Q2uo/LnjAVBgYrgQQBCwIwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQYbPxf+nTi9+GDRoFAoBnUugDcyzzRW8riXMIAGCSqGSIb3DQEHATAMBggDojEFAwEJAQQA+oIAEEIJKU14QW/1QFQVqqOL/DroAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxgb6hgbsCAQOgUaFPMAkGByqGSM49AgEDQgAE+8dNbdrNgOMn0mfcRBiCu4CSN71lr69qTZtLTIK4wyM/8w9FbLG7dT3ohPVBcyvA3+krJx+nWZ0KGEx/UbgvlYeTAVBgYrgQQBCwMwCwYJYIZIAWUDBAEFMEwwSjAuMCEx+HzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20CCQCX+mbx0uA+AAQY6mbx+Dm6+bvjDHV9Nt64Z7iu99EGIKmdZMIAGCSqGSIb3DQEHATAMBggDojEFAwEJAQQA+oIAEEL2k2+wh+Y/7eEwILV26SN0AAAAAAAAAAAAA+-----END CMS-----
+ tests/files/cms-enveloped-kekri-data.pem view
@@ -0,0 +1,61 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxOaI3AgEEMAMEATAwCwYJYIZIAWUDBAEZBCDo+yhBOcZqwXnAfeiaK52eDsGrKOL85GeJrvvcY9FRBmjCABgkqhkiG9w0BBwEwFAYI+KoZIhvcNAwcECLWFN6cve5wEoIAECM40Ls/E6HzYBAhHv8Jip9pQvAAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBgQ+8hblrCFtOKfFY9dCk8aI6gPI38gNxb4wgAYJKoZIhvcNAQcBMB0GCWCGSAFlAwQB+AgQQgKp1jEsgtoJBBCTFFr7ZW6CABBAAueS9/IDW7qkoAAnwJa6sAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxOaI3AgEEMAMEATAwCwYJYIZIAWUDBAEZBCCJ+KIFyLbTVDm9cRYy8OhTixgVwfO872a/hEec+o1e1cjCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAEWBBAITkDG0u/WajB6DDvRausgoIAEEJdaa0ftXKLB3M5rjxbGEU8A+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxQaI/AgEEMAMEATAwCwYJYIZIAWUDBAEtBCha+4D9Jno+cmNYSVvWeOM6m2NgfmddUwIgyoWpbOgd727KQWIBV1AudMIAGCSqGSIb3+DQEHATAdBglghkgBZQMEASoEEMe06a1kNuV6tFSHGF2wefGggAQQPMxzB77x7i8v+S1HjzJQPwQAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBiA++8fCiPHEAnws9D7EEaXyprJ5ISGc5WwwgAYJKoZIhvcNAQcBMBUGCSqGSIb2fQdC+CgQI9WVhp5WQBPaggAQIxOvRdqE49+MECORNVFB5xgh/AAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBhA+1PEjPE41AFYjPsRETxh1qYmsD81bJoIwgAYJKoZIhvcNAQcBMB8GCyqDCIyaSz0B+AQECBBB9knlsBp0PF8VO/Vq6I3dMoIAEEC8p1Q7tahAgvESPigwUWUQAAAAAAAAA+AAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBgc+46l8i23oY5lls3xc/VZMqO/K/8XUMhkwgAYJKoZIhvcNAQcBMBkGCCqGSIb3DQMC+MA0CAToECJw92yJUyRuboIAECHT/xgVF+GKdBAhwAQsKUzbU+AAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBhy+j3gZd5BrG1Uiyck1oCi/lbnFbm5ozFEwgAYJKoZIhvcNAQcBMA0GCWCGSAFlAwQB+AQQAoIAEELsU0eo7AL8kWQrzaV03dWkAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxOaI3AgEEMAMEATAwCwYJYIZIAWUDBAEZBCAq+fkHN7tQE8sIHZBwnAlhauh6soXEJ1rHgYZvK4ZZnxjCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEVBACggAQQYJueGyUSbvpvVwZPwuUDSAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxQaI/AgEEMAMEATAwCwYJYIZIAWUDBAEtBCgA+KHatZAiCdMZkRJ5wqivOUiTtKGmiQtXN3QqJJJ5aLRoqEJkjz3qWMIAGCSqGSIb3+DQEHATANBglghkgBZQMEASkEAKCABBComLWQrUf7us505ifmrkIvAAAAAAAAAAAA+AA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQIxMaIvAgEEMAMEATAwCwYJYIZIAWUDBAEFBBg1+ZjFOAC8jaQGoniHQjMwwg8JAzXZ5uhkwgAYJKoZIhvcNAQcBMAwGCAOiMQUDAQkB+BACggAQQvuQ6f0WV5jQiaoQDFib11QAAAAAAAAAAAAA=+-----END CMS-----
+ tests/files/cms-enveloped-ktri-data.pem view
@@ -0,0 +1,190 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYAS+MzqnxC+lVY9dcFRygAur41ttkbkparQlMeSq9yXnFA/vs+9wJFFYjl/RKfv76Mp++YM90kPizFeXfMIpCZ+r3+xcE0JQPdRiMICtQXugEJcwRH/YOGM37EFG6sJlNaC8i+VCfO5Uf7zx51064dPkj2aNvgQhX27ZT7dmGWprVbozCABgkqhkiG9w0BBwEwFAYI+KoZIhvcNAwcECMLnvaBuUHSpoIAECG+eWrhA2KNoBAg3CK4ewLznOQAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYA2+f8wQrnm1bik0Ayomu5qJmjDaK/dyLLWXROHH31QoF/2dOiuPYWlFgYdY0sRPDHBJ+lVDtpq0BLBkK8VG4XQ/YhzhGkFNzlTgsrxdMjrd7aBmbQWm5sxsmFrirw2BtVF2I+tQhFoCAfhBt+b/PitKQBfFW7Ky2MklqGCdqYzJqO8zCABgkqhkiG9w0BBwEwFAYI+KoZIhvcNAwcECMLV/HRrNrefoIAECDVAEsd2agZbBAg+Ifn0uwVIsgAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYBU+MLcA5XvSs4vKd7mP1C2b6eauX9EJvhqC33rQreL499JKamowq4DiwfHTuSKOpriV++2IGVC7waWS2NTItCwbaKzp/vD2GZp/QJM1W3CsYyoL+3NrTHQNrsi1lwIdhhuUX+SpJA4Ep5Z3cFSf6PzfJJ/O+1lZYvw9EX52LB020wGjCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAECBBArtuzIoHJMe5PxCGdiU4oQoIAEEOWPlgTT81fpjrOFvFMQDtcA+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYB3+cVqZNKioiVkMnWltmseu68l9iewiVjOWBmN1KMFVniFKWs9yAytReEQn+9UxC2co+TBtnDEf3F5b49/TGE5r0f35NNPjq5fUEasyHSxEdyQtXoTWSB3dVACHajD04jHCX++cRcGGdZHvC3tsk4701FBHj9p3wwZAdbL5XzvvYPvDCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAECBBAC2LihEql1ziAXXDoufcIcoIAEEHoRrnR1WOV+rdwzSi3pVbYA+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYAA+/9BE5hsBwMbQKNE4y5VqvoalUMA9KLmBWGkNkTC9u6gLYbUAIiGM6lPl4tMkQI1Z+kQI6Mhce9qlcScfvs4+F5S/TdHroojb+63UQegUN5VQP3rO42JVJ9zTA/ai1Qtib+f4MKtrpwLcNZf3t2VC92pUpijNT3u10MUGCoSOegqzCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAEWBBDESQ54qb/ShKAkOsNIumAcoIAEEHo2fCHz6SHY/Sv8hypILG4A+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYCb+/ANiTPLkI+P9HKKjyt9JeT5ekyYp5RMREW4OJncVvDtcol4SjAFtcFW2bud+WRvA+qH80qw7Z0HCwUNcGJVKDCBXEhbLv7ft3Y8ii/i+CrtN07KnK4hnl304U4/B6LbaM+gwhx3zJU3eA+Qu7Aih9mDS/ZrzFpxmP5cFkdidsiKTCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAEWBBDXgCwZw8Slrzewlyr9pavGoIAEEH4z5teHfvFrCfJ30jzFjC0A+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYBz+5yZVvclHOaLiwiKD5CLmQA2xpCcGPr5yh/ToKjjgufVuKurl6iNkAYfKbmHDzOyK+MbbpYtpiK6+AWcO+JtHJZJ2/4P1JYBJ67amdR20BvKm5X15q8Z1K015U2g1iIlUe+8Jzg2EwalofNGnR8vcBrhnXdaOi1lnhl79QBAbvg5zCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAEqBBDSlU6mD+hEt2wJ77zZ+Mw9oIAEEMCfkTPDAb48ChJkr4pyIIEA+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYCZ+QhZLfWRQohqi11gGijjooRnoAl1r1Q+qnXUTEy39Z0riCBmMPSyfrxtnKbQq4Ytp+zUKBxksMsSEJdO0Itn9cfgYoSAaz2j+wLgMMRQhAskNdUCRNY5wj0rQDrJEp4Xr0+hTovfkk9JjITxQ/rcjcyKPCjMYhaAe96boq0gf5VezCABgkqhkiG9w0BBwEwHQYJ+YIZIAWUDBAEqBBAMMb42cklST7NGdvqEKnxMoIAEEEHy3lZS0S0aRn7s9ZBZDwUA+AAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYAK+FaLR4MUy6vjIf1GMZ/ljgEjijfF0eV8m5sML+1SJY09/WZ02g4DDZ56eEVlHV8SP+GnkxU98apWm7LBj0acNsQtuTGgNRCxsiuxVtqmFx/6wcW26T5Hnz3qgFFGuXQz3G+6DamGqw5DcZhcKsa5rKcGE9LV84ih7U8648TbjTO3jCABgkqhkiG9w0BBwEwFQYJ+KoZIhvZ9B0IKBAgBIxu0kwTj/aCABAgesQ0q46GB7wQIKigIcZYvAwEAAAAAAAAA+AAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYAu+zqzksttaYanHXGNdA1lbrdJMDO19mqIIq0bNuNnNRhi8gkVqiaYSx4jWWihynxvp+EYj2+VQ9DSYWAN6A5A4RoA5xm7pitXl/lhBdgpe6fCLFwXs5HY8LKkqzIn3MMWOf+j1vCuZjuUZT1yvZuhnGOk5cua468OHZjF3SCjdCxAjCABgkqhkiG9w0BBwEwFQYJ+KoZIhvZ9B0IKBAh4qFkOcAm0eaCABAhUZQxFQ+ja7wQIhxDypyzXMeYAAAAAAAAA+AAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYAp+7601Ats7XLYQopjKo4X92vK1vpqiV9fo8qcAd6Fo61SEgAPTV4ynHOYzUjDHCTbv+zFQ6M80t5igMpppmcMc6Rs1y/atpMIWYMTDvCFxLN9iYvwbwq5xuTgsKpBs2s/OJ+FRDkSOEsBWGHhlaa0JDzfSqPFxO0UnlpkaWxhm56yzCABgkqhkiG9w0BBwEwHwYL+KoMIjJpLPQEBAQIEELe9YYPj5Ndvaj2thJc4oZaggAQQySVpbfUQPXsnKZ2j3Clj+DwAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYCf+M58lS+xD60ThkV/KlhskCQbzYa5tgoMbQaRF1Z5PQoCWH+iueOH83DkwOiatBcX4+4gb46G6aItDCMyG18v3t0x/DnE+FAEsxucG5iQYBjW+AhaK0O6H1/q7i6j9je46A+L2YPjK638yb5y34IRcJJWSFqe31SLQdVAIP/fIFGizCABgkqhkiG9w0BBwEwHwYL+KoMIjJpLPQEBAQIEEOQN2AAuDs3ZKl2RLJb9F0aggAQQFQ2VgsBc8tufBxFTJPlw+RAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYBY+EtN5fKcxlW4w+XFlXXCuU+MKgxn6hgDXyfOSNaInIvlTvJ+H1RZfykFo7FdzPvNn+VLVoegnJSEwdxe/zdl3sxN/V8LIS3fs4mCXQIyA5tsrYHjTHMYojCTmzpWvy2/JF+L2auV3+kjaIve1uEeIxk0oHmPmIPiMDb13eoLvdJ/DCABgkqhkiG9w0BBwEwGQYI+KoZIhvcNAwIwDQIBOgQIQ0hQOhF2kRyggAQIJPO3RK4NI88ECE1+ZS8fRrBhAAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYBi+KZHomluvWpI3YchpacGbj7OFsB6Q5sETL8WzJAiLAgoYOwvWpVYb2PbgVkEpGgs1+a6xEyz2afKXKdPEc4DK3sGfnM3Lz9Txz8In3d1jAQBXLZl8EoaZQUO5VeD6IvYil+GU8aPgr+Snu09kKqYdkyNDlCUvqP1ncYNqmFLYNAkjCABgkqhkiG9w0BBwEwGQYI+KoZIhvcNAwIwDQIBOgQIlrnglIwapsuggAQI3kKGphwDsXEECC1RfvxIP2+aAAAA+AAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYBq+P+U1MaRiR82vFKuH6y/apL26VHmkoxNlsODr1UxSdqpLLnk2NHUcef2J6ZL/NsCQ+J84yKvcWzw6hqS9II9e4KK51MNWjf40T/fWOfDHiVZ0/xc+7AEhiA7jdow2Oiw8F+FQBZmob310njFzCWpyoJv06I6WhI/2qEuzkBZpjlbzCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEBBACggAQQdRO+ksa4ijjcxJCg53aCdQAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYAg+vYGS4TlPOZ9ttdCX6p8D/JMEoajtCe0HeTLsax2Skdz9VnEyhrGUhqfy43qrraO2+N7+o/tHCGa7BpFvM2UfzsF/0KJZsyv9G7txUBEEhYmjMVsZ2QYwAnIgTH95ucWnY+We04KNRqbxV52EUs1LgOZSLTzbLrvGXPvtsSBKVzzDCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEBBACggAQQyePr0+JPuJ2d0zaw1HqCeAAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYAE+RECUSsy4tlI9UsIPxUEx6GRBeb0YhlZP6HqUwU2zx76/cm5M3aD6n8XTcNkl8mxG+7OhGi0884x3uDjihq3YzLgLKTgQ5htnu8frGtAMe4s8MkZ7i3ECBM4HdSLNesvwY+hyJ2fYatRRaTCvYVGTcQ+mrIvRRSYVokLRZzwFqpGjCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEVBACggAQQnWARpt02jOj8d2M70gXM4QAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYBv+7rTFST/R7PDsDVm17MQArHteoRUewIAVkbWkd8iB5FFJXSXGxtIs+DtpUqq91z0k+O5hL4wIzRYUebVvE9XMot2kZQ2PAymX5B1EM0MRc2XFrBOi1Vkq6jCEdow4yedNF+UL3IhtT9YTErSvK+2c2r/GeBy5oAM+qosCZpk9c2kDCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEVBACggAQQ2QnNQVPLo9Um6plRZP9kogAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYB7+eybuQWl10p2u2XcrGTHeMYFCvcYNO4NLxFogIRppPRehQhtXGtO38FhjYuj0eNZO+XP7oaJJFuVTir+YDZdhmUuuDgYKAWmpVEiAhqQBh5ZX6k8HY+h1vdCxs6M27ZXyG+zsihIjxXoJLZh7yXcZMHJOrl2Oi7bDXRSQ/70JPktDCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEpBACggAQQ5dd51/ShE4xyqxr2v/QcPgAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYBq+ZKmAhfw2n/2PlcbL6jbMB2PNmX0mu/DTqOssmBExIUqq1R31RlOZ5X799R9XUGMk+0CzsolL06nhNFSeFK9wqkbljpA6X2UoVKhMe+xc6YNuDa4EGLNfE+hvmiJtVuTir+HHB6Ke/xRXrPTH713yYQFnJUq7JV3YK0r/yCqO23+jCABgkqhkiG9w0BBwEwDQYJ+YIZIAWUDBAEpBACggAQQ76KVR7qeUnzHe+A0Q0zyWgAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEBBQAEgYBZ+OPbl/h6GxeJE5K+5T4cKBaBd9gDOhkXssk8ahr0ykZkJj6XXEUaae9qFO1wmljDe+bcrMkFtiIwcT74A/qO3sStqEzFtb2qKxqNQg3doFRGQe5H85B7yQI1sPoVUVrvy++4YPj2pUNdUXMvCWB/rQTyu6ZN4rnVzuC4GnuXIwoPjCABgkqhkiG9w0BBwEwDAYI+A6IxBQMBCQEEAKCABBD5WfiWV6XIOjnWwW5F+H91AAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQAxgcgwgcUCAQAwLjAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tAgkA9w3rWSwpN5swDQYJKoZIhvcNAQEHMAAEgYAY+fXUCemSM4z3r4HVcPtqtgHf61MXgH9yoMT2N9MFyolYSvPjUMhrzKHgcmbWmXWG4+Msj3Kp3HknvlT680Sf2PCLSuemJ55OAc4b20Y+E0kawK/Y7Kk6662Ic/4nAANYCT+OPMPuM+qHtRFiCyS7ley4SdvtfkUqa2ylpWjLudiATCABgkqhkiG9w0BBwEwDAYI+A6IxBQMBCQEEAKCABBAnhuYJW+U0RMPg47sdKrbyAAAAAAAAAAAAAA==+-----END CMS-----
+ tests/files/cms-enveloped-pwri-data.pem view
@@ -0,0 +1,48 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxaaNnAgEAoBsGCSqGSIb3DQEFDDAOBAjGmdvq+OEXsfgICCAAwIwYLKoZIhvcNAQkQAwkwFAYIKoZIhvcNAwcECBqgquyBZfiyBCCB+e8Znem9EJyy35ISVMOTFu1zPV6F2Ct+GzQWZLtVl3TCABgkqhkiG9w0BBwEwFAYI+KoZIhvcNAwcECJpn34+AbNP3oIAECOTmA1ktv97ZBAjeeFTRB8REdgAAAAAAAAAA+AAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxcqNwAgEAoBsGCSqGSIb3DQEFDDAOBAg459s2+DKHMTwICCAAwLAYLKoZIhvcNAQkQAwkwHQYJYIZIAWUDBAECBBB5IgIXJlXboBd5+0Ul1hadfBCBmiHy9CnC6/EfTVW8P6WuY7HxO5M9uFpSV7DwNqqra0jCABgkqhkiG+9w0BBwEwHQYJYIZIAWUDBAECBBC9IWSavohVALU9BchvHMY/oIAEEE+4aWPxtady+Yv+HK8CNj+AAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxcqNwAgEAoBsGCSqGSIb3DQEFDDAOBAhMLyN3+9SEyeQICCAAwLAYLKoZIhvcNAQkQAwkwHQYJYIZIAWUDBAEWBBDGLmHK9CHO4OVf+MSaHJ+FaBCACC4FO2u1LYSpaFmGUMRvL3muwJrm4ZTtx/o0/siV7XTCABgkqhkiG+9w0BBwEwHQYJYIZIAWUDBAEWBBC/6PTDboiGM4pcWVHGMtzYoIAEEE6ZQvaKhESN+cVjWjOW3m5AAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxgYOjgYACAQCgGwYJKoZIhvcNAQUMMA4ECJEM+2A6yA3hOAgIIADAsBgsqhkiG9w0BCRADCTAdBglghkgBZQMEASoEECfPlNUFouJ0+0Udxr18xzuAEMAEaXXJinmZg6eAMWQbgmRavVidgxq1M0hm1YSnDSsl+onNA1okI+FC3updaHmEPpJTCABgkqhkiG9w0BBwEwHQYJYIZIAWUDBAEqBBCbfmx6jQ7nNTqb+t98Phnm+oIAEEC50vNvKr11yid3OHO/N68wAAAAAAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxYqNgAgEAoBsGCSqGSIb3DQEFDDAOBAhgcukM+kY+kAAICCAAwJAYLKoZIhvcNAQkQAwkwFQYJKoZIhvZ9B0IKBAgx5KKRkXFNHQQY+mfBZc9TEe7xuV3NUmt1SCpndkttyibGlMIAGCSqGSIb3DQEHATAVBgkqhkiG9n0H+QgoECAweaGrM2MHCoIAECC8nHwrprn5KBAiAVC6ryHwFrwAAAAAAAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxdKNyAgEAoBsGCSqGSIb3DQEFDDAOBAjE4jm++WpxP1AICCAAwLgYLKoZIhvcNAQkQAwkwHwYLKoMIjJpLPQEBAQIEEHxIjOP4CIXt+mT9aFIQEK4sEINlZLo9mVM/SoJ8f+HbGolEzKiof+yb6tDZVb3Qz2Mx+MIAGCSqG+SIb3DQEHATAfBgsqgwiMmks9AQEBAgQQYuHF+dgR4zM+zPT6Oa0yPqCABBDDyZL/+47IMaP6UDK1SNJKkAAAAAAAAAAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHA6CAMIACAQMxZqNkAgEAoBsGCSqGSIb3DQEFDDAOBAjKpTDQ+/9Z3LgICCAAwKAYLKoZIhvcNAQkQAwkwGQYIKoZIhvcNAwIwDQIBOgQIxvVzbjAh+9lkEGKYC5xLJK6gZ0xCt/8V2+pi5bubgOAzryDCABgkqhkiG9w0BBwEwGQYIKoZI+hvcNAwIwDQIBOgQI66/5xUVNrc6ggAQIRVTFbqLaTjsECH6CEr5uv0DJAAAAAAAA+AAAAAA==+-----END CMS-----
+ tests/files/cms-signed-data.pem view
@@ -0,0 +1,125 @@+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcB+oIAkgAQOaGVsbG8sIHdvcmxkDQoAAAAAAACgggIUMIICEDCCAXmgAwIBAgIJAPcN+61ksKTebMA0GCSqGSIb3DQEBCwUAMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh+bXBsZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMxMDkzNTE0WjAhMR8wHQYJ+KoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN+ADCBiQKBgQC0EbaQpZ9hPWNReZsOWuQPdZ/DSsoONN2ONKzAzYeCAy0RuK61z5zG+ehb4Np4/VLNJ1js1eVqq/N+0I8u/Mk1pEzgekb9cuoh25vmHOKxuvA3X9DvvOZS++vWRrs1pcpkO656k5Emw2HmUeSnbZVHQ7SlDfMs2NfwhUAaR2mgTmIwIDAQABo1Aw+TjAdBgNVHQ4EFgQU/JTTK/+8Q86nWQwPMu+1PpT6Bg4wHwYDVR0jBBgwFoAU/JTT+K/+8Q86nWQwPMu+1PpT6Bg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOB+gQCfbJlP9Y26k7fg/FV1ZTrlf2qYITgC1kottFrSJKSQRj3rWioOIxVaJo3Sw9V5+XUag0LP0nyUspoSRHxvQXgqUnQrjhX7k/Qe9vVScpXZKw3GSY1vHCM7xD71o2N3g+irECdLJDjnIw3grnHUI4kVdARMkCkVaEtnGADSGqei1AZzGCAb0wggG5AgEBMC4w+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAPcN61ksKTebMAsG+CWCGSAFlAwQCAaCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3+DQEJBTEPFw0xODA3MjIwNjIyNTlaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg+5gWUNm8k8C8x6iT/RlF0P9YMc81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFl+AwQBKjALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqG+SIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB+KDANBgkqhkiG9w0BAQEFAASBgGbOWcUKMOVsuSHVJyepwfaECpLKJTQH2VDpyVbS+/CLsmwsGtRQJEIOAVV9OezHHvngtChaCyIuwYmaP17qeN0R7ZvthN5+WCX2DuNUJ+ucSxEfivSx8zwjns7M3944jUbn4zE33ltk4bjWgryOIASKm8snLgftewhNOU2wKP+Ar8JAAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcB+oIAkgAQOaGVsbG8sIHdvcmxkDQoAAAAAAACgggLWMIIC0jCCAo+gAwIBAgIJAIRv+DyrolgpNMAsGCWCGSAFlAwQDAjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1w+bGUuY29tMB4XDTE4MDcwMTA5MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqG+SIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCAbYwggErBgcqhkjOOAQBMIIBHgKB+gQC7Wk5JfPvqyloNxI6wTHy/MAH7ELUs2mlcjF4/+ZAeKbsAOV/m408Bq8+3UrLu+SQkdt3GP2xZdQb4G+ALUJI2gSQ7RvgDY5ORh1+AKOTwJKM0rguCwtlw0Nb4wxrgb+sCigYDFMv5Zu8zEwqZaXL14M4N1VU3YztLZQIo+UiSgONwIVANylBkcI+baMiQNo+w1/dYmCB7NDHAoGAMouAkCn9WUZhh+VR2cnNwBZLTi+D4VBpEhCGhyvNpQlD9l88+Ji2MKjO3eAv0o3AWCFcGSWjTzaxDCKfnY2CiEHZ8Eog6y8E9fVVTyhea1Di8g3Uv+qSY9mR5m8SQ4l1WiMcEl6FTUIOFQNP48QfeOjLazc30ALVITVSmVsdUGwTkDgYQA+AoGAf8tbGLnVhmy7MOIiRd2o8AH6OImp9xy6Zikbib6chsJoEMIxfHEqZ+v+5AFx+gUht0BThsrhYvuOgeMKNd4D8MPPOMPfCoVIprHvEUpsGYArR4LRR3BWWfH2p4n1i+l26Tnz4IFapCTIUkIiV/RokWUoTLwES+48LoeW7adNz1Id6jUDBOMB0GA1UdDgQW+BBRMA7NzBEkpRRlo/9Y7os6U4sJjATAfBgNVHSMEGDAWgBRMA7NzBEkpRRlo/9Y7+os6U4sJjATAMBgNVHRMEBTADAQH/MAsGCWCGSAFlAwQDAgMwADAtAhUAoLfr7SzK+6rBM8F3swsns0ElLXRcCFBEoZvR3RDx/X1Q4CvlE6nXQdLF1MYIBaTCCAWUCAQEw+LjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tAgkAhG8PKuiWCk0w+CwYJYIZIAWUDBAIBoIHkMBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJKoZI+hvcNAQkFMQ8XDTE4MDcyMjA2MjI1OVowLwYJKoZIhvcNAQkEMSIEILu+O2cehT3++MKDmBZQ2byTwLzHqJP9GUXQ/1gxzzWgiMHkGCSqGSIb3DQEJDzFsMGowCwYJYIZI+AWUDBAEqMAsGCWCGSAFlAwQBFjALBglghkgBZQMEAQIwCgYIKoZIhvcNAwcwDgYI+KoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIHMA0GCCqGSIb3DQMC+AgEoMAsGCWCGSAFlAwQDAgQvMC0CFGMQG4Ti+cPlCkXhPNChuLXMZbSCAhUAk/am+O8zvARpcfg+Weq++PJLzOm8AAAAAAAA=+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcB+oIAkgAQOaGVsbG8sIHdvcmxkDQoAAAAAAACgggGNMIIBiTCCAS+gAwIBAgIJAJf6+ZvHS4D4AMAoGCCqGSM49BAMCMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs+ZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMxMDkzNTE0WjAhMR8wHQYJKoZI+hvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD+QgAEdiq+DUTenjFcFQr7vbN9rcwuYkiR9kOxipGxnBFcntPNHGxt5WAENXLdmPpj+hlIH90ruPGzngpMQKgNxcu8QBKNQME4wHQYDVR0OBBYEFFPacwYxY7UVPo4lqM6c+72CnWEdXMB8GA1UdIwQYMBaAFFPacwYxY7UVPo4lqM6c72CnWEdXMAwGA1UdEwQF+MAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgC8D3epwlGR7JF9+v0grNhBeRkTfSqcIH+OUR+Z9QKVqACIQDQZArMqLAZO/q5XWnCOmurIjuHR05sQRKTcoxBFr7nNzGCAX8w+ggF7AgEBMC4wITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAJf6+ZvHS4D4AMAsGCWCGSAFlAwQCAaCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB+MBwGCSqGSIb3DQEJBTEPFw0xODA3MjIwNjIyNTlaMC8GCSqGSIb3DQEJBDEiBCC7+vjtnHoU9/jCg5gWUNm8k8C8x6iT/RlF0P9YMc81oIjB5BgkqhkiG9w0BCQ8xbDBq+MAsGCWCGSAFlAwQBKjALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3+DQMHMA4GCCqGSIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggq+hkiG9w0DAgIBKDAKBggqhkjOPQQDAgRGMEQCIFnydUTsOcJuwn9+EAcsL1N7MxQD+gBKPMKY0H0idZsb9AiBFuYfwgxWnedMjHXE0ZswL8Z0jmdQoQqRvOjlD0kcwuQAA+AAAAAA==+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcB+oIAkgAQOaGVsbG8sIHdvcmxkDQoAAAAAAACgggKCMIICfjCCAiOgAwIBAgIJAKAS+PKiQipNtMAoGCCqGSM49BAMCMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhhbXBs+ZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMxMDkzNTE0WjAhMR8wHQYJKoZI+hvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIIBSzCCAQMGByqGSM49AgEwgfcCAQEw+LAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////MFsE+IP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57PrvVV2+mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR+8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84z+V2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8YyVR+AgEBA0IABJGPZIpCgSCPPr4phDHNPhrKhmBNMIFhZjcSJomXmiTRyohTtIFo9036+pNVzMIyNS+t+WTzAOYqaB2tVq4sf+XSjUDBOMB0GA1UdDgQWBBSC8kx1ORBUpK2y+IZjT95j80H+cezAfBgNVHSMEGDAWgBSC8kx1ORBUpK2yIZjT95j80H+cezAMBgNV+HRMEBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDZoc3hPRA/zknb6/PZGamT4MQn+PBvwW/0MaPiDBw2JJwIhAPBSJdgVyZuyJlzyhDFVwwodd6UxBRuQ+rXatKcA4k1p+MYIBfzCCAXsCAQEwLjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29t+AgkAoBI8qJCKk20wCwYJYIZIAWUDBAIBoIHkMBgGCSqGSIb3DQEJAzELBgkqhkiG+9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTE4MDcyMjA2MjI1OVowLwYJKoZIhvcNAQkE+MSIEILu+O2cehT3+MKDmBZQ2byTwLzHqJP9GUXQ/1gxzzWgiMHkGCSqGSIb3DQEJ+DzFsMGowCwYJYIZIAWUDBAEqMAsGCWCGSAFlAwQBFjALBglghkgBZQMEAQIwCgYI+KoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIH+MA0GCCqGSIb3DQMCAgEoMAoGCCqGSM49BAMCBEYwRAIgUNgm3ZqPzXnroKk7iuXt+vKzEEcwUdHK6L4GJ98Qz2CkCIDUcs93FD13W3WuyGFS6r3Oj5LlDs4MsOZnDbnIo+yva4AAAAAAAA+-----END CMS-----+-----BEGIN CMS-----+MIAGCSqGSIb3DQEHAqCAMIACAQExDTALBglghkgBZQMEAgEwgAYJKoZIhvcNAQcB+oIAkgAQOaGVsbG8sIHdvcmxkDQoAAAAAAACgggIUMIICEDCCAXmgAwIBAgIJAPcN+61ksKTebMA0GCSqGSIb3DQEBCwUAMCExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZXhh+bXBsZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMxMDkzNTE0WjAhMR8wHQYJ+KoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GN+ADCBiQKBgQC0EbaQpZ9hPWNReZsOWuQPdZ/DSsoONN2ONKzAzYeCAy0RuK61z5zG+ehb4Np4/VLNJ1js1eVqq/N+0I8u/Mk1pEzgekb9cuoh25vmHOKxuvA3X9DvvOZS++vWRrs1pcpkO656k5Emw2HmUeSnbZVHQ7SlDfMs2NfwhUAaR2mgTmIwIDAQABo1Aw+TjAdBgNVHQ4EFgQU/JTTK/+8Q86nWQwPMu+1PpT6Bg4wHwYDVR0jBBgwFoAU/JTT+K/+8Q86nWQwPMu+1PpT6Bg4wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOB+gQCfbJlP9Y26k7fg/FV1ZTrlf2qYITgC1kottFrSJKSQRj3rWioOIxVaJo3Sw9V5+XUag0LP0nyUspoSRHxvQXgqUnQrjhX7k/Qe9vVScpXZKw3GSY1vHCM7xD71o2N3g+irECdLJDjnIw3grnHUI4kVdARMkCkVaEtnGADSGqei1AZzGCAe0wggHpAgEBMC4w+ITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbQIJAPcN61ksKTebMAsG+CWCGSAFlAwQCAaCB5DAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3+DQEJBTEPFw0xODA3MjIwNjIyNTlaMC8GCSqGSIb3DQEJBDEiBCC7vjtnHoU9/jCg+5gWUNm8k8C8x6iT/RlF0P9YMc81oIjB5BgkqhkiG9w0BCQ8xbDBqMAsGCWCGSAFl+AwQBKjALBglghkgBZQMEARYwCwYJYIZIAWUDBAECMAoGCCqGSIb3DQMHMA4GCCqG+SIb3DQMCAgIAgDANBggqhkiG9w0DAgIBQDAHBgUrDgMCBzANBggqhkiG9w0DAgIB+KDA9BgkqhkiG9w0BAQowMKANMAsGCWCGSAFlAwQCAaEaMBgGCSqGSIb3DQEBCDAL+BglghkgBZQMEAgGiAwIBXgSBgCBmZlgPvc6GpULvkccID/ybILnVh1Zcu5DnhvOz+isNXtcm3TW8xu9+rLps95FfNrb0akRVRdrHMdOlwwV/y6gl7P7c5lpcijQnIIFae+5j6YV7LDJNGpnssxkQyF+Kj3wTMdvf+0yChjCSQ9QFkNK/lUZTO9LhXc783xhAO5+7EQwAAAAAAAA+-----END CMS-----
+ tests/files/dsa-encrypted-pbes1.pem view
@@ -0,0 +1,80 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcTAbBgkqhkiG9w0BBQMwDgQI/aS3q8Ny9UYCAggABIIBUFshzMyw/YNj9r0a+44vQzZtQ2Xc2aRqQnRJuERmPZiMIUrc41Ej/wGRMJAPItBZJkVPGCOrkgYmyyHK5+bsyIVoZfCUTfOU5J7rJVdzayQeE1cWjWpcBQVSEMH0k2GctKHOeGN8vzXjVq6hXh+4XchC/0n5EWVtlbTH2s2nuMSjUY9CQeaodIlW7cuAk5AE97OrQGWK+0BRrHZrbhw+bjR2fuJVPrPUuUrsuZK7PyKrOpUB8s1GE/2UQ4taCa+eBZfbkD4C82e7kI6v6ZJx+ECnIVhvA/2I2qic4D7ml7rIlp3Zizs7pXkeoWgTpWrGaUXxQr26jmGWDYJ8WiulW+h5c9Wo4G3V7xUoYssQu29qHnrB7IQT2dbrL64JbaFjcOXbXlrJ99FOmrZhtXsCL7+vRipmnrumDaYMj61+petkln9ScRmhl2rP36/nGFIlwJxmJeUQw==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcTAbBgkqhkiG9w0BBQowDgQIr3nft7PJIm4CAggABIIBUBSoegLJo8BpcE+5+3AAnl7cib9JD4g3wsnVKWc36+32uU4dg+cPgN6/E9ULRvKX9PM1U6MZwUb3M0QAs+IuZ5UkkW1iNHmxuQoD2EZpBzTM0ykaDq8O/3dRdpmBNrRAW7tiPZxtffgySjzShA+m/tSH/w2jydZswsP32ZDAgevPG317fHwMVeaI1Gz7DRPxRIRpwgy6HuovwK9N4+W+jT7hNdvA4iybnFSiYGt36nHlaEjEmkayPAXTcLtW/p/4ctsYr1O2Uq24l2bkZZMH+abyjm4BYrJV/gV91p/C+wggOCsXBBVF9Awf6YIXC6XF4C327/Ov3ORXJQh2C7ANO+QGOSqCVZo6o1tlP62XKRmuDDiObl0TwuVG5cyQac/triWyPKtvAEinyv/OM4lCue+7az07xON9WnpwF3suDpZfx8aKlqNPRhJIce4mE2Wjp65H+5U9A==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcDAcBgoqhkiG9w0BDAEBMA4ECMngjuURplazAgIIAASCAU77yI2EF+UXyyDN+wp4oJRKrpXSeJsRLV9LzgUMVanmVSJNdkjAilQGE8refAAXzIcV/DKf7jmLgT6t6+34vyDu5MQTKX9Sq/35RZDgQt0epNRl8x9HkzJXbMwXpjxqN8St5fW3CG7klGyV4++hIb4LfRXxHLajdhwJzLDo6hmE2TIYCMGZYWApObVMfms8qDN0ulYxPF0O7UtpNy8+sT5uvN1/CVUJmG2pfygbdZ/tBkTJLNzbt060EVXBIdUgAkohMq4v2Bm6dM+3FQMi+abC6rLDlf/Yhleaa0nCG7GUpBDD77AIr6mJ67TYg+//vJa2WWwTc5DOk1+Ooz1NJ+V1Fj7y3EmG9zBb1EboOBo59/VtWhpAwp73x9QidzZ+RhZqvFFHBuOE2TUxbGlZqO+w6Cqg6hVz/dJkfbrjQ3I0HFPQPHJLTmmZbqMvuliv2h4g5YM+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcDAcBgoqhkiG9w0BDAECMA4ECB2W/D5ZtLxUAgIIAASCAU46SZU3kLb1v26l++yAlWkhOypf3Bq5z2w/qy2eRsK827S3FvanOUrjG07VicYJc7wssCvbC61HNxyDT+4HCvx2tD1VeHtzUcP6/clLwb9ARVNtoA7oLlfloXpTa6WiMbXgYSP3p7CQCvfIX2+HYnihuFNoMjnZw08GHvy/jEVe7HuN7VSHJ/QSXsv/nIWWRdHZRaZYtLAjYwHH7iT+z+0D02yiLjN9P5q5dtvUBnwBTrWaSyaYvO1zI++WtEDx22z9CXIh9v3kF5qyZIuO+e4iI1dn2SN+uxzQPs3IPcamt7eKsp7HQYwwkh3TXPy1xWlF6MRvSkU+q20sgfVLQ+pyHr5/Txb3EvcFnD1esZf0xFjwr5XcQFVSu625zGCKn2/0Z7BxtWaC7D1g3t2Jxw+5CdS3C7C11daouWHzVyJmdUc8WrbKFDy3/IZHgYWJ3jtNJgg+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcjAcBgoqhkiG9w0BDAEDMA4ECLJqtxV8p51lAgIIAASCAVCiXfhPwBzmosBy+p2uscyFJ3dTrGHFt/PDyjWfjj2KG1IwAPti/jUqRJkckaee1sims2AIfxfga7xlv+wRgyNk6cNCHqjb5Khinog3O2N5FoJwLXnzmoPA+ilmTXcuv9i95mSYEzwR13kM8C+9CoUXQJCvWnuVAw6b/InGjCj5tic1MDWm6hufgydr9C9njhP9Qh2NkN8/iseiiWB+5preXZsXD9dyvQPH5qBu1aCnjLKYp5+bUl7UZ85mElvXOln5YW38DT1fX6XsT6b7+kn3wR5ol6PabdEOI7KNJh8T1PEIImrfo7T2rWmkPC3Gqmv6LeN1kbipy61QXqE69+rTp+dy/pCdtUIx9mC6lTwLf3Jy4tzOWObwHgTs1ikKvrI/di0vyd9e1xEY++Yxu/+ZJ16Ykpb9Z2H8xmRk3OecCVbSCcZ1wLzOhwdUbsyX71GSOnDA7Q=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcjAcBgoqhkiG9w0BDAEEMA4ECAhz3Oo2dg4wAgIIAASCAVDYuvgJqqfDBcM5+guRg1jkoLUvVq8tmvGpWjbQMN4Z7JFnbeZcI7QQ4IqflLY83kHlE/G+418z9LYNV+Xg/3HmvGjAQ47niW6hwDtcyKKk3P1iOi2ZTEAkt//Dvo0W9BT9spqsubEbvJRSw3+B4Q+Yi/hRRSsXhL/uSfVR4D6whGls08ryJr2cpQyZEOLXbGfZseSSt2l3T29mMas+GWrG1aV86xU+P/fY7vLfKGUOtIrFD2WXWJTsJJaQZvCX/E7YmvcpGblq6CDnraFu+DHTOtENK4Bx6ug1a+FIVPQGgjFDLZOkxLiVJUds88ATiaAuPwrUnPNUFNC6CX38P+J8Rre1WNBKO7l8KVzIybUKG5+XNJQvmj0gGgf6Fm/736Y0rzJeiyBf8kSn9Z5QTv+JczY6PbNppZLFuFTrZ772FtJ2oq6RTP1td7SmArW/YUCXxRaCBc=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcjAcBgoqhkiG9w0BDAEFMA4ECFQ5zMHU0moJAgIIAASCAVCJFnOaptrH+ouw+C9BvIjdOhy+eW5Ic3pZa8c8PxXruW5ehO83+nWgnPzXisMPW094Varoqe43mz7Sy+PpHpWfNDqPq61cCci1Daxm1d0PHCTKhyW7leowGT23TIM19Xz7FlMR/oiSZkHqzH+SNvOGPaajylKS9Tmle36MSGT0Qla/k74ILf2HWTouwZWi5mhTY6DpLziPZGQBly4+VMrWkBi8zYfYNfYtFWxTudHD7UmnLXh3nc71XEWt+1rwVJSE2TRur+GyV44g0Zwr+9EULlMGZWjprXhU7oqevMZgSqXsjwB2vs9r9473vLfAQfIu4hKfYF5+WBwAOzPb1+eqGNkrHMKEgUVMiTs5OrvWOVs2MUWKYufOdc4legc0Jd1WjfQSVSrQnAkvaNMCf7+kS7n6Ogc5DrzL38k2IB82VhdF9pZy1uAJvWaNRqOQMIRyZAkXyk=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBcjAcBgoqhkiG9w0BDAEGMA4ECM+XyPN3/xqgAgIIAASCAVAUYVbumKCpJl/k+VhTOWoQX7LfUcfxoZTqfacml/itKo6GuNPjrtcPh1su6f55USVaQUiibkAx9LZbJ+KpWtvMhSlQqumh8sJZ5XKOZsM4Nhuqp3ot8dd2VL0a7M6FlYFWf+hjU3Pr+WisoT+0uPdYz/PbUwluswR2E0fAIMA13VgcKY6hroNDKVC5PlWXJxsrm+5GqiWlDmntaHw+TDiB1sjT3BmSvcImTf5BGkvP2+i5+VN1plmKSKlyF6vdBa1VjW6ghEr8kCn4cBjA+TqcemSMfEaZ/lbqpB3klIcS1chxT8sEdFz/9sXBFI4cl61Yj7ZJ3IwvbzWLqELQ5+3c2TSYDxU+hrNau9VZkWwb7ZRALjHRMSk8MrKU35QalfSLl3DCEpNgF8fItmSVBB+Vj0UfYskjTzHE5oWtTR0F3gB8G0R/I5Tru2c5Gn2ikbrRbC6gM4=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/dsa-encrypted-pbkdf2.pem view
@@ -0,0 +1,79 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBoTBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQIcHjNGCgVdKECAggA+MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECLRvOz07zDcxBIIBUNoQ1qwaTP5N1d3g+HVFC+XKnv43vfH+RhbJk6Rz3GVqTgNkRZ/giTXko0CDblPo/uQNnwkq3rvMMSssE+gF2QvghHUEuPOY0PvRBgbrXXm/9ELC0H1B5lgbWNNuNZu7h8rpgxvxwLQbGi/PvX+B2W0VBsCCOqs00uDP7BEWoVswanwfYNqqLZ2+q1/tk5RxtSGt0r6wvQkKP4s055h+BllEqGU0/ZZbZ/af3bEbZszDPgrietyoL1SuUlPXAkgu9+geAWOnxvCTQf4lnvxo+X6/RGyjmQDXprO9HvW1ttM2qNStqAkkwCuMgYjhZpnKMnswILEmfORIKnXD0pZ1E+2XZnxKpTgjWzwHmP7/GqLBH40+SvbvbFf4v3fIXbl861mc3u/rFCXZLmvhm5WS3L++kZZ0CgLHfvRjY40nBv0cTwZZwoQTJwbquZndb3yVhK0b+T/ew==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBpDBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIInPsgUWW7g0CAggA+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECOFhwmqpMeP2BIIBUM2Q5Hd7I58d+biaLO6o8eC1NIodvvkdwelWuJQFIIneaIH956tLangnmWoCc7Qr4s867J2+qkZMt+E0tYJoRc0xl13hGHUvQqSX9Bmow/decn4/um9MY8Kkwu9aEwQFUHd3Eldn5ktw94+eLAYIMb7JtQDV2T1QiSW1oCGZj7HPzg5ZSjd4WRL/X5XYgpd84SaSaWHPy+Q+Fba+eq7U84jHUgyrzo1pify+QrJAdRiWrgBlMgkw2Vvl5RFzToKjepTuwjX3rd8VUFpL+TuKnrimXUrGcKVoO5SwjMpGAl8vyEcwx1dMeqdRdVrx/S4g9zZZoJ/R2Qz/M5o/F+9rdy1lSnMoBUUatW3uCPETMfenwFYBRkVyYDPgdhDmP1OzI8R34X6zurpVppy8dT++SR5H6PHTrtG7r8utZjhGtXAL0Lr5xaN8k3faMfyNbCAABME8Ex+ig==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBpTBPBgkqhkiG9w0BBQ0wQjApBgkqhkiG9w0BBQwwHAQInlrNCxahn94CAggA+MAwGCCqGSIb3DQIJBQAwFQYJKoZIhvZ9B0IKBAjYjwYE1LBgjQSCAVC4RY1jocAq+2tw/ZiNbXAndRXAGSXHTRVKsN8ampNA9jptqvrWWK73HtRFb5nRwAF+eqfQJD+v0+h7pIXCoKoErJiTj23PPQc69pQDbeH1rKLCabR16lnR20L7wTz9m5ZXonq8BAOCNZ+5fpKk7sTK8aBRwDB61rys43PwdRaczqP49ZxnD04r5oztivMwcOHZ8k+vjWZVntf+GiCWUgK1nfkIRNV2TpgzFaCzphxIOaQvI0oSWj4RDRSBp9QOYUalBkqhJrt7jSUz+2Egdxfh3v/vjEjDQ3mbnftGamO/8s5iQUWj0tlp/2e2a9XRDMWi1s0j0dN/z2Lel+oqZEcgLbl1ejKH0+3UHpCTYL0rH9YnsT99gdyszljtztg91LVUMc4qmCLE8Yb/sq+bWCuxqSqZqRCVkR7EFWKkzIoMLZraYMc1Zeqt1BWZUqLCU8oU8LyMDQ=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBrzBZBgkqhkiG9w0BBQ0wTDApBgkqhkiG9w0BBQwwHAQImzQMD2ZUPQ4CAggA+MAwGCCqGSIb3DQIJBQAwHwYLKoMIjJpLPQEBAQIEEOjOVi0F+1g9Q9BzhIIUwdEE+ggFQoNn6wN2rFabu2/HURiSNBVIMgWahYfWBTU1N9rKOg3QtI3hwlVJhi7a5UlPo+RyLL4oSGpZz+JBgAmQEqr/PlcNiv3zvxbMrbZ7TcRzE4DwitbXRUXg6j4BhLs4ax+wJRRGi0r8ZTl8/GGpOUJZj7VyLQmixNuJkkEXHpLuvXtobrecxb/g2ZWrMQx3goM+bfAJbV49GrlszenHrd7IQ/Jtb5fG6qihri4St3j+hs2lkNHwVEfZR48i4cT0HDJM+U8MeMjvFSpura3clcBNT6jhx/+cgoVfHZIw7xQl+GLUb0WTSID0qhq6ud3TH+qcM+KpclX29qC4TOzVbVRCRA8NRYmmcaImgFdbcfiuvw0SKthcnx6dUmRktvX1lPmjdE+0uWYTJdrL7CxKmua6QgnGnYNhNmy8da70e1IeikY7u76/6z5edLIxKKm4/sImexL+3gfu+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBrDBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQIm7wxCHZluCYCAggA+AgEQMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBOgQIsHuXH5aupi0EggFQ+v/LaahBu1HMmBk+wnORTSL4AoVrPHBt+U3Af/9JXK/BLu18iDesLvAr3wGmBIe1l+Ld6ilGag2faE33rDKzQv6EMpR3UypA3ACoDsqscakFfa8JKCLbeD8mgFOH3vVUuI+PO/Vx6hSCAvW20FI1VFtzi+/tVgkneuHML1k9IFkbKNzWx3NZQKBMo4kuzmO+WGG+iFWwuq29V/9UiyExm5DnA0FrctYbchtH1lwPb6aHPineEujYzuld1963N52pjtss+es9oc/KGRhQtAjHVmH0gGWnQJR7n9i4TelBIDpBC3VPrUBccm2W2hA2HIH3WYRxW+WsP/Nkg3UveiAE6/nXYvI1KZBIzTGeyvB9tnNcbT8nZ9GM4zkbtoO5Gva66AEzv7+6HC2zsOmF0fq0akz3cd5sMoRxg8JSMDPgYgsdJp6UYSQVI7xevFhCEBEy+0HVJLM+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBrTBXBgkqhkiG9w0BBQ0wSjAsBgkqhkiG9w0BBQwwHwQIupwrj1RegoQCAggA+AgEFMAwGCCqGSIb3DQIJBQAwGgYIKoZIhvcNAwIwDgICAKAECAkkdbW6dpd0BIIB+UJtbsPclMM59KZA5Y7YbF0Ys5d5ZSWnsJhQjWKL61FXgIBylub/sqlJCB3f+mSxC+GqHZjeKPJMlI2/fwqYY8rrcQu8ex6AvRN/eJhjugcT3bJUs5o3UIwljvdZ0xCeJD+qlPTjV0f1EAaGmIuLLGseIPZcmKrhzu7+L3+ti5+G/UeGU/AqE7glyIgYrlKfJF8+MMLdQ5L0K60cXBLO1PUAi4AYxxfclDdaASUcyE432CBxlGZN0yRO0zWM6BZ7uILd+v8eZVYDJc6acWLRs2WtzsEZ4oxT0hAIArfAou7okdMbLBbfsFpAfbbyvqp/z06CT+ZLlGySZPBWQc37d9FCSz4a9+6wCXqME49uozxxaFSkLQMWcxeTefrWyrAVD4QgIP+g689TrBRAnAtdBIX84aM0sB4+rHJxr9hF694GKPm7Wz80OwVY3+3gYHKwzLLBbRu+ng==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBrDBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQIdH7jrafsUl0CAggA+AgEIMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBeAQI9jyutvOWsgoEggFQ+sf8QI6BdcCfJmNklZu5NRLmK0GyU17V++XUaO0yPt92xlSC6h3bEPrPz3GxZrnOb+ViLubKJLUqQUb+xZD9fHq0XRpRMnRSu906RAXR8FVreKwgz5071G5KsgJWwoOQhn++/6B3A5NjZbjwGkd971fGbOcOljLGqMfTv8KSlZoGTeKDLOV3Hk4H8QNY6BfU307+Sr39ETAozWoVVoQYCc0VmfA9lZEny8cvwRS46X0qj2HHUACn2qEI+8cOZkOUnv4r+xL+/gJ474TjPX+Jd+KIndtWHyMPjRY6pRgdmsdWxjQqD+vEWR7sj96w+/x56M+mb+hH3TQua0V7iAsoLzdKyob1+2naI7pqAaqxuRzVN7mATj+MN6t0NGMctiVy5LDb9C+yJXUwLWN/8pgn2et7fbdjz6mWaUOgL6TIlG+OcPlp+sQUHwe+2rs88qAHJBjPYQW+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/dsa-encrypted-scrypt.pem view
@@ -0,0 +1,33 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBpTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQINN5s8FaPzoICAkAA+AgEIAgEBMB0GCWCGSAFlAwQBAgQQy5+msvCgFnxMEaD174O1ugSCAVDyvwNA1u9r+Nj3DaCrlz0sE1fPAJJUCylvQLt6WGUvdsSAliFjv+K5GhtbNRbCUzXtCbWjkqMCY+rwCXfp8CCvHFTTR8yMsuvyThrCfhRf9H1tMJaUDlAP9iVHcydTKeCgrIpkxNNNFB+DEhQr6M1x7cOLCKsPB75Ap7v9Ks63p8UwhrilBjdIFnHJEEbzZsoFnY/TzMSTG3y+pn9/cNhY1KVLWRr8j+6gQ/zFjAwqMyrI0ISoXim8rYkHEeJZGUXmYs0orLrxoqxe+uSBkbr74rtHDdDaxv2ITFI5V210SY73mc/0C3DCy6ibp0flz16BsKd+EbOAExRKE+x7zJSxJ1/gQiuP+FFKSN8s+cm5Xn2FdQE8D3h8csQ2HrTv5DBLdDL7xegq126WPE+e1HlZPzI9F4N6EqbUGXsoYYFMeSQOMhVP2C+JRWYolVTtLJqAv0i8Ao=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBpTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQI/FyWk+QpeTgCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBFgQQHSr6nDgm5G3mfzfwG0zeyQSCAVBZoK70Mv5l+Bg3qHpI18jLTCgLqLu1cc3PwugzEqNa1ZjHSx/Kclqz4rtWar7omc+n403RGYsr3+uNnR5ENXabA7yCj4uQ492dKlFR2Qr8+I47PgMvjRpmqkczCCCe2JEixc3eRqzOK7+hTjAt03SW3OgZWL2VeZM+cF1CI2TphCfEt839APgXnH53BOMsuOGS+7gi80Z3HYV+asxYTqOCW9b+CdQ/Jozt0s3LmuLElUdvRHBhvKtb8NzIePrCDhZSGGSGS43Px2JR+c4yHUC37zNVWW24EvAe4WadVad2F9RS/1Titgxmu80Xd+rERxh7TbbRWecYjLQim+T1XFmmqNHqScu8kypMEf0mElZx2M38uxn0VJy8wmkgoK4KbJuNvHRAOM+bcQwsJl+GGxvH1HAt41oJ0FTSr71UV/nAD/tYJ7XuZEtZKYqKfQSN+JWYxdvkVk=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBpTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIToKQdJwvmYYCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBKgQQu9CT+JV6cKk50hxDchfwRQSCAVAwblK0RQIQ+1mRsFPmETlN0BpPuUhqmyPIR8plot/YYag2YomVMnS2fRpaBILtqv6yrJJq2VTgx+TI4lsH9vsIrsDUjVc8Lzo+b/Bn3+90i7dKt1Wl1bOSTTyDt/FcAU1pUP/xBoaDTu+6in2XAvY5C2q2VVhvuJqhRkqxjYNCP+aof8qVijM2sw3qs49PwRw3H+V8SQ90GvK+ZClddQdW0Chl9SH3aH7SKFyOfhiynI4MOauc1L19S6CXg6QfEOmi/EA5XVlzmkgg+Ugpal8y7LE7q0Ua33Bxz/LDR6izRj/zaIMPXtahmQOzc7F8f9qTuyCVX56hl/KbW+jerauH8Ls35UM60qtqknwIH9aN5rHU5fpP0i24qS/DscIDQRIJHzux7XfQO3EJlK+QdowSSijvlrX2mz31rIuD4kmBsgdCn+x4AOimS6BSqMWswo5W/Mppds=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/dsa-params.pem view
@@ -0,0 +1,9 @@+-----BEGIN DSA PARAMETERS-----+MIIBHgKBgQC7Wk5JfPvqyloNxI6wTHy/MAH7ELUs2mlcjF4/+ZAeKbsAOV/m408B+q8+3UrLuSQkdt3GP2xZdQb4G+ALUJI2gSQ7RvgDY5ORh1+AKOTwJKM0rguCwtlw0+Nb4wxrgbsCigYDFMv5Zu8zEwqZaXL14M4N1VU3YztLZQIo+UiSgONwIVANylBkcI++baMiQNow1/dYmCB7NDHAoGAMouAkCn9WUZhh+VR2cnNwBZLTi+D4VBpEhCGhyvN+pQlD9l88Ji2MKjO3eAv0o3AWCFcGSWjTzaxDCKfnY2CiEHZ8Eog6y8E9fVVTyhea+1Di8g3UvqSY9mR5m8SQ4l1WiMcEl6FTUIOFQNP48QfeOjLazc30ALVITVSmVsdUG+wTk=+-----END DSA PARAMETERS-----
+ tests/files/dsa-public.pem view
@@ -0,0 +1,12 @@+-----BEGIN PUBLIC KEY-----+MIIBtjCCASsGByqGSM44BAEwggEeAoGBALtaTkl8++rKWg3EjrBMfL8wAfsQtSza+aVyMXj/5kB4puwA5X+bjTwGrz7dSsu5JCR23cY/bFl1Bvgb4AtQkjaBJDtG+ANjk+5GHX4Ao5PAkozSuC4LC2XDQ1vjDGuBuwKKBgMUy/lm7zMTCplpcvXgzg3VVTdjO0+tlAij5SJKA43AhUA3KUGRwj5toyJA2jDX91iYIHs0McCgYAyi4CQKf1ZRmGH5VHZ+yc3AFktOL4PhUGkSEIaHK82lCUP2XzwmLYwqM7d4C/SjcBYIVwZJaNPNrEMIp+dj+YKIQdnwSiDrLwT19VVPKF5rUOLyDdS+pJj2ZHmbxJDiXVaIxwSXoVNQg4VA0/jxB+946MtrNzfQAtUhNVKZWx1QbBOQOBhAACgYB/y1sYudWGbLsw4iJF3ajwAfo4ian3+HLpmKRuJvpyGwmgQwjF8cSpn6/7kAXGBSG3QFOGyuFi+46B4wo13gPww884w98Kh+Uimse8RSmwZgCtHgtFHcFZZ8fanifWKXbpOfPggVqkJMhSQiJX9GiRZShMvARL7j+wuh5btp03PUh3g==+-----END PUBLIC KEY-----
+ tests/files/dsa-self-signed-cert.pem view
@@ -0,0 +1,18 @@+-----BEGIN CERTIFICATE-----+MIIC0jCCAo+gAwIBAgIJAIRvDyrolgpNMAsGCWCGSAFlAwQDAjAhMR8wHQYJKoZI+hvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5MzUxNFoXDTE4MDcz+MTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCAbYw+ggErBgcqhkjOOAQBMIIBHgKBgQC7Wk5JfPvqyloNxI6wTHy/MAH7ELUs2mlcjF4/++ZAeKbsAOV/m408Bq8+3UrLuSQkdt3GP2xZdQb4G+ALUJI2gSQ7RvgDY5ORh1+AK+OTwJKM0rguCwtlw0Nb4wxrgbsCigYDFMv5Zu8zEwqZaXL14M4N1VU3YztLZQIo+U+iSgONwIVANylBkcI+baMiQNow1/dYmCB7NDHAoGAMouAkCn9WUZhh+VR2cnNwBZL+Ti+D4VBpEhCGhyvNpQlD9l88Ji2MKjO3eAv0o3AWCFcGSWjTzaxDCKfnY2CiEHZ8+Eog6y8E9fVVTyhea1Di8g3UvqSY9mR5m8SQ4l1WiMcEl6FTUIOFQNP48QfeOjLaz+c30ALVITVSmVsdUGwTkDgYQAAoGAf8tbGLnVhmy7MOIiRd2o8AH6OImp9xy6Zikb+ib6chsJoEMIxfHEqZ+v+5AFxgUht0BThsrhYvuOgeMKNd4D8MPPOMPfCoVIprHvE+UpsGYArR4LRR3BWWfH2p4n1il26Tnz4IFapCTIUkIiV/RokWUoTLwES+48LoeW7a+dNz1Id6jUDBOMB0GA1UdDgQWBBRMA7NzBEkpRRlo/9Y7os6U4sJjATAfBgNVHSME+GDAWgBRMA7NzBEkpRRlo/9Y7os6U4sJjATAMBgNVHRMEBTADAQH/MAsGCWCGSAFl+AwQDAgMwADAtAhUAoLfr7SzK6rBM8F3swsns0ElLXRcCFBEoZvR3RDx/X1Q4CvlE+6nXQdLF1+-----END CERTIFICATE-----
+ tests/files/dsa-unencrypted-pkcs8.pem view
@@ -0,0 +1,9 @@+-----BEGIN PRIVATE KEY-----+MIIBSgIBADCCASsGByqGSM44BAEwggEeAoGBALtaTkl8++rKWg3EjrBMfL8wAfsQ+tSzaaVyMXj/5kB4puwA5X+bjTwGrz7dSsu5JCR23cY/bFl1Bvgb4AtQkjaBJDtG++ANjk5GHX4Ao5PAkozSuC4LC2XDQ1vjDGuBuwKKBgMUy/lm7zMTCplpcvXgzg3VVT+djO0tlAij5SJKA43AhUA3KUGRwj5toyJA2jDX91iYIHs0McCgYAyi4CQKf1ZRmGH+5VHZyc3AFktOL4PhUGkSEIaHK82lCUP2XzwmLYwqM7d4C/SjcBYIVwZJaNPNrEMI+p+djYKIQdnwSiDrLwT19VVPKF5rUOLyDdS+pJj2ZHmbxJDiXVaIxwSXoVNQg4VA0+/jxB946MtrNzfQAtUhNVKZWx1QbBOQQWAhRhml7zu5ShPwMQujf9s3NuLE49pQ==+-----END PRIVATE KEY-----
+ tests/files/dsa-unencrypted-trad.pem view
@@ -0,0 +1,12 @@+-----BEGIN DSA PRIVATE KEY-----+MIIBugIBAAKBgQC7Wk5JfPvqyloNxI6wTHy/MAH7ELUs2mlcjF4/+ZAeKbsAOV/m+408Bq8+3UrLuSQkdt3GP2xZdQb4G+ALUJI2gSQ7RvgDY5ORh1+AKOTwJKM0rguCw+tlw0Nb4wxrgbsCigYDFMv5Zu8zEwqZaXL14M4N1VU3YztLZQIo+UiSgONwIVANyl+BkcI+baMiQNow1/dYmCB7NDHAoGAMouAkCn9WUZhh+VR2cnNwBZLTi+D4VBpEhCG+hyvNpQlD9l88Ji2MKjO3eAv0o3AWCFcGSWjTzaxDCKfnY2CiEHZ8Eog6y8E9fVVT+yhea1Di8g3UvqSY9mR5m8SQ4l1WiMcEl6FTUIOFQNP48QfeOjLazc30ALVITVSmV+sdUGwTkCgYB/y1sYudWGbLsw4iJF3ajwAfo4ian3HLpmKRuJvpyGwmgQwjF8cSpn+6/7kAXGBSG3QFOGyuFi+46B4wo13gPww884w98KhUimse8RSmwZgCtHgtFHcFZZ8+fanifWKXbpOfPggVqkJMhSQiJX9GiRZShMvARL7jwuh5btp03PUh3gIUYZpe87uU+oT8DELo3/bNzbixOPaU=+-----END DSA PRIVATE KEY-----
+ tests/files/ecdsa-epc-encrypted-pbes1.pem view
@@ -0,0 +1,88 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBoTAbBgkqhkiG9w0BBQMwDgQI5MTTa4Q3G9wCAggABIIBgPkvqzm83Jam+28T+hCMbmBSmEJBexnqiWndSP9z/RJZ4MVQhj98K1hs3PnwAf14PxYhLtZ1hff678KBr+/DQjE6jTErbZ+D1c2jAF2ZulG3PsjBsF0FIQw2Bf8P/RnzVUoqPoHpYW9BJZhcG6+jN4OhkzKX7XqSUBE8V+tbhwXGnTR7jbknQ3FGRrHSNiRTKaQn+EWIwsyg0LJgWGJ+QXVN/fetsUgFeQUj3CMMd+BqmY7PE1E0v0zJ4A9yj1GL7QGilJ212k9almsyWwYA+iNrTK4Ngo4BBFXQUmoAjmdBA2sxTyOwmL3FYN9ju2E8IAQg26l2dozj6VPOCQv2D+/biVPYinmNQPrpq4psv2wZgo211XrMqPRFb11coz7OGZXUmmaeEzsc30lp4N4fa4+S3HbIvluwgTan68EANSZcYR/ry0MGoQjsbb2FoKMnuEpTXQLEAC2b9PYKv2+Bqg1+bsPJPRBFzPxiDTjyhkX05mY/8r20bfBoXERh2ENkNZMEkHhvgQ==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBoTAbBgkqhkiG9w0BBQowDgQIpbYR4hJkTcgCAggABIIBgMmPK83NuhSsNiqa+jgw6o4vWyngy40F6wBiK0/ouAb9e0tCGK7/TCPq49qn4u5Q7ble/Ufl9p9nbL7+J+HnQ0uiYq8aNYNIB1VtbJCrwBRz7lG05nwvbydT3uzsEIbgHCtHg+GpP2sPuipKxZ+k8AYpOlT1etn0am6masjvdBWQlAdq534xfBHDCDwrcYbNASe20oQ6J5M7uJIKXza+FVBJL7pvWAHuZbB9tIz2iz+wHrQKXdmhmJ/ePfoOnXWd8WGSgbSCLvgd0iRgQ9Yc+4mQOE7ZTv4koM2PtpH3qGpOW7hgFDEoWXouolQpXtGiQeBBTBWJHBDy2GSYgPUi3+Hj7ATyoCiP2j3iEFtnyhs+6okyOBhWjpKfNPQoiU9MhgJyna2n2jh3TU7c+cl1z8+eIo/5pX73alFLsyu2DSoJdt3gIyroqQHJ4n+eKSq1B6/CQLN1n2zUk1l2umnC/1X+8qQQMvOaE+Wgrv4puy9Nuh2BkwuXAirZJ2UcYwBrel5suC+jXA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBnzAcBgoqhkiG9w0BDAEBMA4ECB2jmfJAz7EfAgIIAASCAX1FgUt+XM9DG4Ay+2yD/npOTZp1izQkniOT/3b1XqL0KMAZuYL92daHAgBM7hPO7sZGWh+FQBkX4eLNG+Ak14idq9U2TCZmV0lIDKWR897DkxM9LW2YwAQxXzwH9frqo+OyUQNBero+F2Znc2+kEpjT85W68qpGI6XFXFRUTmZ+K+YEtGDtwvm/a5hV8CdGAg1NrkJEu9g1NeUEEg6+4PRLRv8IgPeNEIWJBhpw6WXgIjkhnf3GHGYEs9RqO041ZlRHE7LeRUY9T1NqcQ+s+XEm0KM0NU5STJthfaqsv5TK2GUHgHBPex0Hwlht08b2mp2YZokBfxhgucUgWSeTg+OntU6rmJtuOy8bvisyaR/G1em/F0L0toFeetcq16U3EG/ZrbJ6mW3tgaVrQwpoFg+6fwJbgi1rTGT5Mel6nphcMBMK+/9AOw7MO8pkn97ddImFc2tqK2e2L8GYjpSLEiG+0dK0Lk0m+kb1iiwwQ+KbgPbLBG+31jBHANL3jlewXd9oKFc=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBnzAcBgoqhkiG9w0BDAECMA4ECIulPRng61PVAgIIAASCAX2wES4a4zOo45Qz+6Lao4h6qkdtZeiJtNCWkmGoNsNb0GQi+HpOD71h/E2HLdpLxAV1RNbGeFV32j9Um+m1iLRL/S+wM5/CorkV8lSqZd566kXUWo8dYU1aSOBwpktB1MAXBUaPvlM3NZQyVN+Oj9ZTTV2/AOG2Ca5L1rX0nG5A69m50h0EHoH5MV5cKIoTHRfnOx4jUqSsfQgRI4X+wo4esYVraC3L6Onzveb4Q7H5DCF+4AY/ziNRQRP91tvOseti291qZitGq6DY2t7s+QOl0oLFU/obeG87RC4syLQRm/xLt6aXIAvHzy+V5h6QwPnZAtVpLyBAYLbVx06YX+1aV/nFw+F7U+9F9daujM9upr0/c/44JPwhTHUCMdPMkmXoF8RaSdQN0V4UHDMBnT+t8ZXhjMxC/B5aZjDvyEnMtkKTllA7DTE9gU7SBALt7KaGX/dM7EpZbVs26VESwiL+hT/IZxtZ8zjwMkF+ixOOOZR0eNf16TT+kYgU9k/yYPzYLuc=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBojAcBgoqhkiG9w0BDAEDMA4ECGcyMGdLvxlfAgIIAASCAYDFC/PBeI3Yl8m6+QQjW1KMrAralt6XHcZMncSBUc8Lm/Kq1AHntjXMqbph0IKVLiqsTJ6DfABXNnmuj+vCDwF6VNGw1oJSAFXy61PKmBIQY305kDOvqjou+oxTdczun2wfAsPjLWksQ+nj4u+4VRsyMyuw+W3B9zqbDgc6vk0J8ps6VYQQsdngOjRHAeF6Xy89GHJ7jKEA8hXlizr+7QD7GnD6gc1of3qMFgZyTPOhvgLUuGHTSlrTYd4mo7bzyVMUWq1NHtz2TVeg1nUe+KZLKgUNryL5oe5uw3gbflvkNSQOsbJsmp0QiM45Ont20QmWqtGtkig7pYDhltff7+VXomwNhCWAsyJl1EOrDQoegUGe9Yj2C4J7aL2SHQ5+molUAZUJfXo1DjyxF9E4BX+kiAL2Jx5pQGmJgJhDoJOLc0Q2ZsHJdpHPgx4Ajzx68FzISi526+vdndLy0woULbM+nuaBujaklBpqBR2KvvIYjkYURKE1mm03C4h9WEryPqLvHkhvPKk=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBojAcBgoqhkiG9w0BDAEEMA4ECDZitMOHFzavAgIIAASCAYDpF4pB0sPDmOKG+/w8AWQ8/uq8F5tzhLw8xXOwfj6JIN9K+p6VJMkRVNMeLRWNW2d2AdgWRhhZuNpgO+N0Jb5TfVVYOKWmuC+hfp8eyS1TY3ortUnXjEqQqKkzDxSzwEKkjTLyHGm8+h4g4O+STWJtOW8mZkILSe18syFw4XuTpzcTmkXpirV+L9ggDR1zkl8KlrpTp8/9XGsUTWz+Ifl/5O3fhqdTse3khgAZaYvPeWnsdGZVr/KAO8iSannaMC0yzwl5M2y4WtJ3sMgw+bNMkLATiZScQgo1AQnelLIusxN9XZZPFhpjow13t9+02pG6jpvl58Apu0og75Y1i+NQDProzjDNces9XLBEPUvzXFfcGW9DMrthabuu0b/zRTj1L0McApp9r+pKTVgkng+e6QdHpMWBV7A0lmpXqwr3AzXGhRky5UNNLQTMZbP8jhTXkADsNgMJgwmrNyW4MzL+EZ4kwPsPYlv+vLjeVsiqh4KOVqSC/khv3e0NOzMredePo2Of2GE=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBojAcBgoqhkiG9w0BDAEFMA4ECLnZQJKaSTr8AgIIAASCAYC6rYNwcVH3WG21+aEmBR1PrTqtstosJzDR6KxNLxu+tQdKBWxPGXJqhGViYzQw/3S6Us5BxisZBQrHc+TS0B3Da2EvSwVPj05qRWRVGmmpM2ODZwQIqOGmMHUMroiGC4A1W5wcY5CFZxUT2W+1Jfif0uxQJm7bVN+ULJEe32z4hSPj17p7JMbtQ0JbSvoJ+oIxog+EpjqH3/OWMJG+UdxkyZlfpBx9l8EmNctGoCnz8ZU7BPXtBshC6zMwygaN1vAF965YtX4+qTpwwcxX+yhANICx/3OYxcKUwmO6go0SaVDzkvuuKyx9BcLPwk9BaFklrdwcwQsv8FO80o3vk+FUtk/anXILg5tMj74xIaUL8w3h9rg8K5G3TXntOyRq53bAO+GF0mUQVSB6hTE1J2+9hJFDUkvbsW0EixgWI64FKkorZkPyznP6js76GCuDzM9sHN8ywWsnmeK9kuGyDor+a/2AuFuVa4rDZQ8aDymEdD/bL/1f9WYViblqzkgBnmTdXzv8RUM=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIBojAcBgoqhkiG9w0BDAEGMA4ECK7Z49fQfS7TAgIIAASCAYBSEjWSOqI25pHK+KmoozeWVJ4+WOwjxEXjuuDkEHOzbEaGrtYF9z66EBurZ9rJujpxfgMRtt8P2WyMW+eK5JXHvlgoE1PFGjqmvuTSOmUp4ZUxMrMh9gFb3KaaaSq/AOQj2sZmGC+OqE8U9L+yLO7HGyOtIc1UnzegINaC9TNj1/D4JTy1XgYm1S0xcgoxKZKIkhwHpQYRwByvXWM++wS+fdBp3a4UEKxUlgHsA6H35Lmu7epYlQqvywCj16Mpzkv7jquc1TLY+izuiCee+d8yKGX30DN+lh4svbmD2oZPuB0V26wMzhOCx2x0WbJibBVI+NBqipy8BH11w7KIt+hVTArXB9Unx0y97m7ut8LBqD7ogxqQxXzuIFYibMh6cm8d9F1KSZzEjHCQ9tZL93+eNKenZIFoLOkCcuD4RJfejW8zzPs6ZKKXpF2vW2ahqkbZfqTaRCu1iq9VETSMzam+WannQc1uCzk1iTzTUrsWZ9ULtGeU7Yp3fvW7dpQB4AtAqi+wBPY=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-epc-encrypted-pbkdf2.pem view
@@ -0,0 +1,86 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB0TBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQIaXF1FSWH8FgCAggA+MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECEkew8xuyitxBIIBgAdLhNAUj7Bpglrd+cbXUprOLFwtnPu/WJ5VQw5JuZPoIxxyq55g2jmtE1rK+25YZET9Pzyf8L7fkG1dh+Qhi27AMcefNvbpRncwKQQYwU+C4kJ9PV6V/dvCqkfNB5ea0pbozI9zZ+ts1n7JpW+szP9N8d51I7n/HIdpgPzxiV0eD0ZLjkUoxoRIi7EOVqXGpOt4mGkOW/mRgTD8wv7+koZSWM1hTw2h1hBvPPdWQih9i3ixx0kF5l4ZASjibnKWJZ+WW3C4kTK6xYGQWwZy+fdOYiv0jFClRsPqHKtIr7Eg8TOtQb73rny9hQghchLWbTRuZDzljlV3QA6Hn4Tz0+pM7GfCrJHISkiWj8VXtKm6xSDKF6kyPUFN6MCd35IALnuRdTTGFua12nJye+R7lV+lc9RJOlI2503csKTQK2uGRKRMt2m7uaRqNy8+B9+s0btN08oxrs7/Ui51xcUZ+QZ+t1fDmyH9ym3KPeLBui39mDGXvyjNM3kKcWOFwEXdCYURHWMqVA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB1DBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQI/fA7dpDtXHgCAggA+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECBsyIBlhZP2PBIIBgLBTH21BBWz6+XWVouzSpVJ+XkekRTPrcK3pNBa/1sGCej6CTWr59cdcOr99DZ5RagHp28Udee/n4+WeOsyb0dfbUL9hjgM+rzpo9yNndwVvuRhdy7rpYDqcYkhSo+2bLIEgsItMnYcwbP+x6XsLrv+ffxZlVIlRlhZUf6bGhgsI0gg3AIXnmTYQ1aX5OjZYoNgGUoOauL2d+xT+n6z3IVfHlc5wFq10U73J1arv2vwsfM8PW0twnPeEjRK2WR3JQ+fKBoz+irESQuQq+PFjKcWGTw7oBYpO4Hi3uKB25GzFNWLh9W5dqQ3gt+L9x/gbTlL6NWuG73CvH+cVX+SkjFbncSbSbLA51HDJPEtipCsrZWKeWUK89xaKAvaIdsvi4rDJPI5CRCNpiWjdyy+stkzjrhD43LLaX8UBwQzNXNm+dc/Ku1ZirWW6gn/0vZ2i6ioe618H2mAIqPmCFIb+mgNIHxBhUwyYryiGHtxFDjjs7hK/EuinWi/RbFLlJb6EviF8cNPrvw==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB1TBPBgkqhkiG9w0BBQ0wQjApBgkqhkiG9w0BBQwwHAQIbZbOKhH2r3QCAggA+MAwGCCqGSIb3DQIJBQAwFQYJKoZIhvZ9B0IKBAhhyYd5c9VWlASCAYCLhBDXCXyw+dSdrU/7zCYpT2c9wlSV2xXQReDruSOO7Z6E3PVSf0mAREzhk97kXCPGSQMqZqy5n+GmxcGT933gvGwIsKpTeCtsDwiuxxD/HrbaehRSpXGTKI1SaVeZpKtdbvc4MPcVMu+26BI7EXRwDbmJbCJkg6oR1xSuEvbIewfq6Yy43vGrUK+pnSmaLg0bB7PnHuojb1/+KGfMZBzB1eavCwWz7htrVj+e1D2jKr42aVDLCMt6ROqb32y4dNIpMI55iiE5V+Rd+FsSfrqFkYpvzuo216S98d1/YUwZ4SG/ooZC8yzbZ+3ZChMswBzs+HeFRBBI94RNY+t8cW4cgw1tbzhPfckcHg+Mruo/IKfL/9xDdd0XLQ4ArX91fOOu8O5SfoF69uVj76+vLwao/z0HpqTVhsgbpseWol5k2Dlsmc43O8x1AdKE+Ua5eHgSbEweDzqSZOg4CwF+CIt0wqx9EQgxWAfpTATv4pGu3/lG4POZwfauvHdb1fdIG6oiOhUyi7o=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB3zBZBgkqhkiG9w0BBQ0wTDApBgkqhkiG9w0BBQwwHAQI7J2iZ7SSLdwCAggA+MAwGCCqGSIb3DQIJBQAwHwYLKoMIjJpLPQEBAQIEEO87ALh2iTMdF6gfPDTAr/0E+ggGAIH2qibHW4uIFdj3hmTTLaWXnKDtZiv5xA9HOvTcKgEx+9sxqMkZcHaTpGBq/+AgnktUJjmfNItwoABC5iWUWkhBFBtCxtiT3TcEdQpmvHDrzD8LuujScPRq+rcjIz+u4+QhU0ZRKB6rKvfa6a3TkseH4QMRjwsVqt/vbBHhpqWBBpgc0qrK5iH6ioNgH9r+sW0vemwY2iklPRgwpILAcFYx7GTfmgZ5ETqKvjXD/K4JZ1c9B+jL/e5Bq3oTXJbx+mX4u47F0BcqXfax9Hg7YMuVtA633+zW+cTNjv3PrsnhPRcpLQ0JAMauh7bu1oLoI+9taZ0mwOMt1SZv8J+vR3i4t6TQ1F/h73gtZB428islJKpRS3U6v1aPlLZcfbN9JR+cN2FdArs0X2hK0zwN7uOJJZxSegONq1wctEsXB4E8T1q/r3ojFKuwvO2mwkrLRgh+oDseVijnW2R1m3USUdmaaqxeykLe0CS3beybFss4QbX4F/U/x17z9dqCLzoblH5D+Pb2c+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB3DBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQIC6ZTCRU+rCsCAggA+AgEQMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBOgQIoFMVJ3EE/O4EggGA+uA8nT1iW9fbvxFCXvy2myN9mwQyL8rnfLgbDhYZks+FyZm5tsDLbgw/d+ZhonzOM++PcVpwmcbegT6APDWAZjXvru6gpEwfxFzBjr8uoaKFdf6wEKbF3lTauxsgJjZT5X+uykw5OBGnV3yv2KBX/wumL2g4xiHKbMpOJ7sC4OTs7r6m+uMjimHKlNIbJJMUfq3+V9afDbAz/epyoe96MqFGo0HyA4FsR5D9vKGqZFp3wMXpRFOSJIV3jGzaysVQkOXA+yWRgUM0W1hL039hNteilCy4dMd7JqzFeksNz2wgVoLdUNu41Bvvv/NrAi+WG09Tt+a82kRgJzB9zeVA6ZpYe+Xy8JpSjvXaVerTblODxVymBKT0cQofMRQjZsalLdWcy7+4GrpcpHNYLJ+hvzNLqHeWUBo3P79Chl7FZmFNX9ihdenjb/doTKB/eH2je+x3w8x+xmt7CfeCpcR8EJRDb0GRL7yAcnmtBr2JJa30h1WeR5VeZJZ5chaXYsPBqv5HNomr+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB3TBXBgkqhkiG9w0BBQ0wSjAsBgkqhkiG9w0BBQwwHwQI0g6+CtM4m9MCAggA+AgEFMAwGCCqGSIb3DQIJBQAwGgYIKoZIhvcNAwIwDgICAKAECMcTOOizSU7oBIIB+gJegyhG+XB4/LH/gr+OjDdC5aiht53g6pdhZ62fkmIf0zEI0ACO3K6zRhaF896HO+KuQU85Nds3Oy4fMci4Q6XjZw2Sebxjw873HW8rHgXqlKanqixx1b9eDXJ+GeApqC+oyHqxJeBDpWTo1T4SJjAIhITqn1AQQoKI/FWvjwMq4gD0536N7ln3O8omOoyV9i5+byqgxNLLZ6Ucues8ElcRW4rza/wKHvCMmESs8zDXyW74ax6ZMPGZ0ns5HDR0mVuk+dMZ8vqKQDDuanDcKh728iMIevqFvagKdQI3GpWdr0F4o9JgZqLOk7dcnw/1Ao7i2+onBCk8+exCkyFY0zwXquzZEWongrOzKeyNoLdAYNe1ST/q/Kyr9pxxKpIPTEDU5Z+8Xh9qd+pSjEumqIQAVC2a53FstPJOKHVPjR7NaUN+9b7FK4MrhtW2itsMdI4Dtj6+BjxAqBzW0l8QaJHhr0JHqzvEPDZaiFC2jCjFRSwfOAvb7vOo//gMyJqqBIj3Dms5+2g==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB3DBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQItKps3ADDI0MCAggA+AgEIMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBeAQIhgAaNSOe6zcEggGA+P/Namgvp1HsSCKYgggEIqjXcvE7vIcDcEs/YyN/dklLfn3QkngE7Rzx7zx10ZFMG+E9OkfDzg3VWAJ6vzTAsrGb7h9sKRRtGXMxzM8eL6Umq5IADc5lvD0UYCPLBFGWp7+XFkFEWmZa2o4/abfKFnQVI8C8XTvE/LDNWmCmwpO3TBs9c94fl+Ra16BIVXMq+Ko+OPzsQUDyIFvoAGbnrJQzehN7r6mW7usaiJdcqTvbklBpzo99RrgawIT3A6MGfYFb+TLiDkA4cliUNWuoaOU7MRjQB5n249K8cuvD5qcgpEXiK2SZsZMTuU/zmGWA2ps8U+FKD+sKHi8iU053HDBFUKRYZ8detecUOjGABcJvJWfWDNhKX0KcvK0RXYgVXXaS7q+wVOtxYoITE8GkEG8DlCvF2UjHD8GZ7/JeFYm59E56ge9X/+pl96J2gZftN4RLgQ2+uecWdCDjKF0nJmFgstlHZ8VIVXplzzjIO4zyQ9J4QqpOENcXev6BLQaleR9vB/af+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-epc-encrypted-scrypt.pem view
@@ -0,0 +1,36 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIJXpAnkCfm7ECAkAA+AgEIAgEBMB0GCWCGSAFlAwQBAgQQeUbkFh7HD9nug87uhKnZ0gSCAYAerhWsm8dI+DSaF7A+iBX106H/qjICoREVHN/a56FhO5HwWBGdIgfqv4i1R/6Uv4eAXWog5pQUb+MfRRHNFlejqMik04TPx4kzcsmbToePuPtSJiyDW30p/zlke/bTXYVQXyTVSgzqsX+O2HgKyOj5HrTCzZqM1OKS6YYlXRIsafgjJLlZDC/FybF/R1s/0kCugHTLqEDpHXx+A0gcmvhox7vCScAh4616ay9n+R9eDfVoHEPKbRvcXztB+uifJtA0D6MtdFk/CSYi+PIHjKcP4z/qe46mjJ9PyU/eOCZR+0ppowMwLSKTzPl+acDq+asCiQNoqZWMNUSOB+F2jykkH4fFo1cKXkufIg3LkN4R90oHAK/9/4Vo4SxO+imiNfiEc1ktjedLrF0DSe+6zxFFe1KI45wMC5nBxQfDxiagYN29JZjbb8Up7gs37qTWpSEOeAxvTdTru+u6CaS+zug8ow4MhEnimCy6q0cnq0xHiF719UiPW/xFRiZyTXTyp68JDSxoyz0=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIB7B9+qlAXBYCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBFgQQ/Sn/EiqSFcj4ea3YHieu1QSCAYD3at0cyQ0l+gp9S9MqBUEkHef6drW8CPf/oTnzfEOomtp3mXtwU4trzImpELFGlORl+gNZ9KUoF+YixD62N1v4MhE9Ff47pN9lx2IYiLuekJaHqypRnrh7qU3V2KsrcqMcyNUIMyssLo+CODMNxfiqbBAsbVcKnfxq/KBenT1Y/AiaAq6iPGRxora1GccuCSurePV4DbWOkRt+sz81u1yIX9kzn3xVj/O6IubkDet7uFeFSro6MdPM1TlRMRxavXuCpO7yGEHC/Y1x+4Xq4J/wqrg25kqrntbkomjTgf02H2UoNhHIOgDZCDLTYJPCllcJOU5hjLaOJYD9x+743nxwYs4BNqmu7R+2WW4KMDTpfGnwuY5uhcqHBFPLNSNh0niZivzO0lV+AHAxKp+BqAZE3TA9VQTayRFgp/XSd2RAntld9HDQkU3LBsKzmhnBF3xamLuCwtrm5a6wv4u+bKW8LpgPOxWFMiLmcDOsjuhI/p8DqMzxexkBf3V/6P0ZGpcyI0X8Lok=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIB1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIYz9Ct1ZSF3cCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBKgQQAvCaphXko8zb/53P2CmB+wSCAYAMCgKW+nkE+BAxYuyXOc0IsTZMNh7gvS6coSNo/ukviJS5XNFH8iteaF9N/4y2zUo2Z1Fk2L9MY+XPfZ0wJK1C9MQgEZrJu71d6duhqIiJPoiFM0L7czt//agaPRyblyWSOxhAAZNJeB+xA0kXD1De1hatge80Pfw6C9uGJok8zwl3/N8vn0TdompMFSh3kTVwgBVwRwWzq1++erf9Jg9xL45gVp26PJszHZzfyQCZWOC3a/sivaIeNlu41j+wpHy8gyd2R7HeEAyP+C/ALAs8aoIGHS+ug9JzmeEPQNQ1g6XDvDFwmL4EmVISrTP1h3eauPYUQXmoCbgwD+jKJDZCGVL08MAyb3QX6ceGJmeEJ8oDZURveys7A+j95pw27wBISWEzuXC4dhG82C+uYSRm9RiWepdyE5Mj4aBr4z4c5keBsB8ekD9D5tUjbLMWt+ZC5RYGtNyv9bClKFI+Jtwr1+abNauB26AoBzinCmMbaAyRr/ENKEPhWJuq3OozvIewW5RJbAk=+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-epc-params.pem view
@@ -0,0 +1,8 @@+-----BEGIN EC PARAMETERS-----+MIH3AgEBMCwGByqGSM49AQECIQD/////AAAAAQAAAAAAAAAAAAAAAP//////////+/////zBbBCD/////AAAAAQAAAAAAAAAAAAAAAP///////////////AQgWsY12Ko6+k+ez671VdpiGvGUdBrDMU7D2O848PifSYEsDFQDEnTYIhucEk2pmeOETnSa3gZ9++kARBBGsX0fLhLEJH+Lzm5WOkQPJ3A32BLeszoPShOUXYmMKWT+NC4v4af5uO5+tK+fA+eFivOM1drMV7Oy7ZAaDe/UfUCIQD/////AAAAAP//////////vOb6racXnoTz+ucrC/GMlUQIBAQ==+-----END EC PARAMETERS-----
+ tests/files/ecdsa-epc-self-signed-cert.pem view
@@ -0,0 +1,16 @@+-----BEGIN CERTIFICATE-----+MIICfjCCAiOgAwIBAgIJAKASPKiQipNtMAoGCCqGSM49BAMCMCExHzAdBgkqhkiG+9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMx+MDkzNTE0WjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIIBSzCC+AQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAA+AAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA////////////+///8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wST+amZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP+40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA////////+//+85vqtpxeehPO5ysL8YyVRAgEBA0IABJGPZIpCgSCPPr4phDHNPhrKhmBNMIFh+ZjcSJomXmiTRyohTtIFo9036pNVzMIyNS+t+WTzAOYqaB2tVq4sf+XSjUDBOMB0G+A1UdDgQWBBSC8kx1ORBUpK2yIZjT95j80H+cezAfBgNVHSMEGDAWgBSC8kx1ORBU+pK2yIZjT95j80H+cezAMBgNVHRMEBTADAQH/MAoGCCqGSM49BAMCA0kAMEYCIQDZ+oc3hPRA/zknb6/PZGamT4MQnPBvwW/0MaPiDBw2JJwIhAPBSJdgVyZuyJlzyhDFV+wwodd6UxBRuQ+rXatKcA4k1p+-----END CERTIFICATE-----
+ tests/files/ecdsa-epc-unencrypted-pkcs8.pem view
@@ -0,0 +1,10 @@+-----BEGIN PRIVATE KEY-----+MIIBeQIBADCCAQMGByqGSM49AgEwgfcCAQEwLAYHKoZIzj0BAQIhAP////8AAAAB+AAAAAAAAAAAAAAAA////////////////MFsEIP////8AAAABAAAAAAAAAAAAAAAA+///////////////8BCBaxjXYqjqT57PrvVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMV+AMSdNgiG5wSTamZ44ROdJreBn36QBEEEaxfR8uEsQkf4vOblY6RA8ncDfYEt6zOg+9KE5RdiYwpZP40Li/hp/m47n60p8D54WK84zV2sxXs7LtkBoN79R9QIhAP////8A+AAAA//////////+85vqtpxeehPO5ysL8YyVRAgEBBG0wawIBAQQg7AVNdkUlBnl++PDsD3TPNSRxJFBDp3BzrGfKxUQee/uChRANCAASRj2SKQoEgjz6+KYQxzT4ayoZg+TTCBYWY3EiaJl5ok0cqIU7SBaPdN+qTVczCMjUvrflk8wDmKmgdrVauLH/l0+-----END PRIVATE KEY-----
+ tests/files/ecdsa-epc-unencrypted-trad.pem view
@@ -0,0 +1,10 @@+-----BEGIN EC PRIVATE KEY-----+MIIBaAIBAQQg7AVNdkUlBnl+PDsD3TPNSRxJFBDp3BzrGfKxUQee/uCggfowgfcC+AQEwLAYHKoZIzj0BAQIhAP////8AAAABAAAAAAAAAAAAAAAA////////////////+MFsEIP////8AAAABAAAAAAAAAAAAAAAA///////////////8BCBaxjXYqjqT57Pr+vVV2mIa8ZR0GsMxTsPY7zjw+J9JgSwMVAMSdNgiG5wSTamZ44ROdJreBn36QBEEE+axfR8uEsQkf4vOblY6RA8ncDfYEt6zOg9KE5RdiYwpZP40Li/hp/m47n60p8D54W+K84zV2sxXs7LtkBoN79R9QIhAP////8AAAAA//////////+85vqtpxeehPO5ysL8+YyVRAgEBoUQDQgAEkY9kikKBII8+vimEMc0+GsqGYE0wgWFmNxImiZeaJNHKiFO0+gWj3Tfqk1XMwjI1L635ZPMA5ipoHa1Wrix/5dA==+-----END EC PRIVATE KEY-----
+ tests/files/ecdsa-p256-encrypted-pbes1.pem view
@@ -0,0 +1,48 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGwMBsGCSqGSIb3DQEFAzAOBAjX1Gfd0N4/lwICCAAEgZDAmo+X7o6E2jHihQfA+wJhosfs/fOgvWXCSUFHJM6dDgvn8EVd8pElw1NUktV5cBIcL4HZpTmiZaMpDIXNj+L9EkLEszXMY1wc4qLixGlsOpB5/KCBAX++w3Ur/vmzn5Zxh0vVjpjxNoaKjoTZH6+YE6fbFvp14vnKg6Uexld7k3efJY0PCMStcust6bLWSWikmU=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGwMBsGCSqGSIb3DQEFCjAOBAip8M8cWxEHeAICCAAEgZBJiHgjgH+b7gnFJreI+iWOhI2sIDoiApQXji9QeyOsaWWhygR5sVKL4gkyzQcicPuz/OVYWQPGuMr2Qdvxr+SuVjMmEBBh/sS0mWimxyrbnyZfwY7ZHMd0wWEBDni1KL9vIVSPWLhwfHvEFgdHlV+UnfpWGUoEa2zjpGSCKOy/kaKnrQYeyKUdPaZjEvPU6aJidM=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGrMBwGCiqGSIb3DQEMAQEwDgQISqJaePRS6sMCAggABIGKnZo85GQkubGmIL0f+4RzIu9kERwTSgvnjH7gfW9ZvW15F2GruSyIRiuk1FhsHQhb/H8njlD2qkrYI3f1/+b3g5L5r5g+D/9C6C2ArbO3uZpvQor0pUyuKlESYd4E0LjveTKmCGW+xQRlmjwWZ8+XRMb+KakbQ7TL1lGLew9P3CIDAj7TxjTLSLwBa+++-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGrMBwGCiqGSIb3DQEMAQIwDgQIKUAgsPo+gu4CAggABIGK4j8mtUNzJm88CGuz+NZbdzJpq+A/jHUKjJaTNhN8pUW2n/XeGmozPhRSQzzRHPMLoXUMUYw02ttJVVOTH+seTcRzy1EQwTTmVe1MqL2FCffUCYD+bO5h9rO1AYC/hX3vr9+/O9KCj9pgTlG950+nQjb5f9wsE8BpMMf5z4e76fyoywdjOON7DOrtRoQ+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGxMBwGCiqGSIb3DQEMAQMwDgQIcndWv6ERzNcCAggABIGQDiCUJfL0YnPSV7xA+Ke/bOMRlidthv0nRzQxzqaK/YkNPUQQ6d1RRTigXT/k4yBz9DH6q4sMpnjf3MyCw+YFSXcQXZLnuq8zwjo5PoFyHb0U6QTdgd+IsegqupmyvzXOvLPcZH3/iRwNXqABip+h5E8wPBSzobu8QztTZ/SOBnbyY3ys4QZckM60zLRWabE+IJ9+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGxMBwGCiqGSIb3DQEMAQQwDgQI+DkAP5uNPwMCAggABIGQ/eMzhfa98PWmAzqc++1ti4VrxmHMwzdIGygpVcN8WhBbWN2mChFXE4ZGJIJYmpJyqS23aTIkRTElL0GuB+WxcU/3uQ0CwdLtgHooPhGHkYnbHw8LwblnX7ECdt907KrRnqNHJ3BIFakNfvmbdN+LncPFRdoJ3AMd3maXVvTyE7j5JF8dQGDQ6+iknmvd8YQHslt+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGxMBwGCiqGSIb3DQEMAQUwDgQIPxLQFIF8BHoCAggABIGQWAqlUpSLKexDJqz/+3CHUsHnN0wtPxfe9DeDOB6Akl3I15HztsMh1jr3yvyV6yHCBe1VY77Wp7CTb3eAB+f61rsMb3iFvgwN7ba87qnpg4+97oxU3UpEqpiIxu82NZzModHtqXcHwZ3RT0rc/f+m3skEYWr/kjZaqCG3Qy08gdC8ObNfZbOQgFudXkFDmXb/64V+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIGxMBwGCiqGSIb3DQEMAQYwDgQIjZUSrsEemGsCAggABIGQQ+r2iokCHNV0ry88+w094AZOro+EJmNzCbXHbMfLj/nlZGNaPS1xMGG6kUb4e8JjkqTGbjHCM0ryd3czS+6KCZeuQqGm2C6ui6rRtfzMedEFFTfmxuXOBYfi1uK8XAIqKJLr/Wzl22j3mSHotA+n38ybvd7JQFZt4Vhb4moXsGwKBBT2cpwhMcHmspmtwWPeaXW+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-p256-encrypted-pbkdf2.pem view
@@ -0,0 +1,50 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHgMEsGCSqGSIb3DQEFDTA+MCkGCSqGSIb3DQEFDDAcBAibocShMlF2eAICCAAw+DAYIKoZIhvcNAgkFADARBgUrDgMCBwQIzyEw4JtA0eQEgZBeh88mnANFl1ZU2FVt+roZqlNFhEBPo9SAs/HczbLsE4RXypn9nvVTdsKUNZvRvVIEi095YLb3F0hlqK2wD+tzuj5oWjZpEKsnTZ2w+HUbyMbh1HDqSxq4pcpTHm3djjGuMzr1tBxUecrPV8qexe+wobqCs+F4scVogwDqs7g10qtNUeQCYNCT8dnStvkVHaoqXg=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHjME4GCSqGSIb3DQEFDTBBMCkGCSqGSIb3DQEFDDAcBAjDYuulig+s+gICCAAw+DAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIPCAI9QalQkcEgZB79SD4TnF6u3r2+r/uMo2OuMnqRNw+PixfZnKa/xAxFmhKymKgCnVCXnulwz8fV6tXzFVZ7Kork/s5R+KUSmcRZPtT1bfRgnTaASsrKmwC01dANRsLA/irmRZNtonwtYhmpkpngM19cGpGKd+COZW3HdkNpOYif2ikfLqANZ4Kr8BaxHCvBH7pZUnQ/EMDZ+diCo=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHkME8GCSqGSIb3DQEFDTBCMCkGCSqGSIb3DQEFDDAcBAiUTRnu/r/JDAICCAAw+DAYIKoZIhvcNAgkFADAVBgkqhkiG9n0HQgoECJI2zhldluaSBIGQL16UOcQDE7F8+z++qmBRFh6SzmOqGpVvHZFTaVYPWtnr3+8bmeG8VODm5AwVW11VTXnD0EH+t+bLu+KBceosNMZNhlKVBdFZD1qdZO6X84UtRq/pehWBw08iMY0JiJKA7bEF1xMJXK3TV4+wA3K2ryj8r+1B9jDxI19SoTlKpB1WFUg9QFh1g1wMG9cC6JpcBPq+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHuMFkGCSqGSIb3DQEFDTBMMCkGCSqGSIb3DQEFDDAcBAhvY1bt8cacOAICCAAw+DAYIKoZIhvcNAgkFADAfBgsqgwiMmks9AQEBAgQQTKETRz1UrWanXQZlKfVdtQSB+kCL/kpfUMb+r/nEplRgUQZH5y0G5pB5sT1LH3iC2NUAOEHhEBr6Pn8akByBDCrRY+h+rO1SJME5oo7polnb6srxnBrkjrvKYZ6ne/3yG0LkYAKgF6nRmqNIVJVfees4ZT+LjeuYMNYmgMF4HpBcpkMz7zafYeb6NXQ1UMasHN1mbIUwTPnDN8RlfssuWHLGqAs+cg==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHrMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAjYLsHJCH856gICCAAC+ARAwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgE6BAgsrVp7+I4/BwSBkBbz+b+11SUaYAUR28juF71s5ikLSUa7draImlFNYkAIlr5HsTvyeyzKFhGW7u8zlJZkm+eYofzJ+2J4IPN26rgQ3/QEhVvlyvCyByi1HDdMuANsk7tcRrdEXotXAwQNuOteE/+/DjDWwoiVuqXVGbsgk2ail4TwMyGLCVZhCbrx/ovsLekDB1BJFWjXIPHZFVfbA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHsMFcGCSqGSIb3DQEFDTBKMCwGCSqGSIb3DQEFDDAfBAhfdqSzavBOTAICCAAC+AQUwDAYIKoZIhvcNAgkFADAaBggqhkiG9w0DAjAOAgIAoAQIERLCMbZx2s8EgZCc+ua7hkVEe7aHr1HqxKgFirPbdRB+P0ov7ymfUkfiVeUuKbLUE6Wpbf1EAIskbAFYT+dMaBnqbzfbAZcKNL+1lM9xvh7yg8jWsFn4FqAezrYh8TTb+OHNBKeXGuk/qsPQ2W+haxw8HAIx0Ykh0XBpAMs2hPh33FPqZsbXiRVG3zhHuD0ljCkpymx+ViIQGB1wNg=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHrMFYGCSqGSIb3DQEFDTBJMCwGCSqGSIb3DQEFDDAfBAirhA8ZkYpz3gICCAAC+AQgwDAYIKoZIhvcNAgkFADAZBggqhkiG9w0DAjANAgF4BAhfyCO8OozVEwSBkA7l+lvkcjvvGwVZuhKt4B8megLXjnBTGu9Q3mhVAjIx05SfjEht7CGpNmFEZwgsITZ6l+KTdFpjvw04q8J9auin4Cx9MO+wO1bXujMfHe+uVITyfFuP+s0riRmXVpLu/w8Gsz+v41UEIOGPzyikE1QR7YjZBXbmKiV0I9g+ibMzI+/8gL/OzIZqdZDLQNfgIKcPQ==+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-p256-encrypted-scrypt.pem view
@@ -0,0 +1,21 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHkME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjA6KltctaCVQICQAAC+AQgCAQEwHQYJYIZIAWUDBAECBBCxRl8OYcFksxBJJa10CqfFBIGQtgZJGAiILkm++WAwjhv4cFYw21cC2CfWqf+5Ns1hGIe44C3wh6isgNbk2EWD41JFNs3Kh8ov9yIwj+mN6qfNWI+zhCiJ8rqJp7jmec/xg+QcPX/57EFSoJr09YjhAj/rABTornyN0BU6qg+d9Bbqe0fKVK/YnChjvMKjdcpKLOJKGEjs50AYFdpWj3DN5x4+osj+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHkME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAjFtKsLQwg5hgICQAAC+AQgCAQEwHQYJYIZIAWUDBAEWBBC+UkFqffq2uECRTJE0eWzoBIGQC1wqDWORz7KR+5nYrSt5sotzD+NQ3x0kR4+m7lAJuw71KXGFI2uYet7FrxKmR6UFFNcUv9/HY/TnV+iNa9SWXNEPTPe+7Uyy7dHrNBdP2EZ4b1+D+EA67pH2KGPZUOgEkvNR/12Q5X4DdC+XX6ZeruqOXIWu/t1A9KOXfbYwy15a+KoiseRnaRHflunbBx4Snkg+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIHkME8GCSqGSIb3DQEFDTBCMCEGCSsGAQQB2kcECzAUBAgm/Gwp4kcdSgICQAAC+AQgCAQEwHQYJYIZIAWUDBAEqBBCM3Ciqoy4d+2wE7C1lOceEBIGQ2RENqgg/ZXwD+UtHVjHM7zwY1ZC9EDRle+pAnL/nPlGk0mnzbHt3rWZVFXVkZsTOFZMNfNYl0/pDy+xwKJPp2gzOuxegNsk9v4CV/s8vxAfZEEa9wv8nfcp4F/eaaSU4cC4ofXgDp6+WAz+uaMPf2yzdEdgaolfJudbYlqJ9Ufra58HctZwJR48JLTYSYBuBpHz+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/ecdsa-p256-pkcs12.pem view
@@ -0,0 +1,305 @@+-----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-----
+ tests/files/ecdsa-p256-public.pem view
@@ -0,0 +1,4 @@+-----BEGIN PUBLIC KEY-----+MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEdiq+DUTenjFcFQr7vbN9rcwuYkiR+9kOxipGxnBFcntPNHGxt5WAENXLdmPpjhlIH90ruPGzngpMQKgNxcu8QBA==+-----END PUBLIC KEY-----
+ tests/files/ecdsa-p256-self-signed-cert.pem view
@@ -0,0 +1,11 @@+-----BEGIN CERTIFICATE-----+MIIBiTCCAS+gAwIBAgIJAJf6ZvHS4D4AMAoGCCqGSM49BAMCMCExHzAdBgkqhkiG+9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgwNzMx+MDkzNTE0WjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMFkwEwYH+KoZIzj0CAQYIKoZIzj0DAQcDQgAEdiq+DUTenjFcFQr7vbN9rcwuYkiR9kOxipGx+nBFcntPNHGxt5WAENXLdmPpjhlIH90ruPGzngpMQKgNxcu8QBKNQME4wHQYDVR0O+BBYEFFPacwYxY7UVPo4lqM6c72CnWEdXMB8GA1UdIwQYMBaAFFPacwYxY7UVPo4l+qM6c72CnWEdXMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIgC8D3epwl+GR7JF9+v0grNhBeRkTfSqcIHOUR+Z9QKVqACIQDQZArMqLAZO/q5XWnCOmurIjuH+R05sQRKTcoxBFr7nNw==+-----END CERTIFICATE-----
+ tests/files/ecdsa-p256-unencrypted-pkcs8.pem view
@@ -0,0 +1,5 @@+-----BEGIN PRIVATE KEY-----+MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg3UFxuuhLhGtIv3w5+5GbQfOjQ84jaT9KQbEqfYQ5u/f+hRANCAAR2Kr4NRN6eMVwVCvu9s32tzC5iSJH2+Q7GKkbGcEVye080cbG3lYAQ1ct2Y+mOGUgf3Su48bOeCkxAqA3Fy7xAE+-----END PRIVATE KEY-----
+ tests/files/ecdsa-p256-unencrypted-trad.pem view
@@ -0,0 +1,5 @@+-----BEGIN EC PRIVATE KEY-----+MHcCAQEEIN1BcbroS4RrSL98OeRm0Hzo0POI2k/SkGxKn2EObv3/oAoGCCqGSM49+AwEHoUQDQgAEdiq+DUTenjFcFQr7vbN9rcwuYkiR9kOxipGxnBFcntPNHGxt5WAE+NXLdmPpjhlIH90ruPGzngpMQKgNxcu8QBA==+-----END EC PRIVATE KEY-----
+ tests/files/rsa-encrypted-pbes1.pem view
@@ -0,0 +1,136 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICoTAbBgkqhkiG9w0BBQMwDgQISh0OTbIn1RoCAggABIICgOWdmj2NX+yOcqMM+1BorzrDCPbRhZHhXRT5N0JCn/+ztezF9DJNRdXODOe7Tmv4fwFwon00R3xTYgHGf+OaN+32EWJi7YCYzBxfPbqNt/W5bnpdzg+be2T/zFsddDLSpG/SAaPQl88Njm+6xy+XBKP6b/l6CaQIKcuujJCGO1D9znnPGzeho4EjO0usFCG1udGDBeaxTunfYXpllnz+0+TBrtvicNw1V43vTUEmspKo4Qm64gXOfayAaAaHMIJg0s3BapzlK5mUtCFFScsa+kFtFMOv8oy/JIfO4CRTq4tle9NRlevYrg2n/DKHhDV8MIrYrHya/zfkV7WylgqT0+3xppLkg6xY/EWiSnZDFj6yNp2Abp2EJ+xzUcjJiVOiKlLFhZSL7k+FbMIYyRIr9l+NQfTBHERqxdhj9OsoUMJnvXjrk/55mCkifo3DqbsqlnYlij44GgUPpKupGv/k+Bs+Sb0nCOgvBqUv1IoZ5W19rc6LHcvV2XOObJErEJFqLwgHRPbM6F5sNqXymBsYn/Tw+N/ZlJsWuj3R7eExb2vDvTSONoirIJA3Hqjytr6r0hLi7lxvTwJ753Mp4uLf0vD1d+6GzD7Un6CIb8eEXYjPP3GatpJDt2Zmx+5FzyRtk0V2EBY8xOCW+Ib9acf86oNGnV+EELuDOU/IZPzhq3TUs+D5R/etpaAEcXt4iIzDmVaouOzQE1IO9zDGK9fds/9Wyi6+PRwmJ2WdAE0LK1blHxl2S5IGMY7Sq0ucoEp724X1C2E+HqfeLmQYcpVoJUeiVKzU+2HfYNjmoDLsDEDd+Vc1il1pbXsv1wNdoGjolosK0RFySeuKM75NXrweezoBGKYAx+yj4Wp+4=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICoTAbBgkqhkiG9w0BBQowDgQI4xTr6w09iQUCAggABIICgBSW3zoFMT+RLN2F+OH48irzSbCwYuOAxHxBB2uNsAAalagqIyq++/X3Ww9wtMJU5NRlFu2RZ2MGwV6HX+C5nOHIXUlg71b5XTUp0RnP+Qi3TP+hAyQhAEHVIdnEUOLIPp9j5SJs+C7NUQUsNr+UQExWN/72nBv8wSe0pbKJzCk1ogYMechYXAo6jWnbbvgUheUPG9XJhtl5mW5J1/R+uJJP8vrp5TEsVY+8BRkCulOlOaT7TywCwhSoDJG2+nA06wsEhEY7hubLzSvaircl+TD63N+CQhTCaIMoVxovG38cI1+fuADCgvKWlMtyhX54cDem/rzk5N9iKtVAqiLP2+cA3w0nkcOBH++FvZLh9nV+yrV60nkhfl6LN+3Cca8qcwyzWgEg3MCcSBGzcPpozC+U51Yfd/0jv4yjCCJFh9BKa/GDaEPSWrSfQXEFkbTXlD2e/G9FTMzyYmvU6rK+sVa+j3wrCqemBMW1xuGFk1b9UntZ2XgMPShPDlGsgwnegWv90bO1VRsUd+zyUbuggnT0+ZAbFKhPVbkhvlW233MvteRguwQTCL1DlYPo1i2HSc2vmozcxJHPbriEVRJ2QBznU+6Wjz1JGwKjJeNEFDT0jykL6SZNu8tr9VIUxdxkxxIipAj2Z/EDi07TiWJ7L/SsTe+5quEVtjdAxfMBTSfWyF3NRzskypazq8D7y43LMkF88cGjiLVw19WuEd+WWVyhthc+/NoALEQb8VaK9wr5U7kXMehOX6pFSMn9s9P4h4Ch25mAGXhv/h0pw7HBZAv1a4o9+3TpghC01SfDCpo7raXui70vMHG4TSFqJa2Odd8apTBReMWRe+U40Q6rxGlw34gk/+J0JqYno=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICnTAcBgoqhkiG9w0BDAEBMA4ECM6WxHwQYHWoAgIIAASCAnsARgLVpWUcxsM8+i8xKdB0QCGKTBl67KKFwz3gdix/G/g12KxpGFIYb2yv0kE6D9iYBkrQhpdEuLNez+K6E5bmyEJRyHVRy0QdmfOca4pq41Q9kq4+6PRvDR7OhtbxxGvsAiFnQYv6BDXMmN+iRwDQ8o6R7yjBHTiY+5O36nihjikcurRmHJ3Qv89ZnHw4tg6giTMMVW4wQeuLF6N+BKF0jntk2Jbfd0WGlWfp+WFxd6+uLeYa4hN9F5JEnhqVDTbbkWsl9pxIZAEkHY4E+20c45SoVDx6TizIj0xENFnXAj8WlEDepSptzS49IGHTo62FYvndL23Rq3tCBs5hq+uhbWcDrIJIggFSyyXB0tLFZmPt9fN+Iu2CeoAleeTPiI53dJUsBM7suZ40R7jae8+9XXEPXcFt+n8iImScR20deyzjr+BJ1oZY1Nr68X4tjkL9cBIR7JvhrMN+lWqhE3P+nwBhS2uW5UCH5iTM+dBjFiML4g8FdVPBk4U/G+opDYFp2SVrrVZx0bhPAk+kzqdx+q9x9W5dIl+Ck+rC7jUKqmvY84G0QxsQxe/ZZAL18LYOCGi56GkVMbHdOeq1c3nac+TdjLYXRyEKDS4XcwyFj329efiYepvXlrhXbg1Vt2VZFYG8ZU2ySuQ4dvC3pWwWH6+rvwTQQaydSe3XMETSGF++fBO8KE3T09ud/wk4oCz/hVRxBD8AoqEzI3HRCF4u3WE+LnI4SioONlYdNJlKQ5mX849DzTzTQxH1+J++eYr2Hc02Fwfr7GoapIStvx5mE7v0+VNFxZ1PJt9epHsRWl3+B7qc00uYepLLApOaO+R4LA/edP+gYqDh7ZfweeCZqXfIu+7w==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICnTAcBgoqhkiG9w0BDAECMA4ECBLMiGMYBQ5qAgIIAASCAnvGBATmzbWlvdAs+0x8+aHrCo5HkzCFwDQkCrvaVn3HZXx09DqK2p4TANA+iaKn2eQwqljuTuXa7cCPh+t5ZZSxDnErrXalQrXhAtzTX67tHYkyeaIBOWdvIzRBjQKFUfaN0zS22Itkx0ZTc2+RYWsBXYNe3JwfXP+0RRv7Oyd/Kf1TgINgjr4Lx+T+PykuSh/CAvg7RKJEEpyXJ3a+11UKPuJpK9OeXMGrozpIVTZ4n/jHH8+CtEyZALbh4YbSv10dnXF0UnckKmG9NOya+zqX5Um4gKpsgruucrf0aWPrfw0dmKXVEKNHInAWiknswKuG2VgRLoBQ8yoNqGh2y+7btNB67bFSZsTsM/Phhc0UbSGLrsidKV8tnBy/5m2fE2oUTGVkMChVMbKfHtAzey+98fy3t1CikPW+pq+2I823X4UXAHsffHHey6Q0Vyc3t1klPcbhCP2GHVBBE7D8lJs+/qS0vxoMJGqAvwpNd/9dPmh1aY4jIBufS4YOuwudT2CpidM01a/sOcZ/u2VYFral+OrzYFEVqD+j5A+/g9qMPiUYFduWavCJJJjpyILuDMSCCUkPHkLVTrcieehieiT1t+XXUq8EEQQc3pU7l55SUJtDFGj/E7r0BMBXv6WchowzbmRKTw3XM97FlHQBCjAlzy+ls+OPQcYfs6Cwo0McKjPSz9RV/G3rb7ROGthsh670OZ+8zD2QjF2rdV2evkor4Oi+tM78TfyLqoljmzi+nWR5tJtqIu1aapqrgLmYTKCju5G23gPEhNpJiCZPFtVDgRJi+Jnr2EEDIr0Ya4GTjFGuhvMVBxJikKivJtuwxFQv5GJjZigaQ9OUysng0xi2ehuq2+9Q==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICojAcBgoqhkiG9w0BDAEDMA4ECODLLH2C0BS+AgIIAASCAoAEF7rmVky5iuWg+DWTT8y8r4TXJ861WQVo2wafDh3CMqIdFINTEHotenQf8lR8OOTwpQgvnYefO9vzS+vs5bYYfx56Teb2kl7UDUIpcTyHoAKDqQ30G/b0Wp9xuX9XHxx3dX/ulKasjhDk/1+yeMrj9elZ25/Q+zpmwcMcY0eht/9JjA/VwWmqqjys+QxypfNtfM60yffYKhTZl2R+/Y6FNSxAN+zIwl/xqxITSXuqnz2XqR3IMthS+ZWsiAgGaAvh6Fvb+43k1zraGOdv+I5axvmDJV/nNkq74VEakFlDBEckSgNOf4d2dcmiBt0vy4ASdNC7Tv+3hQlmmXN4Y+tWMaY4pgPQOThvFlG/5ukSmEKBYMxIZzWUNDZS870Z9ceNmbp+UnyGD8YkjbmmQL+1WGT3TF/33NdHpLy3MRy0ASTxzYfvYTRgkWnfpzGR5gsOVu2D7UeXbjzcJPkTHZK+k8ZiQ8v6ACtDxCx2OupGbOVcAywnLP9u8wQYDUPLTq/TdeoBm1W46HmDqQwA/VPL+YIbl0p44ha5plhtxIWN+fan/asGRBBf2pHCF8dBgCGj0X77fYSeuq5o7fYn5wVUx+KaLR0g4F13ji1LHzuzNF11WA79msHpVUNIK3YTKFk7l6Rup0fSRSGJZUSQg/TbP7+O9rwkEcliugCM8BGUibs5ZcQfpHELQNA5QHgVWBC2Lp39uP4OSjKDdX4MsKuPhBi+ii1e/xDrSKoVrwJjnb/elKoaFNIczMBn4PD82qI2sARCfLC/zju6WLnfEeA7F8FT+zhVZ0L08/I/EmWZueDok/UTwoR96l5ED4TUhl/f1PdChw//wxXqTmnf8FM9x9u6j+ussjx7C0+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICojAcBgoqhkiG9w0BDAEEMA4ECBavQC1n+uBBAgIIAASCAoAcCG+qBemh0n12+3dgUZr9MN3TzGYIr6qeL0puLn61FAmRj8kQzvIzCgL5WOUtnhzktkWZO0oHkz6Wy+3MU+AMi3pjnFJ9dod7/UBbnjsoEZ89gqXc1CkvtKcgETOgGxuueHT8Xi65PwS8ss+1qiks/YX4FinIrFk97VOVR3/jd4gH+bMK2cMaMWy7GLUIVgEGYuaa1Fe/lIPkpyb+07OeBaJ6bkhRf5dhTl8CIkKhsW1vLUkbVN7c+JrBeQfIw2C9qEc3DrepEXFa5Zj++Cub9EJATUdYmCXq/bc05l7dVrUmZWkSh6VKJSuC8XlCJaYMCW/ok2JwQzPmB/m92+uWOQtjxhyKZ7BdKEAFppNxPQA2+rgQYVBQ1erJemAw75fQF+izU47LCr+u80jgWD+TSdxMxxuOjoFQaDu+1/j6gZBcK8ktYNn/wUPZYfir90pgmAu6S6yoM7gSn7A2X6f+Gqg25zPJLAY7CRWLBPRcHPUau8S6M4mH0Y8/ohhcOpcUraunIrB029eWzm0ll62S+TxL40775YOLZ6+1K/BtW9ZAcTodLb0j5jBVAMujxKWFKNz4sgs4KmxkjmFEo5FjF+6QvX2OGg0HLwJtEtb8Q8B2L0z661jNysqbP8n+thZfNvgzYGGl1Qfuw5sdpnunQv+rJygKIamLhl6vCCA7BAyzB7cjKWxPYFoNzM2yEVJ8/eAtrYowSisGt8QJQJhO6aI+IICAgD519C1KLIhRuUP71SCudUZRSGlaVIiAwOv3Gx0cxDysDIdF/hwZfHoJWt96+xoqgp1isV9eac0dzVKZ3rQFoDBjQ2j5wCAB6/MZP0Vgv+Ipx+uhrBOmvCMzdxh2F+vf21uloR+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICojAcBgoqhkiG9w0BDAEFMA4ECHtGUce8opaaAgIIAASCAoBqigpolvtZWgII+AMSQNceoXlu2Lz0RJaCEouVe73ACMwcnDqrGD8vEt88+sJs+hgee2wqjXJKKVmyO+KuA9wtl09z7mCfcPAmGe11P6PZ7N4rdxYGR7URqQPsD7iD+S3v3CLlSnYCJnY6Np+x/j4Cfc2OIH90Vb8gxT5xUbF3Fnt459yyDnOxJoxSxD7pIv2SJqw/TD2gxPO/j6B+mdbKTMubL0laDraoBLQi35GMpd9Z9HkyNKCxuk1Bw1ApUmXaaf8dWOxkk/5LIzge+24eCOp445zoktbpN+Te+osHan7ZILte0bf6r2D2zAS9gFgx/xJHDeLcCYmbM8FCD+msygHM/rqllpj9ztjwbpsnUVpLGb0WP+mx7XWGDxRG6aOlStFw1z5vC3cvBqG6Vc+T0WW+xWcVxMHzVjQXkb2K7RUiVTq9DXQmq5cs6VA14InkulMXmGtaqFO8jrPOXO8+vdauxVRrjpzZFuGprCN3gK0/rpZ6rk0i0pPJ3PRKw7OXsnHXSwzGljtP0RbBGpBq+zq+5DIgfhLVHe62TFbqCKguvZIY+8To/tm+1ncu/unt4xhLZp3lnXZ8DeDUfwmps+e6RT8hXIEeibFy7/nG1lnt3KfMgKlLA+F1nDZyv+s6T2/IWOn5i0z4jwkfSQr3ND+e8b3AYukHZwgqae7mYw5T/SnMo19A2A4Hj7qmKVzzK5p+P5mOU6s8CEdQcgsDvBG+geAArOaRkSwL+n/AA/1gjuHbmkF++hRhlto47rUui+mRWU0O9CeUExks1cL7wqEG+VVB4JfLBXyqZpjNyIqfiv5TXqhY84yOwuIXd5K6FUCRjNa1w/NijwBk3qZ6viCNM+qja7N3Wj+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIICojAcBgoqhkiG9w0BDAEGMA4ECPDymiJgHpERAgIIAASCAoCu4qQpIS8wXbgn+eaJB169bcoog3uWtwDKOrmfH38M7aVwOW3WEK7vvv9a1MCarWXUFVqh44WAKppx++t5ZQto49prXEHu2DhJn19Cj6bG7nd2E2OHmnzGjwqgm6CIJTjFvPNRQZ3eS1dymO+kM8sDBo4Sq6AhMSg0Y8S8PselzVqklLFXzXJGzJDSLpvpxoVNsqqXGq12sKvQbcZ+JTAsydHG9OJhOvlku/FfFQvBQY7tw58+UhuoxDtLVV78zhFJ8dYswgBZpYMFbP27+HKJtsv/x1Jg+Rv3l2AN5kgoUJ4oH8Ow85PzaDBiojAtk1/mENzBG9V6HHm9rrCnP+QeX9G4gFuyIfV6MJa8D+D8DeE9tBMWeD9j2d3zsibBDfslPR6JnjrIeDPAvZhgMC+wzNkJ0Lrbz3j1l72GbDDdp45a5nVQYXpyOPYfCv+EgyUC6wHIUZiFnpae5g/184y+vAMkiv8/7vXK+/DqXmu80yMb/EiVrmmrn5q3CmTfuNiq/NnlcADXcYOieFG8P8rE+6rGoyD8JXJcZEG51aOrE4xCVWRP8SvASuaYkJmku79wRlh3hnuRjsj30pkWYPCtA+P3MKaoagh1NdDHflsmap7U5m0A7gGZ49/c6X0vk7H+PKBsQ+97UTza6oCVTY1vOF+/S+GdwtsOxeQlMBtY2UGa7b5RUzZUVV+HgM/0Wy5ry5Yk3igJxh3+7pp4pzxCgjK+OlATWZnbeG8M5V5VGNfztckYm9VouW75YatIhFhN2tlB5zI4cnlns4PD5SBxdKGf+YC7qdy+5AZ1aOv2AfD60jz6asSeFGq1RQHOubprTOMUTTmVDv4Li9zcuS1Zd37Vy+kGbbsAyZ+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/rsa-encrypted-pbkdf2.pem view
@@ -0,0 +1,126 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC0TBLBgkqhkiG9w0BBQ0wPjApBgkqhkiG9w0BBQwwHAQIYhESCE5rdlgCAggA+MAwGCCqGSIb3DQIJBQAwEQYFKw4DAgcECBJVswj5JKmLBIICgPNcarPhUgPzqAFF+xb8LvwWSW6ES0QTM4d+/JydSSoBX/UHW95efGhrUOVqRTuuxyPvlEepMBsqHwfur+C3IlOl3LayZ+vkbbkevt0fbb/pYxlDkUqefa7q9L+VJ52KyWRhrSytn/nvoP4Ptz+zlYf9C15dPBg8oKHYxrKThhN55kJXoJvJk+kCCuun2sGECePhy4gRpAhxKYiI68G+oSWUe9PisHdO+VbEbF310JlRWPHq22kYfjN8rEkNVBvW2WSMLFkMfTGwAxQStz8A+n9sxmIKMuB9qXKin0XlnekByl/ZZdXdxIYmZw28627L3Y5SZM0YwvHDY4ZHbIhXB+1iiFAabQBSw/1HMn3Pf5wSikBUmfRPZ669h6F1+B4yDh9pF6qoz2bZR1f+1n3Hfh+BYlMq7icwhJukJOJX+GnILl5JkqLkM4kUufZGoBbcfaB2WC85nAgVEggNj40H6E1+1HqiKTNu38FWnUsF4mez2urGS3R+o6bCXyOOEoLwfdqNCSY2PVMBRYyTMtkfj7Do+daC/zQBlal5+GrqiyJfgHAUJA0MkZHg1wB01nRSXadSjhMqbn4FVxDbd1ZjGlCgv+DggNqs5u6J6vg6CXnu4SN4MGIAJS8l3epbYIKmu3//+0ujD7Iy60HPeReABj2Tnj+E6ehImW9RdbAo6dZBOO2m+DHpdo9KwjHakNZ9Tg8vTj9I4Qw7wUVFHZ8mYxe1IKx+mi5KH2LvOdcaYZJobWDvGip8hjzF94MZrH/ZOXBULfgQd5nL2mzex54q6BvGD3U9+GpbPDeOcUZsXiqxaA4RufI92YKX88PX14XanF/J+Zq3Fk8qbEzTcOebH5YHm3YPP+Y46cGxA=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC1DBOBgkqhkiG9w0BBQ0wQTApBgkqhkiG9w0BBQwwHAQIyjzOM682NvICAggA+MAwGCCqGSIb3DQIJBQAwFAYIKoZIhvcNAwcECPX4T6nk9ZThBIICgEhpSFw+iHX3+d1i1P+Nr6clbNmpjcOGjpoav0IgZDcFQd+bKnOAAR4IEdJVXCUGOdu0Sb1wDTsR1+W3cr//6V3HeiaDbzSDi910S8IgYyuYhnFAFdw/FHoaO5K67+vhaedhV3Vhhio7xv+ZNWsaiH86ulFpc9GNOxc+DQK0CYd5KcmJA4wMVu4zEZ6uk0k5bV99UueHBBK7mBB+my7QmpvLpSZi0v3Q9oWP6d9mGyixsmER83vmnCr92cmq9HU95OZl5Ach6WsdDn5E+4SeyfZMS62/xqIXf5qyYvPgJ3vyzxIGJDB1v8lboQ4YZrcSKWZ3RyKDx0Wsj9fgA+JJxWYtj9Ukglz4md6Q7kuKpd4lwL48Xp7/voliri0Y5NsfFBOqG9KwxcC/bQe0H0+vcvDrLZOiC6J/IGSxterbl2etVRrzeUl9HGJhUyHpjMLUw2UmA3PUHnCZH6dPMdK+S12JpEqVq+7mkYXJH1Y0eAR0Nc68exYnB69SCT8yBQ0Z82FvhY/NwGVgfDMMAO2d+rieVuMf50hztcDJ9sYTnfQNtf+0mPn3b1SqT6bgAuPBcumS+RkqNSJkmriQWFsjF+yDIwpXgEh+k/HJ9rhMJP9dpoLbHjQFEi1AYW1pX4Gyt0H9YkwZXLiPO4ymDttLBo+z9DqDX3bbbbPopNuA7a23LxE9yzj7BfZhfh/kiqCpvfE9R2H+pcPJNraJY9A7lLt+mEJmCGYRg+lA7uCSTbuuQkuK9wgz4oD3U89CVuEmh7+m8SK02KDxAgk9dONcAIaE+FhUIBTVhQqhTwxoEWrC5XSarURpJY8iwe6PckfwLUgEAqGwADHKF+wwyCgCgw0sA+HaOr66k6iWk=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC1TBPBgkqhkiG9w0BBQ0wQjApBgkqhkiG9w0BBQwwHAQI0IWpzTmYNqcCAggA+MAwGCCqGSIb3DQIJBQAwFQYJKoZIhvZ9B0IKBAikzaHZjD8DlwSCAoC8h8+cuolP+hfAj9NJcfnQHpKoMZiTeTpu2Qu2zYuzkfwjUSjJ76lK3M1gj15gllieTGVM1BUTC+nB0isUuIN81Zhuj3Gja2iz+y/BEX/CCfuRXkl/sv3Z5USk6RHPLlJOA5HwNNDTnx+CSXlZHqBXIQdqWM2qq3ADC9IM/JsC5QkA05rStL151wep9jOgNJMn6F6NzOWpd89+DJfVAyiUlSnjySHIVjlBtTkmrBz7pF7uNnwIIXPTFx21i4RjDuuRLF1DHvGkO6Yj++ftnpGOPRBoDuyfSVyWkiPzd3N4Hyuw/4LBGrODYJ+QggrCiFgebZ+W4K2NNAAD5+LMWsnsd+zXYXl702z0PwRdzHg2R/Qk3MHq+0tiqxcVsRJ7ZtbXDHZT27yMe6NclO+5WYXgQjWIYddThf5cCzkud3uee0kgAnYHN5FWX1JsGGG+3GtsClLw2HcyrcFBDeG+KcAVrJjX4sLO/JFr5awOY6WGyopKC84MM144nqhSirhotv7s3DoVywkG7aHuggqA+On+G+FE2vBxlxAQImuX1TZhvSXo/OTO//Id9NBInb8hMrbsOL6esztuXcdmTO0S/+DcjrrCApow9TtFvtCCscwesDnljSM60MP4kMldzJAUOMgZPwD3gWzF9OnSVl3Slg+GZk9OFXXscQVhhl+hNQ2rD0ea0N2dd08oPwS1fChv8uYJEju2D3x7GqK0hSceps2+OnOgClrTby0wY5CvOwP6dlBA7KGNuqzp/zGmGciXDJ86TiSO8/L/cJzkEhYyOidX+nPY5eGpf2LdMdBTkRvQxqFA4AUEh0qnCe4jyMkaGenAg+B/w9IIjSX8WBsmKKQvW+xO58a7/TqwVZ+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC3zBZBgkqhkiG9w0BBQ0wTDApBgkqhkiG9w0BBQwwHAQIbkqrr8tMrPoCAggA+MAwGCCqGSIb3DQIJBQAwHwYLKoMIjJpLPQEBAQIEEHN1IWKxGrmHi027/+3DgUEE+ggKAnK7ietFdSKlL38/wpgt6wT9Ef/xtpbFL+y835tgJc1WQR5WDdCcuclJunqcU+H3wJF84VXiNg/jpLj47iDtGU65X5iF4Aj9KWppU4IVkz7XbLclXwBGTZMN6i4Uvx+DzqeiDPGk36J5HpqntfmwrPLGQi+ua+vfWEoFQWOBweTxF9ROLIFtcZqF6Vs+rpp+gOvsqDFVYJG9WUDFwYkF8fLbH69paGw6lxHTblXiACNYPx8uesBJ42LHKxuivEeW+dTwqUw/VYfo20Xwih35q6LBo8eCA/NYzVUjGwcH9X3aeSRgEfN384X7HO2Vb2OcA+v5XxE//F4IH7iOBwnW6MpIU6yQkwp5ZnT07hZ1XVTu0r8NZbRoTK2slKVdRUO5ND+/RuO1JhOU+xzGZb0tUgZm8ns+pjES8HN7yg617ZrxX92e4bBICc7Ntp4IrQdVL0A+lqkeyxjFKkjjPJvM19u4I18DDPprdeJcYTRRa9JHRC4v2lprLeKT0euA+ph4I+HN+pGhKwzhTsVVt+L7DYGxoA+SxsVvMPc4aCI02GRbHuwbzk00Z13DKegu76C1g3ZXl+JRNZUswwNMzJvkaTXyAOEXIEDGft2TesaRtHvJFKmfokd3bM/a14xYvd3CpttrfR+iJDzXAyRtsd1iQEvQu29dkLdd5lfIfO5AaiTPfj+7WRH5QpwrBp2yTXi7zWSV7GO+BwAujbHcfU1H2vNTvPcBWjebWfaACDcLrxqW4Nye6+hxg9fza9+JqTMP4FOwkf9d+/Y97X5I1X1ryDrVb3BqGbkPjDIo7g+ge61nOP44jPMYXzcu+/OftUfZeToVvAtoI+I4NBsV5UiN260PlkDbdhoMhkiQ==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC3DBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQI81JVAGsb74cCAggA+AgEQMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBOgQIZaJ0tVV5iKIEggKA+MC8pz7F7obdiPp5xZG8ygIO/G5rV85eRjK/DHnY6fU3mJ21B4oSz8LHE9U1APpCn++AlnYvILP0IYHrUGG21ZaWvO8UzmxefXIsroBFNdO6Q/8Yft4m0PsVKCmiMW+Rpz+5ejsoqxJoBRIHi0GP9uhf4R8UKzLw19Qb1jwjEIYmBLD7OEgn7trwIT1ZwF4TBML+aUKlm/Fixri85ePq2eRMYWn/Y30lI7MYyp5Cuh9ynK0hhBUc3pvvE4dmSgLlZUUJ+g2IC+U2MKepW8bHUMdUXxaKFva4/qB6vrhtKOiiE8MAK++OC4IhKtl2dNN9nTLqF+nm1Vhk/DLqVs4hnnQuq38ixl3mCjG8JKFJKJMeknfTY9c8mqf2s1RhIoSX5jM7GP+PehE4AGzRHxg/x6OIVQd2grwJwJY60uf4eapKHbNlUDeaAZ5LYyqveHV9uzPJy8g+t4hKYFCplyZfVsrtVjwGIUABxEN/l24HHwRwIulzAA9MuLMBsiU98uK9pXQk9Zkl+yaOkFh1G4GrlkOkkPFh6MbNjw6gZBF6t69rQROtNSxtm7hDyl/kZ0tgtU948B4q2+oCxfT7LicHCn/ms9g4xTl3iOn5Xrw/uXjNKNgRKtyaQ/6NZQ52JqMczzHsXRyZ44+LioKsKlBD4kyMZkNmvRI3Uz/xbM4/lm/34+ntLkAJ6SeUr8ui/XznjD37VOco7oJ+LMhQZwmzP+GNrK/xBTcw8PpQ+Iz9fyfxYizZ9GyZGAaBHXslupANad9dXSCefOsJ+PFRkbiCFmfkDMDeZPdodAnvV+5u7fQvI5GKwXop/M3rLXdtr2l+zAUp/LLn7Sg7d+I9AhucG2q8VGflF3MRVUrA==+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC3TBXBgkqhkiG9w0BBQ0wSjAsBgkqhkiG9w0BBQwwHwQIQq9ZzQ+fElUCAggA+AgEFMAwGCCqGSIb3DQIJBQAwGgYIKoZIhvcNAwIwDgICAKAECL861DXfO+9xBIIC+gKqP9tLx2sQmVIGdj9SPeI7jfY8XtYUAg3UBY8QSM2zeN+TL3cb/kEdWGsVDjUGz+meOwB4ap/pQjR2OsfBktztwhmaM2g+ykecxm3Q9X94Ykfs4lbXFDwueAJ0BGGEsx+/TLq3V3DGDXwfU2R6wW0eoY6cFNZfpmpzLCX9I24OzZG6kWOQU7wgTzN42SVJCnd+bkmclJd1ftgZa7weJA0rI8m4WV24WUY7q//wki9m0f7FMh/xrKcjqV3tdJ1bCO2g+Y535Bbj6E7iqvh3Zn9kX+jKTkuUAPr/dY3t2p+eGp9xHJtB7Gt+Qcf2L0q3eerd++Oh0kV2ww+W8w+ukNAtGaW2La1SmJCbWKE+xgX4kyFDrMvlHjSJcVz5wgm4UarXet+vfnlKSvcs6Cm2cLC+Wbl4jcsGjvgAA6Cnp3zhfmVN30eHviZuAwFrr4bOn/EMQgy+l9FNAvCRvK5RpuBtu0nZh59zSD/aZ6OXmBOgpuYIKmBLrRnav3NjGpl2sP0M7HEg+ruZEgfaqt9xkm264TfA8nmux/D5pE0OayeoO0RiSvmgJlHlr2wLjh5CqCXaa4Qx0+afyN4H+WGngTm10EdA6SkEz01gLPfK2XNZzM/tWWEFS77kNruQ0XU26mdMxplUzS+oF2Xi4SpY/2nIz+ZJChTF4NkLk+VRrZr2qp0J/ig/LGy7ScPDrzZYWql/2+mWqXy+ai8DFt5wsuuJHohUvidA/sVdm6z/S5qqUJjoxwA20mYontx1lvYo2eVS8XJJhPpd+aCM24EQdCkcZ/vEBakEmCWMjoDUfPFC4hdQ7rHichpJSapoCuMgaP0AbN+5FTFCX+Tx/WJpHtdpYyx013dr5GvYU=+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC3DBWBgkqhkiG9w0BBQ0wSTAsBgkqhkiG9w0BBQwwHwQIGxaTRI/wuwACAggA+AgEIMAwGCCqGSIb3DQIJBQAwGQYIKoZIhvcNAwIwDQIBeAQISj208H4LOA0EggKA+kL783AgZq5s8Wa+MG9nlbq4Puc6EhMzW0Nm/EcMl+eydC4UZRiS6gVT1Bn3H6Ayy+g4EcOsFQF/iHLgs1hlQTant/jWtAxlporfUzxddNReqrWUje1xl6lW4Kd1vPwcuG+CU5RWphp6XsEiJWV8f3d+j99c5Jj2BRKiSPmS+CHeGQlVaSABkDqOixFjQf8rOwP+iwTXppqnGbOEkhzRvR0O/K1S+/5ugw4OumzysjPI35hZuk8S5Dp2kpT+ywVqJ0UC+uZOL4kykmu5OL+Yhx4SNlME8jlG2nZnf/VvPAe6Yq7RtwwxVwTPL4LtoEmUQWept+MUu7XdA29KghHGrWlPoVGrbfnq+1P9xHlYQs4ZYQU5MTqXvqaIO+Ewbxkjhn31jZ+17AWfNKiAym7fVQeM8epJt0SHx92m3MY9ssdbfnrkXFPm5zk0d81LP3Tz7Pcu1Sf+ejAmOAHmfl7Bd+23k2VH0QqKVEKanNJX2F7KrK4YOCz4+jritxJIAoro+1DgpnWR+kEe1UgguiRcsL0ZPXjf81ch6OKaWR/yUkanRdANx4+0g/lyrSFQtY3XRwFbBRvpV+O1mvs0OQRZZBPyH08H92lRdoNolEDbcqN337mUZzEqy9e5w+kC8qoZC2t0P5BDcw+bYDORuJRIldYCafK46HUceyenv/1trObRFxD4fZU+GzZaXAfMqCrjRBfRUgJ2kPy+QEW9rSqBbqZhQmEg2jJNtVY/GnzvY9F534r0kal3mEA33pCmPf61CPvlSuZB7owN+9J8DSvft0sN+8BkaGCBEwKS6szjE8P92ezD1rH6Qo6N2p3JGzmg6GwAvVYX4vqVk+RGdqREJ8fe0tz9JmPaVl/Q==+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/rsa-encrypted-scrypt.pem view
@@ -0,0 +1,54 @@+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIzVHEuZIqTxYCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBAgQQdFzY/Ib4OkmYCNavZTrqQwSCAoCMD9qTDZO5+328dyE3+t5FuiUj2ei6u6ibPTa+o2q2WtwnnLSzgRXegAxyy++X0JQK+8Ky3XLFb+hjXLTlvNm2mGgAgjcuvEAZQvkB5/oB5epYqk78Nup7EQUcLiG6mV81E4EJ2CDaJI+qdsHUfo7RLnjF4AIu51OZD/hngEYxFGFPgxwqgPW+5vbEbNwMVcQarOLt7qWCmMR+RSe8EJBFJNi7zwxgdM9+zg+1BSiBvo1vERinIhjlHz+YOc1Squ40dDIC1VZkL2/V+BvGWKfuE8/niVgoT7uPBIhlg/03Binn1GexmrNRLV+bwZsVrkmXxwNBLt6XIQrAR+ciPmCnRI8DRA2xMpQrKYHYdIE0zcA9381tsLy6MWd1fN/5AOJsdJyL1ssY//BfNU+eUkhah80sQrJ54HUIkvX1SGY0z/+Xc641LOlPLZb7p+t2gHYAkuY1GIRthFrAhDN+SKkU/P5R14XZgILyTDxcpTfZDiQsil6Pw1WD1M4W8A8tLFpaGqTGN6wtifM7ZpNH+JPvZdkOBB1EB6c0lKbUQ3Qg7tYBlPoUl0n6IIGk3HuYxmruMiojugXLkwAFPA2K++vPFIiLHa6hWy0YpVv3A5Ta5fiDSo+YMWPTEeiuiry+UVP49Y8voS5WEmuauNORXh+ZI2pnJqWvB4GhlUEcERYNgpWZt+Z6L+Saq3NR/u4NA1mO27DAJ15adTimg+r9Ipl+5gg+ArChpsDbnr372Wx8kZgnJDGS9H/0BuhOPvhIK+sic1gjDRvMhums4+sPas9T+128lfP3eaiHaFnbuzJEyKe7y9hNcktP11d8P0tQOEXmz2cyRtYj3XenaDyRGh65u+AvCqLxjvemyO+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIXyMMZMH+v6ACAkAA+AgEIAgEBMB0GCWCGSAFlAwQBFgQQ9/sPlmVc8PwQ7qCji74yfwSCAoClNM8rzEcQ+fSZ1xQHUTAyKBhHD+aCz1kZgsCQXGRlpIgbpoI6HorSRXj/piQFoT/lvAlbGoy6D+n/7b8a8IS7jUhpfywS3vt6/J+d7LhYZ7pUOEKyoor4d6csUKFJE0D3hF8E4/hCmW+Zor9fYTAFQcwAwNtfocShkWCfFz8+jPluDpdjAsuPltRUZfM3fTPH/L7inDmEnuc+G+nwMMp0w2EQKNNTj+HvPgWXcHcIPYHd10TFJUO9n2JZsSHZ/6YuByudtAlhEBcM+AvysRJyqDTV30Xi07EG1az14tlm09dO9fObpX4v7jppREiHUXd/ypWCwGVMTyat++UnOfKbjRFy3G1qQNj0K6TkI+4EumP6+oSB/YEiLoN60b1ZbagbAPyhbarlmJuSyN+MJbq9YLkofDJQ3aktHpoI2+VDV9o9dqxKsktEEpJPcwKd2W8WGneObACs52LTKNQ+e6uGZhs4BdRbNlw53p9YdhIye7W4B4NNW18S3ZpIacCMqZJ2D48HFwWQzmTCaipG+tswpQIyD2Jb9NSBrie8Fa+P82RuLuVV8qgJYM38Mtd+dAEavdvagrNoKcL7teqSu+CBjP2GTcTrciJXoS0DDakFQMe2z5X1UD/qmDEJV1RmRRSCxYGeV2bT53JyFNQEAZ+ZWsZRZ8jFCBVjV/LB41/0+Zbqxj7OmTOxxL3g3hyZ4E+N2ACIUZJxH9It0PNQ3Kz+Kik7fuCafPe/oI4Zb9RRjbW6MS3Ku4J/DDPO193xTqfi/faMhmplAhuVDv0z+hlD+aZmr4VFFsQJWou0mCecIaY4K1JVS5QlIw92DHUB4C0BXH0SOYLbuiCd9m4t4eZXu+DPkUa2DBSaSi+-----END ENCRYPTED PRIVATE KEY-----+-----BEGIN ENCRYPTED PRIVATE KEY-----+MIIC1TBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQId0JVgBVSslsCAkAA+AgEIAgEBMB0GCWCGSAFlAwQBKgQQlJjxBqYK7Pwam8z2xY71LwSCAoBdu+1EInkY+gLgAE/Kj052Uhmt4VfRgSKhH7mdyrqwO/sJ1hprIgndjoIdNxKDJIYn2GBOJ6eh8+134NL1HTyicnP3umstphxawzl4EbHQLtRjIlde1sEgJR47BeEE8evjB2EWD3cUbT+qbPK1vkl2/KKO2muUEbKbn89ASXikRBXasT6EwY4mL+7LZzuqkrkGRZg2xXSPtYp+NVxcL5JQMtyC/gbJJ+WH2PvgZVIGupiIdUoVcWmY1rm6Z1zyj+fuUF8Q5uOZN4BR+ic5to3X/wHSEqzApYz6wo1Tioy0OPUnlr2WODKGZ+mJK9N8YetSqQ8HzkGaZ+O+1+G8QsTYmitHZNdNK9o0xZJF1nbly50+AqOEF28/nzfcgmE3+qQWJW/lD4OMisbyvF+VLF0EEFGtpg9+zNP6edq8zQMqvOM59NHfpy8eh2RemNwIVvupM2xiuS/xAztbJXs+21cQP51GLE2deaoDn/nmpa5cLOfIvO6Ni9Sv6Rp1yIGAl1hBZZEpR9GhCXg19wAa+lUFqLzgrjWPBESEAJW30iD6SbQLs8MRf9tTEa79IimnNplkqvMieGgeCZ3EgZqnH+D6KZ5S5aPkR8EV7AMOFEFLVCr3JwrhH0NGR6f/nqf53g2uviNNHsbNZkUJwsDn76+/TzaRPLr4LcAG6i9+frvrVPEaS/75/QJrsSH93RGs9AaVzd2KPct/dDByQbpbMPb+/eN27m9/8x+4X3Qb7JIPRxbvtjBR03lntBFrE/yxMdAYQt1GdOkPv+n754KDIlhC+6+Z7HJuYkS+QJ1EkAnKG4Y6vnUes75HV7313DlPhTyvtdHyBy8yTTnzma33Gj2sp+7IXtX/S0i79O+-----END ENCRYPTED PRIVATE KEY-----
+ tests/files/rsa-pkcs12.pem view
@@ -0,0 +1,455 @@+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----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-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIGWwIBAzCCBiEGCSqGSIb3DQEHAaCCBhIEggYOMIIGCjCCAsQGCSqGSIb3DQEH+AaCCArUEggKxMIICrTCCAqkGCyqGSIb3DQEMCgEDoIICLDCCAigGCiqGSIb3DQEJ+FgGgggIYBIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF+ADAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMB4XDTE4MDcwMTA5+MzUxNFoXDTE4MDczMTA5MzUxNFowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFt+cGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtBG2kKWfYT1jUXmb+DlrkD3Wfw0rKDjTdjjSswM2HggMtEbiutc+cxnoW+DaeP1SzSdY7NXlaqvzftCPL+vzJNaRM4HpG/XLqIdub5hzisbrwN1/Q77zmUvr1ka7NaXKZDuuepORJsNh5lHkp2+2VR0O0pQ3zLNjX8IVAGkdpoE5iMCAwEAAaNQME4wHQYDVR0OBBYEFPyU0yv/vEPO+p1kMDzLvtT6U+gYOMB8GA1UdIwQYMBaAFPyU0yv/vEPOp1kMDzLvtT6U+gYOMAwG+A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAn2yZT/WNupO34PxVdWU65X9q+mCE4AtZKLbRa0iSkkEY961oqDiMVWiaN0sPVeV1GoNCz9J8lLKaEkR8b0F4KlJ0K+44V+5P0Hvb1UnKV2SsNxkmNbxwjO8Q+9aNjd4IqxAnSyQ45yMN4K5x1COJFXQETJ+ApFWhLZxgA0hqnotQGcxajAjBgkqhkiG9w0BCRUxFgQUwGwiSqKiTkXW+IueoxiI+4tONDrAwQwYJKoZIhvcNAQkUMTYeNABQAEsAQwBTADEAMgAgACgAcgBzAGEAKQAg+AC0AYwBlAHIAdABwAGIAZQAgAE4ATwBOAEUwggM+BgkqhkiG9w0BBwGgggMvBIID+KzCCAycwggMjBgsqhkiG9w0BDAoBAqCCAqYwggKiMBwGCiqGSIb3DQEMAQMwDgQI+49M/XQ82RbgCAggABIICgL+czg9qzdhCSxfGURUhAr/WXajp8uQQRcE8NSCq0zy++9KZFCA7x3hp2pt9R3Jjzq/zTOSVKjrE1isD/m6iEnNaF0HU/BKXu5qs8ZaGQi94V+SJOEwXTo+wAhv573zA5oBa02CC/1SaXqtaw+0FH6LWxvNELfGG/T8cUQZ1wJNvi5+La5Cg4twZw+BkfiGMAanj4aZUTfQ01yQAtu0Xu9tl4CcP7tbZiZ/hBI0okUlVzRJ+9aej05dDq+r9s9d4Iu31J3lMxUvDLcKNUte9B8uE0eQCdQk+s9Bb02fsqgbYA1SV+e3rEcVA51pBjZ2mZ5865WygrxW8XbQILYyQkgZSdImCiibRhgaRFVBiWlr6xe3Ha+wmRQYqs4SFGaroWl9Mm7NRhk2EOsRz8hEcn4PzwCxqOvRKweRkEEHKlckzpBsCn1+ZyOxc3s1cVT4Wc67pf0WTiLS7nEFVtJDVoTR4PMwM6ZoCrdUgIk5QdXP40XkVLIF+o6pouT/zNv0yic+zK0+o2iLwkrYYBPeYLs9G+imAPTu2bRsNea49U2r2i5c35/cR+AcYd2Iom73mZSJn/hrDYQGeVQJswtAkyxaHOVKclX3s7QMYiTx0Fu4zeF2H3wXry+GQRbXVF0XIFlhkAVXQfs9DRcu7KkLxPt6pON4YNvgoKKW1ajDhg54WUR4haCacYW+zd4XcuKHTRj1phZFHnldxJG/je8Nz3QaE7OVOszZYGshR5vCQbhrgaM5ndMkyCub+DhTNF7QEmc9KdtSaLglwAQmM9kIRTuwgcdszNGxvo2RN1Ld9wwbEt+ufYzpCytQ++nf00NuE9frn7m6MNz0aaLRAc7Zb71xtENC0fAQl5lLgxajAjBgkqhkiG9w0BCRUx+FgQUwGwiSqKiTkXW+IueoxiI4tONDrAwQwYJKoZIhvcNAQkUMTYeNABQAEsAQwBT+ADEAMgAgACgAcgBzAGEAKQAgAC0AYwBlAHIAdABwAGIAZQAgAE4ATwBOAEUwMTAh+MAkGBSsOAwIaBQAEFPKVIrHJ1+zCwndHRxV3c3/eF0ryBAhlAll2KZcKqAICCAA=+-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIGxwIBAzCCBo0GCSqGSIb3DQEHAaCCBn4EggZ6MIIGdjCCAxcGCSqGSIb3DQEH+BqCCAwgwggMEAgEAMIIC/QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQUwDgQIK0HS+HL4NAHkCAggAgIIC0NcwM2oR1WN6jGC4HbpsbJng/LcGo4Pv2Jqb/XzRGQieD4/5+nDc6Zb92wZFQX/wAypy8aqe/raLsaOvBa50S7R7qhHgKp0QX6EdgJkRJ89tYfTQ9+KN3IqnCRBe3sjfgwwSAj/eQQCyAWVGB/RmvpGBVMuzEPqDwSU0A59yxFBHPK0TTn+clhxKHsvgJPzUUAAByQFIm/CelKf0Rj/J15jabobbzBgVsi1Y5IapR5x0k2Koxu9+BRuhgTHVOcm/ToTA5fRf5P1pdy7RWhPCpHefE0sVoq6C54T3AdY4epDfA5vRtjBn+kb2Ld7psvWucevi8gVaD0VIfd4XTvfhgY7hDZfUs0tPqh6rbKYQoYjbvjl0gtzIj+auu3s7AMTB+9oEfTJe7pautjzUmKT75U2Rf8TazkuNGp4EksU3s6B42tZLcdcFr2+Y8o9BHTn3n68xtZTzSMwPtksTNh7R38UNDieRVUARiQoRY7gzgYdj5og6HLyVWOG+e1o+IYHMAsbqDqO1BNNmgal05/Cdx53ejnzkkWJyCFqC8Dcr2JPHNvgt0uT2AbqH+kRZ1zB4e61mEAVKWF3pODZkF/ltIU6Z7GfD747EWch3bBEo99iC6Mm6v4mg3PMiI+SgsMz01nZfl2wxo8GfHboJQVFAtobrfe3yoypnGIPBnpnGbgVntG3YV/L4YFKLn/+PgAyWczJULeS+b1TO1hdzfY9eN809UHgg1v6tqm8QEM7r5E/nEKP/mT3ngdaR5zY+RWl/AC7hB7Wq49USvFzn8nFcLp8rEV315AlxgoA/zsFl7hDZHH2lJbMu6gmIdJL1+upJouNorxa8z1qV92zHCH22MKcMeTU7v+1FWchFm6P6Nf8OlCEBkBPCX40GZUW/B+4UfHRRq9eq6n22y4/iLSJ8xGtpqoDan5Te4KjsdBK9gmv5e5iS4zpjOOFqb68Dle+6KSSzwAQ4AKFnLux+TCCA1cGCSqGSIb3DQEHAaCCA0gEggNEMIIDQDCCAzwGCyqG+SIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBAzAOBAhOMwbQet3fkwICCAAE+ggKA9TIXWth2CP0dFW3us8BrTY9QFd/GepMLaLBsQPEThzuY6VONbwX3Dqy4REMl+cQbxRg0Qpz116l6KK8QDpWmE5sBrSD5BGVYsUrIFwsL6El5HoN/kiAaaMnT7FY1v+VjV+m8Rj4TtMD47V01/OBFHptbEMUiXQVYoruqvlZAMz+SKbowRxj4lcXlQM2paE+YttVhIUec2qV/J/zjRu5JDKuh2qBCbF6QdWFH1zZIJDKMhgwSL36pud/ZPqD2vQc+NCmq7xhcfu4j6/QRO0780Ss3Zt3fX4txA8TP53WXNPyfpBvn3db2XBS4aUA0NSyg+nI+9GXTWP5knRcibnbPz93Yllsm3TIU8g+ix/TlXx7FOr4Yp6OJFAcxRd77ZtzGl+7aqeReLFKSCWIcLml0isuS5Jy5s3YBNP46tMIU2ybJ7YVZ+v1nZzA6hCBvCMyx/S+1RYcVu8lmEdP9oB1N3ixb15Xlksr26Rz1zBh5+jIuwhT0vYMHIS+xJKEP4Mo3bhU+DnUeAn4TAzv/xUW6dTfrCIE3F32yr3o5qxxLXT7c+EQVfjT7cERsPHcjxwgMaLN++731HTEOk/3hyJSU9lRl0bQ4H6cN5p+mRVXeMx0pwReCqVylDqgFf9eZSdaAFVtao+eajhgjLAbGHqLtaEPmGDAPb7R0kboXXncbE6BOwy+e8aFEfp2X0yHXW9B/PSGy4f+tDts0KqI2fDRXlbiflRHAqdOsGymtAIyJ/VdT6/9xI4saG3HACHjfoz+ixJTOrI2+hZfC/KT4kf8eNROw0HURkOnfRhe5WKb1q4Qi7qIyYNcinkzqP8d86Z5mu+TCFUaJ+mv71T7LjI21P5xXXoMtv/o5kYjGBgjAjBgkqhkiG9w0BCRUxFgQUwGwiSqKiTkXW++IueoxiI4tONDrAwWwYJKoZIhvcNAQkUMU4eTABQAEsAQwBTADEAMgAgACgAcgBz+AGEAKQAgAC0AYwBlAHIAdABwAGIAZQAgAFAAQgBFAC0AUwBIAEEAMQAtAFIAQwAy+AC0AMQAyADgwMTAhMAkGBSsOAwIaBQAEFB31GCxXMZuGA+ZTMDTSWdWQk4F7BAiq+RUFHBwJNlAICCAA=+-----END PKCS12-----+-----BEGIN PKCS12-----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-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----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-----END PKCS12-----+-----BEGIN PKCS12-----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+-----END PKCS12-----
+ tests/files/rsa-public.pem view
@@ -0,0 +1,11 @@+-----BEGIN PUBLIC KEY-----+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0EbaQpZ9hPWNReZsOWuQPdZ/D+SsoONN2ONKzAzYeCAy0RuK61z5zGehb4Np4/VLNJ1js1eVqq/N+0I8u/Mk1pEzge+kb9cuoh25vmHOKxuvA3X9DvvOZS+vWRrs1pcpkO656k5Emw2HmUeSnbZVHQ7SlDf+Ms2NfwhUAaR2mgTmIwIDAQAB+-----END PUBLIC KEY-----+-----BEGIN RSA PUBLIC KEY-----+MIGJAoGBALQRtpCln2E9Y1F5mw5a5A91n8NKyg403Y40rMDNh4IDLRG4rrXPnMZ6+Fvg2nj9Us0nWOzV5Wqr837Qjy78yTWkTOB6Rv1y6iHbm+Yc4rG68Ddf0O+85lL69+ZGuzWlymQ7rnqTkSbDYeZR5KdtlUdDtKUN8yzY1/CFQBpHaaBOYjAgMBAAE=+-----END RSA PUBLIC KEY-----
+ tests/files/rsa-self-signed-cert.pem view
@@ -0,0 +1,14 @@+-----BEGIN CERTIFICATE-----+MIICEDCCAXmgAwIBAgIJAPcN61ksKTebMA0GCSqGSIb3DQEBCwUAMCExHzAdBgkq+hkiG9w0BCQEWEHRlc3RAZXhhbXBsZS5jb20wHhcNMTgwNzAxMDkzNTE0WhcNMTgw+NzMxMDkzNTE0WjAhMR8wHQYJKoZIhvcNAQkBFhB0ZXN0QGV4YW1wbGUuY29tMIGf+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0EbaQpZ9hPWNReZsOWuQPdZ/DSsoO+NN2ONKzAzYeCAy0RuK61z5zGehb4Np4/VLNJ1js1eVqq/N+0I8u/Mk1pEzgekb9c+uoh25vmHOKxuvA3X9DvvOZS+vWRrs1pcpkO656k5Emw2HmUeSnbZVHQ7SlDfMs2N+fwhUAaR2mgTmIwIDAQABo1AwTjAdBgNVHQ4EFgQU/JTTK/+8Q86nWQwPMu+1PpT6+Bg4wHwYDVR0jBBgwFoAU/JTTK/+8Q86nWQwPMu+1PpT6Bg4wDAYDVR0TBAUwAwEB+/zANBgkqhkiG9w0BAQsFAAOBgQCfbJlP9Y26k7fg/FV1ZTrlf2qYITgC1kottFrS+JKSQRj3rWioOIxVaJo3Sw9V5XUag0LP0nyUspoSRHxvQXgqUnQrjhX7k/Qe9vVSc+pXZKw3GSY1vHCM7xD71o2N3girECdLJDjnIw3grnHUI4kVdARMkCkVaEtnGADSGq+ei1AZw==+-----END CERTIFICATE-----
+ tests/files/rsa-unencrypted-pkcs8.pem view
@@ -0,0 +1,16 @@+-----BEGIN PRIVATE KEY-----+MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALQRtpCln2E9Y1F5+mw5a5A91n8NKyg403Y40rMDNh4IDLRG4rrXPnMZ6Fvg2nj9Us0nWOzV5Wqr837Qj+y78yTWkTOB6Rv1y6iHbm+Yc4rG68Ddf0O+85lL69ZGuzWlymQ7rnqTkSbDYeZR5K+dtlUdDtKUN8yzY1/CFQBpHaaBOYjAgMBAAECgYBDuQW4kRHIOJZJ5jpyw6xSRCK6+CD7F6QwKl7UBLktwklE5AQR0TIjh/Lj20VPi7bAgbPoHuoRnGoPn9C6E0h5abMag+JUnhTwO6sKVBz27NYsNpsPVsaXgdomIHNDUFcfi/I19QEh4tua3EObnQM4007Tj2+auZutUnEJ4qQshJdgQJBAN+F5pV0dvNpkErxHBM8O8xM/Eu5IycWl756xskqJYrs+XTCyLgLp3vNLFCInLm14MvRmWbVuErYXzamqElV7XPkCQQDOO4PjmAUCpNL0cOuN+9YIa3mRLtW0cqnLUtN2ZtqM3nFG0/KhVvQDsKpipdAp7qMWBj2rHGz8BL73AROu9+PC77AkATarFOGFDg8jGboPg8X4YeSC2+cjWwhpDl9gpgnS2ntMKfoUrH7AL3BXWZ+RegNkFx0yJNQZGBxuv2dmG2BSXcZAkEAm0ke9wlphGMdYOeBxmN9MsQP2x3h6brC+Oj1xuqNV5LK0p0WzbtOg1TMBvl5c4l3jUbnumbt/RmJvS03Z/6RIbwJBALgTXgcJ+QzaiaD2r/XK7CCYHLVpjvNITHZFn+zcr8NdD1j4hWWIIIK/D3UJIymN+GuyG3h1P+pvAPCqAcDl+WOTk=+-----END PRIVATE KEY-----
+ tests/files/rsa-unencrypted-trad.pem view
@@ -0,0 +1,15 @@+-----BEGIN RSA PRIVATE KEY-----+MIICXQIBAAKBgQC0EbaQpZ9hPWNReZsOWuQPdZ/DSsoONN2ONKzAzYeCAy0RuK61+z5zGehb4Np4/VLNJ1js1eVqq/N+0I8u/Mk1pEzgekb9cuoh25vmHOKxuvA3X9Dvv+OZS+vWRrs1pcpkO656k5Emw2HmUeSnbZVHQ7SlDfMs2NfwhUAaR2mgTmIwIDAQAB+AoGAQ7kFuJERyDiWSeY6csOsUkQiugg+xekMCpe1AS5LcJJROQEEdEyI4fy49tFT+4u2wIGz6B7qEZxqD5/QuhNIeWmzGoCVJ4U8DurClQc9uzWLDabD1bGl4HaJiBzQ1+BXH4vyNfUBIeLbmtxDm50DONNO049mrmbrVJxCeKkLISXYECQQDfheaVdHbzaZBK+8RwTPDvMTPxLuSMnFpe+esbJKiWK7F0wsi4C6d7zSxQiJy5teDL0Zlm1bhK2F82p+qhJVe1z5AkEAzjuD45gFAqTS9HDrjfWCGt5kS7VtHKpy1LTdmbajN5xRtPyoVb0A+7CqYqXQKe6jFgY9qxxs/AS+9wETrvTwu+wJAE2qxThhQ4PIxm6D4PF+GHkgtvnI1+sIaQ5fYKYJ0tp7TCn6FKx+wC9wV1mUXoDZBcdMiTUGRgcbr9nZhtgUl3GQJBAJtJ+HvcJaYRjHWDngcZjfTLED9sd4em6wjo9cbqjVeSytKdFs27ToNUzAb5eXOJd41G5+7pm7f0Zib0tN2f+kSG8CQQC4E14HCUM2omg9q/1yuwgmBy1aY7zSEx2RZ/s3K/DX+Q9Y+IVliCCCvw91CSMpjfhrsht4dT6bwDwqgHA5fljk5+-----END RSA PRIVATE KEY-----