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 +5/−0
- LICENSE +30/−0
- README.md +269/−0
- Setup.hs +2/−0
- cryptostore.cabal +96/−0
- src/Crypto/Store/ASN1/Generate.hs +147/−0
- src/Crypto/Store/ASN1/Parse.hs +210/−0
- src/Crypto/Store/CMS.hs +439/−0
- src/Crypto/Store/CMS/Algorithms.hs +1923/−0
- src/Crypto/Store/CMS/Attribute.hs +126/−0
- src/Crypto/Store/CMS/AuthEnveloped.hs +97/−0
- src/Crypto/Store/CMS/Encrypted.hs +99/−0
- src/Crypto/Store/CMS/Enveloped.hs +631/−0
- src/Crypto/Store/CMS/Info.hs +353/−0
- src/Crypto/Store/CMS/OriginatorInfo.hs +185/−0
- src/Crypto/Store/CMS/PEM.hs +71/−0
- src/Crypto/Store/CMS/Signed.hs +240/−0
- src/Crypto/Store/CMS/Type.hs +50/−0
- src/Crypto/Store/CMS/Util.hs +225/−0
- src/Crypto/Store/Cipher/RC2.hs +53/−0
- src/Crypto/Store/Cipher/RC2/Primitive.hs +238/−0
- src/Crypto/Store/Error.hs +65/−0
- src/Crypto/Store/KeyWrap/AES.hs +140/−0
- src/Crypto/Store/KeyWrap/RC2.hs +91/−0
- src/Crypto/Store/KeyWrap/TripleDES.hs +66/−0
- src/Crypto/Store/PEM.hs +35/−0
- src/Crypto/Store/PKCS12.hs +652/−0
- src/Crypto/Store/PKCS5.hs +238/−0
- src/Crypto/Store/PKCS5/PBES1.hs +352/−0
- src/Crypto/Store/PKCS8.hs +600/−0
- src/Crypto/Store/PKCS8/EC.hs +99/−0
- src/Crypto/Store/Util.hs +45/−0
- src/Crypto/Store/X509.hs +178/−0
- tests/CMS/Instances.hs +468/−0
- tests/CMS/Tests.hs +311/−0
- tests/Cipher/RC2.hs +113/−0
- tests/KeyWrap/AES.hs +145/−0
- tests/KeyWrap/RC2.hs +89/−0
- tests/KeyWrap/TripleDES.hs +85/−0
- tests/Main.hs +26/−0
- tests/PKCS12/Instances.hs +71/−0
- tests/PKCS12/Tests.hs +99/−0
- tests/PKCS8/Instances.hs +39/−0
- tests/PKCS8/Tests.hs +101/−0
- tests/Util.hs +44/−0
- tests/X509/Instances.hs +267/−0
- tests/X509/Tests.hs +70/−0
- tests/files/cms-auth-enveloped-data-rfc6476.pem +15/−0
- tests/files/cms-data.pem +3/−0
- tests/files/cms-digested-data.pem +27/−0
- tests/files/cms-encrypted-data.pem +64/−0
- tests/files/cms-enveloped-kari-data.pem +462/−0
- tests/files/cms-enveloped-kekri-data.pem +61/−0
- tests/files/cms-enveloped-ktri-data.pem +190/−0
- tests/files/cms-enveloped-pwri-data.pem +48/−0
- tests/files/cms-signed-data.pem +125/−0
- tests/files/dsa-encrypted-pbes1.pem +80/−0
- tests/files/dsa-encrypted-pbkdf2.pem +79/−0
- tests/files/dsa-encrypted-scrypt.pem +33/−0
- tests/files/dsa-params.pem +9/−0
- tests/files/dsa-public.pem +12/−0
- tests/files/dsa-self-signed-cert.pem +18/−0
- tests/files/dsa-unencrypted-pkcs8.pem +9/−0
- tests/files/dsa-unencrypted-trad.pem +12/−0
- tests/files/ecdsa-epc-encrypted-pbes1.pem +88/−0
- tests/files/ecdsa-epc-encrypted-pbkdf2.pem +86/−0
- tests/files/ecdsa-epc-encrypted-scrypt.pem +36/−0
- tests/files/ecdsa-epc-params.pem +8/−0
- tests/files/ecdsa-epc-self-signed-cert.pem +16/−0
- tests/files/ecdsa-epc-unencrypted-pkcs8.pem +10/−0
- tests/files/ecdsa-epc-unencrypted-trad.pem +10/−0
- tests/files/ecdsa-p256-encrypted-pbes1.pem +48/−0
- tests/files/ecdsa-p256-encrypted-pbkdf2.pem +50/−0
- tests/files/ecdsa-p256-encrypted-scrypt.pem +21/−0
- tests/files/ecdsa-p256-pkcs12.pem +305/−0
- tests/files/ecdsa-p256-public.pem +4/−0
- tests/files/ecdsa-p256-self-signed-cert.pem +11/−0
- tests/files/ecdsa-p256-unencrypted-pkcs8.pem +5/−0
- tests/files/ecdsa-p256-unencrypted-trad.pem +5/−0
- tests/files/rsa-encrypted-pbes1.pem +136/−0
- tests/files/rsa-encrypted-pbkdf2.pem +126/−0
- tests/files/rsa-encrypted-scrypt.pem +54/−0
- tests/files/rsa-pkcs12.pem +455/−0
- tests/files/rsa-public.pem +11/−0
- tests/files/rsa-self-signed-cert.pem +14/−0
- tests/files/rsa-unencrypted-pkcs8.pem +16/−0
- tests/files/rsa-unencrypted-trad.pem +15/−0
+ 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++[](https://travis-ci.org/ocheron/cryptostore)+[](https://en.wikipedia.org/wiki/BSD_licenses)+[](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-----+MIIGqAIBAzCCBl4GCSqGSIb3DQEHAaCCBk8EggZLMIIGRzCCAv8GCSqGSIb3DQEH+BqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIiJXw+tduuHmwCAggAgIICuB5JmQgjBcOUoSq8MWOdPlETIfOE43Hpku47cA4d5szzyYNj+KDWVCYU0FKfZhc2oTeRDg6kE5+/vb2OzarrBCWX8SGnRZTL1F9iY39iq9GZnEMPx+MsjxbeAPumORuANfsWUPN4st0CHaa4oR1XLn/G8sKHYWBG+kErjRNoY7f7hilOhO+D1FF9Rlhr9tmY6AYentx0/dbff4EMJuuhqCq2nnMgdgDH6tsvQEnLmP5fHcekaif+tqjcHRr3KQilq/dy7ezqlC5L6Zk677yTiDcPt7nEzzziABOComFVqFCeFiUaRuyZ+dl3PHIZtb8muLlzwcoWJuVFDT6Bjgn8U7v4xzUL8jffSH/+3CRHnSPyMoCVZjv5D+CbHAKUJERy4h7sgU856nF/pkW8uL2oPMQCGo63WrAWM1FREk4ieOic/fKOb9x7r3+7fnBoOcFND/A4z/W8gsi0r2LdzAQBxgoCN3nnAgGdJJr3J8O1650EayTUj71+gPl+EG0DN2HxUIhg65HU6BQYJMabPH5mz7t8bmVfW5MEQAeqiK53usXLe0FCfRTEyLRp+r7DaYKsvUOxmnHuiLpXfXHakzmYFhNmMClKjLNaGQ2oelhzrALFbwAvgOCfbMM4k+OO1me5zO8JeWwSQHxsWMpcaRRqslhq5IcZO2WUd9DXf3Z26Znt/Yz31U73SIR1AP+LYoVs3k+o4thwTsQZVQZbVwEcP04o1fRzPnoXu+TQIVmU5RxH3OGmQ6laYufc80I+FaunjhfMxAHhBxGMVFxlIvVIDrwJZlHpl9cGbpRh5sKp7ptG36KlqZWORFB2QBmC+zmaQBlyXddV7Vdsff4L7wVNXvUPCuw+ghSGby9T9d9URuW4NG7wIIgd7CsGu2mp8+q0UYJTalWcr2UUtHVodL0YEbuPQ8/OHguItsU3UKXt0471xwSTCCA0AGCSqGSIb3+DQEHAaCCAzEEggMtMIIDKTCCAyUGCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZI+hvcNAQwBAzAOBAhe3h2X+ZgUGwICCAAEggKAmhLX/HNwQqad+UwoFgG49fQ2yFkf+SFc2bunhOncgu8sy9DakSr5LF3/31uOipLy0YZ7DmANAtgaX+2POyo3HgzoMbu6x+XRBXf68Tg1vMyIkvbS0Gh8exoE0jaP3AH4HMlmqI0pn6P5T3Wnk6Nb1wVy+3COXn+8aviX6NFOeiCwkjG/M7nAS2UfYvRRl/lRnCcq8k7+oGTASONgbWDqlD2DfgYF9vf+x2L16c9SFPsqtrJsGMtuQ5TOVAlAN1YcUyr+9kdHNapvIueaIf+Vo+A9G67B00dO+oUCTmJzzA8TVaVnyzx1TxOsKKJ/z8EyYLJKsFWqnQIRUts5y3hNzXXptIDfw9+62+jzh7AXlxuk+SMZupv+wFr++FRiMAXbxuS6dGe4EbO8+HY/faLM/Zu0UkKGWgpVDZ+1ITjrlFK7FJIJbn3iq6PEhT4TM3NaoocIc3UNa0xA1DUoG/dKmwwFIeoMySHS/ih+N8r2VT7YEj511Gi3dVLXo9GxLn6pdQNs+TrqykNgB+yfDwAz3CPmivv2wP4bMYva++RhejwrepWukz1+QH5TU24Z/nZyyxz68JeUabHCYNoLJNxJwrryYrRFbjpjNv1CR+ztGL/tIMSLX/Rhber/kDp1sgvx8cWS68XwC1RawhTLF2kL4GdSLXho1PtzcFMEv4+K0jDDnLqxFnU7LxcBvZgOqMzQFndMNt3sP2uVE9XwFjDnz1oExZofgfsVou960Vx+5Iliup9x2pQ5Jh29lTebW41shAiOMMXXLpCmy1IWlHNPChcIFue/q4g4foBYlKZM+yJBVKIqUCbx1mIrWVRd1oVde/55NT1EPFUA5ANm0O6PoB4RM1BX4xB5PuDFsMCMG+CSqGSIb3DQEJFTEWBBTAbCJKoqJORdb4i56jGIji040OsDBFBgkqhkiG9w0BCRQx+OB42AFAASwBDAFMAMQAyACAAKAByAHMAYQApACAALQBtAGEAYwBhAGwAZwAgAHMA+aABhADIANQA2MEEwMTANBglghkgBZQMEAgEFAAQgxdIPVzeh+4r1A08FgJbrzHFd+Qbe5Scqyes+V0blmkYcECOYC+UPng6RsAgIIAA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIGuAIBAzCCBl4GCSqGSIb3DQEHAaCCBk8EggZLMIIGRzCCAv8GCSqGSIb3DQEH+BqCCAvAwggLsAgEAMIIC5QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIctp8+K6dnywACAggAgIICuNqYGCXbXrFZN+RUaOft2QS6RdIiI7upD9RcnnAZVx/Qjg54+ar1kRTcFbWZTKLejxwgIJH3isXEIwN1vhxRPfRIXskMlTQQeDqQawyv15ehl98KF+3xA50r9QjNnnlBvp4hDVuEHqcjVtM2BJ9HH/90RCmlh3C6v5qtxe9Sg3n/If3rK7+Rn2jqxTTNd/E2z9mktODjjLU915D7RAaWeUbQvsgrqL8iIQEupuZ93NMV96K+b3P+6uBA/HpMczfAYNv85ds4s4lKpVBk+6J6UvLoF4SYhSuCYHTgoIZQKKoJ0EneRFPW+ytG0irSc4NtUDHCOPuFf7LxT0ettk5QIfyKTACzxrsqNKnK3AdYUtOZ0N5V8rZkl+9+cl55oQHVyxsAtZ+03KavZGegsDUcifBJ/OC2lwvvzGkDIla2z8/OhMMD/+3lyw+ARa95QZz3ffDUGS0DYtke3zc6axMCUFf38RH1WJuj9RAsD1F8Gxr/jaky6zVijCM+hpGOXik64qy5rp94ntZ0PPnZ+WbuURWFKJn+8j/rLO85knHzADSgbIugJquk9gqx+kkKWnMlTwhxD3LjvWppbn8RE8AopYseMWkTURy5B3YSVuxkvMfey8ZOzc5XV0gqB+8rIMVW3YUoQftO/3pld9DlSsxI7dbnLVvi1hn4X5MOFdiSPEv67BiZd6KMegC91v+4u6xTcJd5Urm6dBqg7OEgiFhc0JzIA4r+pwESRdgXd9CsIxXptGojXa4A8fnuqYb+ey56VWd2PaTuLEI4rbIZSzAWbRsFQ/R6/rfoJX1cd0Wi6bYcddexX4VMIrs4rhOR+LfkJQzO/hu//b9KEFCSMSPFr6WtvhvzleWe8vORuTXPxfnULGYpYP9Ndaa+9OIUm+3XvNtoJyMEOBjJRMGkHm2eyH7FltchWKcnYqO6QJ0hbcGTQj6zCCA0AGCSqGSIb3+DQEHAaCCAzEEggMtMIIDKTCCAyUGCyqGSIb3DQEMCgECoIICpjCCAqIwHAYKKoZI+hvcNAQwBAzAOBAhU1RnM3d8KhwICCAAEggKAoqW80sLkBPyn5MZIGgwuloAzEts/+zTqlOwPxhRzSQPbY6C+ZOq4AKGcNgM2LBTnFkwvBMnKja91I/oRMmUW1MjVpieHs+FU8WvTAuxN8drahfoI2vmD+5TZc0d1lxfpAis0ibasV2WtrtSWlTyIcIYxITBT7K+jtINMMLKIAdSdHXPHbZ3LYoZiehNQGHmgLc3plm73S6sbkJ147u7K8UO/0+Emwnm+T693basVZZ0FQdU9RCTtgO6A0bobkiw5RDdDA1e7EzbZ1ANbsjvk9YPB0dzGDH8X+ulaxLZb9Oon/6H9FMsG53GwV3RKQ4lTzK7Ur6sY0EkpFa4zHEIjoNuWDOujxffVL+E2dYXBS9r3gCYfL9uLjjX1dDoLgCz+w+QK6LPFqG4ej9iQ2l+iI+qZ2Oc/qjAgHh++Ym7Zjes+PwE2g9alwYVYCpx6QDjtprfoKC2KvS3dxiuu6ZwZwFpVQTiCTb0zO5K+RspGZ8es1y2z0VugVU5QTRFgBRsYf5ULLRDF+OJnxO1Ac5q4H7e5zAscVmuL75yA+F9N3F8ODxjfeZckzOnokWQTs3FPEa8R37HDOlOBGvRF32QArbWXgr1efmE2spoQh+xe6esWG2w5VaNSP2fk8sz3cqBvZa19RxZlXDJBiKKp6x05QoIj7r5oJOHlERWp2s+lOOlGCDTpemC/3PrYKb05UDMz1duMdbJmn3X7fwR5OrDJt5LnhW/TSjS0k5pOyFw+DnHGphZhX6ChBDOEtgPX81eoL1iHwooqaVM5BXS4WdHXidp5gDbBEmPLGcgRmWeE+Hf6+DNHwQhFUMS5f740p+cpDXeei8AsRNv6TndqW44WiInj5NFKZlXzHtzFsMCMG+CSqGSIb3DQEJFTEWBBTAbCJKoqJORdb4i56jGIji040OsDBFBgkqhkiG9w0BCRQx+OB42AFAASwBDAFMAMQAyACAAKAByAHMAYQApACAALQBtAGEAYwBhAGwAZwAgAHMA+aABhADMAOAA0MFEwQTANBglghkgBZQMEAgIFAAQwIjeGs216au95fEcxTik5VnZV+VSE7eqvIyhBRAshh1EtyB3sLY/MknrZqUrz3NqATBAiMSKkq/xXbXQICCAA=+-----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-----+-----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIGxQIBAzCCBosGCSqGSIb3DQEHAaCCBnwEggZ4MIIGdDCCAxcGCSqGSIb3DQEH+BqCCAwgwggMEAgEAMIIC/QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIucFx+TNjMCVMCAggAgIIC0PUROXrifRea+3qUIj+5Wwj22vfa0hYOOo9roE3Hu0ixGowk+c+as3nzgl9BWyQ0t+VC1sHtT5chcQmWsZ1ppnszRY5pY671gey5Lp54YWycrIxno+bsI7Tc/3Qd/vjeexfU17zfuOFesX/PGLczj/Nh10enio6iPvlQYhVSD+5qLj4rrY+9iSRdiGRsQ5chjel1W/jKO9dKzvRoVGS+wzoju43nvQaKzI9YYZkJQi4lMxuxMXr+76sKV8V4lyKvu/57OYEWxuKNUlGOVo60gziV9dzZH1wM9N/SEpxfznkFVC9mgx4y+FrIsBunFwe1M/9bPvYGFY9fET4hCCRGLK3FA5tL6c5DgZzlCk1zk4tZuFSiyMbcP+qvyfrTK5jNZWoWQI814xIk+HiUDgVIPuFDUWQ59dTVhUbyrXuBgIfqToXss97+Kr+idqLcEB/Gl0BPrRTWD5v/EJLUun0JXDQUSoGzzzJdCrrhnhjqZGtSUhvZx4N2GB/+lbgOAFCBZORjIyU179sj6nwx8kzedM175cqdWYJtPjTMCHZKbSx55ItdT8Be6hBb+quFcDayTUWJJ6nZflVXXJhsJ2GNsUTXFZCKTgWVNw5D8ivBOUQ9O+e0pmkFZSJMr+ePwUAsqgw4KMKFij/JvRZIBld2h/r0pEwiGb5nzNNN8CSvZGFSGdt9AXXMKdJL8h+jV2hAhfv5XXAJzDlrvKwAambFlxCwf/NzWjvggNtIOlcGCa5aNEaji/EHK+TuR/g+sJFta4m8cVgfoBP6VcG25AeNuQFsAIyMV5wov4r2HpFPozuoF90BbcFIdKGCRrLV+ki2EKSXkQYlQhV1NJsBjDLD83vFoNgd7OMKOjjOeFW8cLIadhGR/KToB57cwBFjK+O6eLPjtYHFxy315K0xjVB/z7TfzrDbFrZWSoYdB/VuCmYzzqerP96rPMt51UcG0v+6iQPm765LZkGPGjcqTCCA1UGCSqGSIb3DQEHAaCCA0YEggNCMIIDPjCCAzoGCyqG+SIb3DQEMCgECoIICpjCCAqIwHAYKKoZIhvcNAQwBBTAOBAgpG1tZ6N3aXAICCAAE+ggKAUwVXTIxnFSF+kvkP6VVHGfsXh6cH2mIs5BJtoXt16rKFO+Dj4uDdIgVka5bU+fEfcDtrGe+qEe6x/71Vg1rr+GwH7pcU/byKfq4ANr9T/e6s6SUkWoAICzdLwGeAw+rFh105/M13gjOu6EpEtCIJ+1RjjJbIA7/0fEITd6LQRPeG7+bhvXpzm9q6HjHlKL+pYnh+umiQyeqK8rt4YpnOvRHW/GytB3ABFebtx6WXIzsJVSy7XQrtx509eu/xXOr+KpQ7SHSZei65+DXnmdEKYIjOP+vfk9yiXKcCDPwQPqBfF2SY2PyMfmZLt5pxwONz+N+NxB6F8prkfRGj14jXWI84yQNgF+1IhBONTOdDWbkAaMmDE6EPXX5J9XqDKWwuo+TF32jQ3CyWkimSOMVLx56MEfo+f8WjONCqiv5/odyi/51yD7nXNTYxeVzxJTq+3y+aUPCDevaO4be7vKoDZxAOKw1Gyuz7GmFJ16SO/p2VaxMG9ZHhAeHfGitCVFkv+Al+Yf7x0EFoWLD1VAgNn38iX+6rsWLAe+frUpJLRgF00OApTEaKipimwQ9FTveNIQM0+nyBmHZBw1hfs1zZahCSUaF/gv20Ewp6AC1xcJJMM2H0mjOiK6f57/rXMWSu2NBbb+aBKDmJzLNNqdZE7ZsYdQ2YzhNiM4v5i4iMUBjU2MU3hTxcaA1NFm+4BBCD6P3/YP+56Pr5dojyQ+e2ISZH5am+SFu0ZJJoJOAScWjCIOZSYQgeAe4vHUl0mrvbhDjcodg+v59mpSNAJibhOcZUBQTZBbKO0x32r8ZuKDUCyOFK56vhLP0TDTMXVXA4Kl7tAoQJ+WQASZhPSJmM+7mqWNeIASWDbHDGBgDAjBgkqhkiG9w0BCRUxFgQUwGwiSqKiTkXW++IueoxiI4tONDrAwWQYJKoZIhvcNAQkUMUweSgBQAEsAQwBTADEAMgAgACgAcgBz+AGEAKQAgAC0AawBlAHkAcABiAGUAIABQAEIARQAtAFMASABBADEALQBSAEMAMgAt+ADEAMgA4MDEwITAJBgUrDgMCGgUABBSoNEHQ71kWU35Sgmprhdt+YWCrSQQIaDZt+kZqPBPMCAggA+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIGugIBAzCCBoAGCSqGSIb3DQEHAaCCBnEEggZtMIIGaTCCAw8GCSqGSIb3DQEH+BqCCAwAwggL8AgEAMIIC9QYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQIi29c+ZJLr798CAggAgIICyDX4RQtAkr7o637GGgni5Y2Ah12PRo4E5WKIlZYW9aR7qulg+upU3/k7QR9Y85XR+7lmbNW1p8U7waU37BQiJCdMdXyU3Q17szHh5SR4PeGZy+TUY+Wx1FLzbPBVghTDLU1h7lIGGLwi/IjuxLXglUtSQ/Q1/dYsWBbVbXdIZy5MWKTNx9+PztHjqe2078nmaZ4u3cfzh0aPqexMy1E955EtaUrV/MLXteo1C1MvrZ7HtZ/4JZy+3bnoO8OQMpruOjDzR3Yis/PQRH5khvMDbzrQEE7z3P/+snev8SlEIHLGZpn/h2RN+FCV3+y0slGkph4cLt3aAda4iyQGF6G+K5y+YpJ7IJkF8UPcJmvtQB7w7e6ZeET7M+3cf6jQJdU9G0/VLPvSoVf7ye508OiO35lEQxLLluyWW1vfkhdZ/EHPJdxZ2rOQaj+jiuqdQomdXSnp3pJkMcXpsbruwG+N80Ja04vaXEf13OGnQmF54+NVPdTbLYKsBNQ+WRSylgnKY/aIfR6E6EfhhzYvn/6FhhHTIsc0Vn4BRpOeN7eKfgaZeQuejV/bY9+p+Ckd/ZoOnnDPtQGrP9gH69mspdMJ68cFDu9DqJiTgP+aOLb6s9NZh5dT6n0iwz5rH+fCA3H3zS8YdD5qE15Fg82oiR5yUsyvyxPEIFhTRbpyv+HYMt1hUzBPCQX/xv5hNW++aibkfayFVizuXjpDkrcAiNpCNYIutatIXIjFZptPztrk114l9MqFVt7Wt72M87E+D0kAzz9Yr+w2q8qEEcE7Uh/auevsTGBhvuwSu0oE3jtvHpGaEg/hOTwbylrEobvy+jeVYag3HQAnDy8X1XcrJFmm7kVou8Z9dFnaP+lZOfMjESUDPiX1XrXWDIMWZvdh4+jpikJG7Roaaegkq288HwyQGWCcxFG+7m9XWs/RQhc3nPgLemp70DeQu+HTiQKoPV+sO5qSS8wggNSBgkqhkiG9w0BBwGgggNDBIIDPzCCAzswggM3BgsqhkiG9w0BDAoB+AqCCAqYwggKiMBwGCiqGSIb3DQEMAQYwDgQIA936U0ZbYmMCAggABIICgPFqmMDS+MDZjXJg1scRG+eBc7fZ8zt64P3k46BSev9XPG2y+dKuj31Yuk9K/2CPsYO1b4fvo+i0SWb5I85K5kbmVMvEFTS8bRO8RTDzAXf9HVz9S0inX3uqJCQ/lUp1IMadLYyghc+E63Ag+S7Ob74puCSCoHMwKIilHn3+iKZRK+OO8IYiFvOsoj4kdOI2Vd0AtWppIoq+G2GcnUu9cxbyab1VTuEdMoV6cpTiB9yfZUytxrdAR4368eN+xYRTvyY95wyxW0mN+jUuOhN9UZBNH76GNhCj/yeI4J08K7QBxehsTAtpHfo35i3c67sCC3EFjXckRNGL9+nfN3G1i3kGfuilaV3qACxHa6i0Z4DFhF+MG42kUMdQOC5I/tPqGyXI4JUGfU7CwE+FAt2tAxSvUw4H2sJGnGYAOjuWscDkbTAm0qKxXYpMOXIliTjW2DwNxlGBnLuqNOE+sV/OxQwKVZx2RCPmJcfddLaUfPRbHmJu3VGXi/CQnoIaCIL+m/veLWbJrAsngc7V+8a0Bn1oHsZXjzNuAOZxT/rNX72AOCJdtIaXzfCPvzC1sM2scoDVsqKSgxIyqUXMk+4XEYP6bgxqaDlFSBImENIvWcX1ekryw9JKgOP+uIaW8DLmumU1J6S4exZFxTRKh5+2hDI8/Fjry4QnDSDxNyt1Mie77uKEzPt8fHmwRGbs1YCA6y7X+qD5JBkZSrn+dAs+YIgH7UZvbBxaN1NrQDwJjLsLhfj4g8L7dqMXYINXh3RhTeBm6sosD8EOaHcVReqm+7dw2Ej7T982PyCSVD/BQfBaB7KPQy7GVCYWRDcK0R7QfiIHkKMyfgJDXvL8DnxtX+6Xtt+6Vrz/uKQsYxfjAjBgkqhkiG9w0BCRUxFgQUwGwiSqKiTkXW+IueoxiI4tON+DrAwVwYJKoZIhvcNAQkUMUoeSABQAEsAQwBTADEAMgAgACgAcgBzAGEAKQAgAC0A+awBlAHkAcABiAGUAIABQAEIARQAtAFMASABBADEALQBSAEMAMgAtADQAMDAxMCEw+CQYFKw4DAhoFAAQUoSm7SB3q7Zpmgsmf7YtgJpq6RakECP3zlmAjUXYJAgIIAA==+-----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-----