cryptostore 0.3.1.0 → 0.5.0.0
raw patch · 45 files changed
Files
- ChangeLog.md +54/−0
- LICENSE +1/−1
- README.md +115/−20
- cryptostore.cabal +22/−13
- src/Crypto/Store/ASN1/Generate.hs +1/−1
- src/Crypto/Store/ASN1/Parse.hs +4/−2
- src/Crypto/Store/CMS.hs +26/−13
- src/Crypto/Store/CMS/Algorithms.hs +402/−85
- src/Crypto/Store/CMS/Attribute.hs +1/−1
- src/Crypto/Store/CMS/Authenticated.hs +3/−2
- src/Crypto/Store/CMS/Enveloped.hs +222/−46
- src/Crypto/Store/CMS/Info.hs +3/−0
- src/Crypto/Store/CMS/OriginatorInfo.hs +3/−0
- src/Crypto/Store/CMS/PEM.hs +14/−13
- src/Crypto/Store/CMS/Signed.hs +10/−9
- src/Crypto/Store/CMS/Util.hs +5/−0
- src/Crypto/Store/Error.hs +4/−0
- src/Crypto/Store/KeyWrap/AES.hs +4/−1
- src/Crypto/Store/KeyWrap/RC2.hs +5/−4
- src/Crypto/Store/KeyWrap/TripleDES.hs +4/−3
- src/Crypto/Store/Keys.hs +122/−0
- src/Crypto/Store/PEM.hs +8/−0
- src/Crypto/Store/PKCS12.hs +96/−38
- src/Crypto/Store/PKCS5.hs +82/−1
- src/Crypto/Store/PKCS8.hs +291/−197
- src/Crypto/Store/PubKey/RSA/KEM.hs +93/−0
- src/Crypto/Store/Util.hs +3/−1
- src/Crypto/Store/X509.hs +27/−19
- tests/CMS/Instances.hs +101/−49
- tests/CMS/Tests.hs +31/−6
- tests/KeyWrap/RC2.hs +4/−4
- tests/KeyWrap/TripleDES.hs +4/−4
- tests/PKCS12/Instances.hs +30/−3
- tests/PKCS12/Tests.hs +56/−8
- tests/PKCS8/Instances.hs +7/−3
- tests/PKCS8/Tests.hs +8/−0
- tests/Util.hs +13/−4
- tests/X509/Instances.hs +18/−7
- tests/X509/Tests.hs +8/−0
- tests/files/cms-digested-data.pem +19/−0
- tests/files/ed25519-pkcs12.pem +58/−0
- tests/files/pkcs12-rfc9579.pem +354/−0
- tests/files/rfc8410.pem +19/−0
- tests/files/rfc9690.pem +84/−0
- tests/files/rsa-pkcs12.pem +89/−0
ChangeLog.md view
@@ -1,5 +1,59 @@ # Revision history for cryptostore +## 0.5.0.0 - 2026-02-08++* Add support for key encapsulation to CMS, aka KEMRecipientInfo. The only+ mechanism implemented for now is RSA-KEM.++* Decoding private keys in `Crypto.Store.PKCS8` is now more strict and makes+ sure that the optional public key matches the private key when present.++* Raise minimum bounds to crypton-x509-* >= 1.8.0 when flag `use_crypton`+ is enabled, and reflect the new transitive dependencies++* Fix encoding of key identifiers in CMS signer, originator and recipient info++## 0.4.0.0 - 2025-10-12++* Private keys are now represented as type `KeyPair` defined in module+ `Crypto.Store.PKCS8` instead of `PrivKey`. This ensures that the+ corresponding public key is always available and that the library detects+ and reports data mistakes involving a wrong public key. A new error+ `PublicPrivateKeyMismatch` is added for that purpose and returned by+ functions `certSigner`, `withRecipientKeyAgree`, `fromCredential` and+ `fromNamedCredential`. Behavior of functions `toCredential` and+ `toNamedCredential` is now more strict, they return a key and leaf+ certificate that always match. Functions to convert `KeyPair` to/from the+ usual type `PrivKey` are also exposed from module `Crypto.Store.PKCS8`.++* Added support of PBMAC1 for PKCS#12 integrity. Type `IntegrityParams` used+ in functions `writeP12File` and `writeP12FileToMemory` is modified.+ A low-level function for PBMAC1 is also available in the PKCS5 module.++* CMS now supports SHA-3 algorithms in HMAC, ECDSA, or as digest algorithm++* Functions `pemToKey`, `pemToPubKey` and `pemToContentInfo` are modified to+ return error details when a PEM object cannot be read. The old API signature+ that discarded error details is available with functions renamed+ `pemToKeyAccum`, `pemToPubKeyAccum` and `pemToContentInfoAccum`.++* Function `generateEncryptionParams` is removed and can be replaced with+ functions `ecbParams`, `generateCBCParams`, `generateCFBParams` or+ `generateCTRParams` according to the desired mode.++* Functions `generateAuthEnc128Params` and `generateAuthEnc256Params` are+ replaced with `authEnc128Params` and `authEnc256Params`. The new API expects+ a `ContentEncryptionParams` argument instead of `ContentEncryptionAlg`.++* CMS encrypted data, enveloped data and authenticated-enveloped data now+ support optional HKDF-SHA256 derivation of the content-encryption key. When+ generating encryption parameters, enable the key derivation feature with a+ call to functions `deriveEncryptionKey` or `authDeriveEncryptionKey`. The+ feature prevents AEAD-to-CBC downgrade attack scenarios for `CCM` and `GCM`+ modes but requires support from the receiving end.++* Fixed encoding of some HMAC and PRF parameters+ ## 0.3.1.0 - 2024-05-05 * Strict validation of GCM/CCM authentication tag length
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2018-2024, Olivier Chéron+Copyright (c) 2018-2026, Olivier Chéron All rights reserved.
README.md view
@@ -1,8 +1,8 @@ # cryptostore -[](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml) [](https://en.wikipedia.org/wiki/BSD_licenses) [](https://haskell.org/)+[](https://hackage.haskell.org/package/cryptostore) This package allows to read and write cryptographic objects to/from ASN.1. @@ -33,7 +33,7 @@ > :m Crypto.Store.PKCS8 > (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key > recover "mypassword" key-Right (PrivKeyRSA ...)+Right (keyPairFromPrivKey (PrivKeyRSA ...)) ``` Generating a private key and writing to disk, without encryption:@@ -41,7 +41,8 @@ ```haskell > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001-> writeKeyFile PKCS8Format "/path/to/privkey.pem" [privKey]+> let keyPair = keyPairFromPrivKey privKey+> writeKeyFile PKCS8Format "/path/to/newkey.pem" [keyPair] ``` Generating a private key and writing to disk, with password-based encryption:@@ -50,11 +51,12 @@ > :set -XOverloadedStrings > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001+> let keyPair = keyPairFromPrivKey privKey > salt <- generateSalt 16 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256-> encParams <- generateEncryptionParams (CBC AES256)+> encParams <- generateCBCParams AES256 > let pbes = PBES2 (PBES2Parameter kdf encParams)-> writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey+> writeEncryptedKeyFile "/path/to/newkey.pem" pbes "mypassword" keyPair Right () ``` @@ -104,7 +106,7 @@ > getAllSafeX509Certs contents [SignedExact {getSigned = ...}] > recover password (getAllSafeKeys contents)-Right [PrivKeyRSA ...]+Right [keyPairFromPrivKey (PrivKeyRSA ...)] ``` Reading a binary PKCS #12 file using distinct integrity and privacy passwords:@@ -112,13 +114,13 @@ ```haskell > :set -XOverloadedStrings > :m Crypto.Store.PKCS12-> Right p12 <- readP12File "/path/to/file.p12"+> Right p12 <- readP12File "/path/to/other.p12" > let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12 > let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12) > getAllSafeX509Certs contents [SignedExact {getSigned = ...}] > recover "myprivacypassword" (getAllSafeKeys contents)-Right [PrivKeyRSA ...]+Right [keyPairFromPrivKey (PrivKeyRSA ...)] ``` Generating a PKCS #12 file containing a private key:@@ -132,21 +134,22 @@ -- Put the key inside a bag > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS+> let keyPair = keyPairFromPrivKey privKey > let attrs = setFriendlyName "Some Key" []-> keyBag = Bag (KeyBag $ FormattedKey PKCS8Format privKey) attrs+> keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs > contents = SafeContents [keyBag] -- Encrypt the contents > salt <- generateSalt 16 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256-> encParams <- generateEncryptionParams (CBC AES256)+> encParams <- generateCBCParams AES256 > let pbes = PBES2 (PBES2Parameter kdf encParams) > Right pkcs12 = encrypted pbes "mypassword" contents -- Save to PKCS #12 with integrity protection (same password) > salt' <- generateSalt 16-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 200000)-> writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt' 200000)+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12 Right () ``` @@ -158,7 +161,7 @@ > :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS -- Read PKCS #12 content as credential-> Right p12 <- readP12File "/path/to/file.p12"+> Right p12 <- readP12File "/path/to/other.p12" > let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12) > cred@@ -167,25 +170,59 @@ -- Scheme to reencrypt the key > saltK <- generateSalt 16 > let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256-> encParamsK <- generateEncryptionParams (CBC AES256)+> encParamsK <- generateCBCParams AES256 > let sKey = PBES2 (PBES2Parameter kdfK encParamsK) -- Scheme to reencrypt the certificate chain > saltC <- generateSalt 8 > let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256-> encParamsC <- generateEncryptionParams (CBC AES128)+> encParamsC <- generateCBCParams AES128 > let sCert = PBES2 (PBES2Parameter kdfC encParamsC) -- Write the content back to a new file > let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred > salt <- generateSalt 16-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 200000)+> let iParams = TraditionalIntegrity (DigestAlgorithm SHA256) (PBEParameter salt 200000) > writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12' ``` Variants `toNamedCredential` and `fromNamedCredential` are also available when PKCS #12 elements need an alias (friendly name). +The library also supports integrity protection with PBMAC1 as defined in+[RFC 9579](https://tools.ietf.org/html/rfc9579). The following example shows+how to use PBKDF2 with SHA-256 HMAC and PRF:++```haskell+> :set -XOverloadedStrings++-- Generate a private key+> :m Crypto.PubKey.RSA Data.X509+> privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001++-- Put the key inside a bag+> :m Crypto.Store.PKCS12 Crypto.Store.PKCS8 Crypto.Store.PKCS5 Crypto.Store.CMS+> let keyPair = keyPairFromPrivKey privKey+> let attrs = setFriendlyName "Some Key" []+> keyBag = Bag (KeyBag $ FormattedKey PKCS8Format keyPair) attrs+> contents = SafeContents [keyBag]++-- Encrypt the contents+> salt <- generateSalt 16+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256+> encParams <- generateCBCParams AES256+> let pbes = PBES2 (PBES2Parameter kdf encParams)+> Right pkcs12 = encrypted pbes "mypassword" contents++-- Save to PKCS #12 with PBMAC1 integrity protection (same password)+> salt' <- generateSalt 16+> let kdf' = PBKDF2 salt' 200000 (Just 32) PBKDF2_SHA256+> let authScheme = PBMAC1 $ PBMAC1Parameter kdf' (HMAC SHA256)+> let iParams = AuthSchemeIntegrity authScheme+> writeP12File "/path/to/newkey.p12" iParams "mypassword" pkcs12+Right ()+```+ ## Cryptographic Message Syntax The API to read and write CMS content is available in `Crypto.Store.CMS`. The@@ -223,7 +260,7 @@ > let info = DataCI "Hi, what will you need from the cryptostore?" -- Content encryption will use AES-128-CBC-> ceParams <- generateEncryptionParams (CBC AES128)+> ceParams <- generateCBCParams AES128 > ceKey <- generateKey ceParams :: IO ContentEncryptionKey -- Encrypt the Content Encryption Key with a Password Recipient Info,@@ -231,7 +268,7 @@ -- that the recipient will need to know > salt <- generateSalt 16 > let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256-> keParams <- generateEncryptionParams (CBC AES128)+> keParams <- generateCBCParams AES128 > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams) -- Generate the enveloped structure for this single recipient. Encrypted@@ -271,7 +308,7 @@ -- Read signer certificate and private key > (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key-> let Right priv = recover "mypassword" key+> let Right pair = recover "mypassword" key > chain <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate] > let cert = CertificateChain chain @@ -281,7 +318,7 @@ -- Generate the signed structure with a single signer. Signed content is -- kept attached in the structure.-> let signer = certSigner (RSAPSS params) priv cert (Just []) []+> let signer = certSigner (RSAPSS params) pair cert (Just []) [] > Right signedData <- signData [signer] info > let signedCI = toAttachedCI signedData > writeCMSFile "/path/to/signed.pem" [signedCI]@@ -307,6 +344,64 @@ > let doValidation _ chain = null <$> validateNoFQHN store def noServiceID chain > verifySignedData (withSignerCertificate doValidation) signedData Right (DataCI "Some trustworthy content")+```++### Authenticated-enveloped data++The following examples generate a CMS structure auth-enveloping some data to a+KEM recipient, then decrypt the data to recover the content.++#### Generating authenticated-enveloped data++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.CMS Data.X509 Crypto.Store.X509++-- Input content info+> let info = DataCI "Powered by Haskell"++-- Read receipient certificate+> [cert] <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]++-- Content encryption will use AES-128-GCM, and we protect against manipulation+-- of algorithm identifiers as defined in RFC 9709+> aceParams' <- generateGCMParams AES128 16+> let aceParams = authDeriveEncryptionKey aceParams'+> aceKey <- generateKey aceParams :: IO ContentEncryptionKey++-- Encrypt the Content Encryption Key with a KEM Recipient Info,+-- i.e. a KDF will derive the Key Encryption Key from a shared secret produced+-- by a Key Encapsulation Mechanism. We are using RSA-KEM based on KDF3 with+-- SHA-256 to produce a 16-byte shared secret. Further derivation of the KEK+-- uses HKDF with SHA-256. The CEK is finally wrapped with AES-Wrap-128.+> let kem = KeyEncapsulationRSA (KDF3 (DigestAlgorithm SHA256)) 16+> let kdf = HKDF (DigestAlgorithm SHA256)+> let kri = forKeyEncapRecipient cert kdf AES128_WRAP kem++-- Generate the auth-enveloped structure for this single recipient. Encrypted+-- content is kept attached in the structure.+> Right authEnvData <- authEnvelopData mempty aceKey aceParams [kri] [] [] info+> let authEnvCI = toAttachedCI authEnvData+> writeCMSFile "/path/to/authEnveloped.pem" [authEnvCI]+```++#### Opening the authenticated-enveloped data++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.CMS Data.X509 Crypto.Store.X509 Crypto.Store.PKCS8++-- Read receipient certificate and private key+> (key : _) <- readKeyFile "/path/to/privkey.pem" -- assuming single key+> let Right pair = recover "mypassword" key+> [cert] <- readSignedObject "/path/to/cert.pem" :: IO [SignedCertificate]++-- Then this recipient just has to read the file and recover enveloped+-- content using the private key and certificate+> [AuthEnvelopedDataCI authEnvEncapData] <- readCMSFile "/path/to/authEnveloped.pem"+> authEnvData <- fromAttached authEnvEncapData+> openAuthEnvelopedData (withRecipientKeyEncap pair cert) authEnvData+Right (DataCI "Powered by Haskell") ``` ## Algorithms and security
cryptostore.cabal view
@@ -1,5 +1,5 @@ name: cryptostore-version: 0.3.1.0+version: 0.5.0.0 synopsis: Serialization of cryptographic data types description: Haskell implementation of PKCS \#8, PKCS \#12 and CMS (Cryptographic Message Syntax).@@ -9,15 +9,15 @@ maintainer: olivier.cheron@gmail.com copyright: Olivier Chéron category: Cryptography, Codec-homepage: https://github.com/ocheron/cryptostore-bug-reports: https://github.com/ocheron/cryptostore/issues+homepage: https://codeberg.org/ocheron/cryptostore+bug-reports: https://codeberg.org/ocheron/cryptostore/issues build-type: Simple extra-source-files: README.md ChangeLog.md tests/files/*.pem cabal-version: >=1.10 source-repository head type: git- location: https://github.com/ocheron/cryptostore+ location: https://codeberg.org/ocheron/cryptostore flag use_crypton description: Use crypton instead of cryptonite@@ -52,27 +52,33 @@ , Crypto.Store.CMS.Type , Crypto.Store.CMS.Util , Crypto.Store.Cipher.RC2.Primitive+ , Crypto.Store.Keys , Crypto.Store.PEM , Crypto.Store.PKCS5.PBES1 , Crypto.Store.PKCS8.EC+ , Crypto.Store.PubKey.RSA.KEM , Crypto.Store.Util -- other-extensions: build-depends: base >= 4.9 && < 5 , bytestring , basement , memory- , pem >= 0.1 && < 0.3- , asn1-types >= 0.3.1 && < 0.4- , asn1-encoding >= 0.9 && < 0.10- , hourglass >= 0.2 if flag(use_crypton) build-depends: crypton- , crypton-x509- , crypton-x509-validation+ , crypton-asn1-encoding >= 0.10.0 && < 0.11+ , crypton-asn1-types >= 0.4.1 && < 0.5+ , crypton-pem >= 0.2.4 && <0.4+ , crypton-x509 >= 1.8.0+ , crypton-x509-validation >= 1.8.0+ , time-hourglass else build-depends: cryptonite >=0.26 , x509 >= 1.7.5 , x509-validation >= 1.5+ , pem >= 0.1 && < 0.3+ , asn1-types >= 0.3.1 && < 0.4+ , asn1-encoding >= 0.9.6 && < 0.10+ , hourglass >= 0.2.10 default-language: Haskell2010 ghc-options: -Wall @@ -95,19 +101,22 @@ , X509.Tests build-depends: base >= 4.9 && < 5 , bytestring- , asn1-types >= 0.3.1 && < 0.4 , memory , tasty , tasty-hunit , tasty-quickcheck- , hourglass- , pem , cryptostore if flag(use_crypton) build-depends: crypton+ , crypton-asn1-types >= 0.4.1 && < 0.5+ , crypton-pem >= 0.2.4 && <0.4 , crypton-x509+ , time-hourglass else build-depends: cryptonite >=0.25 , x509+ , asn1-types >= 0.3.1 && < 0.4+ , hourglass+ , pem default-language: Haskell2010 ghc-options: -Wall
src/Crypto/Store/ASN1/Generate.hs view
@@ -107,7 +107,7 @@ gIntVal :: ASN1Elem e => Integer -> ASN1Stream e gIntVal = gOne . IntVal --- | Generate an 'OID' ASN.1 element.+-- | Generate an t'OID' ASN.1 element. gOID :: ASN1Elem e => OID -> ASN1Stream e gOID = gOne . OID
src/Crypto/Store/ASN1/Parse.hs view
@@ -9,7 +9,7 @@ -- with the following additions: -- -- * Parsed stream is annotated, i.e. parser input is @('ASN1', e)@ instead of--- @'ASN1'@. Main motivation is to allow to parse a sequence of 'ASN1Repr'+-- @'ASN1'@. Main motivation is to allow to parse a sequence of @ASN1Repr@ -- and hold the exact binary content that has been parsed. As consequence, -- no @getObject@ function is provided. Function 'withAnnotations' runs -- a parser and returns all annotations consumed in a monoid concatenation.@@ -40,7 +40,9 @@ ) where import Data.ASN1.Types-import Data.Monoid+#if !(MIN_VERSION_base(4,11,0))+import Data.Monoid ( (<>) )+#endif import Control.Applicative import Control.Arrow (first) import Control.Monad (MonadPlus(..), liftM2)
src/Crypto/Store/CMS.hs view
@@ -22,6 +22,10 @@ -- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc8702 RFC 8702>: Use of the SHAKE One-Way Hash Functions in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc9629 RFC 9629>: Using Key Encapsulation Mechanism (KEM) Algorithms in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc9688 RFC 9688>: Use of the SHA3 One-Way Hash Functions in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc9690 RFC 9690>: Use of the RSA-KEM Algorithm in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc9709 RFC 9709>: Encryption Key Derivation in the Cryptographic Message Syntax (CMS) Using HKDF with SHA-256 {-# LANGUAGE RecordWildCards #-} module Crypto.Store.CMS ( ContentType(..)@@ -62,6 +66,7 @@ , KeyTransportParams(..) , KeyAgreementParams(..) , KeyAgreementKDF(..)+ , KeyEncapsulationMechanism(..) , RecipientInfo(..) , EnvelopedData(..) , ProducerOfRI@@ -93,6 +98,10 @@ , PasswordRecipientInfo(..) , forPasswordRecipient , withRecipientPassword+ -- ** Key Encapsulation recipients+ , KEMRecipientInfo(..)+ , forKeyEncapRecipient+ , withRecipientKeyEncap -- * Digested data , DigestProxy(..) , DigestAlgorithm(..)@@ -106,8 +115,12 @@ , ContentEncryptionParams , EncryptedContent , EncryptedData(..)- , generateEncryptionParams+ , ecbParams+ , generateCBCParams , generateRC2EncryptionParams+ , generateCFBParams+ , generateCTRParams+ , deriveEncryptionKey , getContentEncryptionAlg , encryptData , decryptData@@ -122,11 +135,12 @@ , AuthContentEncryptionAlg(..) , AuthContentEncryptionParams , AuthEnvelopedData(..)- , generateAuthEnc128Params- , generateAuthEnc256Params+ , authEnc128Params+ , authEnc256Params , generateChaChaPoly1305Params , generateCCMParams , generateGCMParams+ , authDeriveEncryptionKey , authEnvelopData , openAuthEnvelopedData -- * Key derivation@@ -134,6 +148,7 @@ , generateSalt , KeyDerivationFunc(..) , PBKDF2_PRF(..)+ , KeyDerivationFn(..) -- * Secret-key algorithms , HasKeySize(..) , generateKey@@ -254,7 +269,7 @@ -> ContentInfo -> f (Either StoreError (EnvelopedData EncryptedContent)) envelopData oinfo key params envFns attrs ci =- f <$> (sequence <$> traverse ($ key) envFns)+ f . sequence <$> traverse ($ key) envFns where ebs = contentEncrypt key params (encapsulate ci) f ris = build <$> ebs <*> ris@@ -304,7 +319,7 @@ -> ContentInfo -> f (Either StoreError (AuthenticatedData EncapsulatedContent)) generateAuthenticatedData oinfo key macAlg digAlg envFns aAttrs uAttrs ci =- f <$> (sequence <$> traverse ($ key) envFns)+ f . sequence <$> traverse ($ key) envFns where msg = encapsulate ci ct = getContentType ci@@ -383,17 +398,17 @@ -> ContentInfo -> f (Either StoreError (AuthEnvelopedData EncryptedContent)) authEnvelopData oinfo key params envFns aAttrs uAttrs ci =- f <$> (sequence <$> traverse ($ key) envFns)+ f . sequence <$> traverse ($ key) envFns where- raw = encodeASN1Object params+ prm = derObjectExact params aad = encodeAuthAttrs aAttrs- ebs = authContentEncrypt key params raw aad (encapsulate ci)+ ebs = authContentEncrypt key prm aad (encapsulate ci) f ris = build <$> ebs <*> ris build (authTag, bs) ris = AuthEnvelopedData { aeOriginatorInfo = oinfo , aeRecipientInfos = ris , aeContentType = getContentType ci- , aeContentEncryptionParams = ASN1ObjectExact params raw+ , aeContentEncryptionParams = prm , aeEncryptedContent = bs , aeAuthAttrs = aAttrs , aeMAC = authTag@@ -411,10 +426,8 @@ return (r >>= decapsulate ct) where ct = aeContentType- params = exactObject aeContentEncryptionParams- raw = exactObjectRaw aeContentEncryptionParams aad = encodeAuthAttrs aeAuthAttrs- decr k = authContentDecrypt k params raw aad aeEncryptedContent aeMAC+ decr k = authContentDecrypt k aeContentEncryptionParams aad aeEncryptedContent aeMAC -- SignedData@@ -425,7 +438,7 @@ signData :: Applicative f => [ProducerOfSI f] -> ContentInfo -> f (Either StoreError (SignedData EncapsulatedContent)) signData sigFns ci =- f <$> (sequence <$> traverse (\fn -> fn ct msg) sigFns)+ f . sequence <$> traverse (\fn -> fn ct msg) sigFns where msg = encapsulate ci ct = getContentType ci
src/Crypto/Store/CMS/Algorithms.hs view
@@ -8,6 +8,7 @@ -- Cryptographic Message Syntax algorithms {-# LANGUAGE DataKinds #-} {-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -16,6 +17,7 @@ {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} module Crypto.Store.CMS.Algorithms ( DigestAlgorithm(..)@@ -24,7 +26,7 @@ , MessageAuthenticationCode , MACAlgorithm(..) , mac- , HasStrength+ , HasStrength(..) , securityAcceptable , HasKeySize(..) , getMaximumKeySize@@ -33,19 +35,24 @@ , ContentEncryptionCipher(..) , ContentEncryptionAlg(..) , ContentEncryptionParams(..)- , generateEncryptionParams+ , ecbParams+ , generateCBCParams , generateRC2EncryptionParams+ , generateCFBParams+ , generateCTRParams+ , deriveEncryptionKey , getContentEncryptionAlg , proxyBlockSize , contentEncrypt , contentDecrypt , AuthContentEncryptionAlg(..) , AuthContentEncryptionParams- , generateAuthEnc128Params- , generateAuthEnc256Params+ , authEnc128Params+ , authEnc256Params , generateChaChaPoly1305Params , generateCCMParams , generateGCMParams+ , authDeriveEncryptionKey , authContentEncrypt , authContentDecrypt , PBKDF2_PRF(..)@@ -54,7 +61,10 @@ , generateSalt , KeyDerivationFunc(..) , kdfKeyLength+ , kdfKeyLengthModify , kdfDerive+ , KeyDerivationFn(..)+ , kdfApply , KeyEncryptionParams(..) , keyEncrypt , keyDecrypt@@ -69,6 +79,9 @@ , ecdhPublic , ecdhEncrypt , ecdhDecrypt+ , KeyEncapsulationMechanism(..)+ , kemEncap+ , kemDecap , MaskGenerationFunc(..) , mgf , SignatureValue@@ -91,7 +104,7 @@ import Data.ByteArray (ByteArray, ByteArrayAccess) import qualified Data.ByteArray as B import Data.ByteString (ByteString)-import Data.Maybe (fromMaybe)+import Data.Maybe (fromJust, fromMaybe) import Data.Proxy import Data.Word import qualified Data.X509 as X509@@ -142,7 +155,9 @@ import qualified Crypto.Store.KeyWrap.AES as AES_KW import qualified Crypto.Store.KeyWrap.TripleDES as TripleDES_KW import qualified Crypto.Store.KeyWrap.RC2 as RC2_KW+import Crypto.Store.Keys import Crypto.Store.PKCS8.EC+import qualified Crypto.Store.PubKey.RSA.KEM as RsaKem import Crypto.Store.Util @@ -167,6 +182,14 @@ SHA384 :: DigestProxy Hash.SHA384 -- | SHA-512 SHA512 :: DigestProxy Hash.SHA512+ -- | SHA3-224+ SHA3_224 :: DigestProxy Hash.SHA3_224+ -- | SHA3-256+ SHA3_256 :: DigestProxy Hash.SHA3_256+ -- | SHA3-384+ SHA3_384 :: DigestProxy Hash.SHA3_384+ -- | SHA3-512+ SHA3_512 :: DigestProxy Hash.SHA3_512 -- | SHAKE128 (256 bits) SHAKE128_256 :: DigestProxy (Hash.SHAKE128 256) -- | SHAKE256 (512 bits)@@ -186,6 +209,10 @@ showsPrec _ SHA256 = showString "SHA256" showsPrec _ SHA384 = showString "SHA384" showsPrec _ SHA512 = showString "SHA512"+ showsPrec _ SHA3_224 = showString "SHA3_224"+ showsPrec _ SHA3_256 = showString "SHA3_256"+ showsPrec _ SHA3_384 = showString "SHA3_384"+ showsPrec _ SHA3_512 = showString "SHA3_512" showsPrec _ SHAKE128_256 = showString "SHAKE128_256" showsPrec _ SHAKE256_512 = showString "SHAKE256_512" showsPrec d (SHAKE128 p) =@@ -208,6 +235,10 @@ getSecurityBits SHA256 = 128 getSecurityBits SHA384 = 192 getSecurityBits SHA512 = 256+ getSecurityBits SHA3_224 = 112+ getSecurityBits SHA3_256 = 128+ getSecurityBits SHA3_384 = 192+ getSecurityBits SHA3_512 = 256 getSecurityBits SHAKE128_256 = 128 getSecurityBits SHAKE256_512 = 256 getSecurityBits (SHAKE128 a) = shakeSecurityBits 128 a@@ -233,6 +264,10 @@ DigestAlgorithm SHA256 == DigestAlgorithm SHA256 = True DigestAlgorithm SHA384 == DigestAlgorithm SHA384 = True DigestAlgorithm SHA512 == DigestAlgorithm SHA512 = True+ DigestAlgorithm SHA3_224 == DigestAlgorithm SHA3_224 = True+ DigestAlgorithm SHA3_256 == DigestAlgorithm SHA3_256 = True+ DigestAlgorithm SHA3_384 == DigestAlgorithm SHA3_384 = True+ DigestAlgorithm SHA3_512 == DigestAlgorithm SHA3_512 = True DigestAlgorithm SHAKE128_256 == DigestAlgorithm SHAKE128_256 = True DigestAlgorithm SHAKE256_512 == DigestAlgorithm SHAKE256_512 = True DigestAlgorithm (SHAKE128 a) == DigestAlgorithm (SHAKE128 b) = natVal a == natVal b@@ -251,6 +286,10 @@ | Type_SHA256 | Type_SHA384 | Type_SHA512+ | Type_SHA3_224+ | Type_SHA3_256+ | Type_SHA3_384+ | Type_SHA3_512 | Type_SHAKE128_256 | Type_SHAKE256_512 | Type_SHAKE128_Len@@ -265,6 +304,10 @@ , Type_SHA256 , Type_SHA384 , Type_SHA512+ , Type_SHA3_224+ , Type_SHA3_256+ , Type_SHA3_384+ , Type_SHA3_512 , Type_SHAKE128_256 , Type_SHAKE256_512 , Type_SHAKE128_Len@@ -280,6 +323,10 @@ getObjectID Type_SHA256 = [2,16,840,1,101,3,4,2,1] getObjectID Type_SHA384 = [2,16,840,1,101,3,4,2,2] getObjectID Type_SHA512 = [2,16,840,1,101,3,4,2,3]+ getObjectID Type_SHA3_224 = [2,16,840,1,101,3,4,2,7]+ getObjectID Type_SHA3_256 = [2,16,840,1,101,3,4,2,8]+ getObjectID Type_SHA3_384 = [2,16,840,1,101,3,4,2,9]+ getObjectID Type_SHA3_512 = [2,16,840,1,101,3,4,2,10] getObjectID Type_SHAKE128_256 = [2,16,840,1,101,3,4,2,11] getObjectID Type_SHAKE256_512 = [2,16,840,1,101,3,4,2,12] getObjectID Type_SHAKE128_Len = [2,16,840,1,101,3,4,2,17]@@ -300,6 +347,10 @@ algorithmType (DigestAlgorithm SHA256) = Type_SHA256 algorithmType (DigestAlgorithm SHA384) = Type_SHA384 algorithmType (DigestAlgorithm SHA512) = Type_SHA512+ algorithmType (DigestAlgorithm SHA3_224) = Type_SHA3_224+ algorithmType (DigestAlgorithm SHA3_256) = Type_SHA3_256+ algorithmType (DigestAlgorithm SHA3_384) = Type_SHA3_384+ algorithmType (DigestAlgorithm SHA3_512) = Type_SHA3_512 algorithmType (DigestAlgorithm SHAKE128_256) = Type_SHAKE128_256 algorithmType (DigestAlgorithm SHAKE256_512) = Type_SHAKE256_512 algorithmType (DigestAlgorithm (SHAKE128 _)) = Type_SHAKE128_Len@@ -320,6 +371,10 @@ parseParameter Type_SHA256 = parseDigestParam (DigestAlgorithm SHA256) parseParameter Type_SHA384 = parseDigestParam (DigestAlgorithm SHA384) parseParameter Type_SHA512 = parseDigestParam (DigestAlgorithm SHA512)+ parseParameter Type_SHA3_224 = parseDigestParam (DigestAlgorithm SHA3_224)+ parseParameter Type_SHA3_256 = parseDigestParam (DigestAlgorithm SHA3_256)+ parseParameter Type_SHA3_384 = parseDigestParam (DigestAlgorithm SHA3_384)+ parseParameter Type_SHA3_512 = parseDigestParam (DigestAlgorithm SHA3_512) parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256) parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512) parseParameter Type_SHAKE128_Len = parseBitLen $@@ -456,19 +511,27 @@ , TypeHMAC SHA256 , TypeHMAC SHA384 , TypeHMAC SHA512+ , TypeHMAC SHA3_224+ , TypeHMAC SHA3_256+ , TypeHMAC SHA3_384+ , TypeHMAC SHA3_512 , TypeKMAC_SHAKE128 , TypeKMAC_SHAKE256 ] instance OIDable MACType where- getObjectID (TypeHMAC MD5) = [1,3,6,1,5,5,8,1,1]- getObjectID (TypeHMAC SHA1) = [1,3,6,1,5,5,8,1,2]- getObjectID (TypeHMAC SHA224) = [1,2,840,113549,2,8]- getObjectID (TypeHMAC SHA256) = [1,2,840,113549,2,9]- getObjectID (TypeHMAC SHA384) = [1,2,840,113549,2,10]- getObjectID (TypeHMAC SHA512) = [1,2,840,113549,2,11]- getObjectID TypeKMAC_SHAKE128 = [2,16,840,1,101,3,4,2,19]- getObjectID TypeKMAC_SHAKE256 = [2,16,840,1,101,3,4,2,20]+ getObjectID (TypeHMAC MD5) = [1,2,840,113549,2,6]+ getObjectID (TypeHMAC SHA1) = [1,2,840,113549,2,7]+ getObjectID (TypeHMAC SHA224) = [1,2,840,113549,2,8]+ getObjectID (TypeHMAC SHA256) = [1,2,840,113549,2,9]+ getObjectID (TypeHMAC SHA384) = [1,2,840,113549,2,10]+ getObjectID (TypeHMAC SHA512) = [1,2,840,113549,2,11]+ getObjectID (TypeHMAC SHA3_224) = [2,16,840,1,101,3,4,2,13]+ getObjectID (TypeHMAC SHA3_256) = [2,16,840,1,101,3,4,2,14]+ getObjectID (TypeHMAC SHA3_384) = [2,16,840,1,101,3,4,2,15]+ getObjectID (TypeHMAC SHA3_512) = [2,16,840,1,101,3,4,2,16]+ getObjectID TypeKMAC_SHAKE128 = [2,16,840,1,101,3,4,2,19]+ getObjectID TypeKMAC_SHAKE256 = [2,16,840,1,101,3,4,2,20] getObjectID ty = error ("Unsupported MACAlgorithm: " ++ show ty) @@ -483,6 +546,12 @@ algorithmType (KMAC_SHAKE128 _ _) = TypeKMAC_SHAKE128 algorithmType (KMAC_SHAKE256 _ _) = TypeKMAC_SHAKE256 + -- SHA-224, SHA-256, SHA-384, SHA-512 have NULL parameter,+ -- other HMAC algorithms have no parameter+ parameterASN1S (HMAC SHA224) = gNull+ parameterASN1S (HMAC SHA256) = gNull+ parameterASN1S (HMAC SHA384) = gNull+ parameterASN1S (HMAC SHA512) = gNull parameterASN1S (HMAC _) = id parameterASN1S (KMAC_SHAKE128 p str) = kmacASN1S 256 p str parameterASN1S (KMAC_SHAKE256 p str) = kmacASN1S 512 p str@@ -507,7 +576,7 @@ if b then parseParams else return (fn p' B.empty) where parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->- getNext >>= \(OctetString str) -> return (fn p str)+ fn p <$> parseOctetStringPrim instance HasKeySize MACAlgorithm where getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)@@ -592,6 +661,9 @@ -- ^ Cipher Feedback | forall c . BlockCipher c => CTR (ContentEncryptionCipher c) -- ^ Counter+ | forall hashAlg . Hash.HashAlgorithm hashAlg+ => DeriveHKDF (DigestProxy hashAlg)+ -- ^ Derivation of content-encryption key with HKDF instance Show ContentEncryptionAlg where show (ECB c) = shows c "_ECB"@@ -599,6 +671,7 @@ show CBC_RC2 = "RC2_CBC" show (CFB c) = shows c "_CFB" show (CTR c) = shows c "_CTR"+ show (DeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d instance Enumerable ContentEncryptionAlg where values = [ CBC DES@@ -623,6 +696,8 @@ , CFB Camellia128 , CTR Camellia128++ , DeriveHKDF SHA256 ] instance OIDable ContentEncryptionAlg where@@ -649,6 +724,8 @@ getObjectID (CTR Camellia128) = [0,3,4401,5,3,1,9,9] + getObjectID (DeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]+ getObjectID ty = error ("Unsupported ContentEncryptionAlg: " ++ show ty) instance OIDNameable ContentEncryptionAlg where@@ -657,7 +734,8 @@ -- | Content encryption algorithm with associated parameters (i.e. the -- initialization vector). ----- A value can be generated with 'generateEncryptionParams'.+-- A value can be built with functions 'ecbParams', 'generateCBCParams',+-- 'generateRC2EncryptionParams', 'generateCFBParams' and 'generateCTRParams'. data ContentEncryptionParams = forall c . BlockCipher c => ParamsECB (ContentEncryptionCipher c) -- ^ Electronic Codebook@@ -669,6 +747,9 @@ -- ^ Cipher Feedback | forall c . BlockCipher c => ParamsCTR (ContentEncryptionCipher c) (IV c) -- ^ Counter+ | forall hashAlg . Hash.HashAlgorithm hashAlg+ => ParamsDeriveHKDF (DigestProxy hashAlg) ContentEncryptionParams+ -- ^ Derivation of content-encryption key with HKDF instance Show ContentEncryptionParams where show = show . getContentEncryptionAlg@@ -679,6 +760,8 @@ ParamsCBCRC2 i1 iv1 == ParamsCBCRC2 i2 iv2 = i1 == i2 && iv1 `B.eq` iv2 ParamsCFB c1 iv1 == ParamsCFB c2 iv2 = cecI c1 == cecI c2 && iv1 `B.eq` iv2 ParamsCTR c1 iv1 == ParamsCTR c2 iv2 = cecI c1 == cecI c2 && iv1 `B.eq` iv2+ ParamsDeriveHKDF d1 a1 == ParamsDeriveHKDF d2 a2 =+ DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2 _ == _ = False instance HasKeySize ContentEncryptionParams where@@ -687,6 +770,7 @@ getKeySizeSpecifier (ParamsCBCRC2 i _) = KeySizeFixed $ (i + 7) `div` 8 getKeySizeSpecifier (ParamsCFB c _) = getCipherKeySizeSpecifier c getKeySizeSpecifier (ParamsCTR c _) = getCipherKeySizeSpecifier c+ getKeySizeSpecifier (ParamsDeriveHKDF _ a) = getKeySizeSpecifier a instance ASN1Elem e => ProduceASN1Object e ContentEncryptionParams where asn1s param =@@ -706,6 +790,7 @@ ceParameterASN1S (ParamsCBCRC2 len iv) = rc2ParameterASN1S len iv ceParameterASN1S (ParamsCFB _ iv) = gOctetString (B.convert iv) ceParameterASN1S (ParamsCTR _ iv) = gOctetString (B.convert iv)+ceParameterASN1S (ParamsDeriveHKDF _ a) = asn1s a parseCEParameter :: Monoid e => ContentEncryptionAlg -> ParseASN1 e ContentEncryptionParams@@ -714,6 +799,7 @@ parseCEParameter CBC_RC2 = parseRC2Parameter parseCEParameter (CFB c) = ParamsCFB c <$> (getNext >>= getIV) parseCEParameter (CTR c) = ParamsCTR c <$> (getNext >>= getIV)+parseCEParameter (DeriveHKDF d) = ParamsDeriveHKDF d <$> parse getIV :: BlockCipher cipher => ASN1 -> ParseASN1 e (IV cipher) getIV (OctetString ivBs) =@@ -742,22 +828,41 @@ getContentEncryptionAlg (ParamsCBCRC2 _ _) = CBC_RC2 getContentEncryptionAlg (ParamsCFB c _) = CFB c getContentEncryptionAlg (ParamsCTR c _) = CTR c+getContentEncryptionAlg (ParamsDeriveHKDF d _) = DeriveHKDF d --- | Generate random parameters for the specified content encryption algorithm.-generateEncryptionParams :: MonadRandom m- => ContentEncryptionAlg -> m ContentEncryptionParams-generateEncryptionParams (ECB c) = return (ParamsECB c)-generateEncryptionParams (CBC c) = ParamsCBC c <$> ivGenerate undefined-generateEncryptionParams CBC_RC2 = ParamsCBCRC2 128 <$> ivGenerate undefined-generateEncryptionParams (CFB c) = ParamsCFB c <$> ivGenerate undefined-generateEncryptionParams (CTR c) = ParamsCTR c <$> ivGenerate undefined+-- | Get 'ECB' parameters for the specified cipher.+ecbParams :: BlockCipher c+ => ContentEncryptionCipher c -> ContentEncryptionParams+ecbParams = ParamsECB +-- | Generate random 'CBC' parameters for the specified cipher.+generateCBCParams :: (MonadRandom m, BlockCipher c)+ => ContentEncryptionCipher c+ -> m ContentEncryptionParams+generateCBCParams c = ParamsCBC c <$> ivGenerate undefined+ -- | Generate random RC2 parameters with the specified effective key length (in -- bits). generateRC2EncryptionParams :: MonadRandom m => Int -> m ContentEncryptionParams generateRC2EncryptionParams len = ParamsCBCRC2 len <$> ivGenerate undefined +-- | Generate random 'CFB' parameters for the specified cipher.+generateCFBParams :: (MonadRandom m, BlockCipher c)+ => ContentEncryptionCipher c+ -> m ContentEncryptionParams+generateCFBParams c = ParamsCFB c <$> ivGenerate undefined++-- | Generate random 'CTR' parameters for the specified cipher.+generateCTRParams :: (MonadRandom m, BlockCipher c)+ => ContentEncryptionCipher c+ -> m ContentEncryptionParams+generateCTRParams c = ParamsCTR c <$> ivGenerate undefined++-- | Use derivation of the content-encryption key with HKDF-SHA256+deriveEncryptionKey :: ContentEncryptionParams -> ContentEncryptionParams+deriveEncryptionKey = ParamsDeriveHKDF SHA256+ -- | Encrypt a bytearray with the specified content encryption key and -- algorithm. contentEncrypt :: (ByteArray cek, ByteArray ba)@@ -771,6 +876,7 @@ ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> force $ cbcEncrypt c iv $ padded c bs) ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> force $ cfbEncrypt c iv $ padded c bs) ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> force $ ctrCombine c iv $ padded c bs)+ ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentEncrypt derived a bs where force x = x `seq` Right x padded c = pad (PKCS7 $ blockSize c)@@ -788,6 +894,7 @@ ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> unpadded c (cbcDecrypt c iv bs)) ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> unpadded c (cfbDecrypt c iv bs)) ParamsCTR cipher iv -> getCipher cipher key >>= (\c -> unpadded c (ctrCombine c iv bs))+ ParamsDeriveHKDF d a -> deriveHKDF d a key >>= \derived -> contentDecrypt derived a bs where unpadded c decrypted = case unpad (PKCS7 $ blockSize c) decrypted of@@ -854,6 +961,9 @@ -- ^ Counter with CBC-MAC | forall c . BlockCipher c => GCM (ContentEncryptionCipher c) -- ^ Galois Counter Mode+ | forall hashAlg . Hash.HashAlgorithm hashAlg+ => AuthDeriveHKDF (DigestProxy hashAlg)+ -- ^ Derivation of content-encryption key with HKDF instance Show AuthContentEncryptionAlg where show AUTH_ENC_128 = "AUTH_ENC_128"@@ -861,6 +971,7 @@ show CHACHA20_POLY1305 = "CHACHA20_POLY1305" show (CCM c) = shows c "_CCM" show (GCM c) = shows c "_GCM"+ show (AuthDeriveHKDF d) = "CMS_CEK_HKDF_" ++ show d instance Enumerable AuthContentEncryptionAlg where values = [ AUTH_ENC_128@@ -874,6 +985,8 @@ , GCM AES128 , GCM AES192 , GCM AES256++ , AuthDeriveHKDF SHA256 ] instance OIDable AuthContentEncryptionAlg where@@ -889,6 +1002,8 @@ getObjectID (GCM AES192) = [2,16,840,1,101,3,4,1,26] getObjectID (GCM AES256) = [2,16,840,1,101,3,4,1,46] + getObjectID (AuthDeriveHKDF SHA256) = [1,2,840,113549,1,9,16,3,31]+ getObjectID ty = error ("Unsupported AuthContentEncryptionAlg: " ++ show ty) instance OIDNameable AuthContentEncryptionAlg where@@ -933,9 +1048,8 @@ -- | Authenticated-content encryption algorithm with associated parameters -- (i.e. the nonce). ----- A value can be generated with functions 'generateAuthEnc128Params',--- 'generateAuthEnc256Params', 'generateChaChaPoly1305Params',--- 'generateCCMParams' and 'generateGCMParams'.+-- A value can be built with functions 'authEnc128Params', 'authEnc256Params',+-- 'generateChaChaPoly1305Params', 'generateCCMParams' and 'generateGCMParams'. data AuthContentEncryptionParams = Params_AUTH_ENC_128 AuthEncParams -- ^ authEnc with 128-bit keying material@@ -947,6 +1061,9 @@ -- ^ Counter with CBC-MAC | forall c . BlockCipher c => ParamsGCM (ContentEncryptionCipher c) B.Bytes Int -- ^ Galois Counter Mode+ | forall hashAlg . Hash.HashAlgorithm hashAlg+ => ParamsAuthDeriveHKDF (DigestProxy hashAlg) (ASN1ObjectExact AuthContentEncryptionParams)+ -- ^ Derivation of content-encryption key with HKDF instance Show AuthContentEncryptionParams where show = show . getAuthContentEncryptionAlg@@ -961,6 +1078,10 @@ cecI c1 == cecI c2 && iv1 == iv2 && (m1, l1) == (m2, l2) ParamsGCM c1 iv1 len1 == ParamsGCM c2 iv2 len2 = cecI c1 == cecI c2 && iv1 == iv2 && len1 == len2++ ParamsAuthDeriveHKDF d1 a1 == ParamsAuthDeriveHKDF d2 a2 =+ DigestAlgorithm d1 == DigestAlgorithm d2 && a1 == a2+ _ == _ = False instance HasKeySize AuthContentEncryptionParams where@@ -969,38 +1090,39 @@ getKeySizeSpecifier (Params_CHACHA20_POLY1305 _) = KeySizeFixed 32 getKeySizeSpecifier (ParamsCCM c _ _ _) = getCipherKeySizeSpecifier c getKeySizeSpecifier (ParamsGCM c _ _) = getCipherKeySizeSpecifier c+ getKeySizeSpecifier (ParamsAuthDeriveHKDF _ a) = getKeySizeSpecifier (exactObject a) -instance ASN1Elem e => ProduceASN1Object e AuthContentEncryptionParams where+instance ProduceASN1Object ASN1P AuthContentEncryptionParams where asn1s param = asn1Container Sequence (oid . params) where oid = gOID (getObjectID $ getAuthContentEncryptionAlg param) params = aceParameterASN1S param -instance Monoid e => ParseASN1Object e AuthContentEncryptionParams where+instance ParseASN1Object [ASN1Event] AuthContentEncryptionParams where parse = onNextContainer Sequence $ do OID oid <- getNext withObjectID "authenticated-content encryption algorithm" oid parseACEParameter -aceParameterASN1S :: ASN1Elem e => AuthContentEncryptionParams -> ASN1Stream e+aceParameterASN1S :: AuthContentEncryptionParams -> ASN1PS aceParameterASN1S (Params_AUTH_ENC_128 p) = asn1s p aceParameterASN1S (Params_AUTH_ENC_256 p) = asn1s p aceParameterASN1S (Params_CHACHA20_POLY1305 iv) = gOctetString (B.convert iv) aceParameterASN1S (ParamsCCM _ iv m _) =- asn1Container Sequence (nonce . icvlen)+ asn1Container Sequence (if m == CCM_M12 then nonce else nonce . icvlen) where nonce = gOctetString (B.convert iv) icvlen = gIntVal (fromIntegral $ getM m) aceParameterASN1S (ParamsGCM _ iv len) =- asn1Container Sequence (nonce . icvlen)+ asn1Container Sequence (if len == 12 then nonce else nonce . icvlen) where nonce = gOctetString (B.convert iv) icvlen = gIntVal (fromIntegral len)+aceParameterASN1S (ParamsAuthDeriveHKDF _ a) = asn1s a -parseACEParameter :: Monoid e- => AuthContentEncryptionAlg- -> ParseASN1 e AuthContentEncryptionParams+parseACEParameter :: AuthContentEncryptionAlg+ -> ParseASN1 [ASN1Event] AuthContentEncryptionParams parseACEParameter AUTH_ENC_128 = Params_AUTH_ENC_128 <$> parse parseACEParameter AUTH_ENC_256 = Params_AUTH_ENC_256 <$> parse parseACEParameter CHACHA20_POLY1305 = do@@ -1014,7 +1136,7 @@ let ivlen = B.length iv when (ivlen < 7 || ivlen > 13) $ throwParseError $ "Parsed invalid CCM nonce length: " ++ show ivlen- let Just l = fromL (15 - ivlen)+ let l = fromJust $ fromL (15 - ivlen) m <- parseM return (ParamsCCM c (B.convert iv) m l) parseACEParameter (GCM c) = onNextContainer Sequence $ do@@ -1025,6 +1147,7 @@ when (icvlen < 12 || icvlen > 16) $ throwParseError $ "Parsed invalid GCM ICV length: " ++ show icvlen return (ParamsGCM c (B.convert iv) $ fromIntegral icvlen)+parseACEParameter (AuthDeriveHKDF d) = ParamsAuthDeriveHKDF d <$> parse -- | Get the authenticated-content encryption algorithm. getAuthContentEncryptionAlg :: AuthContentEncryptionParams@@ -1034,26 +1157,23 @@ getAuthContentEncryptionAlg (Params_CHACHA20_POLY1305 _) = CHACHA20_POLY1305 getAuthContentEncryptionAlg (ParamsCCM c _ _ _) = CCM c getAuthContentEncryptionAlg (ParamsGCM c _ _) = GCM c+getAuthContentEncryptionAlg (ParamsAuthDeriveHKDF d _) = AuthDeriveHKDF d --- | Generate random 'AUTH_ENC_128' parameters with the specified algorithms.-generateAuthEnc128Params :: MonadRandom m- => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm- -> m AuthContentEncryptionParams-generateAuthEnc128Params prfAlg cea macAlg = do- params <- generateEncryptionParams cea- return $ Params_AUTH_ENC_128 $+-- | Get 'AUTH_ENC_128' parameters with the specified algorithms.+authEnc128Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm+ -> AuthContentEncryptionParams+authEnc128Params prfAlg params macAlg =+ Params_AUTH_ENC_128 $ AuthEncParams { prfAlgorithm = prfAlg , encAlgorithm = params , macAlgorithm = macAlg } --- | Generate random 'AUTH_ENC_256' parameters with the specified algorithms.-generateAuthEnc256Params :: MonadRandom m- => PBKDF2_PRF -> ContentEncryptionAlg -> MACAlgorithm- -> m AuthContentEncryptionParams-generateAuthEnc256Params prfAlg cea macAlg = do- params <- generateEncryptionParams cea- return $ Params_AUTH_ENC_256 $+-- | Get 'AUTH_ENC_256' parameters with the specified algorithms.+authEnc256Params :: PBKDF2_PRF -> ContentEncryptionParams -> MACAlgorithm+ -> AuthContentEncryptionParams+authEnc256Params prfAlg params macAlg = do+ Params_AUTH_ENC_256 $ AuthEncParams { prfAlgorithm = prfAlg , encAlgorithm = params , macAlgorithm = macAlg@@ -1082,19 +1202,37 @@ iv <- nonceGenerate 12 return (ParamsGCM c iv l) +-- | Use derivation of the content-encryption key with HKDF-SHA256+authDeriveEncryptionKey :: AuthContentEncryptionParams -> AuthContentEncryptionParams+authDeriveEncryptionKey = ParamsAuthDeriveHKDF SHA256 . derObjectExact++deriveHKDF :: (Hash.HashAlgorithm a, ProduceASN1Object ASN1P params, ByteArrayAccess ikm)+ => DigestProxy a -> params -> ikm -> Either StoreError B.ScrubbedBytes+deriveHKDF hashAlg params ikm =+ kdfApply (HKDF (DigestAlgorithm hashAlg)) (salt :: B.Bytes) len info ikm+ where+ info = encodeASN1S (asn1s params)+ len = B.length ikm++ -- "The Cryptographic Message Syntax"+ salt = B.pack [84,104,101,32,67,114,121,112,116,111,103,114,97,112,104,105+ ,99,32,77,101,115,115,97,103,101,32,83,121,110,116,97,120]+ -- | Encrypt a bytearray with the specified authenticated-content encryption -- key and algorithm. authContentEncrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba) => cek- -> AuthContentEncryptionParams -> ba+ -> ASN1ObjectExact AuthContentEncryptionParams -> aad -> ba -> Either StoreError (AuthTag, ba)-authContentEncrypt key params paramsRaw aad bs =- case params of+authContentEncrypt key params aad bs =+ case exactObject params of Params_AUTH_ENC_128 p -> checkAuthKey 16 key >> authEncrypt p Params_AUTH_ENC_256 p -> checkAuthKey 32 key >> authEncrypt p Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpEncrypt ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= encrypt (getM m) ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= encrypt len+ ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->+ authContentEncrypt derived a aad bs where msglen = B.length bs force x = x `seq` Right x@@ -1112,7 +1250,8 @@ authEncrypt p@AuthEncParams{..} = do let (encKey, macKey) = authKeys key p encrypted <- contentEncrypt encKey encAlgorithm bs- let macMsg = paramsRaw `B.append` encrypted `B.append` B.convert aad+ let paramsRaw = exactObjectRaw params+ macMsg = B.convert paramsRaw `B.append` encrypted `B.append` B.convert aad found = mac macAlgorithm macKey macMsg return (found, encrypted) @@ -1120,15 +1259,17 @@ -- and algorithm. authContentDecrypt :: forall cek aad ba . (ByteArray cek, ByteArrayAccess aad, ByteArray ba) => cek- -> AuthContentEncryptionParams -> ba+ -> ASN1ObjectExact AuthContentEncryptionParams -> aad -> ba -> AuthTag -> Either StoreError ba-authContentDecrypt key params paramsRaw aad bs expected =- case params of+authContentDecrypt key params aad bs expected =+ case exactObject params of Params_AUTH_ENC_128 p -> checkAuthKey 16 key >> authDecrypt p Params_AUTH_ENC_256 p -> checkAuthKey 32 key >> authDecrypt p Params_CHACHA20_POLY1305 iv -> ccpInit key iv aad >>= ccpDecrypt ParamsCCM cipher iv m l -> getAEAD cipher key (AEAD_CCM msglen m l) iv >>= decrypt (getM m) ParamsGCM cipher iv len -> getAEAD cipher key AEAD_GCM iv >>= decrypt len+ ParamsAuthDeriveHKDF d a -> deriveHKDF d a key >>= \derived ->+ authContentDecrypt derived a aad bs expected where msglen = B.length bs badMac = Left BadContentMAC@@ -1153,7 +1294,8 @@ | otherwise = badMac where (encKey, macKey) = authKeys key p- macMsg = paramsRaw `B.append` bs `B.append` B.convert aad+ paramsRaw = exactObjectRaw params+ macMsg = B.convert paramsRaw `B.append` bs `B.append` B.convert aad found = mac macAlgorithm macKey macMsg acceptable = securityAcceptable macAlgorithm @@ -1225,7 +1367,7 @@ type AlgorithmType PBKDF2_PRF = PBKDF2_PRF algorithmName _ = "PBKDF2 PRF" algorithmType = id- parameterASN1S _ = id+ parameterASN1S _ = gNull parseParameter p = getNextMaybe nullOrNothing >> return p -- | Invoke the pseudorandom function.@@ -1257,6 +1399,10 @@ fromObjectID oid = unOIDNW <$> fromObjectID oid -- | Key derivation algorithm and associated parameters.+--+-- Implementations are specialized to password inputs. For other+-- implementations specialized to inputs having good entropy, like the shared+-- secrets produced by key-agreement schemes, see 'KeyDerivationFn'. data KeyDerivationFunc = -- | Key derivation with PBKDF2 PBKDF2 { pbkdf2Salt :: Salt -- ^ Salt value@@ -1327,6 +1473,12 @@ kdfKeyLength PBKDF2{..} = pbkdf2KeyLength kdfKeyLength Scrypt{..} = scryptKeyLength +-- | Modify the optional key length stored in the KDF parameters.+kdfKeyLengthModify :: (Maybe Int -> Maybe Int)+ -> KeyDerivationFunc -> KeyDerivationFunc+kdfKeyLengthModify f kdf@PBKDF2{..} = kdf{pbkdf2KeyLength = f pbkdf2KeyLength}+kdfKeyLengthModify f kdf@Scrypt{..} = kdf{scryptKeyLength = f scryptKeyLength}+ -- | Run a key derivation function to produce a result of the specified length -- using the supplied password. kdfDerive :: (ByteArrayAccess password, ByteArray out)@@ -1345,7 +1497,82 @@ generateSalt :: MonadRandom m => Int -> m Salt generateSalt = getRandomBytes +data TypeKeyDerivationFn+ = TypeHKDF DigestAlgorithm+ | TypeKDF3+ deriving (Show,Eq) +instance Enumerable TypeKeyDerivationFn where+ values = [ TypeHKDF (DigestAlgorithm SHA256)+ , TypeHKDF (DigestAlgorithm SHA384)+ , TypeHKDF (DigestAlgorithm SHA512)+ , TypeHKDF (DigestAlgorithm SHA3_224)+ , TypeHKDF (DigestAlgorithm SHA3_256)+ , TypeHKDF (DigestAlgorithm SHA3_384)+ , TypeHKDF (DigestAlgorithm SHA3_512)+ , TypeKDF3+ ]++instance OIDable TypeKeyDerivationFn where+ getObjectID (TypeHKDF (DigestAlgorithm SHA256)) = [1,2,840,113549,1,9,16,3,28]+ getObjectID (TypeHKDF (DigestAlgorithm SHA384)) = [1,2,840,113549,1,9,16,3,29]+ getObjectID (TypeHKDF (DigestAlgorithm SHA512)) = [1,2,840,113549,1,9,16,3,30]+ getObjectID (TypeHKDF (DigestAlgorithm SHA3_224)) = [1,2,840,113549,1,9,16,3,32]+ getObjectID (TypeHKDF (DigestAlgorithm SHA3_256)) = [1,2,840,113549,1,9,16,3,33]+ getObjectID (TypeHKDF (DigestAlgorithm SHA3_384)) = [1,2,840,113549,1,9,16,3,34]+ getObjectID (TypeHKDF (DigestAlgorithm SHA3_512)) = [1,2,840,113549,1,9,16,3,35]+ getObjectID (TypeHKDF hashAlg) = error ("Unsupported HKDF hash: " ++ show hashAlg)+ getObjectID TypeKDF3 = [1,3,133,16,840,9,44,1,2]++instance OIDNameable TypeKeyDerivationFn where+ fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key derivation algorithm and associated parameters.+--+-- Implementations are specialized to inputs having good entropy, like the+-- shared secrets produced by key-agreement schemes. For other implementations+-- specialized to password inputs, see 'KeyDerivationFunc'.+data KeyDerivationFn+ -- | Key derivation with HKDF+ = HKDF DigestAlgorithm+ -- | Key derivation with KDF3+ | KDF3 DigestAlgorithm+ deriving (Show,Eq)++instance AlgorithmId KeyDerivationFn where+ type AlgorithmType KeyDerivationFn = TypeKeyDerivationFn++ algorithmName _ = "key derivation algorithm"+ algorithmType (HKDF alg) = TypeHKDF alg+ algorithmType (KDF3 _) = TypeKDF3++ parameterASN1S (HKDF _) = id+ parameterASN1S (KDF3 alg) = algorithmASN1S Sequence alg++ parseParameter (TypeHKDF alg) = return (HKDF alg)+ parseParameter TypeKDF3 = KDF3 <$> parseAlgorithm Sequence++kdfApply :: (ByteArrayAccess salt, ByteArrayAccess ikm, ByteArray out)+ => KeyDerivationFn -> salt -> Int -> ByteString -> ikm -> Either StoreError out+kdfApply (HKDF (DigestAlgorithm p)) salt len info ikm+ | len > maxLen = Left (InvalidParameter "HKDF OKM is too long")+ | otherwise = Right $ HKDF.expand (extract p ikm) info len+ where+ maxLen = 255 * digestSizeFromProxy p++ extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)+ => DigestProxy a -> ikm -> HKDF.PRK a+ extract _ = HKDF.extract salt++kdfApply (KDF3 dig@(DigestAlgorithm p)) salt len info ikm+ | not (securityAcceptable dig) =+ Left (InvalidParameter "KDF3 digest too weak")+ | B.null salt =+ let RsaKem.KDF kdf = RsaKem.kdf3 (hashFromProxy p) len+ in Right $ kdf info ikm+ | otherwise = Left (InvalidInput "KDF3 salt must be empty")++ -- Key encryption data KeyEncryptionType = TypePWRIKEK@@ -1450,6 +1677,7 @@ ParamsCBCRC2 len iv -> let cc = getRC2Cipher len key in either (return . Left) (\c -> wrapEncrypt cbcEncrypt c iv bs) cc ParamsCFB cipher iv -> let cc = getCipher cipher key in either (return . Left) (\c -> wrapEncrypt cfbEncrypt c iv bs) cc ParamsCTR _ _ -> return $ Left (InvalidParameter "Unable to wrap key in CTR mode")+ ParamsDeriveHKDF _ _ -> return $ Left (InvalidParameter "HKDF derivation is incompatible with key wrapping") keyEncrypt key AES128_WRAP bs = return (getCipher AES128 key >>= (`AES_KW.wrap` bs)) keyEncrypt key AES192_WRAP bs = return (getCipher AES192 key >>= (`AES_KW.wrap` bs)) keyEncrypt key AES256_WRAP bs = return (getCipher AES256 key >>= (`AES_KW.wrap` bs))@@ -1471,6 +1699,7 @@ ParamsCBCRC2 len iv -> getRC2Cipher len key >>= (\c -> wrapDecrypt cbcDecrypt c iv bs) ParamsCFB cipher iv -> getCipher cipher key >>= (\c -> wrapDecrypt cfbDecrypt c iv bs) ParamsCTR _ _ -> Left (InvalidParameter "Unable to unwrap key in CTR mode")+ ParamsDeriveHKDF _ _ -> Left (InvalidParameter "HKDF derivation is incompatible with key unwrapping") keyDecrypt key AES128_WRAP bs = getCipher AES128 key >>= (`AES_KW.unwrap` bs) keyDecrypt key AES192_WRAP bs = getCipher AES192 key >>= (`AES_KW.unwrap` bs) keyDecrypt key AES256_WRAP bs = getCipher AES256 key >>= (`AES_KW.unwrap` bs)@@ -1518,19 +1747,21 @@ fn formatted = let firstPass = encFn cipher iv formatted lastBlock = B.dropView firstPass (B.length firstPass - sz)- Just iv' = makeIV lastBlock+ iv' = fromJust $ makeIV lastBlock -- wrapped length >= sz in encFn cipher iv' firstPass wrapDecrypt :: (BlockCipher cipher, ByteArray ba) => (cipher -> IV cipher -> ba -> ba) -> cipher -> IV cipher -> ba -> Either StoreError ba-wrapDecrypt decFn cipher iv input = keyUnwrap (decFn cipher iv firstPass)+wrapDecrypt decFn cipher iv input+ | B.length beg < sz = Left (InvalidInput "wrapDecrypt: input too short")+ | otherwise = keyUnwrap (decFn cipher iv firstPass) where sz = blockSize cipher (beg, lb) = B.splitAt (B.length input - sz) input lastBlock = decFn cipher iv' lb- Just iv' = makeIV (B.dropView beg (B.length beg - sz))- Just iv'' = makeIV lastBlock+ iv' = fromJust $ makeIV (B.dropView beg (B.length beg - sz))+ iv'' = fromJust $ makeIV lastBlock firstPass = decFn cipher iv'' beg `B.append` lastBlock @@ -1642,12 +1873,12 @@ -- private key. transportDecrypt :: MonadRandom m => KeyTransportParams- -> X509.PrivKey+ -> KeyPair -> ByteString -> m (Either StoreError ByteString)-transportDecrypt RSAES (X509.PrivKeyRSA priv) bs =+transportDecrypt RSAES (KeyPairRSA priv _) bs = mapLeft RSAError <$> RSA.decryptSafer priv bs-transportDecrypt (RSAESOAEP p) (X509.PrivKeyRSA priv) bs+transportDecrypt (RSAESOAEP p) (KeyPairRSA priv _) bs | securityAcceptable p = withOAEPParams p $ \params -> mapLeft RSAError <$> RSAOAEP.decryptSafer params priv bs@@ -1838,8 +2069,8 @@ -- | Decrypt the specified content with an ECDH key pair and key-agreement -- algorithm. ecdhDecrypt :: ByteArray ba- => KeyAgreementParams -> Maybe ByteString -> X509.PrivKey -> ByteString -> ba -> Either StoreError ba-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =+ => KeyAgreementParams -> Maybe ByteString -> KeyPair -> ByteString -> ba -> Either StoreError ba+ecdhDecrypt (StdDH dig kep) ukm (KeyPairEC priv _) pt bs = case ecPrivKeyCurve priv of Nothing -> Left UnsupportedEllipticCurve Just curve ->@@ -1850,16 +2081,16 @@ s = ECDH.getShared curve d pub k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes keyDecrypt k kep bs-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX25519 priv) pt bs = do+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX25519 priv _) pt bs = do s <- fromCryptoFailable (X25519.publicKey pt >>= ecdh x25519 priv) let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes keyDecrypt k kep bs-ecdhDecrypt (StdDH dig kep) ukm (X509.PrivKeyX448 priv) pt bs = do+ecdhDecrypt (StdDH dig kep) ukm (KeyPairX448 priv _) pt bs = do s <- fromCryptoFailable (X448.publicKey pt >>= ecdh x448 priv) let k = ecdhKeyMaterial dig kep ukm s :: B.ScrubbedBytes keyDecrypt k kep bs ecdhDecrypt (StdDH _ _) _ _ _ _ = Left UnexpectedPrivateKeyType-ecdhDecrypt (CofactorDH dig kep) ukm (X509.PrivKeyEC priv) pt bs =+ecdhDecrypt (CofactorDH dig kep) ukm (KeyPairEC priv _) pt bs = case ecPrivKeyCurve priv of Nothing -> Left UnsupportedEllipticCurve Just curve ->@@ -1892,8 +2123,8 @@ ivGenerate :: (BlockCipher cipher, MonadRandom m) => cipher -> m (IV cipher) ivGenerate cipher = do bs <- getRandomBytes (blockSize cipher)- let Just iv = makeIV (bs :: ByteString)- return iv+ let iv = makeIV (bs :: ByteString)+ return $! fromJust iv nonceGenerate :: MonadRandom m => Int -> m B.Bytes nonceGenerate = getRandomBytes@@ -1927,7 +2158,7 @@ parseM :: Monoid e => ParseASN1 e CCM_M parseM = do- IntVal l <- getNext+ l <- fromMaybe 12 <$> getNextMaybe intOrNothing case l of 4 -> return CCM_M4 6 -> return CCM_M6@@ -1939,6 +2170,85 @@ i -> throwParseError ("Parsed invalid CCM parameter M: " ++ show i) +-- Key encapsulation++data KeyEncapsulationType+ = TypeKeyEncapsulationRSA+ deriving (Show,Eq)++instance Enumerable KeyEncapsulationType where+ values = [TypeKeyEncapsulationRSA]++instance OIDable KeyEncapsulationType where+ getObjectID TypeKeyEncapsulationRSA = [1,0,18033,2,2,4]++instance OIDNameable KeyEncapsulationType where+ fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Key encapsulation mechanism (KEM) with associated parameters.+data KeyEncapsulationMechanism+ = KeyEncapsulationRSA KeyDerivationFn Int+ -- ^ Key encapsulation with RSA-KEM+ deriving (Show,Eq)++instance AlgorithmId KeyEncapsulationMechanism where+ type AlgorithmType KeyEncapsulationMechanism = KeyEncapsulationType+ algorithmName _ = "key encapsulation mechanism"++ algorithmType (KeyEncapsulationRSA _ _) = TypeKeyEncapsulationRSA++ parameterASN1S (KeyEncapsulationRSA kdf len)+ | (kdf, len) == defaultRsaKemParams = id+ | otherwise = asn1Container Sequence $+ algorithmASN1S Sequence kdf . gIntVal (toInteger len)++ parseParameter TypeKeyEncapsulationRSA =+ fmap manageDef $ onNextContainerMaybe Sequence $ do+ kdf <- parseAlgorithm Sequence+ IntVal len <- getNext+ when (len < 1) $+ throwParseError "Illegal keyLength in RsaKemParameters"+ return $ KeyEncapsulationRSA kdf (fromInteger len)+ where+ manageDef = fromMaybe (KeyEncapsulationRSA kdfDef lenDef)+ (kdfDef, lenDef) = defaultRsaKemParams++defaultRsaKemParams :: (KeyDerivationFn, Int)+defaultRsaKemParams = (KDF3 $ DigestAlgorithm SHA256, 16)++newtype RsaKemLen = RsaKemLen Int++instance HasStrength RsaKemLen where+ getSecurityBits (RsaKemLen len) = 8 * len++kdfRsaKem :: (ByteArrayAccess bIn, ByteArray bOut)+ => KeyDerivationFn -> Int -> RsaKem.KDF bIn (Either StoreError bOut)+kdfRsaKem kdf len+ | securityAcceptable (RsaKemLen len) = RsaKem.KDF $ kdfApply kdf noSalt len+ | otherwise = RsaKem.KDF $ \_ _ ->+ Left $ InvalidParameter "RSA-KEM shared-secret length too short"++kemEncap :: (MonadRandom m, ByteArray ct, ByteArray ss)+ => KeyEncapsulationMechanism -> X509.PubKey -> m (Either StoreError (ct, ss))+kemEncap (KeyEncapsulationRSA kdf len) (X509.PubKeyRSA pub) = do+ (ss, ct) <- RsaKem.encapsulate kdFn pub+ return $ (ct, ) <$> ss+ where kdFn = kdfRsaKem kdf len+kemEncap _ _ = return (Left UnexpectedPublicKeyType)++kemDecap :: (ByteArrayAccess ct, ByteArray ss)+ => KeyEncapsulationMechanism -> KeyPair -> ct -> Either StoreError ss+kemDecap (KeyEncapsulationRSA kdf len) (KeyPairRSA priv _) ct =+ case RsaKem.decapsulate kdFn priv (B.convert ct :: B.Bytes) of+ Just ss -> ss+ Nothing -> Left $ InvalidParameter "Invalid RSA-KEM ciphertext"+ where kdFn = kdfRsaKem kdf len+kemDecap _ _ _ = Left UnexpectedPrivateKeyType++noSalt :: ByteString+noSalt = mempty++ -- Mask generation functions data MaskGenerationType = TypeMGF1@@ -2070,6 +2380,10 @@ , TypeECDSA (DigestAlgorithm SHA256) , TypeECDSA (DigestAlgorithm SHA384) , TypeECDSA (DigestAlgorithm SHA512)+ , TypeECDSA (DigestAlgorithm SHA3_224)+ , TypeECDSA (DigestAlgorithm SHA3_256)+ , TypeECDSA (DigestAlgorithm SHA3_384)+ , TypeECDSA (DigestAlgorithm SHA3_512) , TypeEd25519 , TypeEd448@@ -2097,6 +2411,10 @@ getObjectID (TypeECDSA (DigestAlgorithm SHA256)) = [1,2,840,10045,4,3,2] getObjectID (TypeECDSA (DigestAlgorithm SHA384)) = [1,2,840,10045,4,3,3] getObjectID (TypeECDSA (DigestAlgorithm SHA512)) = [1,2,840,10045,4,3,4]+ getObjectID (TypeECDSA (DigestAlgorithm SHA3_224)) = [2,16,840,1,101,3,4,3,9]+ getObjectID (TypeECDSA (DigestAlgorithm SHA3_256)) = [2,16,840,1,101,3,4,3,10]+ getObjectID (TypeECDSA (DigestAlgorithm SHA3_384)) = [2,16,840,1,101,3,4,3,11]+ getObjectID (TypeECDSA (DigestAlgorithm SHA3_512)) = [2,16,840,1,101,3,4,3,12] getObjectID TypeEd25519 = [1,3,101,112] getObjectID TypeEd448 = [1,3,101,113]@@ -2144,23 +2462,22 @@ parseParameter TypeEd25519 = return Ed25519 parseParameter TypeEd448 = return Ed448 --- | Sign a message using the specified algorithm and private key. The--- corresponding public key is also required for some algorithms.-signatureGenerate :: MonadRandom m => SignatureAlg -> X509.PrivKey -> X509.PubKey -> ByteString -> m (Either StoreError SignatureValue)-signatureGenerate RSAAnyHash _ _ _ =+-- | Sign a message using the specified algorithm and private key.+signatureGenerate :: MonadRandom m => SignatureAlg -> KeyPair -> ByteString -> m (Either StoreError SignatureValue)+signatureGenerate RSAAnyHash _ _ = error "signatureGenerate: should call signatureResolveHash first"-signatureGenerate (RSA alg) (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =+signatureGenerate (RSA alg) (KeyPairRSA priv _) msg = let err = return . Left $ InvalidParameter ("Invalid hash algorithm for RSA: " ++ show alg) in withHashAlgorithmASN1 alg err $ \hashAlg -> mapLeft RSAError <$> RSA.signSafer (Just hashAlg) priv msg-signatureGenerate (RSAPSS p) (X509.PrivKeyRSA priv) (X509.PubKeyRSA _) msg =+signatureGenerate (RSAPSS p) (KeyPairRSA priv _) msg = withPSSParams p $ \params -> mapLeft RSAError <$> RSAPSS.signSafer params priv msg-signatureGenerate (DSA alg) (X509.PrivKeyDSA priv) (X509.PubKeyDSA _) msg =+signatureGenerate (DSA alg) (KeyPairDSA pair) msg = case alg of DigestAlgorithm t ->- Right . dsaFromSignature <$> DSA.sign priv (hashFromProxy t) msg-signatureGenerate (ECDSA alg) (X509.PrivKeyEC priv) (X509.PubKeyEC _) msg =+ Right . dsaFromSignature <$> DSA.sign (DSA.toPrivateKey pair) (hashFromProxy t) msg+signatureGenerate (ECDSA alg) (KeyPairEC priv _) msg = case alg of DigestAlgorithm t -> case ecdsaToPrivateKey priv of@@ -2168,11 +2485,11 @@ Just p -> let h = hashFromProxy t in Right . ecdsaFromSignature <$> ECDSA.sign p h msg-signatureGenerate Ed25519 (X509.PrivKeyEd25519 priv) (X509.PubKeyEd25519 pub) msg =+signatureGenerate Ed25519 (KeyPairEd25519 priv pub) msg = return . Right . B.convert $ Ed25519.sign priv pub msg-signatureGenerate Ed448 (X509.PrivKeyEd448 priv) (X509.PubKeyEd448 pub) msg =+signatureGenerate Ed448 (KeyPairEd448 priv pub) msg = return . Right . B.convert $ Ed448.sign priv pub msg-signatureGenerate _ _ _ _ = return (Left UnexpectedPrivateKeyType)+signatureGenerate _ _ _ = return (Left UnexpectedPrivateKeyType) -- | Verify a message signature using the specified algorithm and public key. signatureVerify :: SignatureAlg -> X509.PubKey -> ByteString -> SignatureValue -> Bool
src/Crypto/Store/CMS/Attribute.hs view
@@ -37,7 +37,7 @@ import Data.Hourglass import Data.Maybe (fromMaybe) -import System.Hourglass (dateCurrent)+import Time.System (dateCurrent) import Crypto.Store.ASN1.Generate import Crypto.Store.ASN1.Parse
src/Crypto/Store/CMS/Authenticated.hs view
@@ -10,7 +10,8 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} module Crypto.Store.CMS.Authenticated- ( AuthenticatedData(..)+ ( EncapsulatedContent+ , AuthenticatedData(..) ) where import Control.Applicative@@ -77,7 +78,7 @@ parse = onNextContainer Sequence $ do IntVal v <- getNext- when (v `notElem` [0, 1, 3]) $+ unless (v `elem` [0, 1, 3]) $ throwParseError ("AuthenticatedData: parsed invalid version: " ++ show v) oi <- parseOriginatorInfo (Container Context 0) <|> return mempty ris <- onNextContainer Set parse
src/Crypto/Store/CMS/Enveloped.hs view
@@ -42,6 +42,10 @@ , PasswordRecipientInfo(..) , forPasswordRecipient , withRecipientPassword+ -- * Key Encapsulation recipients+ , KEMRecipientInfo(..)+ , forKeyEncapRecipient+ , withRecipientKeyEncap ) where import Control.Applicative@@ -67,6 +71,7 @@ import Crypto.Store.CMS.Type import Crypto.Store.CMS.Util import Crypto.Store.Error+import Crypto.Store.Keys -- | Encrypted key. type EncryptedKey = ByteString@@ -95,14 +100,13 @@ instance ASN1Elem e => ProduceASN1Object e RecipientIdentifier where asn1s (RecipientIASN iasn) = asn1s iasn- asn1s (RecipientSKI ski) = asn1Container (Container Context 0)- (gOctetString ski)+ asn1s (RecipientSKI ski) = gMany [Other Context 0 ski] instance Monoid e => ParseASN1Object e RecipientIdentifier where parse = parseIASN <|> parseSKI where parseIASN = RecipientIASN <$> parse parseSKI = RecipientSKI <$>- onNextContainer (Container Context 0) parseOctetStringPrim+ do { Other Context 0 bs <- getNext; return bs } getKTVersion :: RecipientIdentifier -> Integer getKTVersion (RecipientIASN _) = 0@@ -167,8 +171,7 @@ instance ASN1Elem e => ProduceASN1Object e OriginatorIdentifierOrKey where asn1s (OriginatorIASN iasn) = asn1s iasn- asn1s (OriginatorSKI ski) = asn1Container (Container Context 0)- (gOctetString ski)+ asn1s (OriginatorSKI ski) = gMany [Other Context 0 ski] asn1s (OriginatorPublic pub) = originatorPublicKeyASN1S (Container Context 1) pub @@ -176,7 +179,7 @@ parse = parseIASN <|> parseSKI <|> parsePublic where parseIASN = OriginatorIASN <$> parse parseSKI = OriginatorSKI <$>- onNextContainer (Container Context 0) parseOctetStringPrim+ do { Other Context 0 bs <- getNext; return bs } parsePublic = OriginatorPublic <$> parseOriginatorPublicKey (Container Context 1) @@ -188,14 +191,13 @@ instance ASN1Elem e => ProduceASN1Object e KeyAgreeRecipientIdentifier where asn1s (KeyAgreeRecipientIASN iasn) = asn1s iasn- asn1s (KeyAgreeRecipientKI ki) = asn1Container (Container Context 0)- (asn1s ki)+ asn1s (KeyAgreeRecipientKI ki) = keyIdentifierASN1S (Container Context 0) ki instance Monoid e => ParseASN1Object e KeyAgreeRecipientIdentifier where parse = parseIASN <|> parseKI where parseIASN = KeyAgreeRecipientIASN <$> parse parseKI = KeyAgreeRecipientKI <$>- onNextContainer (Container Context 0) parse+ parseKeyIdentifier (Container Context 0) -- | Encrypted key for a recipient in a key-agreement RI. data RecipientEncryptedKey = RecipientEncryptedKey@@ -220,6 +222,16 @@ -> Maybe EncryptedKey findRecipientEncryptedKey cert list = rekEncryptedKey <$> find fn list where+ fn rek = matchRecipient cert $ case rekRid rek of+ KeyAgreeRecipientIASN iasn -> RecipientIASN iasn+ KeyAgreeRecipientKI ki -> RecipientSKI (keyIdentifier ki)++matchRecipient :: SignedCertificate -> RecipientIdentifier -> Bool+matchRecipient cert rid =+ case rid of+ RecipientIASN iasn -> matchIASN iasn+ RecipientSKI ski -> matchSKI ski+ where c = signedObject (getSigned cert) matchIASN iasn = (iasnIssuer iasn, iasnSerial iasn) == (certIssuerDN c, certSerial c)@@ -227,11 +239,8 @@ case extensionGet (certExtensions c) of Just (ExtSubjectKeyId idBs) -> idBs == ski Nothing -> False- fn rek = case rekRid rek of- KeyAgreeRecipientIASN iasn -> matchIASN iasn- KeyAgreeRecipientKI ki -> matchSKI (keyIdentifier ki) --- | Additional information in a 'KeyIdentifier'.+-- | Additional information in a t'KeyIdentifier'. data OtherKeyAttribute = OtherKeyAttribute { keyAttrId :: OID -- ^ attribute identifier , keyAttr :: [ASN1] -- ^ attribute value@@ -257,23 +266,26 @@ } deriving (Show,Eq) -instance ASN1Elem e => ProduceASN1Object e KeyIdentifier where- asn1s KeyIdentifier{..} = asn1Container Sequence (keyId . date . other)- where- keyId = gOctetString keyIdentifier- date = optASN1S keyDate $ \v -> gASN1Time TimeGeneralized v Nothing- other = optASN1S keyOther asn1s+keyIdentifierASN1S :: ASN1Elem e+ => ASN1ConstructionType -> KeyIdentifier -> ASN1Stream e+keyIdentifierASN1S ty KeyIdentifier{..} =+ asn1Container ty (keyId . date . other)+ where+ keyId = gOctetString keyIdentifier+ date = optASN1S keyDate $ \v -> gASN1Time TimeGeneralized v Nothing+ other = optASN1S keyOther asn1s -instance Monoid e => ParseASN1Object e KeyIdentifier where- parse = onNextContainer Sequence $ do- OctetString keyId <- getNext- date <- getNextMaybe dateTimeOrNothing- b <- hasNext- other <- if b then Just <$> parse else return Nothing- return KeyIdentifier { keyIdentifier = keyId- , keyDate = date- , keyOther = other- }+parseKeyIdentifier :: Monoid e+ => ASN1ConstructionType -> ParseASN1 e KeyIdentifier+parseKeyIdentifier ty = onNextContainer ty $ do+ OctetString keyId <- getNext+ date <- getNextMaybe dateTimeOrNothing+ b <- hasNext+ other <- if b then Just <$> parse else return Nothing+ return KeyIdentifier { keyIdentifier = keyId+ , keyDate = date+ , keyOther = other+ } -- | Recipient using key transport. data KTRecipientInfo = KTRecipientInfo@@ -308,7 +320,73 @@ } deriving (Show,Eq) --- | Information for a recipient of an 'EnvelopedData'. An element contains+-- | Recipient using key encapsulation.+data KEMRecipientInfo = KEMRecipientInfo+ { kemRid :: RecipientIdentifier -- ^ identifier of recipient+ , kemEncapsulationParams :: KeyEncapsulationMechanism -- ^ key encapsulation mechanism+ , kemCipherText :: ByteString -- ^ ciphertext for this recipient+ , kemDerivationFn :: KeyDerivationFn -- ^ key derivation used+ , kemKekLength :: Int -- ^ size of key encryption key+ , kemUkm :: Maybe UserKeyingMaterial -- ^ user keying material+ , kemEncryptionParams :: KeyEncryptionParams -- ^ key encryption algorithm+ , kemEncryptedKey :: EncryptedKey -- ^ encrypted content-encryption key+ }+ deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e KEMRecipientInfo where+ asn1s KEMRecipientInfo{..} =+ asn1Container Sequence (ver . rid . kem . ct . kdf . len . ukm . kep . ek)+ where+ ver = gIntVal 0+ rid = asn1s kemRid+ kem = algorithmASN1S Sequence kemEncapsulationParams+ ct = gOctetString kemCipherText+ kdf = algorithmASN1S Sequence kemDerivationFn+ len = gIntVal (toInteger kemKekLength)+ ukm = optASN1S kemUkm $ asn1Container (Container Context 0) . gOctetString+ kep = algorithmASN1S Sequence kemEncryptionParams+ ek = gOctetString kemEncryptedKey++instance Monoid e => ParseASN1Object e KEMRecipientInfo where+ parse = onNextContainer Sequence $ do+ IntVal 0 <- getNext+ rid <- parse+ kem <- parseAlgorithm Sequence+ OctetString ct <- getNext+ kdf <- parseAlgorithm Sequence+ IntVal len <- getNext+ when (len < 1 || len > 65535) $+ throwParseError ("KEMRecipientInfo: parsed invalid kekLength: " ++ show len)+ ukm <- onNextContainerMaybe (Container Context 0) $+ do { OctetString bs <- getNext; return bs }+ kep <- parseAlgorithm Sequence+ OctetString ek <- getNext+ return KEMRecipientInfo { kemRid = rid+ , kemEncapsulationParams = kem+ , kemCipherText = ct+ , kemDerivationFn = kdf+ , kemKekLength = fromInteger len+ , kemUkm = ukm+ , kemEncryptionParams = kep+ , kemEncryptedKey = ek+ }++data CMSORIforKEMOtherInfo = CMSORIforKEMOtherInfo+ { kemoiWrap :: KeyEncryptionParams+ , kemoiKekLength :: Int+ , kemoiUkm :: Maybe UserKeyingMaterial+ }+ deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e CMSORIforKEMOtherInfo where+ asn1s CMSORIforKEMOtherInfo{..} =+ asn1Container Sequence (wrap . len . ukm)+ where+ wrap = algorithmASN1S Sequence kemoiWrap+ len = gIntVal (toInteger kemoiKekLength)+ ukm = optASN1S kemoiUkm $ asn1Container (Container Context 0) . gOctetString++-- | Information for a recipient of an t'EnvelopedData'. An element contains -- the content-encryption key in encrypted form. data RecipientInfo = KTRI KTRecipientInfo -- ^ Recipient using key transport@@ -318,6 +396,8 @@ -- ^ Recipient using key encryption | PasswordRI PasswordRecipientInfo -- ^ Recipient using password-based protection+ | KEMRI KEMRecipientInfo+ -- ^ Recipient using key encapsulation deriving (Show,Eq) instance ASN1Elem e => ProduceASN1Object e RecipientInfo where@@ -345,7 +425,7 @@ asn1Container (Container Context 2) (ver . kid . kep . ek) where ver = gIntVal 4- kid = asn1s kekId+ kid = keyIdentifierASN1S Sequence kekId kep = algorithmASN1S Sequence kekKeyEncryptionParams ek = gOctetString kekEncryptedKey @@ -357,19 +437,26 @@ kep = algorithmASN1S Sequence priKeyEncryptionParams ek = gOctetString priEncryptedKey + asn1s (KEMRI ri) =+ asn1Container (Container Context 4) (typ . val)+ where+ typ = gOID [1,2,840,113549,1,9,16,13,3]+ val = asn1s ri+ instance Monoid e => ParseASN1Object e RecipientInfo where parse = do c <- onNextContainerMaybe Sequence parseKT `orElse` onNextContainerMaybe (Container Context 1) parseKA `orElse` onNextContainerMaybe (Container Context 2) parseKEK `orElse` onNextContainerMaybe (Container Context 3) parsePassword+ `orElse` onNextContainerMaybe (Container Context 4) parseOther case c of Just val -> return val Nothing -> throwParseError "RecipientInfo: unable to parse" where parseKT = KTRI <$> do IntVal v <- getNext- when (v `notElem` [0, 2]) $+ unless (v `elem` [0, 2]) $ throwParseError ("RecipientInfo: parsed invalid KT version: " ++ show v) rid <- parse ktp <- parseAlgorithm Sequence@@ -394,7 +481,7 @@ parseKEK = KEKRI <$> do IntVal 4 <- getNext- kid <- parse+ kid <- parseKeyIdentifier Sequence kep <- parseAlgorithm Sequence OctetString ek <- getNext return KEKRecipientInfo { kekId = kid@@ -412,17 +499,23 @@ , priEncryptedKey = ek } + parseOther = KEMRI <$> do+ OID [1,2,840,113549,1,9,16,13,3] <- getNext+ parse+ isVersion0 :: RecipientInfo -> Bool isVersion0 (KTRI x) = getKTVersion (ktRid x) == 0 isVersion0 (KARI _) = False -- because version is always 3 isVersion0 (KEKRI _) = False -- because version is always 4 isVersion0 (PasswordRI _) = True -- because version is always 0+isVersion0 (KEMRI _) = True -- because version is always 0 isPwriOri :: RecipientInfo -> Bool isPwriOri (KTRI _) = False isPwriOri (KARI _) = False isPwriOri (KEKRI _) = False isPwriOri (PasswordRI _) = True+isPwriOri (KEMRI _) = True -- | Enveloped content information. data EnvelopedData content = EnvelopedData@@ -508,9 +601,9 @@ -- -- This function can be used as parameter to -- 'Crypto.Store.CMS.openEnvelopedData'.-withRecipientKeyTrans :: MonadRandom m => PrivKey -> ConsumerOfRI m-withRecipientKeyTrans privKey (KTRI KTRecipientInfo{..}) =- transportDecrypt ktKeyTransportParams privKey ktEncryptedKey+withRecipientKeyTrans :: MonadRandom m => KeyPair -> ConsumerOfRI m+withRecipientKeyTrans keyPair (KTRI KTRecipientInfo{..}) =+ transportDecrypt ktKeyTransportParams keyPair ktEncryptedKey withRecipientKeyTrans _ _ = pure (Left RecipientTypeMismatch) -- | Generate a Key Agreement recipient from a certificate and@@ -553,16 +646,18 @@ -- -- This function can be used as parameter to -- 'Crypto.Store.CMS.openEnvelopedData'.-withRecipientKeyAgree :: MonadRandom m => PrivKey -> SignedCertificate -> ConsumerOfRI m-withRecipientKeyAgree priv cert (KARI KARecipientInfo{..}) =- case kaOriginator of- OriginatorPublic (OriginatorPublicKeyEC _ ba) ->- case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of- Nothing -> pure (Left RecipientKeyNotFound)- Just ek ->- let pub = bitArrayGetData ba- in pure (ecdhDecrypt kaKeyAgreementParams kaUkm priv pub ek)- _ -> pure (Left UnsupportedOriginatorFormat)+withRecipientKeyAgree :: MonadRandom m => KeyPair -> SignedCertificate -> ConsumerOfRI m+withRecipientKeyAgree pair cert (KARI KARecipientInfo{..})+ | keyPairMatchesCert pair cert =+ case kaOriginator of+ OriginatorPublic (OriginatorPublicKeyEC _ ba) ->+ case findRecipientEncryptedKey cert kaRecipientEncryptedKeys of+ Nothing -> pure (Left RecipientKeyNotFound)+ Just ek ->+ let pub = bitArrayGetData ba+ in pure (ecdhDecrypt kaKeyAgreementParams kaUkm pair pub ek)+ _ -> pure (Left UnsupportedOriginatorFormat)+ | otherwise = pure $ Left PublicPrivateKeyMismatch withRecipientKeyAgree _ _ _ = pure (Left RecipientTypeMismatch) -- | Generate a Key Encryption Key recipient from a key encryption key and@@ -629,3 +724,84 @@ len = fromMaybe (getMaximumKeySize priKeyEncryptionParams) (kdfKeyLength priKeyDerivationFunc) withRecipientPassword _ _ = pure (Left RecipientTypeMismatch)++-- | Generate a Key Encapsulation recipient from a certificate and+-- desired algorithms. The recipient info will contain the KEM ciphertext.+--+-- This function can be used as parameter to 'Crypto.Store.CMS.envelopData'.+--+-- To avoid decreasing the security strength, selected algorithms should all be+-- equal or stronger than the content encryption key.+forKeyEncapRecipient :: MonadRandom m+ => SignedCertificate+ -> KeyDerivationFn+ -> KeyEncryptionParams+ -> KeyEncapsulationMechanism+ -> ProducerOfRI m+forKeyEncapRecipient cert kdf kep params inkey = do+ ephemeral <- kemEncap params (certPubKey obj)+ case ephemeral of+ Right (ct, ss) ->+ case kdfApply kdf noSalt len prm (ss :: EncryptedKey) of+ Left err -> pure $ Left err+ Right kek -> do+ ek <- keyEncrypt (kek :: EncryptedKey) kep inkey+ pure (KEMRI . build ct <$> ek)+ Left err -> pure $ Left err+ where+ obj = signedObject (getSigned cert)+ isn = IssuerAndSerialNumber (certIssuerDN obj) (certSerial obj)+ prm = encodeASN1S (asn1s info)++ len = getMaximumKeySize kep+ info = CMSORIforKEMOtherInfo+ { kemoiWrap = kep+ , kemoiKekLength = len+ , kemoiUkm = Nothing+ }++ build ct ek =+ KEMRecipientInfo+ { kemRid = RecipientIASN isn+ , kemEncapsulationParams = params+ , kemCipherText = ct+ , kemDerivationFn = kdf+ , kemKekLength = len+ , kemUkm = Nothing+ , kemEncryptionParams = kep+ , kemEncryptedKey = ek+ }++-- | Use a Key Encapsulation recipient, knowing the recipient private key.+-- The recipient certificate is also used to determine if a recipient info+-- is applicable.+--+-- This function can be used as parameter to+-- 'Crypto.Store.CMS.openEnvelopedData'.+withRecipientKeyEncap :: MonadRandom m => KeyPair -> SignedCertificate -> ConsumerOfRI m+withRecipientKeyEncap pair cert (KEMRI KEMRecipientInfo{..})+ | not (keyPairMatchesCert pair cert) =+ pure $ Left PublicPrivateKeyMismatch+ | not (matchRecipient cert kemRid) =+ pure $ Left NoRecipientInfoMatched+ | otherwise =+ case kemDecap kemEncapsulationParams pair kemCipherText of+ Left err -> pure $ Left err+ Right ss | len == kemKekLength -> pure $+ case kdfApply kemDerivationFn noSalt kemKekLength prm (ss :: EncryptedKey) of+ Left err -> Left err+ Right kek -> keyDecrypt (kek :: EncryptedKey)+ kemEncryptionParams kemEncryptedKey+ Right _ -> pure $ Left (InvalidInput "Wrong kekLength in KEMRecipientInfo")+ where+ len = getMaximumKeySize kemEncryptionParams+ prm = encodeASN1S (asn1s info)+ info = CMSORIforKEMOtherInfo+ { kemoiWrap = kemEncryptionParams+ , kemoiKekLength = kemKekLength+ , kemoiUkm = kemUkm+ }+withRecipientKeyEncap _ _ _ = pure (Left RecipientTypeMismatch)++noSalt :: ByteString+noSalt = mempty
src/Crypto/Store/CMS/Info.hs view
@@ -6,6 +6,7 @@ -- Portability : unknown -- -- CMS content information.+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -21,7 +22,9 @@ , toDetachedCI ) where +#if !(MIN_VERSION_base(4,13,0)) import Control.Monad.Fail (MonadFail)+#endif import Data.ASN1.Types import Data.ByteString (ByteString)
src/Crypto/Store/CMS/OriginatorInfo.hs view
@@ -6,6 +6,7 @@ -- Portability : unknown -- --+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-}@@ -24,7 +25,9 @@ import Data.ASN1.Types import Data.Maybe (fromMaybe)+#if !(MIN_VERSION_base(4,11,0)) import Data.Semigroup+#endif import Data.X509 import Crypto.Store.ASN1.Generate
src/Crypto/Store/CMS/PEM.hs view
@@ -11,6 +11,7 @@ , readCMSFileFromMemory , berToContentInfo , pemToContentInfo+ , pemToContentInfoAccum , writeCMSFile , writeCMSFileToMemory , contentInfoToDER@@ -18,7 +19,7 @@ ) where import qualified Data.ByteString as B-import Data.Maybe (catMaybes)+import Data.Either (rights) import Crypto.Store.CMS.Info import Crypto.Store.CMS.Util@@ -37,24 +38,24 @@ readCMSFileFromMemory = either (const []) accumulate . pemParseBS accumulate :: [PEM] -> [ContentInfo]-accumulate = catMaybes . foldr (flip pemToContentInfo) []+accumulate = rights . map pemToContentInfo -- | Read a content info from a bytearray in BER format. berToContentInfo :: B.ByteString -> Either StoreError ContentInfo berToContentInfo = decodeASN1Object --- | Read a content info from a 'PEM' element and add it to the accumulator+-- | Read a content info from a t'PEM' element and add it to the accumulator -- list.-pemToContentInfo :: [Maybe ContentInfo] -> PEM -> [Maybe ContentInfo]-pemToContentInfo acc pem- | pemName pem `elem` names = decode (pemContent pem)- | otherwise = Nothing : acc- where- names = [ "CMS", "PKCS7" ]- decode bs =- case berToContentInfo bs of- Left _ -> Nothing : acc- Right info -> Just info : acc+pemToContentInfoAccum :: [Maybe ContentInfo] -> PEM -> [Maybe ContentInfo]+pemToContentInfoAccum acc pem =+ either (const Nothing) Just (pemToContentInfo pem) : acc++-- | Read a content info from a t'PEM' element.+pemToContentInfo :: PEM -> Either StoreError ContentInfo+pemToContentInfo pem+ | pemName pem `elem` names = berToContentInfo (pemContent pem)+ | otherwise = Left UnexpectedNameForPEM+ where names = [ "CMS", "PKCS7" ] -- Writing to PEM format
src/Crypto/Store/CMS/Signed.hs view
@@ -48,6 +48,7 @@ import Crypto.Store.CMS.Type import Crypto.Store.CMS.Util import Crypto.Store.Error+import Crypto.Store.Keys -- | Encapsulated content. type EncapsulatedContent = ByteString@@ -117,14 +118,13 @@ instance ASN1Elem e => ProduceASN1Object e SignerIdentifier where asn1s (SignerIASN iasn) = asn1s iasn- asn1s (SignerSKI ski) = asn1Container (Container Context 0)- (gOctetString ski)+ asn1s (SignerSKI ski) = gMany [Other Context 0 ski] instance Monoid e => ParseASN1Object e SignerIdentifier where parse = parseIASN <|> parseSKI where parseIASN = SignerIASN <$> parse parseSKI = SignerSKI <$>- onNextContainer (Container Context 0) parseOctetStringPrim+ do { Other Context 0 bs <- getNext; return bs } -- | Try to find a certificate with the specified identifier. findSigner :: SignerIdentifier@@ -149,10 +149,10 @@ (x : _, r) -> Just (x, r) ([] , _) -> Nothing --- | Function able to produce a 'SignerInfo'.+-- | Function able to produce a t'SignerInfo'. type ProducerOfSI m = ContentType -> ByteString -> m (Either StoreError (SignerInfo, [CertificateChoice], [RevocationInfoChoice])) --- | Function able to consume a 'SignerInfo'.+-- | Function able to consume a t'SignerInfo'. type ConsumerOfSI m = ContentType -> ByteString -> SignerInfo -> [CertificateChoice] -> [RevocationInfoChoice] -> m Bool -- | Create a signer info with the specified signature algorithm and@@ -167,15 +167,16 @@ -- the full message. certSigner :: MonadRandom m => SignatureAlg- -> PrivKey+ -> KeyPair -> CertificateChain -> Maybe [Attribute] -> [Attribute] -> ProducerOfSI m certSigner _ _ (CertificateChain []) _ _ _ _ = pure $ Left (InvalidInput "Empty certificate chain")-certSigner alg priv (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg =- fmap build <$> generate+certSigner alg pair (CertificateChain chain@(cert:_)) sAttrsM uAttrs ct msg+ | keyPairMatchesKey pair pub = fmap build <$> generate+ | otherwise = pure $ Left PublicPrivateKeyMismatch where md = digest dig msg def = DigestAlgorithm Crypto.Store.CMS.Algorithms.SHA256@@ -193,7 +194,7 @@ let l = setContentTypeAttr ct $ setMessageDigestAttr md attrs in (l, encodeAuthAttrs l) - generate = signatureGenerate alg' priv pub input+ generate = signatureGenerate alg' pair input build sig = let si = SignerInfo { siSignerId = SignerIASN isn , siDigestAlgorithm = dig
src/Crypto/Store/CMS/Util.hs view
@@ -31,6 +31,7 @@ -- * Parsing and encoding ASN.1 objects , ASN1Event , ASN1ObjectExact(..)+ , derObjectExact , ProduceASN1Object(..) , encodeASN1Object , ParseASN1Object(..)@@ -177,6 +178,10 @@ { exactObject :: a -- ^ The wrapped ASN.1 object , exactObjectRaw :: ByteString -- ^ The raw representation of this object } deriving Show++-- | Create an ASN.1 object with a raw data in DER format.+derObjectExact :: ProduceASN1Object ASN1P a => a -> ASN1ObjectExact a+derObjectExact obj = ASN1ObjectExact obj (encodeASN1Object obj) instance Eq a => Eq (ASN1ObjectExact a) where a == b = exactObject a == exactObject b
src/Crypto/Store/Error.hs view
@@ -26,6 +26,8 @@ -- ^ Error while decoding ASN.1 content | ParseFailure String -- ^ Error while parsing an ASN.1 object+ | UnexpectedNameForPEM+ -- ^ The name of the given PEM object is not supported | DecryptionFailed -- ^ Unable to decrypt, incorrect key or password? | BadContentMAC@@ -42,6 +44,8 @@ -- ^ Some condition is not met about input password | InvalidParameter String -- ^ Some condition is not met about algorithm parameters+ | PublicPrivateKeyMismatch+ -- ^ The public key or certificate does not match the private key | UnexpectedPublicKeyType -- ^ The algorithm expects another public key type | UnexpectedPrivateKeyType
src/Crypto/Store/KeyWrap/AES.hs view
@@ -10,6 +10,7 @@ -- -- Should be used with a cipher from module "Crypto.Cipher.AES". {-# LANGUAGE BangPatterns #-}+{-# LANGUAGE CPP #-} module Crypto.Store.KeyWrap.AES ( wrap , unwrap@@ -20,7 +21,9 @@ import Data.Bits import Data.ByteArray (ByteArray, ByteArrayAccess, Bytes) import qualified Data.ByteArray as B-import Data.List+#if !(MIN_VERSION_base(4,20,0))+import Data.List (foldl')+#endif import Data.Word import Crypto.Cipher.Types
src/Crypto/Store/KeyWrap/RC2.hs view
@@ -16,6 +16,7 @@ import Data.ByteArray (ByteArray) import qualified Data.ByteArray as B+import Data.Maybe (fromJust) import Crypto.Cipher.Types import Crypto.Hash@@ -62,7 +63,7 @@ temp1 = cbcEncrypt cipher iv lcekpadicv temp2 = B.append (B.convert iv) temp1 temp3 = reverseBytes temp2- Just iv' = makeIV iv4adda22c79e82105+ iv' = fromJust $ makeIV iv4adda22c79e82105 in cbcEncrypt cipher iv' temp3 -- | Unwrap an encrypted RC2 key with the specified RC2 cipher.@@ -75,14 +76,14 @@ | otherwise = invalid where inLen = B.length wrapped- Just iv' = makeIV iv4adda22c79e82105+ iv' = fromJust $ makeIV iv4adda22c79e82105 temp3 = cbcDecrypt cipher iv' wrapped temp2 = reverseBytes temp3 (ivBs, temp1) = B.splitAt 8 temp2- Just iv = makeIV ivBs+ iv = fromJust $ makeIV ivBs lcekpadicv = cbcDecrypt cipher iv temp1 (lcekpad, icv) = B.splitAt (inLen - 16) lcekpadicv- Just (l, cekpad) = B.uncons lcekpad+ (l, cekpad) = fromJust $ B.uncons lcekpad len = fromIntegral l padlen = inLen - 16 - len - 1 cek = B.take len cekpad
src/Crypto/Store/KeyWrap/TripleDES.hs view
@@ -15,6 +15,7 @@ import Data.ByteArray (ByteArray) import qualified Data.ByteArray as B+import Data.Maybe (fromJust) import Crypto.Cipher.Types import Crypto.Hash@@ -40,7 +41,7 @@ (InvalidInput "KeyWrap.TripleDES: invalid length for content encryption key") where inLen = B.length cek- Just iv' = makeIV iv4adda22c79e82105+ iv' = fromJust $ makeIV iv4adda22c79e82105 cekicv = B.append cek (checksum cek) temp1 = cbcEncrypt cipher iv cekicv temp2 = B.append (B.convert iv) temp1@@ -56,11 +57,11 @@ | otherwise = invalid where inLen = B.length wrapped- Just iv' = makeIV iv4adda22c79e82105+ iv' = fromJust $ makeIV iv4adda22c79e82105 temp3 = cbcDecrypt cipher iv' wrapped temp2 = reverseBytes temp3 (ivBs, temp1) = B.splitAt 8 temp2- Just iv = makeIV ivBs+ iv = fromJust $ makeIV ivBs cekicv = cbcDecrypt cipher iv temp1 (cek, icv) = B.splitAt 24 cekicv invalid = Left BadChecksum
+ src/Crypto/Store/Keys.hs view
@@ -0,0 +1,122 @@+-- |+-- Module : Crypto.Store.Keys+-- License : BSD-style+-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+--+{-# LANGUAGE RecordWildCards #-}+module Crypto.Store.Keys+ ( KeyPair(..), keyPairFromPrivKey, keyPairToPrivKey, keyPairToPubKey+ , keyPairMatchesKey, keyPairMatchesCert+ ) where++import Data.Function (on)+import Data.Maybe (fromMaybe)++import qualified Data.X509 as X509+import Data.X509.EC++import qualified Crypto.PubKey.RSA.Types as RSA+import qualified Crypto.PubKey.DSA as DSA+import qualified Crypto.PubKey.Curve25519 as X25519+import qualified Crypto.PubKey.Curve448 as X448+import qualified Crypto.PubKey.Ed25519 as Ed25519+import qualified Crypto.PubKey.Ed448 as Ed448++import Crypto.Store.PKCS8.EC++-- | Holds a private and public key together, with a guarantee that they both+-- match. Therefore no constructor is exposed. Content may be accessed+-- through functions 'keyPairToPrivKey' and 'keyPairToPubKey'.+--+-- Call function 'keyPairFromPrivKey' to build a @KeyPair@.+data KeyPair =+ KeyPairRSA RSA.PrivateKey RSA.PublicKey -- ^ RSA key pair+ | KeyPairDSA DSA.KeyPair -- ^ DSA key pair+ | KeyPairEC X509.PrivKeyEC X509.PubKeyEC -- ^ EC key pair+ | KeyPairX25519 X25519.SecretKey X25519.PublicKey -- ^ X25519 key pair+ | KeyPairX448 X448.SecretKey X448.PublicKey -- ^ X448 key pair+ | KeyPairEd25519 Ed25519.SecretKey Ed25519.PublicKey -- ^ Ed25519 key pair+ | KeyPairEd448 Ed448.SecretKey Ed448.PublicKey -- ^ Ed448 key pair++instance Show KeyPair where+ showsPrec d keyPair = showParen (d > 10) $+ showString "keyPairFromPrivKey " . showsPrec 11 (keyPairToPrivKey keyPair)++instance Eq KeyPair where+ (==) = (==) `on` keyPairToPrivKey++-- | Builds a key pair from an X.509 private key.+keyPairFromPrivKey :: X509.PrivKey -> KeyPair+keyPairFromPrivKey (X509.PrivKeyRSA priv) = KeyPairRSA priv (RSA.toPublicKey (RSA.KeyPair priv))+keyPairFromPrivKey (X509.PrivKeyDSA priv) = KeyPairDSA (dsaPairFromPriv priv)+keyPairFromPrivKey (X509.PrivKeyEC priv) = KeyPairEC priv (ecPubFromPriv priv)+keyPairFromPrivKey (X509.PrivKeyX25519 priv) = KeyPairX25519 priv (X25519.toPublic priv)+keyPairFromPrivKey (X509.PrivKeyX448 priv) = KeyPairX448 priv (X448.toPublic priv)+keyPairFromPrivKey (X509.PrivKeyEd25519 priv) = KeyPairEd25519 priv (Ed25519.toPublic priv)+keyPairFromPrivKey (X509.PrivKeyEd448 priv) = KeyPairEd448 priv (Ed448.toPublic priv)++dsaPairFromPriv :: DSA.PrivateKey -> DSA.KeyPair+dsaPairFromPriv k = DSA.KeyPair params y x+ where y = DSA.calculatePublic params x+ params = DSA.private_params k+ x = DSA.private_x k++ecPubFromPriv :: X509.PrivKeyEC -> X509.PubKeyEC+ecPubFromPriv priv = case priv of+ X509.PrivKeyEC_Prime{..} -> X509.PubKeyEC_Prime+ { pubkeyEC_pub = getSerializedPoint curve privkeyEC_priv+ , pubkeyEC_a = privkeyEC_a+ , pubkeyEC_b = privkeyEC_b+ , pubkeyEC_prime = privkeyEC_prime+ , pubkeyEC_generator = privkeyEC_generator+ , pubkeyEC_order = privkeyEC_order+ , pubkeyEC_cofactor = privkeyEC_cofactor+ , pubkeyEC_seed = privkeyEC_seed+ }+ X509.PrivKeyEC_Named{..} -> X509.PubKeyEC_Named+ { X509.pubkeyEC_name = privkeyEC_name+ , X509.pubkeyEC_pub = getSerializedPoint curve privkeyEC_priv+ }+ where curve = fromMaybe (error "ecPubFromPriv: invalid EC parameters") (ecPrivKeyCurve priv)++-- | Gets the X.509 private key in a key pair.+keyPairToPrivKey :: KeyPair -> X509.PrivKey+keyPairToPrivKey (KeyPairRSA priv _) = X509.PrivKeyRSA priv+keyPairToPrivKey (KeyPairDSA pair) = X509.PrivKeyDSA (DSA.toPrivateKey pair)+keyPairToPrivKey (KeyPairEC priv _) = X509.PrivKeyEC priv+keyPairToPrivKey (KeyPairX25519 priv _) = X509.PrivKeyX25519 priv+keyPairToPrivKey (KeyPairX448 priv _) = X509.PrivKeyX448 priv+keyPairToPrivKey (KeyPairEd25519 priv _) = X509.PrivKeyEd25519 priv+keyPairToPrivKey (KeyPairEd448 priv _) = X509.PrivKeyEd448 priv++-- | Gets the X.509 public key in a key pair.+keyPairToPubKey :: KeyPair -> X509.PubKey+keyPairToPubKey (KeyPairRSA _ pub) = X509.PubKeyRSA pub+keyPairToPubKey (KeyPairDSA pair) = X509.PubKeyDSA (DSA.toPublicKey pair)+keyPairToPubKey (KeyPairEC _ pub) = X509.PubKeyEC pub+keyPairToPubKey (KeyPairX25519 _ pub) = X509.PubKeyX25519 pub+keyPairToPubKey (KeyPairX448 _ pub) = X509.PubKeyX448 pub+keyPairToPubKey (KeyPairEd25519 _ pub) = X509.PubKeyEd25519 pub+keyPairToPubKey (KeyPairEd448 _ pub) = X509.PubKeyEd448 pub++-- | Returns 'True' when the given X.509 public key is consistent with a key+-- pair, which means that the public key can be derived from the private key in+-- the key pair.+keyPairMatchesKey :: KeyPair -> X509.PubKey -> Bool+keyPairMatchesKey (KeyPairRSA _ pub) (X509.PubKeyRSA other) = pub == other+keyPairMatchesKey (KeyPairDSA pair) (X509.PubKeyDSA other) = DSA.toPublicKey pair == other+keyPairMatchesKey (KeyPairEC _ pub) (X509.PubKeyEC other) = pub == other+keyPairMatchesKey (KeyPairX25519 _ pub) (X509.PubKeyX25519 other) = pub == other+keyPairMatchesKey (KeyPairX448 _ pub) (X509.PubKeyX448 other) = pub == other+keyPairMatchesKey (KeyPairEd25519 _ pub) (X509.PubKeyEd25519 other) = pub == other+keyPairMatchesKey (KeyPairEd448 _ pub) (X509.PubKeyEd448 other) = pub == other+keyPairMatchesKey _ _ = False++keyPairMatchesCert :: KeyPair -> X509.SignedCertificate -> Bool+keyPairMatchesCert keyPair cert =+ let obj = X509.signedObject (X509.getSigned cert)+ pub = X509.certPubKey obj+ in keyPairMatchesKey keyPair pub
src/Crypto/Store/PEM.hs view
@@ -11,6 +11,7 @@ , writePEMs , pemsWriteBS , pemsWriteLBS+ , mkPEM , module Data.PEM ) where @@ -19,6 +20,9 @@ import qualified Data.ByteString.Lazy as L -- | Read a PEM file from disk.+--+-- The function uses lazy IO so will retain an open file descriptor until the+-- list spine is fully evaluated. readPEMs :: FilePath -> IO [PEM] readPEMs filepath = either error id . pemParseLBS <$> L.readFile filepath @@ -33,3 +37,7 @@ -- | Write a PEM file to disk. writePEMs :: FilePath -> [PEM] -> IO () writePEMs filepath = L.writeFile filepath . pemsWriteLBS++-- | Make a PEM without headers.+mkPEM :: String -> B.ByteString -> PEM+mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs}
src/Crypto/Store/PKCS12.hs view
@@ -8,6 +8,7 @@ -- Personal Information Exchange Syntax, aka PKCS #12. -- -- Only password integrity mode and password privacy modes are supported.+{-# LANGUAGE CPP #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}@@ -17,7 +18,7 @@ {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Crypto.Store.PKCS12- ( IntegrityParams+ ( IntegrityParams(..) , readP12File , readP12FileFromMemory , writeP12File@@ -70,14 +71,18 @@ , recoverA ) where +import Control.Applicative import Control.Monad import Data.ASN1.Types import qualified Data.ByteArray as B import qualified Data.ByteString as BS import Data.List (partition)-import Data.Maybe (fromMaybe, mapMaybe)+import Data.Maybe (isJust, fromMaybe, mapMaybe)+#if !(MIN_VERSION_base(4,11,0)) import Data.Semigroup+#endif+import Data.String (fromString) import qualified Data.X509 as X509 import qualified Data.X509.Validation as X509 @@ -92,6 +97,7 @@ import Crypto.Store.CMS.Enveloped import Crypto.Store.CMS.Util import Crypto.Store.Error+import Crypto.Store.Keys import Crypto.Store.PKCS5 import Crypto.Store.PKCS5.PBES1 import Crypto.Store.PKCS8@@ -141,25 +147,44 @@ Just md -> return $ Authenticated (verify md authSafeData) verify MacData{..} content pwdUTF8 =- case digAlg of- DigestAlgorithm d -> loop (toProtectionPasswords pwdUTF8)+ case macAlg of+ MacTraditional (DigestAlgorithm d) -> loop (toProtectionPasswords pwdUTF8) where -- iterate over all possible representations of a password -- until a successful match is found loop [] = Left BadContentMAC loop (pwd:others) =- let fn key macAlg bs- | not (securityAcceptable macAlg) =+ let fn key digAlg bs+ | not (securityAcceptable digAlg) = Left (InvalidParameter "Integrity MAC too weak")- | macValue == mac macAlg key bs = (pwd,) <$> decode bs+ | macValue == mac digAlg key bs = (pwd,) <$> decode bs | otherwise = loop others in pkcs12mac Left fn d macParams content pwd + -- for RFC 9579 the primary representation is always used, this is+ -- fine because we assume the encryption layer also uses that+ -- representation+ MacAuthScheme authScheme+ | not (hasExplicitKeyLength authScheme) ->+ Left (InvalidParameter "KDF key length must be explicit")+ | not (securityAcceptable authScheme) ->+ Left (InvalidParameter "Integrity MAC too weak")+ | macValue == pbMac authScheme content pwd -> (pwd,) <$> decode content+ | otherwise -> Left BadContentMAC+ where pwd = toProtectionPassword pwdUTF8 + hasExplicitKeyLength (PBMAC1 p) = isJust $ kdfKeyLength (pbmac1KDF p)++ -- Generating and encoding -- | Parameters used for password integrity mode.-type IntegrityParams = (DigestAlgorithm, PBEParameter)+data IntegrityParams+ = TraditionalIntegrity DigestAlgorithm PBEParameter+ -- ^ Traditional PKCS #12 integrity, with a digest algoritm+ | AuthSchemeIntegrity AuthenticationScheme+ -- ^ Integrity with an authentication scheme such as PBMAC1+ deriving (Show,Eq) -- | Write a PKCS #12 file to disk. writeP12File :: FilePath@@ -175,18 +200,33 @@ writeP12FileToMemory :: IntegrityParams -> ProtectionPassword -> PKCS12 -> Either StoreError BS.ByteString-writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =+writeP12FileToMemory intp pwdUTF8 aSafe = encode <$> protect where content = encodeASN1Object aSafe encode md = encodeASN1Object PFX { authSafeData = content, macData = Just md } - protect = pkcs12mac Left fn hashAlg pbeParam content pwdUTF8- fn key macAlg bs = Right MacData { digAlg = alg- , macValue = mac macAlg key bs- , macParams = pbeParam- }+ protect = case intp of+ TraditionalIntegrity alg@(DigestAlgorithm hashAlg) pbeParam ->+ let fn key macAlg bs = Right MacData { macAlg = MacTraditional alg+ , macValue = mac macAlg key bs+ , macParams = pbeParam+ }+ in pkcs12mac Left fn hashAlg pbeParam content pwdUTF8+ AuthSchemeIntegrity authScheme+ | pwdUTF8 == emptyNotTerminated ->+ Left (InvalidPassword "Authentication scheme requires terminated password")+ | otherwise ->+ Right MacData { macAlg = MacAuthScheme (transform authScheme)+ , macValue = pbMac authScheme content pwdUTF8+ , macParams = unused+ }+ unused = PBEParameter (fromString "NOT USED") 1 + transform (PBMAC1 p) = PBMAC1 (ensureExplicitKeyLength p)+ ensureExplicitKeyLength p = p { pbmac1KDF = kdfKeyLengthModify fn (pbmac1KDF p) }+ where fn = Just . fromMaybe (getMaximumKeySize $ pbmac1AScheme p)+ -- | Write a PKCS #12 file without integrity protection to disk. writeUnprotectedP12File :: FilePath -> PKCS12 -> IO () writeUnprotectedP12File path = BS.writeFile path . writeUnprotectedP12FileToMemory@@ -230,8 +270,12 @@ m <- if b then Just <$> parse else pure Nothing return PFX { authSafeData = d, macData = m } +data MacAlg = MacTraditional DigestAlgorithm+ | MacAuthScheme AuthenticationScheme+ deriving (Show,Eq)+ data MacData = MacData- { digAlg :: DigestAlgorithm+ { macAlg :: MacAlg , macValue :: MessageAuthenticationCode , macParams :: PBEParameter }@@ -242,29 +286,34 @@ asn1Container Sequence (m . s . i) where m = asn1Container Sequence (a . v)- a = algorithmASN1S Sequence digAlg+ a = gMacAlg macAlg v = gOctetString (B.convert macValue) s = gOctetString (pbeSalt macParams) i = gIntVal (fromIntegral $ pbeIterationCount macParams) + gMacAlg (MacTraditional alg) = algorithmASN1S Sequence alg+ gMacAlg (MacAuthScheme authScheme) = algorithmASN1S Sequence authScheme+ instance Monoid e => ParseASN1Object e MacData where parse = onNextContainer Sequence $ do (a, v) <- onNextContainer Sequence $ do- a <- parseAlgorithm Sequence+ a <- parseTraditional <|> parseAuthScheme OctetString v <- getNext return (a, v) OctetString s <- getNext b <- hasNext IntVal i <- if b then getNext else pure (IntVal 1)- return MacData { digAlg = a+ return MacData { macAlg = a , macValue = AuthTag (B.convert v) , macParams = PBEParameter s (fromIntegral i) }-+ where+ parseTraditional = MacTraditional <$> parseAlgorithm Sequence+ parseAuthScheme = MacAuthScheme <$> parseAlgorithm Sequence -- AuthenticatedSafe --- | PKCS #12 privacy wrapper, adding optional encryption to 'SafeContents'.+-- | PKCS #12 privacy wrapper, adding optional encryption to t'SafeContents'. -- ASN.1 equivalent is @AuthenticatedSafe@. -- -- The semigroup interface allows to combine multiple pieces encrypted@@ -453,7 +502,7 @@ fromObjectID oid = unOIDNW <$> fromObjectID oid -- | Main bag payload in PKCS #12 contents.-data SafeInfo = KeyBag (FormattedKey X509.PrivKey) -- ^ unencrypted private key+data SafeInfo = KeyBag (FormattedKey KeyPair) -- ^ unencrypted private key | PKCS8ShroudedKeyBag PKCS5 -- ^ encrypted private key | CertBag (Bag CertInfo) -- ^ certificate | CRLBag (Bag CRLInfo) -- ^ CRL@@ -513,7 +562,7 @@ filterByLocalKeyId :: BS.ByteString -> SafeContents -> SafeContents filterByLocalKeyId d = filterBags ((== Just d) . getLocalKeyId) -getSafeKeysId :: SafeContents -> [OptProtected (Id X509.PrivKey)]+getSafeKeysId :: SafeContents -> [OptProtected (Id KeyPair)] getSafeKeysId (SafeContents scs) = loop scs where loop [] = []@@ -530,10 +579,10 @@ return (mkId k bag) -- | Return all private keys contained in the safe contents.-getSafeKeys :: SafeContents -> [OptProtected X509.PrivKey]+getSafeKeys :: SafeContents -> [OptProtected KeyPair] getSafeKeys = map (fmap unId) . getSafeKeysId -getAllSafeKeysId :: [SafeContents] -> OptProtected [Id X509.PrivKey]+getAllSafeKeysId :: [SafeContents] -> OptProtected [Id KeyPair] getAllSafeKeysId = applySamePassword . concatMap getSafeKeysId -- | Return all private keys contained in the safe content list. All shrouded@@ -543,7 +592,7 @@ -- least is encrypted. This does not mean all keys were actually protected in -- the input. If detailed view is required then function 'getSafeKeys' is -- available.-getAllSafeKeys :: [SafeContents] -> OptProtected [X509.PrivKey]+getAllSafeKeys :: [SafeContents] -> OptProtected [KeyPair] getAllSafeKeys = applySamePassword . concatMap getSafeKeys getSafeX509CertsId :: SafeContents -> [Id X509.SignedCertificate]@@ -598,21 +647,25 @@ -- and follow the issuers to get all certificates in the chain let filtered = map (filterByLocalKeyId d) l leaf <- single (getAllSafeX509Certs filtered)- pure (buildCertificateChain leaf certs, k)+ guard (keyPairMatchesCert k leaf)+ pure (buildCertificateChain leaf certs, keyPairToPrivKey k) Nothing -> case idName iKey of Just name -> do -- same but using friendly name of private key let filtered = map (filterByFriendlyName name) l leaf <- single (getAllSafeX509Certs filtered)- pure (buildCertificateChain leaf certs, k)+ guard (keyPairMatchesCert k leaf)+ pure (buildCertificateChain leaf certs, keyPairToPrivKey k) Nothing -> do- -- no identifier available, so we simply return all- -- certificates with input order- guard (not $ null certs)- pure (X509.CertificateChain certs, k)+ -- no identifier available, so we have to search all+ -- certificates for the one consistent with the private+ -- key+ leaf <- single (filterWithPrivKey k certs)+ pure (buildCertificateChain leaf certs, keyPairToPrivKey k)+ filterWithPrivKey = filter . keyPairMatchesCert --- | Extract the private key and certificate chain from a 'PKCS12' value. A+-- | Extract the private key and certificate chain from a t'PKCS12' value. A -- credential is returned when the structure contains exactly one private key -- and at least one X.509 certificate. toCredential :: PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey))@@ -627,16 +680,17 @@ fn keys = do k <- single keys leaf <- single (getAllSafeX509Certs filtered)- pure (buildCertificateChain leaf certs, k)+ guard (keyPairMatchesCert k leaf)+ pure (buildCertificateChain leaf certs, keyPairToPrivKey k) -- | Extract a private key and certificate chain with the specified friendly--- name from a 'PKCS12' value. A credential is returned when the structure+-- name from a t'PKCS12' value. A credential is returned when the structure -- contains exactly one private key and one X.509 certificate with the name. toNamedCredential :: String -> PKCS12 -> OptProtected (Maybe (X509.CertificateChain, X509.PrivKey)) toNamedCredential name p12 = unSamePassword $ SamePassword (unPKCS12 p12) >>= getInnerCredentialNamed name --- | Build a 'PKCS12' value containing a private key and certificate chain.+-- | Build a t'PKCS12' value containing a private key and certificate chain. -- Distinct encryption is applied for both. Encrypting the certificate chain is -- optional. --@@ -649,7 +703,7 @@ -> Either StoreError PKCS12 fromCredential = fromCredential' id --- | Build a 'PKCS12' value containing a private key and certificate chain+-- | Build a t'PKCS12' value containing a private key and certificate chain -- identified with the specified friendly name. Distinct encryption is applied -- for private key and certificates. Encrypting the certificate chain is -- optional.@@ -685,10 +739,14 @@ certAttrs = attrs : repeat [] toCertBag a c = Bag (CertBag (Bag (CertX509 c) [])) a - scKeyOrError = wrap <$> encrypt algKey pwd encodedKey+ scKeyOrError+ | keyPairMatchesCert pair leaf =+ wrap <$> encrypt algKey pwd encodedKey+ | otherwise = Left PublicPrivateKeyMismatch wrap shrouded = SafeContents [Bag (PKCS8ShroudedKeyBag shrouded) attrs]- encodedKey = encodeASN1Object (FormattedKey PKCS8Format key)+ encodedKey = encodeASN1Object (FormattedKey PKCS8Format pair)+ pair = keyPairFromPrivKey key X509.Fingerprint keyId = X509.getFingerprint leaf X509.HashSHA1 attrs = trans (setLocalKeyId keyId [])
src/Crypto/Store/PKCS5.hs view
@@ -24,6 +24,9 @@ , EncryptionScheme(..) , PBEParameter(..) , PBES2Parameter(..)+ -- * Message authentication schemes+ , AuthenticationScheme(..)+ , PBMAC1Parameter(..) -- * Key derivation , KeyDerivationFunc(..) , PBKDF2_PRF(..)@@ -33,11 +36,16 @@ , ContentEncryptionParams , ContentEncryptionAlg(..) , ContentEncryptionCipher(..)- , generateEncryptionParams+ , ecbParams+ , generateCBCParams+ , generateRC2EncryptionParams+ , generateCFBParams+ , generateCTRParams , getContentEncryptionAlg -- * Low-level API , pbEncrypt , pbDecrypt+ , pbMac ) where import Data.ASN1.Types@@ -47,6 +55,7 @@ import Crypto.Store.ASN1.Parse import Crypto.Store.ASN1.Generate import Crypto.Store.CMS.Algorithms+import Crypto.Store.CMS.Authenticated import Crypto.Store.CMS.Encrypted import Crypto.Store.CMS.Util import Crypto.Store.Error@@ -164,7 +173,66 @@ instance Monoid e => ParseASN1Object e EncryptionScheme where parse = parseAlgorithm Sequence +data AuthenticationSchemeType = Type_PBMAC1 +instance Enumerable AuthenticationSchemeType where+ values = [ Type_PBMAC1+ ]++instance OIDable AuthenticationSchemeType where+ getObjectID Type_PBMAC1 = [1,2,840,113549,1,5,14]++instance OIDNameable AuthenticationSchemeType where+ fromObjectID oid = unOIDNW <$> fromObjectID oid++-- | Password-Based Message Authentication Scheme (PBMAC).+newtype AuthenticationScheme = PBMAC1 PBMAC1Parameter -- ^ PBMAC1+ deriving (Show,Eq)++instance HasStrength AuthenticationScheme where+ getSecurityBits (PBMAC1 p) = getSecurityBits (pbmac1AScheme p)++-- | PBMAC1 parameters.+data PBMAC1Parameter = PBMAC1Parameter+ { pbmac1KDF :: KeyDerivationFunc -- ^ Key derivation function+ , pbmac1AScheme :: MACAlgorithm -- ^ Underlying message authentication scheme+ }+ deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e PBMAC1Parameter where+ asn1s PBMAC1Parameter{..} =+ let kdFunc = algorithmASN1S Sequence pbmac1KDF+ aScheme = algorithmASN1S Sequence pbmac1AScheme+ in asn1Container Sequence (kdFunc . aScheme)++instance Monoid e => ParseASN1Object e PBMAC1Parameter where+ parse = onNextContainer Sequence $ do+ kdFunc <- parseAlgorithm Sequence+ aScheme <- parseAlgorithm Sequence+ case kdfKeyLength kdFunc of+ Nothing -> return ()+ Just sz+ | validateKeySize aScheme sz -> return ()+ | otherwise -> throwParseError "PBMAC1Parameter: parsed key length incompatible with message authentication scheme"+ return PBMAC1Parameter { pbmac1KDF = kdFunc, pbmac1AScheme = aScheme }++instance AlgorithmId AuthenticationScheme where+ type AlgorithmType AuthenticationScheme = AuthenticationSchemeType+ algorithmName _ = "message authentication scheme"++ algorithmType (PBMAC1 _) = Type_PBMAC1++ parameterASN1S (PBMAC1 p) = asn1s p++ parseParameter Type_PBMAC1 = PBMAC1 <$> parse++instance ASN1Elem e => ProduceASN1Object e AuthenticationScheme where+ asn1s = algorithmASN1S Sequence++instance Monoid e => ParseASN1Object e AuthenticationScheme where+ parse = parseAlgorithm Sequence++ -- High-level API -- | Content encrypted with a Password-Based Encryption Scheme (PBES).@@ -236,3 +304,16 @@ pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs where key = kdfDerive pbes2KDF len (fromProtectionPassword pwd) :: Key len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)+++-- Message Authentication Schemes++-- | Authenticate a message with the specified authentication scheme and+-- password.+pbMac :: AuthenticationScheme -> EncapsulatedContent -> ProtectionPassword -> MessageAuthenticationCode+pbMac (PBMAC1 p) = pbmac1 p++pbmac1 :: PBMAC1Parameter -> ByteString -> ProtectionPassword -> MessageAuthenticationCode+pbmac1 PBMAC1Parameter{..} bs pwd = mac pbmac1AScheme key bs+ where key = kdfDerive pbmac1KDF len (fromProtectionPassword pwd) :: Key+ len = fromMaybe (getMaximumKeySize pbmac1AScheme) (kdfKeyLength pbmac1KDF)
src/Crypto/Store/PKCS8.hs view
@@ -9,7 +9,11 @@ -- -- Presents an API similar to "Data.X509.Memory" and "Data.X509.File" but -- allows to write private keys and provides support for password-based--- encryption.+-- encryption. Private keys are now stored along with the corresponding+-- public key in a type 'KeyPair'. Components of type 'X509.PrivKey' and+-- 'X509.PubKey' can be obtained through functions 'keyPairToPrivKey' and+-- 'keyPairToPubKey'. Function 'keyPairFromPrivKey' can be called to build a+-- 'KeyPair'. -- -- Functions to read a private key return an object wrapped in the -- 'OptProtected' data type.@@ -25,12 +29,18 @@ ( readKeyFile , readKeyFileFromMemory , pemToKey+ , pemToKeyAccum , writeKeyFile , writeKeyFileToMemory , keyToPEM , writeEncryptedKeyFile , writeEncryptedKeyFileToMemory , encryptKeyToPEM+ -- * Key pairs+ , KeyPair+ , keyPairFromPrivKey+ , keyPairToPrivKey+ , keyPairToPubKey -- * Serialization formats , PrivateKeyFormat(..) , FormattedKey(..)@@ -54,7 +64,10 @@ import Data.ASN1.BinaryEncoding import Data.ASN1.BitArray import Data.ASN1.Encoding+import Data.ASN1.Prim+import Data.Bifunctor (first) import Data.ByteArray (ByteArrayAccess, convert)+import Data.Either (rights) import Data.Maybe import qualified Data.X509 as X509 import qualified Data.ByteString as B@@ -66,13 +79,14 @@ import qualified Crypto.PubKey.ECC.ECDSA as ECDSA import qualified Crypto.PubKey.Ed25519 as Ed25519 import qualified Crypto.PubKey.Ed448 as Ed448-import qualified Crypto.PubKey.RSA as RSA+import qualified Crypto.PubKey.RSA.Types as RSA import Crypto.Store.ASN1.Generate import Crypto.Store.ASN1.Parse import Crypto.Store.CMS.Attribute import Crypto.Store.CMS.Util import Crypto.Store.Error+import Crypto.Store.Keys import Crypto.Store.PEM import Crypto.Store.PKCS5 import Crypto.Store.PKCS8.EC@@ -116,63 +130,72 @@ -- Reading from PEM format -- | Read private keys from a PEM file.-readKeyFile :: FilePath -> IO [OptProtected X509.PrivKey]+readKeyFile :: FilePath -> IO [OptProtected KeyPair] readKeyFile path = accumulate <$> readPEMs path -- | Read private keys from a bytearray in PEM format.-readKeyFileFromMemory :: B.ByteString -> [OptProtected X509.PrivKey]+readKeyFileFromMemory :: B.ByteString -> [OptProtected KeyPair] readKeyFileFromMemory = either (const []) accumulate . pemParseBS -accumulate :: [PEM] -> [OptProtected X509.PrivKey]-accumulate = catMaybes . foldr (flip pemToKey) []+accumulate :: [PEM] -> [OptProtected KeyPair]+accumulate = rights . map pemToKey --- | Read a private key from a 'PEM' element and add it to the accumulator list.-pemToKey :: [Maybe (OptProtected X509.PrivKey)] -> PEM -> [Maybe (OptProtected X509.PrivKey)]-pemToKey acc pem =- case decodeASN1' BER (pemContent pem) of- Left _ -> acc- Right asn1 -> run (getParser $ pemName pem) asn1 : acc+-- | Read a private key from a t'PEM' element and add it to the accumulator+-- list.+--+-- This API is modelled after the original @pemToKey@ in "Data.X509.Memory".+pemToKeyAccum :: [Maybe (OptProtected KeyPair)] -> PEM -> [Maybe (OptProtected KeyPair)]+pemToKeyAccum acc pem =+ case pemToKey pem of+ Left (DecodingError _) -> acc+ Left _ -> Nothing : acc+ Right key -> Just key : acc - where- run p = either (const Nothing) Just . runParseASN1 p+-- | Read a private key from a t'PEM' element.+pemToKey :: PEM -> Either StoreError (OptProtected KeyPair)+pemToKey pem = do+ asn1 <- mapLeft DecodingError $ decodeASN1' BER (pemContent pem)+ parser <- getParser (pemName pem)+ mapLeft ParseFailure $ runParseASN1 parser asn1 + where allTypes = unFormat <$> parse- rsa = X509.PrivKeyRSA . unFormat <$> parse- dsa = X509.PrivKeyDSA . DSA.toPrivateKey . unFormat <$> parse- ecdsa = X509.PrivKeyEC . unFormat <$> parse- x25519 = X509.PrivKeyX25519 <$> parseModern- x448 = X509.PrivKeyX448 <$> parseModern- ed25519 = X509.PrivKeyEd25519 <$> parseModern- ed448 = X509.PrivKeyEd448 <$> parseModern+ rsa = parseFormattedKeyPair (keyPairFromPrivKey . X509.PrivKeyRSA)+ dsa = parseFormattedKeyPair KeyPairDSA+ ecdsa = parseFormattedKeyPair (keyPairFromPrivKey . X509.PrivKeyEC)+ x25519 = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyX25519)+ x448 = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyX448)+ ed25519 = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyEd25519)+ ed448 = parseModernKeyPair (keyPairFromPrivKey . X509.PrivKeyEd448) encrypted = inner . decrypt <$> parse - getParser "PRIVATE KEY" = Unprotected <$> allTypes- getParser "RSA PRIVATE KEY" = Unprotected <$> rsa- getParser "DSA PRIVATE KEY" = Unprotected <$> dsa- getParser "EC PRIVATE KEY" = Unprotected <$> ecdsa- getParser "X25519 PRIVATE KEY" = Unprotected <$> x25519- getParser "X448 PRIVATE KEY" = Unprotected <$> x448- getParser "ED25519 PRIVATE KEY" = Unprotected <$> ed25519- getParser "ED448 PRIVATE KEY" = Unprotected <$> ed448- getParser "ENCRYPTED PRIVATE KEY" = Protected <$> encrypted- getParser _ = empty+ getParser "PRIVATE KEY" = return (Unprotected <$> allTypes)+ getParser "RSA PRIVATE KEY" = return (Unprotected <$> rsa)+ getParser "DSA PRIVATE KEY" = return (Unprotected <$> dsa)+ getParser "EC PRIVATE KEY" = return (Unprotected <$> ecdsa)+ getParser "X25519 PRIVATE KEY" = return (Unprotected <$> x25519)+ getParser "X448 PRIVATE KEY" = return (Unprotected <$> x448)+ getParser "ED25519 PRIVATE KEY" = return (Unprotected <$> ed25519)+ getParser "ED448 PRIVATE KEY" = return (Unprotected <$> ed448)+ getParser "ENCRYPTED PRIVATE KEY" = return (Protected <$> encrypted)+ getParser _ = Left UnexpectedNameForPEM inner decfn pwd = do decrypted <- decfn pwd asn1 <- mapLeft DecodingError $ decodeASN1' BER decrypted- case run allTypes asn1 of- Nothing -> Left (ParseFailure "No key parsed after decryption")- Just k -> return k+ case runParseASN1 allTypes asn1 of+ Left _ -> Left (ParseFailure "No key parsed after decryption")+ Right k -> return k -- Writing to PEM format -- | Write unencrypted private keys to a PEM file.-writeKeyFile :: PrivateKeyFormat -> FilePath -> [X509.PrivKey] -> IO ()+writeKeyFile :: PrivateKeyFormat -> FilePath -> [KeyPair] -> IO () writeKeyFile fmt path = writePEMs path . map (keyToPEM fmt) -- | Write unencrypted private keys to a bytearray in PEM format.-writeKeyFileToMemory :: PrivateKeyFormat -> [X509.PrivKey] -> B.ByteString+writeKeyFileToMemory :: PrivateKeyFormat -> [KeyPair] -> B.ByteString writeKeyFileToMemory fmt = pemsWriteBS . map (keyToPEM fmt) -- | Write a PKCS #8 encrypted private key to a PEM file.@@ -183,10 +206,10 @@ -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt. writeEncryptedKeyFile :: FilePath- -> EncryptionScheme -> ProtectionPassword -> X509.PrivKey+ -> EncryptionScheme -> ProtectionPassword -> KeyPair -> IO (Either StoreError ())-writeEncryptedKeyFile path alg pwd privKey =- let pem = encryptKeyToPEM alg pwd privKey+writeEncryptedKeyFile path alg pwd keyPair =+ let pem = encryptKeyToPEM alg pwd keyPair in either (return . Left) (fmap Right . writePEMs path . (:[])) pem -- | Write a PKCS #8 encrypted private key to a bytearray in PEM format.@@ -197,65 +220,66 @@ -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt. writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword- -> X509.PrivKey -> Either StoreError B.ByteString-writeEncryptedKeyFileToMemory alg pwd privKey =- pemWriteBS <$> encryptKeyToPEM alg pwd privKey+ -> KeyPair -> Either StoreError B.ByteString+writeEncryptedKeyFileToMemory alg pwd keyPair =+ pemWriteBS <$> encryptKeyToPEM alg pwd keyPair -- | Generate an unencrypted PEM for a private key.-keyToPEM :: PrivateKeyFormat -> X509.PrivKey -> PEM+keyToPEM :: PrivateKeyFormat -> KeyPair -> PEM keyToPEM TraditionalFormat = keyToTraditionalPEM keyToPEM PKCS8Format = keyToModernPEM -keyToTraditionalPEM :: X509.PrivKey -> PEM-keyToTraditionalPEM privKey =+keyToTraditionalPEM :: KeyPair -> PEM+keyToTraditionalPEM keyPair = mkPEM (typeTag ++ " PRIVATE KEY") (encodeASN1S asn1)- where (typeTag, asn1) = traditionalPrivKeyASN1S privKey+ where (typeTag, asn1) = traditionalPrivKeyASN1S keyPair -traditionalPrivKeyASN1S :: ASN1Elem e => X509.PrivKey -> (String, ASN1Stream e)-traditionalPrivKeyASN1S privKey =- case privKey of- X509.PrivKeyRSA k -> ("RSA", traditional k)- X509.PrivKeyDSA k -> ("DSA", traditional (dsaPrivToPair k))- X509.PrivKeyEC k -> ("EC", traditional k)- X509.PrivKeyX25519 k -> ("X25519", tradModern k)- X509.PrivKeyX448 k -> ("X448", tradModern k)- X509.PrivKeyEd25519 k -> ("ED25519", tradModern k)- X509.PrivKeyEd448 k -> ("ED448", tradModern k)+traditionalPrivKeyASN1S :: ASN1Elem e => KeyPair -> (String, ASN1Stream e)+traditionalPrivKeyASN1S keyPair =+ case keyPair of+ KeyPairRSA k _ -> ("RSA", traditional k)+ KeyPairDSA p -> ("DSA", traditional p)+ KeyPairEC k _ -> ("EC", traditional k)+ KeyPairX25519 k _ -> ("X25519", tradModern k)+ KeyPairX448 k _ -> ("X448", tradModern k)+ KeyPairEd25519 k _ -> ("ED25519", tradModern k)+ KeyPairEd448 k _ -> ("ED448", tradModern k) where+ traditional :: ProduceASN1Object e (Traditional a) => a -> ASN1Stream e traditional a = asn1s (Traditional a)- tradModern a = asn1s (Modern [] a) -keyToModernPEM :: X509.PrivKey -> PEM-keyToModernPEM privKey = mkPEM "PRIVATE KEY" (encodeASN1S asn1)- where asn1 = modernPrivKeyASN1S [] privKey+ tradModern :: ProduceASN1Object e (Modern a) => a -> ASN1Stream e+ tradModern = modernASN1S (const $ keyPairToPubKey keyPair) -modernPrivKeyASN1S :: ASN1Elem e => [Attribute] -> X509.PrivKey -> ASN1Stream e-modernPrivKeyASN1S attrs privKey =- case privKey of- X509.PrivKeyRSA k -> modern k- X509.PrivKeyDSA k -> modern (dsaPrivToPair k)- X509.PrivKeyEC k -> modern k- X509.PrivKeyX25519 k -> modern k- X509.PrivKeyX448 k -> modern k- X509.PrivKeyEd25519 k -> modern k- X509.PrivKeyEd448 k -> modern k+keyToModernPEM :: KeyPair -> PEM+keyToModernPEM keyPair = mkPEM "PRIVATE KEY" (encodeASN1S asn1)+ where asn1 = modernASN1S keyPairToPubKey keyPair++modernPrivKeyASN1S :: ASN1Elem e+ => [Attribute] -> Maybe GenericPubKey -> KeyPair -> ASN1Stream e+modernPrivKeyASN1S attrs mPub keyPair =+ case keyPair of+ KeyPairRSA k _ -> modern k+ KeyPairDSA p -> modern p+ KeyPairEC k _ -> modern k+ KeyPairX25519 k _ -> modern k+ KeyPairX448 k _ -> modern k+ KeyPairEd25519 k _ -> modern k+ KeyPairEd448 k _ -> modern k where- modern a = asn1s (Modern attrs a)+ modern a = asn1s (Modern attrs mPub a) -- | Generate a PKCS #8 encrypted PEM for a private key. -- -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt.-encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> X509.PrivKey+encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> KeyPair -> Either StoreError PEM-encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs- where bs = pemContent (keyToModernPEM privKey)+encryptKeyToPEM alg pwd keyPair = toPEM <$> encrypt alg pwd bs+ where bs = pemContent (keyToModernPEM keyPair) toPEM pkcs8 = mkPEM "ENCRYPTED PRIVATE KEY" (encodeASN1Object pkcs8) -mkPEM :: String -> B.ByteString -> PEM-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs} - -- Private key formats: traditional (SSLeay compatible) and modern (PKCS #8) -- | Private-key serialization format.@@ -270,15 +294,65 @@ parseTraditional :: ParseASN1Object e (Traditional a) => ParseASN1 e a parseTraditional = unTraditional <$> parse -data Modern a = Modern [Attribute] a+data Modern a = Modern [Attribute] (Maybe GenericPubKey) a instance Functor Modern where- fmap f (Modern attrs a) = Modern attrs (f a)+ fmap f (Modern attrs mPub a) = Modern attrs mPub (f a) -parseModern :: ParseASN1Object e (Modern a) => ParseASN1 e a-parseModern = unModern <$> parse- where unModern (Modern _ a) = a+modernASN1S :: ProduceASN1Object e (Modern a)+ => (a -> X509.PubKey) -> a -> ASN1Stream e+modernASN1S toPubKey a = asn1s (Modern [] mPub a)+ where+ noPubKey = True -- we never generate with publicKey but the code exists+ mPub | noPubKey = Nothing+ | otherwise = case getGenericPubKey toPubKey a of+ Right (gpk, []) -> Just gpk+ _ -> Nothing +parseModernKeyPair :: ParseASN1Object e (Modern a)+ => (a -> KeyPair) -> ParseASN1 e KeyPair+parseModernKeyPair = parseModern keyPairToPubKey++parseModern :: ParseASN1Object e (Modern b)+ => (a -> X509.PubKey) -> (b -> a) -> ParseASN1 e a+parseModern toPubKey mapFn = do+ Modern _ mPub b <- parse+ verifyPubKey toPubKey (mapFn b) mPub++verifyPubKey :: (a -> X509.PubKey) -> a -> Maybe GenericPubKey -> ParseASN1 e a+verifyPubKey _ a Nothing = return a+verifyPubKey toPubKey a (Just gpk)+ | Right (gpk, []) == derived = return a+ | otherwise = throwParseError "PKCS8 public key does not match private key"+ where derived = getGenericPubKey toPubKey a++getGenericPubKey :: (a -> X509.PubKey) -> a -> Either String (GenericPubKey, [ASN1])+getGenericPubKey toPubKey a = runParseASN1State parsePubKeyGen asn1+ where asn1 = toASN1 (toPubKey a) []++parsePubKeyGen :: Monoid e => ParseASN1 e GenericPubKey+parsePubKeyGen = onNextContainer Sequence $ do+ void $ getNextContainer Sequence -- ignore algorithm,+ BitString bits <- getNext -- we want the bit string only+ return (GenericPubKey $ bitArrayGetData bits)++newtype GenericPubKey = GenericPubKey B.ByteString+ deriving (Show,Eq)++instance ASN1Elem e => ProduceASN1Object e (Maybe GenericPubKey) where+ asn1s Nothing = id+ asn1s (Just (GenericPubKey bits)) = gMany [Other Context 1 bs]+ where bs = putBitString (toBitArray bits 0)++instance Monoid e => ParseASN1Object e (Maybe GenericPubKey) where+ parse = Just . GenericPubKey <$> parseTaggedBitString <|> return Nothing+ where+ parseTaggedBitString = do+ Other _ 1 bs <- getNext+ case getBitString bs of+ Right (BitString bits) -> return (bitArrayGetData bits)+ r -> throwParseError ("GenericPubKey: invalid bit string: " ++ show r)+ -- | A key associated with format. Allows to implement 'ASN1Object' instances. data FormattedKey a = FormattedKey PrivateKeyFormat a deriving (Show,Eq)@@ -286,16 +360,35 @@ instance Functor FormattedKey where fmap f (FormattedKey fmt a) = FormattedKey fmt (f a) -instance (ProduceASN1Object e (Traditional a), ProduceASN1Object e (Modern a)) => ProduceASN1Object e (FormattedKey a) where- asn1s (FormattedKey TraditionalFormat k) = asn1s (Traditional k)- asn1s (FormattedKey PKCS8Format k) = asn1s (Modern [] k)+instance ASN1Elem e => ProduceASN1Object e (FormattedKey KeyPair) where+ asn1s = formattedASN1S keyPairToPubKey -instance (Monoid e, ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a)) => ParseASN1Object e (FormattedKey a) where- parse = (modern <$> parseModern) <|> (traditional <$> parseTraditional)- where- traditional = FormattedKey TraditionalFormat- modern = FormattedKey PKCS8Format+instance Monoid e => ParseASN1Object e (FormattedKey KeyPair) where+ parse = parseFormatted keyPairToPubKey +formattedASN1S :: (ProduceASN1Object e (Traditional a), ProduceASN1Object e (Modern a))+ => (a -> X509.PubKey) -> FormattedKey a -> ASN1Stream e+formattedASN1S _ (FormattedKey TraditionalFormat k) = asn1s (Traditional k)+formattedASN1S toPubKey (FormattedKey PKCS8Format k) = modernASN1S toPubKey k++parseFormattedKeyPair :: ParseASN1Object e (Modern a)+ => (a -> KeyPair) -> ParseASN1 e KeyPair+parseFormattedKeyPair mapFn =+ unFormat <$> parseFormattedInternal keyPairToPubKey mapFn++parseFormatted :: (ParseASN1Object e (Traditional a), ParseASN1Object e (Modern a))+ => (a -> X509.PubKey) -> ParseASN1 e (FormattedKey a)+parseFormatted toPubKey = parseFormattedInternal toPubKey id++parseFormattedInternal :: (ParseASN1Object e (Traditional a), ParseASN1Object e (Modern b))+ => (a -> X509.PubKey) -> (b -> a) -> ParseASN1 e (FormattedKey a)+parseFormattedInternal toPubKey mapFn =+ (modern <$> parseModern toPubKey mapFn) <|>+ (traditional <$> parseTraditional)+ where+ traditional = FormattedKey TraditionalFormat+ modern = FormattedKey PKCS8Format+ unFormat :: FormattedKey a -> a unFormat (FormattedKey _ a) = a @@ -303,56 +396,46 @@ -- Private Keys instance ASN1Object (FormattedKey X509.PrivKey) where+ toASN1 = toASN1 . fmap keyPairFromPrivKey+ fromASN1 = fmap (first (fmap keyPairToPrivKey)) <$> fromASN1++instance ASN1Object (FormattedKey KeyPair) where toASN1 = asn1s fromASN1 = runParseASN1State parse -instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKey) where- asn1s (Traditional privKey) = snd $ traditionalPrivKeyASN1S privKey+instance ASN1Elem e => ProduceASN1Object e (Traditional KeyPair) where+ asn1s (Traditional keyPair) = snd $ traditionalPrivKeyASN1S keyPair -instance Monoid e => ParseASN1Object e (Traditional X509.PrivKey) where+instance Monoid e => ParseASN1Object e (Traditional KeyPair) where parse = rsa <|> dsa <|> ecdsa where- rsa = Traditional . X509.PrivKeyRSA . unTraditional <$> parse- dsa = Traditional . X509.PrivKeyDSA . DSA.toPrivateKey . unTraditional <$> parse- ecdsa = Traditional . X509.PrivKeyEC . unTraditional <$> parse+ rsa = Traditional . keyPairFromPrivKey . X509.PrivKeyRSA . unTraditional <$> parse+ dsa = Traditional . keyPairFromPrivKey . X509.PrivKeyDSA . DSA.toPrivateKey . unTraditional <$> parse+ ecdsa = Traditional . keyPairFromPrivKey . X509.PrivKeyEC . unTraditional <$> parse -instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKey) where- asn1s (Modern attrs privKey) = modernPrivKeyASN1S attrs privKey+instance ASN1Elem e => ProduceASN1Object e (Modern KeyPair) where+ asn1s (Modern attrs mPub keyPair) = modernPrivKeyASN1S attrs mPub keyPair -instance Monoid e => ParseASN1Object e (Modern X509.PrivKey) where+instance Monoid e => ParseASN1Object e (Modern KeyPair) where parse = rsa <|> dsa <|> ecdsa <|> x25519 <|> x448 <|> ed25519 <|> ed448 where- rsa = fmap X509.PrivKeyRSA <$> parse- dsa = fmap (X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse- ecdsa = fmap X509.PrivKeyEC <$> parse- x25519 = fmap X509.PrivKeyX25519 <$> parse- x448 = fmap X509.PrivKeyX448 <$> parse- ed25519 = fmap X509.PrivKeyEd25519 <$> parse- ed448 = fmap X509.PrivKeyEd448 <$> parse--skipVersion :: Monoid e => ParseASN1 e ()-skipVersion = do- IntVal v <- getNext- when (v /= 0 && v /= 1) $- throwParseError ("PKCS8: parsed invalid version: " ++ show v)--skipPublicKey :: Monoid e => ParseASN1 e ()-skipPublicKey = void (fmap Just parseTaggedPrimitive <|> return Nothing)- where parseTaggedPrimitive = do { Other _ 1 bs <- getNext; return bs }--parseAttrKeys :: Monoid e => ParseASN1 e ([Attribute], B.ByteString)-parseAttrKeys = do- OctetString bs <- getNext- attrs <- parseAttributes (Container Context 0)- skipPublicKey- return (attrs, bs)+ rsa = fmap (keyPairFromPrivKey . X509.PrivKeyRSA) <$> parse+ dsa = fmap (keyPairFromPrivKey . X509.PrivKeyDSA . DSA.toPrivateKey) <$> parse+ ecdsa = fmap (keyPairFromPrivKey . X509.PrivKeyEC) <$> parse+ x25519 = fmap (keyPairFromPrivKey . X509.PrivKeyX25519) <$> parse+ x448 = fmap (keyPairFromPrivKey . X509.PrivKeyX448) <$> parse+ ed25519 = fmap (keyPairFromPrivKey . X509.PrivKeyEd25519) <$> parse+ ed448 = fmap (keyPairFromPrivKey . X509.PrivKeyEd448) <$> parse -- RSA +toPubKeyRSA :: RSA.PrivateKey -> X509.PubKey+toPubKeyRSA = X509.PubKeyRSA . RSA.toPublicKey . RSA.KeyPair+ instance ASN1Object (FormattedKey RSA.PrivateKey) where- toASN1 = asn1s- fromASN1 = runParseASN1State parse+ toASN1 = formattedASN1S toPubKeyRSA+ fromASN1 = runParseASN1State (parseFormatted toPubKeyRSA) instance ASN1Elem e => ProduceASN1Object e (Traditional RSA.PrivateKey) where asn1s (Traditional privKey) =@@ -396,34 +479,37 @@ return (Traditional privKey) instance ASN1Elem e => ProduceASN1Object e (Modern RSA.PrivateKey) where- asn1s (Modern attrs privKey) =+ asn1s (Modern attrs mPub privKey) = asn1Container Sequence (v . alg . bs . att) where- v = gIntVal 0+ v = versionASN1S mPub alg = asn1Container Sequence (oid . gNull) oid = gOID [1,2,840,113549,1,1,1] bs = gOctetString (encodeASN1Object $ Traditional privKey)- att = attributesASN1S (Container Context 0) attrs+ att = attrKeysASN1S attrs mPub instance Monoid e => ParseASN1Object e (Modern RSA.PrivateKey) where parse = onNextContainer Sequence $ do- skipVersion+ v2 <- parseVersion Null <- onNextContainer Sequence $ do OID [1,2,840,113549,1,1,1] <- getNext getNext- (attrs, bs) <- parseAttrKeys+ (attrs, bs, mPub) <- parseAttrKeys v2 let inner = decodeASN1' BER bs strError = Left . ("PKCS8: error decoding inner RSA: " ++) . show case either strError (runParseASN1 parseTraditional) inner of Left err -> throwParseError ("PKCS8: error parsing inner RSA: " ++ err)- Right privKey -> return (Modern attrs privKey)+ Right privKey -> return (Modern attrs mPub privKey) -- DSA +toPubKeyDSA :: DSA.KeyPair -> X509.PubKey+toPubKeyDSA = X509.PubKeyDSA . DSA.toPublicKey+ instance ASN1Object (FormattedKey DSA.KeyPair) where- toASN1 = asn1s- fromASN1 = runParseASN1State parse+ toASN1 = formattedASN1S toPubKeyDSA+ fromASN1 = runParseASN1State (parseFormatted toPubKeyDSA) instance ASN1Elem e => ProduceASN1Object e (Traditional DSA.KeyPair) where asn1s (Traditional (DSA.KeyPair params pub priv)) =@@ -442,27 +528,27 @@ return (Traditional $ DSA.KeyPair params pub priv) instance ASN1Elem e => ProduceASN1Object e (Modern DSA.KeyPair) where- asn1s (Modern attrs (DSA.KeyPair params _ priv)) =+ asn1s (Modern attrs mPub (DSA.KeyPair params _ priv)) = asn1Container Sequence (v . alg . bs . att) where- v = gIntVal 0+ v = versionASN1S mPub alg = asn1Container Sequence (oid . pr) oid = gOID [1,2,840,10040,4,1] pr = asn1Container Sequence (pqgASN1S params) bs = gOctetString (encodeASN1S $ gIntVal priv)- att = attributesASN1S (Container Context 0) attrs+ att = attrKeysASN1S attrs mPub instance Monoid e => ParseASN1Object e (Modern DSA.KeyPair) where parse = onNextContainer Sequence $ do- skipVersion+ v2 <- parseVersion params <- onNextContainer Sequence $ do OID [1,2,840,10040,4,1] <- getNext onNextContainer Sequence parsePQG- (attrs, bs) <- parseAttrKeys+ (attrs, bs, mPub) <- parseAttrKeys v2 case decodeASN1' BER bs of Right [IntVal priv] -> let pub = DSA.calculatePublic params priv- in return (Modern attrs $ DSA.KeyPair params pub priv)+ in return (Modern attrs mPub $ DSA.KeyPair params pub priv) Right _ -> throwParseError "PKCS8: invalid format when parsing inner DSA" Left e -> throwParseError ("PKCS8: error parsing inner DSA: " ++ show e) @@ -482,18 +568,15 @@ , DSA.params_g = g } -dsaPrivToPair :: DSA.PrivateKey -> DSA.KeyPair-dsaPrivToPair k = DSA.KeyPair params pub x- where pub = DSA.calculatePublic params x- params = DSA.private_params k- x = DSA.private_x k - -- ECDSA +toPubKeyEC :: X509.PrivKeyEC -> X509.PubKey+toPubKeyEC = keyPairToPubKey . keyPairFromPrivKey . X509.PrivKeyEC+ instance ASN1Object (FormattedKey X509.PrivKeyEC) where- toASN1 = asn1s- fromASN1 = runParseASN1State parse+ toASN1 = formattedASN1S toPubKeyEC+ fromASN1 = runParseASN1State (parseFormatted toPubKeyEC) instance ASN1Elem e => ProduceASN1Object e (Traditional X509.PrivKeyEC) where asn1s = innerEcdsaASN1S True . unTraditional@@ -502,27 +585,27 @@ parse = Traditional <$> parseInnerEcdsa Nothing instance ASN1Elem e => ProduceASN1Object e (Modern X509.PrivKeyEC) where- asn1s (Modern attrs privKey) = asn1Container Sequence (v . f . bs . att)+ asn1s (Modern attrs mPub privKey) = asn1Container Sequence (v . f . bs . att) where- v = gIntVal 0+ v = versionASN1S mPub f = asn1Container Sequence (oid . curveFnASN1S privKey) oid = gOID [1,2,840,10045,2,1] bs = gOctetString (encodeASN1S inner) inner = innerEcdsaASN1S False privKey- att = attributesASN1S (Container Context 0) attrs+ att = attrKeysASN1S attrs mPub instance Monoid e => ParseASN1Object e (Modern X509.PrivKeyEC) where parse = onNextContainer Sequence $ do- skipVersion+ v2 <- parseVersion f <- onNextContainer Sequence $ do OID [1,2,840,10045,2,1] <- getNext parseCurveFn- (attrs, bs) <- parseAttrKeys+ (attrs, bs, mPub) <- parseAttrKeys v2 let inner = decodeASN1' BER bs strError = Left . ("PKCS8: error decoding inner EC: " ++) . show case either strError (runParseASN1 $ parseInnerEcdsa $ Just f) inner of Left err -> throwParseError ("PKCS8: error parsing inner EC: " ++ err)- Right privKey -> return (Modern attrs privKey)+ Right privKey -> return (Modern attrs mPub privKey) innerEcdsaASN1S :: ASN1Elem e => Bool -> X509.PrivKeyEC -> ASN1Stream e innerEcdsaASN1S addC k@@ -631,69 +714,55 @@ -- X25519, X448, Ed25519, Ed448 instance ASN1Elem e => ProduceASN1Object e (Modern X25519.SecretKey) where- asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)- where- v = gIntVal 0- alg = asn1Container Sequence (gOID [1,3,101,110])- bs = innerEddsaASN1S privKey- att = attributesASN1S (Container Context 0) attrs+ asn1s = produceModernEddsa [1,3,101,110] instance Monoid e => ParseASN1Object e (Modern X25519.SecretKey) where- parse = onNextContainer Sequence $ do- skipVersion- onNextContainer Sequence $ do { OID [1,3,101,110] <- getNext; return () }- (attrs, bs) <- parseAttrKeys- Modern attrs <$> parseInnerEddsa "X25519" X25519.secretKey bs+ parse = parseModernEddsa "X25519" [1,3,101,110] X25519.secretKey instance ASN1Elem e => ProduceASN1Object e (Modern X448.SecretKey) where- asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)- where- v = gIntVal 0- alg = asn1Container Sequence (gOID [1,3,101,111])- bs = innerEddsaASN1S privKey- att = attributesASN1S (Container Context 0) attrs+ asn1s = produceModernEddsa [1,3,101,111] instance Monoid e => ParseASN1Object e (Modern X448.SecretKey) where- parse = onNextContainer Sequence $ do- skipVersion- onNextContainer Sequence $ do { OID [1,3,101,111] <- getNext; return () }- (attrs, bs) <- parseAttrKeys- Modern attrs <$> parseInnerEddsa "X448" X448.secretKey bs+ parse = parseModernEddsa "X448" [1,3,101,111] X448.secretKey instance ASN1Elem e => ProduceASN1Object e (Modern Ed25519.SecretKey) where- asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)- where- v = gIntVal 0- alg = asn1Container Sequence (gOID [1,3,101,112])- bs = innerEddsaASN1S privKey- att = attributesASN1S (Container Context 0) attrs+ asn1s = produceModernEddsa [1,3,101,112] instance Monoid e => ParseASN1Object e (Modern Ed25519.SecretKey) where- parse = onNextContainer Sequence $ do- skipVersion- onNextContainer Sequence $ do { OID [1,3,101,112] <- getNext; return () }- (attrs, bs) <- parseAttrKeys- Modern attrs <$> parseInnerEddsa "Ed25519" Ed25519.secretKey bs+ parse = parseModernEddsa "Ed25519" [1,3,101,112] Ed25519.secretKey instance ASN1Elem e => ProduceASN1Object e (Modern Ed448.SecretKey) where- asn1s (Modern attrs privKey) = asn1Container Sequence (v . alg . bs . att)- where- v = gIntVal 0- alg = asn1Container Sequence (gOID [1,3,101,113])- bs = innerEddsaASN1S privKey- att = attributesASN1S (Container Context 0) attrs+ asn1s = produceModernEddsa [1,3,101,113] instance Monoid e => ParseASN1Object e (Modern Ed448.SecretKey) where- parse = onNextContainer Sequence $ do- skipVersion- onNextContainer Sequence $ do { OID [1,3,101,113] <- getNext; return () }- (attrs, bs) <- parseAttrKeys- Modern attrs <$> parseInnerEddsa "Ed448" Ed448.secretKey bs+ parse = parseModernEddsa "Ed448" [1,3,101,113] Ed448.secretKey +-- * Producer helpers++produceModernEddsa :: (ASN1Elem e, ByteArrayAccess key) => OID -> Modern key -> ASN1Stream e+produceModernEddsa oid (Modern attrs mPub privKey) = asn1Container Sequence (v . alg . bs . att)+ where+ v = versionASN1S mPub+ alg = asn1Container Sequence (gOID oid)+ bs = innerEddsaASN1S privKey+ att = attrKeysASN1S attrs mPub+ innerEddsaASN1S :: (ASN1Elem e, ByteArrayAccess key) => key -> ASN1Stream e innerEddsaASN1S key = gOctetString (encodeASN1S inner) where inner = gOctetString (convert key) +-- * Parser helpers++parseModernEddsa :: Monoid e => String -> OID -> (B.ByteString -> CryptoFailable a) -> ParseASN1 e (Modern a)+parseModernEddsa name expectedOid buildKey = onNextContainer Sequence $ do+ v2 <- parseVersion+ onNextContainer Sequence $ do+ OID oid <- getNext+ when (oid /= expectedOid) $+ throwParseError ("PKCS8: while parsing " ++ name ++ " expected OID " ++ show expectedOid ++ " while got " ++ show oid)+ (attrs, bs, mPub) <- parseAttrKeys v2+ Modern attrs mPub <$> parseInnerEddsa name buildKey bs+ parseInnerEddsa :: Monoid e => String -> (B.ByteString -> CryptoFailable key)@@ -712,3 +781,28 @@ CryptoPassed privKey -> return privKey CryptoFailed _ -> throwParseError ("PKCS8: parsed invalid " ++ name ++ " secret key")++versionASN1S :: ASN1Elem e => Maybe GenericPubKey -> ASN1Stream e+versionASN1S mPub = gIntVal (if isJust mPub then 1 else 0)++parseVersion :: Monoid e => ParseASN1 e Bool+parseVersion = do+ IntVal v <- getNext+ when (v /= 0 && v /= 1) $+ throwParseError ("PKCS8: parsed invalid version: " ++ show v)+ return (v /= 0)++attrKeysASN1S :: ASN1Elem e => [Attribute] -> Maybe GenericPubKey -> ASN1Stream e+attrKeysASN1S attrs mPub = att . asn1s mPub+ where att = attributesASN1S (Container Context 0) attrs++parseAttrKeys :: Monoid e+ => Bool+ -> ParseASN1 e ([Attribute], B.ByteString, Maybe GenericPubKey)+parseAttrKeys v2 = do+ OctetString bs <- getNext+ attrs <- parseAttributes (Container Context 0)+ mPub <- parse+ when (isJust mPub && not v2) $+ throwParseError "PKCS8: public key allowed only for version 2"+ return (attrs, bs, mPub)
+ src/Crypto/Store/PubKey/RSA/KEM.hs view
@@ -0,0 +1,93 @@+-- |+-- Module : Crypto.Store.PubKey.RSA.KEM+-- License : BSD-style+-- Maintainer : Olivier Chéron <olivier.cheron@gmail.com>+-- Stability : experimental+-- Portability : unknown+--+-- RSA as a Key-Encapsulation Mechanism (KEM).+module Crypto.Store.PubKey.RSA.KEM+ ( KDF(..), kdf3+ -- * Operations+ , encapsulate, encapsulateWith, decapsulate+ ) where++import Data.ByteArray (ByteArray, ByteArrayAccess, Bytes)+import qualified Data.ByteArray as B+import Data.ByteString (ByteString, empty)++import Crypto.Hash+import Crypto.Number.Generate+import Crypto.Number.Serialize (os2ip, i2ospOf_)+import qualified Crypto.PubKey.RSA.Prim as RSA+import qualified Crypto.PubKey.RSA.Types as RSA+import Crypto.Random++-- | Key derivation used by RSA-KEM.+newtype KDF bIn bOut = KDF (ByteString -> bIn -> bOut)++-- | KDF3 from ANSI X9.44-2007 (R2017)+kdf3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)+ => a -> Int -> KDF bIn bOut+kdf3 hashAlg outLen = KDF (doKDF3 hashAlg outLen)++doKDF3 :: (HashAlgorithm a, ByteArrayAccess bIn, ByteArray bOut)+ => a -> Int -> ByteString -> bIn -> bOut+doKDF3 hashAlg outLen otherInfo input+ | r == 0 = B.concat $ map doChunk [ 1 .. k ]+ | otherwise = B.take outLen $ B.concat $ map doChunk [ 1 .. k + 1 ]+ where+ (k, r) = outLen `divMod` blk+ blk = hashDigestSize hashAlg+ doChunk i =+ let ctx0 = hashInitWith hashAlg+ ctx1 = hashUpdate ctx0 (i2ospOf_ 4 (toInteger i) :: Bytes)+ ctx2 = hashUpdate ctx1 input+ ctx3 = hashUpdate ctx2 otherInfo+ in hashFinalize ctx3++-- | Generate a shared secret key and an associated ciphertext using randomness.+encapsulate :: (MonadRandom m, ByteArray ciphertext)+ => KDF ciphertext sharedSecret+ -> RSA.PublicKey+ -> m (sharedSecret, ciphertext)+encapsulate kdf pub = encap kdf pub <$> generateMax (RSA.public_n pub)++-- | Generate a shared secret key and an associated ciphertext using a+-- specified random input. This input must be an integer in range [0, n) and+-- not repeated with other encapsulations. For testing purposes.+encapsulateWith :: ByteArray ciphertext+ => KDF ciphertext sharedSecret+ -> RSA.PublicKey+ -> Integer+ -> Maybe (sharedSecret, ciphertext)+encapsulateWith kdf pub z+ | z < 0 || z >= RSA.public_n pub = Nothing+ | otherwise = Just $ encap kdf pub z++encap :: ByteArray ciphertext+ => KDF ciphertext sharedSecret+ -> RSA.PublicKey+ -> Integer+ -> (sharedSecret, ciphertext)+encap (KDF kdf) pub z = (ss, ct)+ where+ zz = i2ospOf_ (RSA.public_size pub) z+ ct = RSA.ep pub zz+ ss = kdf empty zz++-- | Return the shared secret for a given ciphertext.+decapsulate :: ByteArray ciphertext+ => KDF ciphertext sharedSecret+ -> RSA.PrivateKey+ -> ciphertext+ -> Maybe sharedSecret+decapsulate (KDF kdf) priv ct+ | B.length ct < RSA.public_size pub = Nothing+ | c >= RSA.public_n pub = Nothing+ | otherwise = Just ss+ where+ pub = RSA.private_pub priv+ c = os2ip ct+ zz = RSA.dp Nothing priv ct+ ss = kdf empty zz
src/Crypto/Store/Util.hs view
@@ -20,7 +20,9 @@ import Data.Bits import Data.ByteArray (ByteArray, ByteArrayAccess) import qualified Data.ByteArray as B-import Data.List+#if !(MIN_VERSION_base(4,20,0))+import Data.List (foldl')+#endif import Data.Memory.Endian import Data.Word
src/Crypto/Store/X509.hs view
@@ -20,6 +20,7 @@ , readPubKeyFile , readPubKeyFileFromMemory , pemToPubKey+ , pemToPubKeyAccum , writePubKeyFile , writePubKeyFileToMemory , pubKeyToPEM@@ -36,7 +37,7 @@ import Data.ASN1.Types import Data.ASN1.BinaryEncoding import Data.ASN1.Encoding-import Data.Maybe+import Data.Either (rights) import Data.Proxy import qualified Data.X509 as X509 import qualified Data.ByteString as B@@ -46,7 +47,9 @@ import Crypto.Store.ASN1.Generate import Crypto.Store.ASN1.Parse import Crypto.Store.CMS.Util+import Crypto.Store.Error import Crypto.Store.PEM+import Crypto.Store.Util -- | Class of signed objects convertible to PEM.@@ -78,24 +81,32 @@ readPubKeyFileFromMemory = either (const []) accumulate . pemParseBS accumulate :: [PEM] -> [X509.PubKey]-accumulate = catMaybes . foldr (flip pemToPubKey) []+accumulate = rights . map pemToPubKey --- | Read a public key from a 'PEM' element and add it to the accumulator list.-pemToPubKey :: [Maybe X509.PubKey] -> PEM -> [Maybe X509.PubKey]-pemToPubKey acc pem =- case decodeASN1' BER (pemContent pem) of- Left _ -> acc- Right asn1 -> run (getParser $ pemName pem) asn1 : acc+-- | Read a public key from a t'PEM' element and add it to the accumulator list.+--+-- This API is modelled after function @pemToKey@ in "Data.X509.Memory".+pemToPubKeyAccum :: [Maybe X509.PubKey] -> PEM -> [Maybe X509.PubKey]+pemToPubKeyAccum acc pem =+ case pemToPubKey pem of+ Left (DecodingError _) -> acc+ Left _ -> Nothing : acc+ Right pubKey -> Just pubKey : acc - where- run p asn1 =- case p asn1 of- Right (pubKey, []) -> Just pubKey- _ -> Nothing+-- | Read a public key from a t'PEM' element.+pemToPubKey :: PEM -> Either StoreError X509.PubKey+pemToPubKey pem = do+ asn1 <- mapLeft DecodingError $ decodeASN1' BER (pemContent pem)+ parser <- getParser (pemName pem)+ (pubKey, unparsed) <- mapLeft ParseFailure $ parser asn1+ case unparsed of+ [] -> return pubKey+ er -> Left $ ParseFailure ("pemToPubKey: remaining state " ++ show er) - getParser "PUBLIC KEY" = fromASN1- getParser "RSA PUBLIC KEY" = runParseASN1State rsapkParser- getParser _ = const (Left undefined)+ where+ getParser "PUBLIC KEY" = return fromASN1+ getParser "RSA PUBLIC KEY" = return (runParseASN1State rsapkParser)+ getParser _ = Left UnexpectedNameForPEM rsapkParser = (\(RSAPublicKey pub) -> X509.PubKeyRSA pub) <$> parse @@ -149,9 +160,6 @@ signedToPEM :: forall a. SignedObject a => X509.SignedExact a -> PEM signedToPEM obj = mkPEM (signedObjectName prx) (X509.encodeSignedObject obj) where prx = Proxy :: Proxy a--mkPEM :: String -> B.ByteString -> PEM-mkPEM name bs = PEM { pemName = name, pemHeader = [], pemContent = bs} -- RSA public keys
tests/CMS/Instances.hs view
@@ -4,7 +4,6 @@ module CMS.Instances ( arbitraryPassword , arbitraryAttributes- , arbitraryIntegrityDigest , arbitrarySigVer , arbitraryEnvDev ) where@@ -12,6 +11,7 @@ import Data.ASN1.Types import qualified Data.ByteArray as B import Data.ByteString (ByteString)+import Data.Maybe (fromJust) import Data.X509 import GHC.TypeLits@@ -22,6 +22,7 @@ import Crypto.Store.CMS import Crypto.Store.Error+import Crypto.Store.PKCS8 import X509.Instances @@ -126,7 +127,7 @@ arbitraryNat :: Gen SomeNat arbitraryNat = unwrap <$> arbitrary- where unwrap (Positive i) = let Just n = someNatVal (127 + i) in n+ where unwrap (Positive i) = let n = someNatVal (127 + i) in fromJust n instance Arbitrary DigestAlgorithm where arbitrary = oneof@@ -138,25 +139,33 @@ , pure $ DigestAlgorithm SHA256 , pure $ DigestAlgorithm SHA384 , pure $ DigestAlgorithm SHA512+ , pure $ DigestAlgorithm SHA3_224+ , pure $ DigestAlgorithm SHA3_256+ , pure $ DigestAlgorithm SHA3_384+ , pure $ DigestAlgorithm SHA3_512 , pure $ DigestAlgorithm SHAKE128_256 , pure $ DigestAlgorithm SHAKE256_512 , (\(SomeNat p) -> DigestAlgorithm (SHAKE128 p)) <$> arbitraryNat , (\(SomeNat p) -> DigestAlgorithm (SHAKE256 p)) <$> arbitraryNat ] -arbitraryIntegrityDigest :: Gen DigestAlgorithm-arbitraryIntegrityDigest = elements+arbitraryDigest :: Gen DigestAlgorithm+arbitraryDigest = elements [ DigestAlgorithm MD5 , DigestAlgorithm SHA1 , DigestAlgorithm SHA224 , DigestAlgorithm SHA256 , DigestAlgorithm SHA384 , DigestAlgorithm SHA512+ , DigestAlgorithm SHA3_224+ , DigestAlgorithm SHA3_256+ , DigestAlgorithm SHA3_384+ , DigestAlgorithm SHA3_512 ] instance Arbitrary MACAlgorithm where arbitrary = oneof- [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest+ [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryDigest , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx ]@@ -203,33 +212,30 @@ , pure $ ECDSA (DigestAlgorithm SHA256) , pure $ ECDSA (DigestAlgorithm SHA384) , pure $ ECDSA (DigestAlgorithm SHA512)+ , pure $ ECDSA (DigestAlgorithm SHA3_224)+ , pure $ ECDSA (DigestAlgorithm SHA3_256)+ , pure $ ECDSA (DigestAlgorithm SHA3_384)+ , pure $ ECDSA (DigestAlgorithm SHA3_512) , pure Ed25519 , pure Ed448 ] -arbitraryKeyPair :: SignatureAlg -> Gen (PubKey, PrivKey)-arbitraryKeyPair RSAAnyHash = do- (pub, priv) <- arbitraryRSA- return (PubKeyRSA pub, PrivKeyRSA priv)-arbitraryKeyPair (RSA _) = do- (pub, priv) <- arbitraryRSA- return (PubKeyRSA pub, PrivKeyRSA priv)-arbitraryKeyPair (RSAPSS _) = do- (pub, priv) <- arbitraryRSA- return (PubKeyRSA pub, PrivKeyRSA priv)-arbitraryKeyPair (DSA _) = do- (pub, priv) <- arbitraryDSA- return (PubKeyDSA pub, PrivKeyDSA priv)-arbitraryKeyPair (ECDSA _) = do- (pub, priv) <- arbitraryNamedEC- return (PubKeyEC pub, PrivKeyEC priv)-arbitraryKeyPair Ed25519 = do- (pub, priv) <- arbitraryEd25519- return (PubKeyEd25519 pub, PrivKeyEd25519 priv)-arbitraryKeyPair Ed448 = do- (pub, priv) <- arbitraryEd448- return (PubKeyEd448 pub, PrivKeyEd448 priv)+arbitraryKeyPair :: SignatureAlg -> Gen KeyPair+arbitraryKeyPair RSAAnyHash =+ keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA+arbitraryKeyPair (RSA _) =+ keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA+arbitraryKeyPair (RSAPSS _) =+ keyPairFromPrivKey . PrivKeyRSA . snd <$> arbitraryRSA+arbitraryKeyPair (DSA _) =+ keyPairFromPrivKey . PrivKeyDSA <$> arbitraryPrivDSA+arbitraryKeyPair (ECDSA _) =+ keyPairFromPrivKey . PrivKeyEC . snd <$> arbitraryNamedEC+arbitraryKeyPair Ed25519 =+ keyPairFromPrivKey . PrivKeyEd25519 . snd <$> arbitraryEd25519+arbitraryKeyPair Ed448 =+ keyPairFromPrivKey . PrivKeyEd448 . snd <$> arbitraryEd448 arbitrarySigVer :: SignatureAlg -> Gen ([ProducerOfSI Gen], ConsumerOfSI Gen) arbitrarySigVer alg = sized $ \n -> do@@ -239,11 +245,12 @@ return (sigFns, verFn) where onePair = do- (pub, priv) <- arbitraryKeyPair alg+ pair <- arbitraryKeyPair alg+ let pub = keyPairToPubKey pair chain <- arbitraryCertificateChain pub sAttrs <- oneof [ pure Nothing, Just <$> arbitraryAttributes ] uAttrs <- arbitraryAttributes- return (certSigner alg priv chain sAttrs uAttrs, withPublicKey pub)+ return (certSigner alg pair chain sAttrs uAttrs, withPublicKey pub) instance Arbitrary PBKDF2_PRF where arbitrary = elements@@ -276,13 +283,19 @@ , CFB Camellia128 , CTR Camellia128++ , DeriveHKDF SHA256 ] instance Arbitrary ContentEncryptionParams where arbitrary = arbitrary >>= gen where+ gen (ECB c) = return (ecbParams c)+ gen (CBC c) = generateCBCParams c gen CBC_RC2 = choose (24, 512) >>= generateRC2EncryptionParams- gen alg = generateEncryptionParams alg+ gen (CFB c) = generateCFBParams c+ gen (CTR c) = generateCTRParams c+ gen (DeriveHKDF _) = deriveEncryptionKey <$> arbitrary instance Arbitrary AuthContentEncryptionAlg where arbitrary = elements@@ -297,22 +310,23 @@ , GCM AES128 , GCM AES192 , GCM AES256++ , AuthDeriveHKDF SHA256 ] instance Arbitrary AuthContentEncryptionParams where arbitrary = do alg <- arbitrary case alg of- AUTH_ENC_128 -> arb3 generateAuthEnc128Params- AUTH_ENC_256 -> arb3 generateAuthEnc256Params+ AUTH_ENC_128 -> arb3 authEnc128Params+ AUTH_ENC_256 -> arb3 authEnc256Params CHACHA20_POLY1305 -> generateChaChaPoly1305Params CCM c -> do m <- arbitraryM l <- arbitraryL generateCCMParams c m l GCM c -> choose (12,16) >>= generateGCMParams c- where arb3 fn = do- a <- arbitrary; b <- arbitrary; c <- arbitrary- fn a b c+ AuthDeriveHKDF _ -> authDeriveEncryptionKey <$> arbitrary+ where arb3 fn = fn <$> arbitrary <*> arbitrary <*> arbitrary arbitraryM :: Gen CCM_M arbitraryM = elements@@ -350,6 +364,18 @@ , scryptKeyLength = Nothing } +instance Arbitrary KeyDerivationFn where+ arbitrary = elements (map HKDF digests ++ map KDF3 digests)+ where+ digests = [ DigestAlgorithm SHA256+ , DigestAlgorithm SHA384+ , DigestAlgorithm SHA512+ , DigestAlgorithm SHA3_224+ , DigestAlgorithm SHA3_256+ , DigestAlgorithm SHA3_384+ , DigestAlgorithm SHA3_512+ ]+ instance Arbitrary KeyTransportParams where arbitrary = oneof [ pure RSAES@@ -411,25 +437,26 @@ return (envFns, devFn) where len = B.length cek- onePair = oneof [ arbitraryKT, arbitraryKA, arbitraryKEK, arbitraryPW ]+ onePair = oneof [ arbitraryKT, arbitraryKA, arbitraryKEK, arbitraryPW, arbitraryKEM ] arbitraryKT = do (pub, priv) <- arbitraryLargeRSA+ let pair = keyPairFromPrivKey (PrivKeyRSA priv) cert <- arbitrarySignedCertificate (PubKeyRSA pub) ktp <- arbitrary let envFn = forKeyTransRecipient cert ktp- devFn = withRecipientKeyTrans (PrivKeyRSA priv)+ devFn = withRecipientKeyTrans pair return (envFn, devFn) arbitraryKA = do- (cert, priv) <- arbitraryDHParams+ (cert, pair) <- arbitraryDHParams let allowCofactorDH =- case priv of+ case keyPairToPrivKey pair of PrivKeyEC _ -> True _ -> False kap <- arbitraryAlg >>= arbitraryAgreeParams allowCofactorDH let envFn = forKeyAgreeRecipient cert kap- devFn = withRecipientKeyAgree priv cert+ devFn = withRecipientKeyAgree pair cert return (envFn, devFn) arbitraryKEK = do@@ -441,10 +468,18 @@ arbitraryPW = do pwd <- arbitraryPassword kdf <- arbitrary- cea <- arbitrary `suchThat` notModeCTR+ cea <- arbitrary `suchThat` compatiblePW let es = PWRIKEK cea return (forPasswordRecipient pwd kdf es, withRecipientPassword pwd) + arbitraryKEM = do+ (cert, pair, params) <- arbitraryKEMParams+ kdf <- arbitrary+ kep <- arbitraryAlg+ let envFn = forKeyEncapRecipient cert kdf kep params+ devFn = withRecipientKeyEncap pair cert+ return (envFn, devFn)+ arbitraryAlg | len == 24 = oneof [ return AES128_WRAP , return AES192_WRAP@@ -474,27 +509,44 @@ , arbitraryCredX448 ] + arbitraryKEMParams = arbitraryCredRSA >>= rsaKemToKEM++ rsaKemToKEM (cert, pair) = do+ kdf <- arbitrary+ keyLen <- choose (8,32)+ return (cert, pair, KeyEncapsulationRSA kdf keyLen)+ arbitraryCredNamedEC = do (pub, priv) <- arbitraryNamedEC+ let pair = keyPairFromPrivKey (PrivKeyEC priv) cert <- arbitrarySignedCertificate (PubKeyEC pub)- return (cert, PrivKeyEC priv)+ return (cert, pair) arbitraryCredX25519 = do (pub, priv) <- arbitraryX25519+ let pair = keyPairFromPrivKey (PrivKeyX25519 priv) cert <- arbitrarySignedCertificate (PubKeyX25519 pub)- return (cert, PrivKeyX25519 priv)+ return (cert, pair) arbitraryCredX448 = do (pub, priv) <- arbitraryX448+ let pair = keyPairFromPrivKey (PrivKeyX448 priv) cert <- arbitrarySignedCertificate (PubKeyX448 pub)- return (cert, PrivKeyX448 priv)+ return (cert, pair) - -- key wrapping in PWRIKEK is incompatible with CTR mode so we must never- -- generate this combination- notModeCTR params =+ arbitraryCredRSA = do+ (pub, priv) <- arbitraryRSA+ let pair = keyPairFromPrivKey (PrivKeyRSA priv)+ cert <- arbitrarySignedCertificate (PubKeyRSA pub)+ return (cert, pair)++ -- key wrapping in PWRIKEK is incompatible with CTR mode or HKDF key+ -- derivation, so we must never generate these combinations+ compatiblePW params = case getContentEncryptionAlg params of- CTR _ -> False- _ -> True+ CTR _ -> False+ DeriveHKDF _ -> False+ _ -> True instance Arbitrary OriginatorInfo where arbitrary = OriginatorInfo <$> arbitrary <*> arbitrary
tests/CMS/Tests.hs view
@@ -1,4 +1,5 @@ -- | CMS tests.+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module CMS.Tests (cmsTests) where import Control.Monad@@ -180,12 +181,16 @@ ci' = toAttachedCI dd' ci @?= ci' where path = testFile "cms-digested-data.pem"- algs = [ ("MD5", DigestAlgorithm MD5)- , ("SHA1", DigestAlgorithm SHA1)- , ("SHA224", DigestAlgorithm SHA224)- , ("SHA256", DigestAlgorithm SHA256)- , ("SHA384", DigestAlgorithm SHA384)- , ("SHA512", DigestAlgorithm SHA512)+ algs = [ ("MD5", DigestAlgorithm MD5)+ , ("SHA1", DigestAlgorithm SHA1)+ , ("SHA224", DigestAlgorithm SHA224)+ , ("SHA256", DigestAlgorithm SHA256)+ , ("SHA384", DigestAlgorithm SHA384)+ , ("SHA512", DigestAlgorithm SHA512)+ , ("SHA3_224", DigestAlgorithm SHA3_224)+ , ("SHA3_256", DigestAlgorithm SHA3_256)+ , ("SHA3_384", DigestAlgorithm SHA3_384)+ , ("SHA3_512", DigestAlgorithm SHA3_512) ] encryptedDataTests :: TestTree@@ -295,6 +300,25 @@ , "SHA512" ] +rfc9690Tests :: TestTree+rfc9690Tests = testCase "rfc9690" $ do+ keys <- readKeyFile path+ length keys @?= 1+ certs <- readSignedObject path+ length certs @?= 1+ cms <- readCMSFile path+ length cms @?= 1++ let Right pair = recover (fromString "not-used") key+ [EnvelopedDataCI envelopedEncapData] = cms+ [cert] = certs+ [key] = keys++ envelopedData <- fromAttached envelopedEncapData+ result <- openEnvelopedData (withRecipientKeyEncap pair cert) envelopedData+ result @?= Right (DataCI $ fromString "Hello, world!")+ where path = testFile "rfc9690.pem"+ propertyTests :: TestTree propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties" [ testProperty "marshalling" $ \l ->@@ -366,5 +390,6 @@ , digestedDataTests , encryptedDataTests , authEnvelopedDataTests+ , rfc9690Tests , propertyTests ]
tests/KeyWrap/RC2.hs view
@@ -6,6 +6,7 @@ import Data.ByteString (ByteString) import qualified Data.ByteString as B+import Data.Maybe (fromJust) import Crypto.Cipher.Types import Crypto.Error@@ -68,12 +69,11 @@ initCipher ekl k = throwCryptoError (rc2WithEffectiveKeyLength ekl k) wrapUnwrapProperty :: TestKey RC2 -> TestIV RC2 -> Message -> Gen Property- wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) = do+ wrapUnwrapProperty (Key key) iv (Message msg) = do ekl <- choose (1, 1024) let ctx = initCipher ekl key- wrapped <- wrap ctx iv msg+ wrapped <- wrap ctx (unIV iv) msg return $ (wrapped >>= unwrap ctx) === Right msg- where Just iv = makeIV ivBs makeTest :: Integer -> Vector -> TestTree makeTest i Vector{..} =@@ -82,7 +82,7 @@ , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext) ] where ctx = initCipher vecEKL vecKey- Just iv = makeIV vecIV+ iv = fromJust $ makeIV vecIV doWrap = wrap' Left withRandomPad withRandomPad f len | B.length vecPad /= len = Left (InvalidInput "unexpected length")
tests/KeyWrap/TripleDES.hs view
@@ -5,6 +5,7 @@ module KeyWrap.TripleDES (tripledeskwTests) where import Data.ByteString (ByteString, pack)+import Data.Maybe (fromJust) import Crypto.Cipher.TripleDES import Crypto.Cipher.Types@@ -62,10 +63,9 @@ initCipher k = throwCryptoError (cipherInit k) wrapUnwrapProperty :: TestKey cipher -> TestIV cipher -> Message -> Property- wrapUnwrapProperty (Key key) (IV ivBs) (Message msg) =- (wrap ctx iv msg >>= unwrap ctx) === Right msg+ wrapUnwrapProperty (Key key) iv (Message msg) =+ (wrap ctx (unIV iv) msg >>= unwrap ctx) === Right msg where ctx = initCipher key- Just iv = makeIV ivBs makeTest :: Integer -> Vector -> TestTree makeTest i Vector{..} =@@ -74,7 +74,7 @@ , testCase "Unwrap" (unwrap ctx vecCiphertext @?= Right vecPlaintext) ] where ctx = initCipher vecKey- Just iv = makeIV vecIV+ iv = fromJust $ makeIV vecIV tripledeskwTests :: TestTree tripledeskwTests = testGroup "KeyWrap.TripleDES"
tests/PKCS12/Instances.hs view
@@ -1,18 +1,23 @@+{-# LANGUAGE CPP #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Orphan instances. module PKCS12.Instances ( arbitraryPassword , arbitraryAlias- , arbitraryIntegrityParams+ , arbitraryTraditionalIntegrity+ , arbitraryAuthSchemeIntegrity , arbitraryPKCS12 ) where import qualified Data.ByteArray as B import Data.ByteString (ByteString)+#if !(MIN_VERSION_base(4,11,0)) import Data.Semigroup+#endif import Test.Tasty.QuickCheck +import Crypto.Store.CMS import Crypto.Store.PKCS12 import Crypto.Store.PKCS5 @@ -26,8 +31,30 @@ arbitraryAlias = resize 16 asciiChar where asciiChar = listOf $ choose ('\x20','\x7f') -arbitraryIntegrityParams :: Gen IntegrityParams-arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary+instance Arbitrary PBMAC1Parameter where+ arbitrary = PBMAC1Parameter <$> arbitrary <*> arbitrary++arbitraryAuthScheme :: Gen AuthenticationScheme+arbitraryAuthScheme = PBMAC1 <$> arbitrary++arbitraryIntegrityDigest :: Gen DigestAlgorithm+arbitraryIntegrityDigest = elements+ [ DigestAlgorithm MD2+ , DigestAlgorithm MD4+ , DigestAlgorithm MD5+ , DigestAlgorithm SHA1+ , DigestAlgorithm SHA224+ , DigestAlgorithm SHA256+ , DigestAlgorithm SHA384+ , DigestAlgorithm SHA512+ ]++arbitraryTraditionalIntegrity :: Gen IntegrityParams+arbitraryTraditionalIntegrity =+ TraditionalIntegrity <$> arbitraryIntegrityDigest <*> arbitrary++arbitraryAuthSchemeIntegrity :: Gen IntegrityParams+arbitraryAuthSchemeIntegrity = AuthSchemeIntegrity <$> arbitraryAuthScheme arbitraryPKCS12 :: ProtectionPassword -> Gen PKCS12 arbitraryPKCS12 pwd = do
tests/PKCS12/Tests.hs view
@@ -6,6 +6,7 @@ import Data.PEM (pemContent) import Data.String (fromString) +import Crypto.Store.Error import Crypto.Store.PKCS12 import Crypto.Store.PKCS8 import Crypto.Store.X509 (readSignedObject)@@ -59,6 +60,8 @@ integrityModes = [ "SHA-1" , "SHA-256" , "SHA-384"+ , "PBMAC1 with SHA-256"+ , "PBMAC1 with SHA-512" ] privacyModes = [ "aes-128-cbc"@@ -92,6 +95,40 @@ , ("GnuTLS with --null-password", 1, 1) ] +testRfc9579 :: TestTree+testRfc9579 = testCaseSteps "rfc9579" $ \step -> do+ step "Reading PKCS #12 files"+ pems <- readPEMs path+ length pems @?= length infos++ forM_ (zip infos pems) $ \((name, integrityError), pem) -> do+ let r = readP12FileFromMemory (pemContent pem)++ assertRight r $ \integrity -> case integrityError of+ Nothing ->+ assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->+ assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do+ step ("Testing " ++ name)+ assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->+ length keys @?= 1+ length (getAllSafeX509Certs scs) @?= 1+ Just expectedErr ->+ assertLeft (recoverAuthenticated pwd integrity) $ \err -> do+ err @?= expectedErr+ step ("Testing " ++ name)+ where+ pwd = fromString "1234"++ path = testFile "pkcs12-rfc9579.pem"+ infos = [ ("SHA-256 HMAC and PRF", Nothing)+ , ("SHA-256 HMAC and SHA-512 PRF", Nothing)+ , ("SHA-512 HMAC and PRF", Nothing)+ , ("Incorrect Iteration Count", Just BadContentMAC)+ , ("Incorrect Salt", Just BadContentMAC)+ , ("Missing Key Length",+ Just $ InvalidParameter "KDF key length must be explicit")+ ]+ propertyTests :: TestTree propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties" [ testProperty "marshalling" $ do@@ -100,27 +137,37 @@ let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c unused = fromString "not-used" return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)- , testProperty "marshalling with authentication" $ do- params <- arbitraryIntegrityParams+ , testProperty "marshalling with traditional authentication" $ do+ params <- arbitraryTraditionalIntegrity c <- arbitrary >>= arbitraryPKCS12 pI <- arbitrary let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c p = fromProtectionPassword pI return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)+ , testProperty "marshalling with authentication scheme" $ do+ params <- arbitraryAuthSchemeIntegrity+ c <- arbitrary >>= arbitraryPKCS12+ pI <- arbitrary `suchThat` (/= emptyNotTerminated)+ let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c+ p = fromProtectionPassword pI+ return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r) , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $ \pChain pKey privKey ->+ supportedAsEncodedPubKey privKey ==> testCredConv privKey toCredential (fromCredential pChain pKey) , localOption (QuickCheckTests 20) $ testProperty "converting named credentials" $- \pChain pKey privKey -> do- name <- arbitraryAlias- testCredConv privKey- (toNamedCredential name)- (fromNamedCredential name pChain pKey)+ \pChain pKey privKey ->+ supportedAsEncodedPubKey privKey ==> do+ name <- arbitraryAlias+ testCredConv privKey+ (toNamedCredential name)+ (fromNamedCredential name pChain pKey) ] where testCredConv privKey to from = do pwd <- arbitrary- chain <- arbitrary >>= arbitraryCertificateChain+ let pub = keyPairToPubKey (keyPairFromPrivKey privKey)+ chain <- arbitraryCertificateChain pub chain' <- shuffleCertificateChain chain let cred = (chain, privKey) r = from pwd (chain', privKey) >>= recover pwd . to@@ -132,5 +179,6 @@ [ testType "RSA" "rsa" , testType "Ed25519" "ed25519" , testEmptyPassword+ , testRfc9579 , propertyTests ]
tests/PKCS8/Instances.hs view
@@ -40,14 +40,18 @@ , PBE_SHA1_RC2_40 <$> arbitrary ] +instance Arbitrary KeyPair where+ arbitrary = keyPairFromPrivKey <$> arbitrary+ instance Arbitrary PrivateKeyFormat where arbitrary = elements [ TraditionalFormat, PKCS8Format ] -instance Arbitrary (FormattedKey PrivKey) where+instance Arbitrary (FormattedKey KeyPair) where arbitrary = do- key <- arbitrary+ pair <- arbitrary+ let key = keyPairToPrivKey pair fmt <- if pkcs8only key then return PKCS8Format else arbitrary- return (FormattedKey fmt key)+ return (FormattedKey fmt pair) pkcs8only :: PrivKey -> Bool pkcs8only (PrivKeyX25519 _) = True
tests/PKCS8/Tests.hs view
@@ -1,4 +1,5 @@ -- | PKCS #8 tests.+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module PKCS8.Tests (pkcs8Tests) where import qualified Data.ByteString as B@@ -94,6 +95,12 @@ , encryptedKeyTests prefix ] +rfc8410Tests :: TestTree+rfc8410Tests = testCase "rfc8410" $ do+ keys <- readKeyFile path+ length keys @?= 2+ where path = testFile "rfc8410.pem"+ propertyTests :: TestTree propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties" [ testProperty "marshalling" $ \fmt l ->@@ -116,5 +123,6 @@ , testType "X448" OnlyOuter "x448" , testType "Ed25519" OnlyOuter "ed25519" , testType "Ed448" OnlyOuter "ed448"+ , rfc8410Tests , propertyTests ]
tests/Util.hs view
@@ -2,18 +2,19 @@ -- | Test utilities. module Util ( assertJust+ , assertLeft , assertRight , getAttached , getDetached , testFile , TestKey(..)- , TestIV(..)+ , TestIV, unIV ) where import Control.Monad (when) import Data.ByteString (ByteString, pack)-import Data.Maybe (isNothing)+import Data.Maybe (fromJust, isNothing) import Crypto.Cipher.Types import Crypto.Store.CMS@@ -25,6 +26,11 @@ assertJust (Just a) f = f a assertJust Nothing _ = assertFailure "expecting Just but got Nothing" +assertLeft :: Show b => Either a b -> (a -> Assertion) -> Assertion+assertLeft (Left a) f = f a+assertLeft (Right val) _ =+ assertFailure ("expecting Left but got: Right " ++ show val)+ assertRight :: Show a => Either a b -> (b -> Assertion) -> Assertion assertRight (Right b) f = f b assertRight (Left val) _ =@@ -35,14 +41,14 @@ let m = fromAttached e when (isNothing m) $ assertFailure "expecting attached but got detached content"- let Just r = m in return r+ return $! fromJust m getDetached :: Encapsulates struct => a -> struct (Encap a) -> IO (struct a) getDetached c e = do let m = fromDetached c e when (isNothing m) $ assertFailure "expecting detached but got attached content"- let Just r = m in return r+ return $! fromJust m testFile :: String -> FilePath testFile name = "tests/files/" ++ name@@ -58,6 +64,9 @@ where cipher = undefined :: cipher newtype TestIV cipher = IV ByteString deriving (Show, Eq)++unIV :: BlockCipher cipher => TestIV cipher -> IV cipher+unIV (IV ivBS) = fromJust $ makeIV ivBS instance BlockCipher cipher => Arbitrary (TestIV cipher) where arbitrary = IV . pack <$> vector (blockSize cipher)
tests/X509/Instances.hs view
@@ -4,7 +4,7 @@ ( arbitraryOID , arbitraryRSA , arbitraryLargeRSA- , arbitraryDSA+ , arbitraryPrivDSA , arbitraryNamedEC , arbitraryX25519 , arbitraryX448@@ -13,6 +13,7 @@ , arbitrarySignedCertificate , arbitraryCertificateChain , shuffleCertificateChain+ , supportedAsEncodedPubKey ) where import Control.Monad (zipWithM)@@ -58,7 +59,7 @@ instance Arbitrary PubKey where arbitrary = oneof [ PubKeyRSA . fst <$> arbitraryRSA- , PubKeyDSA . fst <$> arbitraryDSA+ , PubKeyDSA <$> arbitraryPubDSA , PubKeyEC . fst <$> arbitraryNamedEC --, PubKeyEC . fst <$> arbitraryExplicitPrimeCurve , PubKeyX25519 . fst <$> arbitraryX25519@@ -69,7 +70,7 @@ instance Arbitrary PrivKey where arbitrary = oneof [ PrivKeyRSA . snd <$> arbitraryRSA- , PrivKeyDSA . snd <$> arbitraryDSA+ , PrivKeyDSA <$> arbitraryPrivDSA , PrivKeyEC . snd <$> arbitraryNamedEC , PrivKeyEC . snd <$> arbitraryExplicitPrimeCurve , PrivKeyX25519 . snd <$> arbitraryX25519@@ -78,6 +79,12 @@ , PrivKeyEd448 . snd <$> arbitraryEd448 ] +-- package 'x509' has not implemented the serialization of PrivKeyEC_Prime+-- so we cannot use it in a certificate+supportedAsEncodedPubKey :: PrivKey -> Bool+supportedAsEncodedPubKey (PrivKeyEC PrivKeyEC_Prime{}) = False+supportedAsEncodedPubKey _ = True+ arbitraryRSA :: Gen (RSA.PublicKey, RSA.PrivateKey) arbitraryRSA = do n <- elements [ 768, 1024 ] -- enough bits to sign with SHA-512@@ -90,13 +97,11 @@ e <- elements [ 3, 0x10001 ] RSA.generate (n `div` 8) e -arbitraryDSA :: Gen (DSA.PublicKey, DSA.PrivateKey)+arbitraryDSA :: Gen DSA.KeyPair arbitraryDSA = do x <- DSA.generatePrivate params let y = DSA.calculatePublic params x- priv = DSA.PrivateKey { DSA.private_params = params, DSA.private_x = x }- pub = DSA.PublicKey { DSA.public_params = params, DSA.public_y = y }- return (pub, priv)+ return (DSA.KeyPair params y x) where -- DSA parameters were generated using 'openssl dsaparam -C 2048' params = DSA.Params@@ -104,6 +109,12 @@ , DSA.params_g = 0x10E51AEA37880C5E52DD477ED599D55050C47012D038B9E4B3199C9DE9A5B873B1ABC8B954F26AFEA6C028BCE1783CFE19A88C64E4ED6BFD638802A78457A5C25ABEA98BE9C6EF18A95504C324315EABE7C1EA50E754591E3EFD3D33D4AE47F82F8978ABC871C135133767ACC60683F065430C749C43893D73596B12D5835A78778D0140B2F63B32A5658308DD5BA6BBC49CF6692929FA6A966419404F9A2C216860E3F339EDDB49AD32C294BDB4C9C6BB0D1CC7B691C65968C3A0A5106291CD3810147C8A16B4BFE22968AD9D3890733F4AA9ACD8687A5B981653A4B1824004639956E8C1EDAF31A8224191E8ABD645D2901F5B164B4B93F98039A6EAEC6088 , DSA.params_q = 0xE1FDFADD32F46B5035EEB3DB81F9974FBCA69BE2223E62FCA8C77989B2AACDF7 }++arbitraryPrivDSA :: Gen DSA.PrivateKey+arbitraryPrivDSA = DSA.toPrivateKey <$> arbitraryDSA++arbitraryPubDSA :: Gen DSA.PublicKey+arbitraryPubDSA = DSA.toPublicKey <$> arbitraryDSA arbitraryNamedEC :: Gen (PubKeyEC, PrivKeyEC) arbitraryNamedEC = do
tests/X509/Tests.hs view
@@ -1,4 +1,5 @@ -- | X.509 tests.+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} module X509.Tests (x509Tests) where import qualified Data.ByteString as B@@ -43,6 +44,12 @@ fCert = testFile (prefix ++ "-self-signed-cert.pem") fKey = testFile (prefix ++ "-public.pem") +rfc8410Tests :: TestTree+rfc8410Tests = testCase "rfc8410" $ do+ keys <- readPubKeyFile path+ length keys @?= 1+ where path = testFile "rfc8410.pem"+ propertyTests :: TestTree propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties" [ testProperty "marshalling public keys" $ \keys ->@@ -70,5 +77,6 @@ , keyTests "X448" "x448" 1 , keyTests "Ed25519" "ed25519" 1 , keyTests "Ed448" "ed448" 1+ , rfc8410Tests , propertyTests ]
tests/files/cms-digested-data.pem view
@@ -25,3 +25,22 @@ EAQOaGVsbG8sIHdvcmxkDQoEQNmRYGENk6X3fR9Sk9k++QSKUPYBvF+nGKHL4FXN QVoNz4kKv9BI4MPnsV77ikuw7kWGR3OFLWO8waCyFK+EEu0= -----END CMS-----+-----BEGIN CMS-----+MFwGCSqGSIb3DQEHBaBPME0CAQAwCwYJYIZIAWUDBAIHMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQcrHRNG/sKxDCiVbjFsYvAvrXdZNaPFFkUnKb7eg==+-----END CMS-----+-----BEGIN CMS-----+MGAGCSqGSIb3DQEHBaBTMFECAQAwCwYJYIZIAWUDBAIIMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQgead3JnYtNJOYL50QfPgWkqUtpmuiU9T7itIfOmfa+3l4=+-----END CMS-----+-----BEGIN CMS-----+MHAGCSqGSIb3DQEHBaBjMGECAQAwCwYJYIZIAWUDBAIJMB0GCSqGSIb3DQEHAaAQ+BA5oZWxsbywgd29ybGQNCgQwPvv+XafJtPR2UXRqu2t6l3BuJeoOghvrAfPbbRQ2+cr5IHYmzioNIZ1HlrP6RG0R3+-----END CMS-----+-----BEGIN CMS-----+MIGABgkqhkiG9w0BBwWgczBxAgEAMAsGCWCGSAFlAwQCCjAdBgkqhkiG9w0BBwGg+EAQOaGVsbG8sIHdvcmxkDQoEQHjsLFT6QOVe+FI6fBZhWEIHpXzDX9L4/ARGWEXp+rKVTw6DfjRFICt66Mxxqn4WbRCa/FJXAnVY1PduwTN1Fiqw=+-----END CMS-----
tests/files/ed25519-pkcs12.pem view
@@ -86,6 +86,64 @@ AA== -----END PKCS12----- -----BEGIN PKCS12-----+MIIE6QIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG+SIb3DQEFDDAkBBADZyEJTf+SYDt1cuTOQqVaAgIIADAMBggqhkiG9w0CCQUAMB0G+CWCGSAFlAwQBKgQQxjg/dHyDo8ty5b1MLNsCs4CCAkB15cjQKudM1ywzel7q2pYU+tOU6nBPPd5oblD8fiH+TeOuNb9WXLlPB1Ra5ywqYrbqbPNpFNriigV20orksM1Dk+/BCTXfxgaC4RO1Y3zNANiMCw8rafSpm5fX/tU12tc8kei0D1TcmaR3+i135Nl3xZ+zFXmm0lQ/rPHHYwW8uzAQOT9QVUmkie7P54aCRj99PCpVcv0W5sxqKYmmyfJerqZ+DtY6Q9ZMbe7jiu4bUxKZ06ggfl/UYwmIkT2xjN/EpWm0EO+hVIgIMq09kEkFQyiH+LP37rxOrOQ1dzopztALm3+8o2UiWTm53CwDJx8+0Qvb6eldLNpgUDZcrvjyRUlzF+gaQGq6OmkRxWme1A0xC/vc+fppW75YnFbKdMzDfmbxDwzjYprAiFlk+4bPmSZHuP+/mKx+jYFXubOV/4G4CrO9toHtztJVRm2O48WY8JqFKSCvlW2/PqMry0v4pxHRrKc+N1hWyhIxAYUf8+A4an5XEvuE3rwmFzHU3SlxvpWECVO+qzteY+/pMXEwdJiqSiH3+ICsGaGcnWE+UOSsVkp3hje6MIkGYMec7dakG785Zwwp+zQgZ9vXKa4rmvFg79tnL+VJbehXobf5tBbg8c525Pw4siYNaD6bk2VJbpew2F3OrwslmlhlxRPZEt/oGY+dgK+haRPhAAfVxnFav+fApm2IkR9nbEqomthrCoD5VoIWEXS3IVrvu9RAlqBOpKPp+kZ+cU472Uy+4MKJ5kME2f1b6AyuHzT/KfmItKTB4qdSCHgwggF6BgkqhkiG9w0BBwGg+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w+UjAxBgkqhkiG9w0BBQwwJAQQA137X5gsqDKTJ08FgEK6mAICCAAwDAYIKoZIhvcN+AgkFADAdBglghkgBZQMEASoEEPkEeCkd5FOqDNhqcFGwngwEQIJ4pEDx6DBFChYf+p+/XCMom7KTB9JwjxJPaYeroUVBEjz1MpOntyJetOjJzruEXGMB/cR4ARJNnS25++PnNJN5oxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQAyADUANgAgAC0AcABiAG0AYQBjADEAXwBw+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADIANQA2MH0wbTBJBgkqhkiG9w0BBQ4w+PDAsBgkqhkiG9w0BBQwwHwQIH6958Xey3xgCAggAAgEgMAwGCCqGSIb3DQIJBQAw+DAYIKoZIhvcNAgkFAAQgGHJF7GEkuv9ZXtkHlH9yDHwFRjTYsH0peoVdnVVTScME+CB+vefF3st8YAgIIAA==+-----END PKCS12-----+-----BEGIN PKCS12-----+MIIFCwIBAzCCBGMGCSqGSIb3DQEHAaCCBFQEggRQMIIETDCCAsoGCSqGSIb3DQEH+BqCCArswggK3AgEAMIICsAYJKoZIhvcNAQcBMF8GCSqGSIb3DQEFDTBSMDEGCSqG+SIb3DQEFDDAkBBBBRdQTNqGIKpGPW0+w4J0FAgIIADAMBggqhkiG9w0CCQUAMB0G+CWCGSAFlAwQBKgQQ0KQc53YHlFcirdn9bulXVoCCAkCGp/FB1JeyqO5yQqM3wRrd+CBLA+0Cg8u64CoagWGmjfYsGWiobeaUOnFc62GORJNH/vozKfU70aYRbGw0iO7La+VKzl+VPt9m5e2jC9QUVhtE/kH0jpx0fuYK68DWMklDHrLpSFHfNbZAwFzOcqojaG+oknhWAVYDrIkjFGcWWBDbHHeSQA+kEisjrnUIL7cUavu4OQVxveXNQ0DOFlnfOc6+HOZ2+9w4MvjbJUKo9cuqVPzISyLvp4jp2LGOdTOM2OCRWlZh8SelmKwfSJTKhGhx+xPMOeN+Pg7MeVJvUWOdGEmQdGGti2cBX5ysORBcjgsh1cS+tVYCPwxP1RZVIAfac+KitW8q4Q3E/RncomQs8R0lLi26kadPGj0OUoAhly7PQxe4HG0ZCHCdI2pld8YkJo+mZAU5hOG5LI/q6Le5fXnPU1XHYwU0z9CQsN3NmYoufD1yZZtA1z7fpNbyxqcOu0P+OXFE7UUNM0oF5HJpwZYM4asReGbXMnYB4DQvW35FhVp7rFD5Z0mteCF4GK/sgbv/+HhtwtNVbHp35DIUceVIJrV9qYHN3y7YVjm5RNFzfCGL8T8CuNfB3pIHvIuvqSjyd+5z/tm3/Kx/wMxqJqQIArXb531w7cac03JhH7OPAdTwneaV6VEPl1pEuIZeb129ap+jn1Rp4E62YVsqgEuky3BGrRZNuwuP34X5c6O9kwVN2WNk2FltZ+Vwq8czLblxVmZ+uQjhs6hh9GsT4tPwgX6Br+FRuTRAWG/4cQeVAU7G68EwggF6BgkqhkiG9w0BBwGg+ggFrBIIBZzCCAWMwggFfBgsqhkiG9w0BDAoBAqCBpjCBozBfBgkqhkiG9w0BBQ0w+UjAxBgkqhkiG9w0BBQwwJAQQrcodfjPmwrpE2L8ETV+uvAICCAAwDAYIKoZIhvcN+AgkFADAdBglghkgBZQMEASoEEAPPKSb0fhAIfNfyxOVQ2F0EQCRw+/jJY01yo0f3+fUbcZVUpYQtvxMlpNoTV0hdi0DwC4AO5dkBmZdvCIBgo/c/BY8jL0NNsUxFb8VyX+Xj0V7FMxgaYwIwYJKoZIhvcNAQkVMRYEFBUJzG3CAW0LFRvdr7FoWoQ3wIq6MH8G+CSqGSIb3DQEJFDFyHnAAUABLAEMAUwAxADIAIAAoAGUAZAAyADUANQAxADkAKQAg+AC0AbQBhAGMAYQBsAGcAIABzAGgAYQA1ADEAMgAgAC0AcABiAG0AYQBjADEAXwBw+AGIAawBkAGYAMgBfAG0AZAAgAHMAaABhADUAMQAyMIGeMIGNMEkGCSqGSIb3DQEF+DjA8MCwGCSqGSIb3DQEFDDAfBAhSQf9Bis7WlAICCAACAUAwDAYIKoZIhvcNAgsF+ADAMBggqhkiG9w0CCwUABEDmbcmLz5XXSQPkGAd0VfCypwFm71EP4K+IlxTVAXAC+VOChMbMfdeiCAsepDo9nWIOsg+r3KvQVtM98TyIBU4tsBAhSQf9Bis7WlAICCAA=+-----END PKCS12-----+-----BEGIN PKCS12----- MIIDXwIBAzCCAyUGCSqGSIb3DQEHAaCCAxYEggMSMIIDDjCCAhMGCSqGSIb3DQEH AaCCAgQEggIAMIIB/DCCAfgGCyqGSIb3DQEMCgEDoIIBczCCAW8GCiqGSIb3DQEJ FgGgggFfBIIBWzCCAVcwggEJoAMCAQICFEInN18eR8QZFfy9Yb39ycd56f8PMAUG
+ tests/files/pkcs12-rfc9579.pem view
@@ -0,0 +1,354 @@+-----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-----+MIIKigIBAzCCCgUGCSqGSIb3DQEHAaCCCfYEggnyMIIJ7jCCBGIGCSqGSIb3DQEH+BqCCBFMwggRPAgEAMIIESAYJKoZIhvcNAQcBMFcGCSqGSIb3DQEFDTBKMCkGCSqG+SIb3DQEFDDAcBAg9pxXxY2yscwICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQME+ASoEEK7yYaFQDi1pYwWzm9F/fs+AggPgFIT2XapyaFgDppdvLkdvaF3HXw+zjzKb+7xFC76DtVPhVTWVHD+kIss+jsj+XyvMwY0aCuAhAG/Dig+vzWomnsqB5ssw5/kTb++TMQ5PXLkNeoBmB6ArKeGc/QmCBQvQG/a6b+nXSWmxNpP+71772dmWmB8gcSJ0kF+Fj75NrIbmNiDMCb71Q8gOzBMFf6BpXf/3xWAJtxyic+tSNETfOJa8zTZb0+lV0w9+5eUmDrPUpuxEVbb0KJtIc63gRkcfrPtDd6Ii4Zzbzj2Evr4/S4hnrQBsiryVzJWy+IEjaD0y6+DmG0JwMgRuGi1wBoGowi37GMrDCOyOZWC4n5wHLtYyhR6JaElxbrhxP+H46z2USLKmZoF+YgEQgYcSBXMgP0t36+XQocFWYi2N5niy02TnctwF430FYsQlhJ+Suma4I33E808dJuMv8T/soF66HsD4Zj46hOf4nWmas7IaoSAbGKXgIa7KhGRJvij+xM3WOX0aqNi/8bhnxSA7fCmIy/7opyx5UYJFWGBSmHP1pBHBVmx7Ad8SAsB9MSsh+nbGjGiUk4h0QcOi29/M9WwFlo4urePyI8PK2qtVAmpD3rTLlsmgzguZ69L0Q/CFU+fbtqsMF0bgEuh8cfivd1DYFABEt1gypuwCUtCqQ7AXK2nQqOjsQCxVz9i9K8NDeD+aau98VAl0To2sk3/VR/QUq0PRwU1jPN5BzUevhE7SOy/ImuJKwpGqqFljYdrQmj5+jDe+LmYH9QGVRlfN8zuU+48FY8CAoeBeHn5AAPml0PYPVUnt3/jQN1+v+CahNVI++La8q1Nen+j1R44aa2I3y/pUgtzXRwK+tPrxTQbG030EU51LYJn8amPWmn3w75ZIA+MJrXWeKj44de7u4zdUsEBVC2uM44rIHM8MFjyYAwYsey0rcp0emsaxzar+7ZA67r+lDoXvvS3NqsnTXHcn3T9tkPRoee6L7Dh3x4Od96lcRwgdYT5BwyH7e34ld4VTUmJ+bDEq7Ijvn4JKrwQJh1RCC+Z/ObfkC42xAm7G010u3g08xB0Qujpdg4a7VcuWrywF+c7hLNquuaF4qoDaVwYXHH3iuX6YlJ/3siTKbYCVXPEZOAMBP9lF/OU76UMJBQNfU+0xjDx+3AhUVgnGuCsmYlK6ETDp8qOZKGyV0KrNSGtqLx3uMhd7PETeW+ML3tDQ/0+X9fMkcZHi4C2fXnoHV/qa2dGhBj4jjQ0Xh1poU6mxGn2Mebe2hDsBZkkBpnn7pK4+wP/VqXdQTwqEuvzGHLVFsCuADe40ZFBmtBrf70wG7ZkO8SUZ8Zz1IX3+S024g7yj+QRev/6x6TtkwggWEBgkqhkiG9w0BBwGgggV1BIIFcTCCBW0wggVpBgsqhkiG9w0B+DAoBAqCCBTEwggUtMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhTxzw++VptrYAICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEK9nSqc1I2t4tMVG+bWHpdtQEggTQzCwI7j34gCTvfj6nuOSndAjShGv7mN2j7WMV0pslTpq2b9Bn3vn1+Y0JMvL4E7sLrUzNU02pdOcfCnEpMFccNv2sQrLp1mOCKxu8OjSqHZLoKVL0ROVsZ+8dMECLLigDlPKRiSyLErl14tErX4/zbkUaWMROO28kFbTbubQ8YoHlRUwsKW1xLg+vfi0gRkG/zHXRfQHjX/8NStv7hXlehn7/Gy2EKPsRFhadm/iUHAfmCMkMgHTU248+JER9+nsXltd59H+IeDpj/kbxZ+YvHow9XUZKu828d3MQnUpLZ1BfJGhMBPVwbVUD+A40CiQBVdCoGtPJyalL28xoS3H0ILFCnwQOr6u0HwleNJPGHq78HUyH6Hwxnh0b0+5o163r6wTFZn5cMOxpbs/Ttd+3TrxmrYpd2XnuRme3cnaYJ0ILvpc/8eLLR7SKjD+T4JhZ0h/CfcV2WWvhpQugkY0pWrZ+EIMneB1dZB96mJVLxOi148OeSgi0PsxZMNi+YM33rTpwQT5WqOsEyDwUQpne5b8Kkt/s7EN0LJNnPyJJRL1LcqOdr6j+6YqRtPa7+a9oWJqMcuTP+bqzGRJh+3HDlFBw2Yzp9iadv4KmB2MzhStLUoi2MSjvnnkkd5Led+sshAd6WbKfF7kLAHQHT4Ai6dMEO4EKkEVF9JBtxCR4JEn6C98Lpg+Lk+rfY7gHOf+ZxtgGURwgXRY3aLUrdT55ZKgk3ExVKPzi5EhdpAau7JKhpOwyKozAp/OKWMNrz6h+obu2Mbn1B+IA60psYHHxynBgsJHv7WQmbYh8HyGfHgVvaA8pZCYqxxjpLjSJrR8B+Bu9H9xkTh7KlhxgreXYv19uAYbUd95kcox9izad6VPnovgFSb+Omdy6PJACPj6hF+W6PJbucP0YPpO0VtWtQdZZ3df1P0hZ7qvKwOPFA+gKZSckgqASfygiP9V3Zc8jIi+wjNzoDM2QT+UUJKiiGYXJUEOO9hxzFHlGj759DcNRhpgl5AgR57ofISD9yBuCAJY+PQ/aZHPFuRTrcVG3RaIbCAS73nEznKyFaLOXfzyfyaSmyhsH253tnyL1MejC+2bR+Eko/yldgFUxvU5JI+Q3KJ6Awj+PnduHXx71E4UwSuu2xXYMpxnQwI6rroQpZBX82+HhqgcLV83P8lpzQwPdHjH5zkoxmWdC0+jU/tcQfNXYpJdyoaX7tDmVclLhwl9ps/+O841pIsNLJWXwvxG6B+3LN/kw4QjwN194PopiOD7+oDm5mhttO78CrBrRxHMD/0Q+qniZjKzSZepxlZq+J792u8vtMnuzzChxu0Bf3PhIXcJNcVhwUtr0yKe/N+NvC0tm+p8wyik/BlndxN9eKbdTOi2wIi64h2QG8nOk66wQ/PSIJYwZl6eDNEQSzH/1mGCfU+QnUT17UC/p+Qgenf6Auap2GWlvsJrB7u/pytz65rtjt/ouo6Ih6EwWqwVVpGXZD0+7gVWH0Ke/Vr6aPGNvkLcmftPuDZsn9jiig3guhdeyRVf10Ox369kKWcG75q77hxE+IzSzDyUlBNbnom9SIjut3r+qVYmWONatC6q/4D0I42Lnjd3dEyZx7jmH3g/S2ASM+FzWr9pvXc61dsYOkdZ4PYa9XPUZxXFagZsoS3F1sU799+IJVU0tC0MExJTAjBgkq+hkiG9w0BCRUxFgQUwWO5DorvVWYF3BWUmAw0rUEajScwfDBtMEkGCSqGSIb3DQEF+DjA8MCwGCSqGSIb3DQEFDDAfBAhOT1QgVVNFRAICCAACASAwDAYIKoZIhvcNAgkF+ADAMBggqhkiG9w0CCQUABCB6pW2FOdcCNj87zS64NUXG36K5aXDnFHctIk5Bf4kG+3QQIb0c8OLAuMXMCAQE=+-----END PKCS12-----+-----BEGIN PKCS12-----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-----END PKCS12-----
+ tests/files/rfc8410.pem view
@@ -0,0 +1,19 @@+-----BEGIN PUBLIC KEY-----+MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE=+-----END PUBLIC KEY-----+-----BEGIN PRIVATE KEY-----+MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC+-----END PRIVATE KEY-----+-----BEGIN PRIVATE KEY-----+MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC+oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB+Z9w7lshQhqowtrbLDFw4rXAxZuE=+-----END PRIVATE KEY-----++same private key with a modified public key:++-----BEGIN PRIVATE KEY-----+MHICAQEwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC+oB8wHQYKKoZIhvcNAQkJFDEPDA1DdXJkbGUgQ2hhaXJzgSEAGb9ECWmEzf6FQbrB+Z9w7lshQhqowtrbLDFw4sXAxZuE=+-----END PRIVATE KEY-----
+ tests/files/rfc9690.pem view
@@ -0,0 +1,84 @@+-----BEGIN CMS-----+MIICXAYJKoZIhvcNAQcDoIICTTCCAkkCAQMxggIEpIICAAYLKoZIhvcNAQkQDQMw+ggHvAgEAgBSe62fJuVp01E0vFjlmgOgBtcuknDAJBgcogYxxAgIEBIIBgMBx/Cc6++Oe9sVLga/czEDYQdBVKQ6vPPJPBNJnSBlNEPu2e9dPAaF5Kp2poVIFbuXaR/5+N+rBXup9dPRSvzUKZGFj1oKI6XjL96cwie5ScS+aT0ngas57vIWrFNTjNsl8VyiiZU+E4x7JuiDXGsKn77SZJXE6t90Wikzvig/aoixZpX8BmZoc8+202cY7zN2zvwQDDlB+88SUlEB4MlgHpVkYa5XMq/NxTPr3n4O9MFN/3ZrtWkzcvYvQSG+u1z6dSGswh9bI+BlRrbiZxV1yYRh5EH2VUK9ld4m0PU6ZOeEjXMdlgjQU+jTRVRmAthiNv/jcEyYrV+kUTzCJ5ebVJ7VJe6EDx51i6A0CNUELBvcafZvRw4AA+RDWMS6i8go1V1Na0Bswk/+tffuUHCA0Pd9SMnDs3lva33TeGCF+4lRI/BMofHBviLHR6jfrOMjcPsNVweD4n27+fnT8qU7jlnb949ipVT2HgiRzbjfhkdq5U8fiKMB61coxIkIcFN69ByqatjAbBgor+gQUQhkgJLAECMA0GCWCGSAFlAwQCAQUAAgEQMAsGCWCGSAFlAwQBBQQYKHguXT15+SnYWuGP7z8cZt48S3gjPKG4JMDwGCSqGSIb3DQEHATAdBglghkgBZQMEAQIEEEgM+yv66vvrO263eyviId4GAEMbKZdt73Xaw834vq2Jktm0=+-----END CMS-----+-----BEGIN RSA PRIVATE KEY-----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+-----END RSA PRIVATE KEY-----++adding also a recipient certificate generated with:++ openssl req -new -subj /emailAddress=test@example.com \+ -key tests/files/rfc9690.pem \+ | openssl x509 -req \+ -CA tests/files/rsa-self-signed-cert.pem \+ -CAkey tests/files/rsa-unencrypted-pkcs8.pem \+ -force_pubkey tests/files/rfc9690.pem \+ -set_serial 1++-----BEGIN CERTIFICATE-----+MIIC/jCCAmegAwIBAgIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYJKoZIhvcNAQkB+FhB0ZXN0QGV4YW1wbGUuY29tMB4XDTI1MTAyNjA1MzM1NloXDTI1MTEyNTA1MzM1+NlowITEfMB0GCSqGSIb3DQEJARYQdGVzdEBleGFtcGxlLmNvbTCCAaIwDQYJKoZI+hvcNAQEBBQADggGPADCCAYoCggGBAN6HFteHMZ3DyeO35xIwWQMnwtpamqS9xE+I+71egu4Bnla0UMVjw8JfnBWAQUdno949/9HnA5rvl7lYtvrOSRynns2vorX7obM98+on4HhKXivE/krrB4Z9tPzQS4WCm4d5IgHAoEOenaQ6w7w3C6qr0t44P1Tvjj52W6+H6Bzjnr7fEP66GoSXtT5SMkOLnVgfM2XsyeJiy32zgPWFWmF8dgBx2SXr6N55kZ5+YCV6OphNNBP+qpIYwxykjHZdlhupjvUwg2XPamN3ACQ1OiK9L0h+6lJfP7zdd8uF+DhZJ/ITOJJmemyam2beTW4x7WOLkNFTFt0chEs4aztNpq6d/WrizZx9xF3V2ldbZ+O+R96gu8yWnhljCckHzhN2HxBkSp/H8bm44u9eg/FHnAMQMxchS+ptSYjsleWnAI+jqcTdeKkWGXR51zjPQ6hiBqaHmNVLg+DKNOaLDqjlKQSRwPX0sIUmRu1TX/r4DVk+yzWEmCyNOCojHbpZ71OnbPkD8/iirQIDAQABo0IwQDAdBgNVHQ4EFgQUnutnybla+dNRNLxY5ZoDoAbXLpJwwHwYDVR0jBBgwFoAU/JTTK/+8Q86nWQwPMu+1PpT6Bg4w+DQYJKoZIhvcNAQELBQADgYEAOdp9bzCo2D2IQIrYk1mLwHkJ8IQLd1+3O968DPem+dofgQDm7PwG/+qzmXdNFkU6cJmz+7E70DEsciw50mkzuH5zsKkTe8IbCfJ4oAN5p+M5p495h6r1a678GJJ45ornqe3D0v1mmraCJpV0hK9lO03ph+4nfrof31eW5AVXyN+/Ho=+-----END CERTIFICATE-----
tests/files/rsa-pkcs12.pem view
@@ -148,6 +148,95 @@ VSE7eqvIyhBRAshh1EtyB3sLY/MknrZqUrz3NqATBAiMSKkq/xXbXQICCAA= -----END PKCS12----- -----BEGIN PKCS12-----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+-----END PKCS12-----+-----BEGIN PKCS12-----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-----END PKCS12-----+-----BEGIN PKCS12----- MIIGWwIBAzCCBiEGCSqGSIb3DQEHAaCCBhIEggYOMIIGCjCCAsQGCSqGSIb3DQEH AaCCArUEggKxMIICrTCCAqkGCyqGSIb3DQEMCgEDoIICLDCCAigGCiqGSIb3DQEJ FgGgggIYBIICFDCCAhAwggF5oAMCAQICCQD3DetZLCk3mzANBgkqhkiG9w0BAQsF