packages feed

cryptostore 0.2.3.0 → 0.3.0.0

raw patch · 22 files changed

+775/−191 lines, 22 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Crypto.Store.PKCS5: type Password = ByteString
- Crypto.Store.PKCS8: type Password = ByteString
+ Crypto.Store.CMS: KA_HKDF :: DigestProxy hashAlg -> KeyAgreementKDF
+ Crypto.Store.CMS: KA_X963_KDF :: DigestProxy hashAlg -> KeyAgreementKDF
+ Crypto.Store.CMS: KMAC_SHAKE128 :: Proxy n -> ByteString -> MACAlgorithm
+ Crypto.Store.CMS: KMAC_SHAKE256 :: Proxy n -> ByteString -> MACAlgorithm
+ Crypto.Store.CMS: data KeyAgreementKDF
+ Crypto.Store.CMS: getSigningTimeAttr :: [Attribute] -> Maybe DateTime
+ Crypto.Store.CMS: setSigningTimeAttr :: DateTime -> [Attribute] -> [Attribute]
+ Crypto.Store.CMS: setSigningTimeAttrCurrent :: MonadIO m => [Attribute] -> m [Attribute]
+ Crypto.Store.PKCS12: Authenticated :: (Password -> Either StoreError (ProtectionPassword, a)) -> OptAuthenticated a
+ Crypto.Store.PKCS12: Unauthenticated :: a -> OptAuthenticated a
+ Crypto.Store.PKCS12: data OptAuthenticated a
+ Crypto.Store.PKCS12: data ProtectionPassword
+ Crypto.Store.PKCS12: emptyNotTerminated :: ProtectionPassword
+ Crypto.Store.PKCS12: fromProtectionPassword :: ProtectionPassword -> ByteString
+ Crypto.Store.PKCS12: instance GHC.Base.Functor Crypto.Store.PKCS12.OptAuthenticated
+ Crypto.Store.PKCS12: recoverAuthenticated :: Password -> OptAuthenticated a -> Either StoreError (ProtectionPassword, a)
+ Crypto.Store.PKCS12: toProtectionPassword :: ByteString -> ProtectionPassword
+ Crypto.Store.PKCS5: data ProtectionPassword
+ Crypto.Store.PKCS5: emptyNotTerminated :: ProtectionPassword
+ Crypto.Store.PKCS5: fromProtectionPassword :: ProtectionPassword -> ByteString
+ Crypto.Store.PKCS5: toProtectionPassword :: ByteString -> ProtectionPassword
+ Crypto.Store.PKCS8: data ProtectionPassword
+ Crypto.Store.PKCS8: emptyNotTerminated :: ProtectionPassword
+ Crypto.Store.PKCS8: fromProtectionPassword :: ProtectionPassword -> ByteString
+ Crypto.Store.PKCS8: toProtectionPassword :: ByteString -> ProtectionPassword
- Crypto.Store.CMS: CofactorDH :: DigestAlgorithm -> KeyEncryptionParams -> KeyAgreementParams
+ Crypto.Store.CMS: CofactorDH :: KeyAgreementKDF -> KeyEncryptionParams -> KeyAgreementParams
- Crypto.Store.CMS: StdDH :: DigestAlgorithm -> KeyEncryptionParams -> KeyAgreementParams
+ Crypto.Store.CMS: StdDH :: KeyAgreementKDF -> KeyEncryptionParams -> KeyAgreementParams
- Crypto.Store.CMS: withSignerCertificate :: Applicative f => (CertificateChain -> f Bool) -> ConsumerOfSI f
+ Crypto.Store.CMS: withSignerCertificate :: Applicative f => (Maybe DateTime -> CertificateChain -> f Bool) -> ConsumerOfSI f
- Crypto.Store.PKCS12: Protected :: (Password -> Either StoreError a) -> OptProtected a
+ Crypto.Store.PKCS12: Protected :: (ProtectionPassword -> Either StoreError a) -> OptProtected a
- Crypto.Store.PKCS12: encrypted :: EncryptionScheme -> Password -> SafeContents -> Either StoreError PKCS12
+ Crypto.Store.PKCS12: encrypted :: EncryptionScheme -> ProtectionPassword -> SafeContents -> Either StoreError PKCS12
- Crypto.Store.PKCS12: fromCredential :: Maybe EncryptionScheme -> EncryptionScheme -> Password -> (CertificateChain, PrivKey) -> Either StoreError PKCS12
+ Crypto.Store.PKCS12: fromCredential :: Maybe EncryptionScheme -> EncryptionScheme -> ProtectionPassword -> (CertificateChain, PrivKey) -> Either StoreError PKCS12
- Crypto.Store.PKCS12: fromNamedCredential :: String -> Maybe EncryptionScheme -> EncryptionScheme -> Password -> (CertificateChain, PrivKey) -> Either StoreError PKCS12
+ Crypto.Store.PKCS12: fromNamedCredential :: String -> Maybe EncryptionScheme -> EncryptionScheme -> ProtectionPassword -> (CertificateChain, PrivKey) -> Either StoreError PKCS12
- Crypto.Store.PKCS12: readP12File :: FilePath -> IO (Either StoreError (OptProtected PKCS12))
+ Crypto.Store.PKCS12: readP12File :: FilePath -> IO (Either StoreError (OptAuthenticated PKCS12))
- Crypto.Store.PKCS12: readP12FileFromMemory :: ByteString -> Either StoreError (OptProtected PKCS12)
+ Crypto.Store.PKCS12: readP12FileFromMemory :: ByteString -> Either StoreError (OptAuthenticated PKCS12)
- Crypto.Store.PKCS12: recover :: Password -> OptProtected a -> Either StoreError a
+ Crypto.Store.PKCS12: recover :: ProtectionPassword -> OptProtected a -> Either StoreError a
- Crypto.Store.PKCS12: recoverA :: Applicative f => f Password -> OptProtected a -> f (Either StoreError a)
+ Crypto.Store.PKCS12: recoverA :: Applicative f => f ProtectionPassword -> OptProtected a -> f (Either StoreError a)
- Crypto.Store.PKCS12: writeP12File :: FilePath -> IntegrityParams -> Password -> PKCS12 -> IO (Either StoreError ())
+ Crypto.Store.PKCS12: writeP12File :: FilePath -> IntegrityParams -> ProtectionPassword -> PKCS12 -> IO (Either StoreError ())
- Crypto.Store.PKCS12: writeP12FileToMemory :: IntegrityParams -> Password -> PKCS12 -> Either StoreError ByteString
+ Crypto.Store.PKCS12: writeP12FileToMemory :: IntegrityParams -> ProtectionPassword -> PKCS12 -> Either StoreError ByteString
- Crypto.Store.PKCS5: decrypt :: PKCS5 -> Password -> Either StoreError ByteString
+ Crypto.Store.PKCS5: decrypt :: PKCS5 -> ProtectionPassword -> Either StoreError ByteString
- Crypto.Store.PKCS5: encrypt :: EncryptionScheme -> Password -> ByteString -> Either StoreError PKCS5
+ Crypto.Store.PKCS5: encrypt :: EncryptionScheme -> ProtectionPassword -> ByteString -> Either StoreError PKCS5
- Crypto.Store.PKCS5: pbDecrypt :: EncryptionScheme -> EncryptedContent -> Password -> Either StoreError ByteString
+ Crypto.Store.PKCS5: pbDecrypt :: EncryptionScheme -> EncryptedContent -> ProtectionPassword -> Either StoreError ByteString
- Crypto.Store.PKCS5: pbEncrypt :: EncryptionScheme -> ByteString -> Password -> Either StoreError EncryptedContent
+ Crypto.Store.PKCS5: pbEncrypt :: EncryptionScheme -> ByteString -> ProtectionPassword -> Either StoreError EncryptedContent
- Crypto.Store.PKCS8: Protected :: (Password -> Either StoreError a) -> OptProtected a
+ Crypto.Store.PKCS8: Protected :: (ProtectionPassword -> Either StoreError a) -> OptProtected a
- Crypto.Store.PKCS8: encryptKeyToPEM :: EncryptionScheme -> Password -> PrivKey -> Either StoreError PEM
+ Crypto.Store.PKCS8: encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> PrivKey -> Either StoreError PEM
- Crypto.Store.PKCS8: recover :: Password -> OptProtected a -> Either StoreError a
+ Crypto.Store.PKCS8: recover :: ProtectionPassword -> OptProtected a -> Either StoreError a
- Crypto.Store.PKCS8: recoverA :: Applicative f => f Password -> OptProtected a -> f (Either StoreError a)
+ Crypto.Store.PKCS8: recoverA :: Applicative f => f ProtectionPassword -> OptProtected a -> f (Either StoreError a)
- Crypto.Store.PKCS8: writeEncryptedKeyFile :: FilePath -> EncryptionScheme -> Password -> PrivKey -> IO (Either StoreError ())
+ Crypto.Store.PKCS8: writeEncryptedKeyFile :: FilePath -> EncryptionScheme -> ProtectionPassword -> PrivKey -> IO (Either StoreError ())
- Crypto.Store.PKCS8: writeEncryptedKeyFileToMemory :: EncryptionScheme -> Password -> PrivKey -> Either StoreError ByteString
+ Crypto.Store.PKCS8: writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword -> PrivKey -> Either StoreError ByteString

Files

ChangeLog.md view
@@ -1,5 +1,28 @@ # Revision history for cryptostore +## 0.3.0.0 - 2023-01-14++* API change in PKCS5, PKCS8 and PKCS12 modules to handle better password-based+  encryption derived from an empty password.  All encryption/decryption+  functions now expect an opaque `ProtectionPassword` data type.  Conversion+  functions `toProtectionPassword` and `fromProtectionPassword` are provided.+  Additionnally in the PKCS12 module, the type `OptProtected` is replaced with+  `OptAuthenticated` when dealing with password integrity.  Similarly at that+  level, function `recover` is to be replaced with `recoverAuthenticated`.++* Added support for KMAC (Keccak Message Authentication Code) in CMS+  authenticated data, through constructors `KMAC_SHAKE128` and `KMAC_SHAKE256`.++* CMS key agreement now supports derivation with HKDF along with X9.63.  Data+  type `KeyAgreementParams` is modified to include a KDF instead of simply the+  digest algorithm.  HKDF has assigned OIDs only for standard DH and cannot be+  used with cofactor DH.++* Added CMS utility functions to deal with the `signingTime` attribute.++* Changed `withSignerCertificate` validation callback API to include the+  `signingTime` value when available.+ ## 0.2.3.0 - 2022-11-05  * Fix RC2 on big-endian architectures
LICENSE view
@@ -1,4 +1,4 @@-Copyright (c) 2018-2022, Olivier Chéron+Copyright (c) 2018-2023, Olivier Chéron  All rights reserved. 
README.md view
@@ -1,8 +1,8 @@ # cryptostore -[![Build Status](https://travis-ci.org/ocheron/cryptostore.png?branch=master)](https://travis-ci.org/ocheron/cryptostore)-[![BSD](https://b.repl.ca/v1/license-BSD-blue.png)](https://en.wikipedia.org/wiki/BSD_licenses)-[![Haskell](https://b.repl.ca/v1/language-haskell-lightgrey.png)](https://haskell.org/)+[![Build Status](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml)+[![BSD](https://img.shields.io/badge/License-BSD-blue)](https://en.wikipedia.org/wiki/BSD_licenses)+[![Haskell](https://img.shields.io/badge/Language-Haskell-lightgrey)](https://haskell.org/)  This package allows to read and write cryptographic objects to/from ASN.1. @@ -50,8 +50,8 @@ > :set -XOverloadedStrings > :m Crypto.PubKey.RSA Crypto.Store.PKCS8 Data.X509 Crypto.Store.PKCS5 > privKey <- PrivKeyRSA . snd <$> generate (2048 `div` 8) 0x10001-> salt <- generateSalt 8-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> salt <- generateSalt 16+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256 > encParams <- generateEncryptionParams (CBC AES256) > let pbes = PBES2 (PBES2Parameter kdf encParams) > writeEncryptedKeyFile "/path/to/privkey.pem" pbes "mypassword" privKey@@ -59,8 +59,8 @@ ```  Parameters used in this example are AES-256-CBC as cipher, PBKDF2 as-key-derivation function, with an 8-byte salt, 2048 iterations and SHA-256 as-pseudorandom function.+key-derivation function, with a 16-byte salt, 200,000 iterations and SHA-256+as pseudorandom function.  ## Public Keys and Signed Objects @@ -92,13 +92,28 @@ API to read PKCS #12 files requires some password at each layer.  This API is available in module `Crypto.Store.PKCS12`. +Reading a binary PKCS #12 file using a single password doing both integrity+and privacy (usual case):++```haskell+> :set -XOverloadedStrings+> :m Crypto.Store.PKCS12+> Right p12 <- readP12File "/path/to/file.p12"+> let Right (password, pkcs12) = recoverAuthenticated "mypassword" p12+> let Right contents = recover password (unPKCS12 pkcs12)+> getAllSafeX509Certs contents+[SignedExact {getSigned = ...}]+> recover password (getAllSafeKeys contents)+Right [PrivKeyRSA ...]+```+ Reading a binary PKCS #12 file using distinct integrity and privacy passwords:  ```haskell > :set -XOverloadedStrings > :m Crypto.Store.PKCS12 > Right p12 <- readP12File "/path/to/file.p12"-> let Right pkcs12 = recover "myintegrityassword" p12+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12 > let Right contents = recover "myprivacypassword" (unPKCS12 pkcs12) > getAllSafeX509Certs contents [SignedExact {getSigned = ...}]@@ -122,15 +137,15 @@ >     contents = SafeContents [keyBag]  -- Encrypt the contents-> salt <- generateSalt 8-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> salt <- generateSalt 16+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256 > encParams <- generateEncryptionParams (CBC AES256) > let pbes = PBES2 (PBES2Parameter kdf encParams) >     Right pkcs12 = encrypted pbes "mypassword" contents  -- Save to PKCS #12 with integrity protection (same password)-> salt' <- generateSalt 8-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 2048)+> salt' <- generateSalt 16+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt' 200000) > writeP12File "/path/to/privkey.p12" iParams "mypassword" pkcs12 Right () ```@@ -144,28 +159,28 @@  -- Read PKCS #12 content as credential > Right p12 <- readP12File "/path/to/file.p12"-> let Right pkcs12 = recover "myintegrityassword" p12+> let Right (_, pkcs12) = recoverAuthenticated "myintegritypassword" p12 > let Right (Just cred) = recover "myprivacypassword" (toCredential pkcs12) > cred (CertificateChain [...], PrivKeyRSA (...))  -- Scheme to reencrypt the key-> saltK <- generateSalt 8-> let kdfK = PBKDF2 saltK 2048 Nothing PBKDF2_SHA256+> saltK <- generateSalt 16+> let kdfK = PBKDF2 saltK 200000 Nothing PBKDF2_SHA256 > encParamsK <- generateEncryptionParams (CBC AES256) > let sKey = PBES2 (PBES2Parameter kdfK encParamsK)  -- Scheme to reencrypt the certificate chain > saltC <- generateSalt 8-> let kdfC = PBKDF2 saltC 1024 Nothing PBKDF2_SHA256+> let kdfC = PBKDF2 saltC 100000 Nothing PBKDF2_SHA256 > encParamsC <- generateEncryptionParams (CBC AES128) > let sCert = PBES2 (PBES2Parameter kdfC encParamsC)  -- Write the content back to a new file > let Right pkcs12' = fromCredential (Just sCert) sKey "myprivacypassword" cred-> salt <- generateSalt 8-> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 2048)-> writeP12File "/path/to/newfile.p12" iParams "myintegrityassword" pkcs12'+> salt <- generateSalt 16+> let iParams = (DigestAlgorithm SHA256, PBEParameter salt 200000)+> writeP12File "/path/to/newfile.p12" iParams "myintegritypassword" pkcs12' ```  Variants `toNamedCredential` and `fromNamedCredential` are also available when@@ -214,8 +229,8 @@ -- Encrypt the Content Encryption Key with a Password Recipient Info, -- i.e. a KDF will derive the Key Encryption Key from a password -- that the recipient will need to know-> salt <- generateSalt 8-> let kdf = PBKDF2 salt 2048 Nothing PBKDF2_SHA256+> salt <- generateSalt 16+> let kdf = PBKDF2 salt 200000 Nothing PBKDF2_SHA256 > keParams <- generateEncryptionParams (CBC AES128) > let pri = forPasswordRecipient "mypassword" kdf (PWRIKEK keParams) @@ -289,7 +304,7 @@ > :m Crypto.Store.CMS Data.Default.Class > [SignedDataCI signedEncapData] <- readCMSFile "/path/to/signed.pem" > signedData <- fromAttached signedEncapData-> let doValidation chain = null <$> validateNoFQHN store def noServiceID chain+> let doValidation _ chain = null <$> validateNoFQHN store def noServiceID chain > verifySignedData (withSignerCertificate doValidation) signedData Right (DataCI "Some trustworthy content") ```
cryptostore.cabal view
@@ -1,5 +1,5 @@ name:                cryptostore-version:             0.2.3.0+version:             0.3.0.0 synopsis:            Serialization of cryptographic data types description:         Haskell implementation of PKCS \#8, PKCS \#12 and CMS                      (Cryptographic Message Syntax).
src/Crypto/Store/CMS.hs view
@@ -21,6 +21,7 @@ -- * <https://tools.ietf.org/html/rfc8103 RFC 8103>: Using ChaCha20-Poly1305 Authenticated Encryption in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc8418 RFC 8418>: Use of the Elliptic Curve Diffie-Hellman Key Agreement Algorithm with X25519 and X448 in the Cryptographic Message Syntax (CMS) -- * <https://tools.ietf.org/html/rfc8419 RFC 8419>: Use of Edwards-Curve Digital Signature Algorithm (EdDSA) Signatures in the Cryptographic Message Syntax (CMS)+-- * <https://tools.ietf.org/html/rfc8702 RFC 8702>: Use of the SHAKE One-Way Hash Functions in the Cryptographic Message Syntax (CMS) {-# LANGUAGE RecordWildCards #-} module Crypto.Store.CMS     ( ContentType(..)@@ -60,6 +61,7 @@     , KeyEncryptionParams(..)     , KeyTransportParams(..)     , KeyAgreementParams(..)+    , KeyAgreementKDF(..)     , RecipientInfo(..)     , EnvelopedData(..)     , ProducerOfRI@@ -144,6 +146,10 @@     , findAttribute     , setAttribute     , filterAttributes+    -- * CMS standard attributes+    , getSigningTimeAttr+    , setSigningTimeAttr+    , setSigningTimeAttrCurrent     -- * Originator information     , OriginatorInfo(..)     , CertificateChoice(..)@@ -158,7 +164,7 @@ import Data.ASN1.Encoding import Data.ByteString (ByteString) import Data.Maybe (isJust)-import Data.List (nub, unzip3)+import Data.List (nub)  import Crypto.Hash 
src/Crypto/Store/CMS/Algorithms.hs view
@@ -63,6 +63,7 @@     , transportEncrypt     , transportDecrypt     , KeyAgreementParams(..)+    , KeyAgreementKDF(..)     , ECDHPair     , ecdhGenerate     , ecdhPublic@@ -109,9 +110,11 @@ import           Crypto.ECC (Curve_X25519, Curve_X448, ecdh) import           Crypto.Error import qualified Crypto.Hash as Hash+import qualified Crypto.KDF.HKDF as HKDF import qualified Crypto.KDF.PBKDF2 as PBKDF2 import qualified Crypto.KDF.Scrypt as Scrypt import qualified Crypto.MAC.HMAC as HMAC+import qualified Crypto.MAC.KMAC as KMAC import qualified Crypto.MAC.Poly1305 as Poly1305 import           Crypto.Number.Serialize import qualified Crypto.PubKey.Curve25519 as X25519@@ -173,7 +176,27 @@     -- | SHAKE256 (variable size)     SHAKE256 :: KnownNat n => Proxy n -> DigestProxy (Hash.SHAKE256 n) -deriving instance Show (DigestProxy hashAlg)++instance Show (DigestProxy hashAlg) where+    showsPrec _ MD2          = showString "MD2"+    showsPrec _ MD4          = showString "MD4"+    showsPrec _ MD5          = showString "MD5"+    showsPrec _ SHA1         = showString "SHA1"+    showsPrec _ SHA224       = showString "SHA224"+    showsPrec _ SHA256       = showString "SHA256"+    showsPrec _ SHA384       = showString "SHA384"+    showsPrec _ SHA512       = showString "SHA512"+    showsPrec _ SHAKE128_256 = showString "SHAKE128_256"+    showsPrec _ SHAKE256_512 = showString "SHAKE256_512"+    showsPrec d (SHAKE128 p) =+        showParen (d > 10) $ showString "SHAKE128 " . showNat 11 p+    showsPrec d (SHAKE256 p) =+        showParen (d > 10) $ showString "SHAKE256 " . showNat 11 p++showNat :: KnownNat n => Int -> Proxy n -> ShowS+showNat d n = showParen (d > 0) $+    shows n . showString " :: Proxy " . shows (natVal n)+ deriving instance Eq (DigestProxy hashAlg)  instance HasStrength (DigestProxy hashAlg) where@@ -300,9 +323,9 @@     parseParameter Type_SHAKE128_256 = parseDigestParam (DigestAlgorithm SHAKE128_256)     parseParameter Type_SHAKE256_512 = parseDigestParam (DigestAlgorithm SHAKE256_512)     parseParameter Type_SHAKE128_Len = parseBitLen $-        \(SomeNat p) -> DigestAlgorithm (SHAKE128 p)+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE128 p)     parseParameter Type_SHAKE256_Len = parseBitLen $-        \(SomeNat p) -> DigestAlgorithm (SHAKE256 p)+        \(SomeNat p) -> return $ DigestAlgorithm (SHAKE256 p)  -- | Compute the digest of a message. digest :: ByteArrayAccess message => DigestAlgorithm -> message -> ByteString@@ -318,13 +341,16 @@ parseDigestParam :: Monoid e => DigestAlgorithm -> ParseASN1 e DigestAlgorithm parseDigestParam p = getNextMaybe nullOrNothing >> return p -parseBitLen :: Monoid e => (SomeNat -> a) -> ParseASN1 e a-parseBitLen build = do+parseBitLen :: Monoid e => (SomeNat -> ParseASN1 e a) -> ParseASN1 e a+parseBitLen fn = do     IntVal n <- getNext     case someNatVal n of         Nothing -> throwParseError ("Invalid bit length: " ++ show n)-        Just sn -> return (build sn)+        Just sn -> fn sn +p256 :: Proxy 256+p256 = Proxy+ p512 :: Proxy 512 p512 = Proxy @@ -384,52 +410,115 @@ -- | Message authentication code.  Equality is time constant. type MessageAuthenticationCode = AuthTag +data MACType+    = forall hashAlg . Hash.HashAlgorithm hashAlg+        => TypeHMAC (DigestProxy hashAlg)+    | TypeKMAC_SHAKE128+    | TypeKMAC_SHAKE256++deriving instance Show MACType+ -- | Message Authentication Code (MAC) Algorithm. data MACAlgorithm     = forall hashAlg . Hash.HashAlgorithm hashAlg         => HMAC (DigestProxy hashAlg)+    | forall n . KnownNat n => KMAC_SHAKE128 (Proxy n) ByteString+    | forall n . KnownNat n => KMAC_SHAKE256 (Proxy n) ByteString -deriving instance Show MACAlgorithm+instance Show MACAlgorithm where+    showsPrec d (HMAC p) = showParen (d > 10) $+        showString "HMAC " . showsPrec 11 p+    showsPrec d (KMAC_SHAKE128 p s) = showParen (d > 10) $+        showString "KMAC_SHAKE128 " . showNat 11 p .+            showChar ' ' . showsPrec 11 s+    showsPrec d (KMAC_SHAKE256 p s) = showParen (d > 10) $+        showString "KMAC_SHAKE256 " . showNat 11 p .+            showChar ' ' . showsPrec 11 s  instance Eq MACAlgorithm where-    HMAC a1 == HMAC a2 = DigestAlgorithm a1 == DigestAlgorithm a2+    HMAC a1             == HMAC a2             =+        DigestAlgorithm a1 == DigestAlgorithm a2+    KMAC_SHAKE128 p1 s1 == KMAC_SHAKE128 p2 s2 =+        natVal p1 == natVal p2 && s1 == s2+    KMAC_SHAKE256 p1 s1 == KMAC_SHAKE256 p2 s2 =+        natVal p1 == natVal p2 && s1 == s2+    _                   == _                   = False  instance HasStrength MACAlgorithm where     getSecurityBits (HMAC a) = getSecurityBits (DigestAlgorithm a)+    getSecurityBits (KMAC_SHAKE128 p _) = shakeSecurityBits 128 p+    getSecurityBits (KMAC_SHAKE256 p _) = shakeSecurityBits 256 p -instance Enumerable MACAlgorithm where-    values = [ HMAC MD5-             , HMAC SHA1-             , HMAC SHA224-             , HMAC SHA256-             , HMAC SHA384-             , HMAC SHA512+instance Enumerable MACType where+    values = [ TypeHMAC MD5+             , TypeHMAC SHA1+             , TypeHMAC SHA224+             , TypeHMAC SHA256+             , TypeHMAC SHA384+             , TypeHMAC SHA512+             , TypeKMAC_SHAKE128+             , TypeKMAC_SHAKE256              ] -instance OIDable MACAlgorithm where-    getObjectID (HMAC MD5)    = [1,3,6,1,5,5,8,1,1]-    getObjectID (HMAC SHA1)   = [1,3,6,1,5,5,8,1,2]-    getObjectID (HMAC SHA224) = [1,2,840,113549,2,8]-    getObjectID (HMAC SHA256) = [1,2,840,113549,2,9]-    getObjectID (HMAC SHA384) = [1,2,840,113549,2,10]-    getObjectID (HMAC SHA512) = [1,2,840,113549,2,11]+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 ty = error ("Unsupported MACAlgorithm: " ++ show ty) -instance OIDNameable MACAlgorithm where+instance OIDNameable MACType where     fromObjectID oid = unOIDNW <$> fromObjectID oid  instance AlgorithmId MACAlgorithm where-    type AlgorithmType MACAlgorithm = MACAlgorithm+    type AlgorithmType MACAlgorithm = MACType     algorithmName _  = "mac algorithm"-    algorithmType    = id-    parameterASN1S _ = id-    parseParameter p = getNextMaybe nullOrNothing >> return p +    algorithmType (HMAC alg)          = TypeHMAC alg+    algorithmType (KMAC_SHAKE128 _ _) = TypeKMAC_SHAKE128+    algorithmType (KMAC_SHAKE256 _ _) = TypeKMAC_SHAKE256++    parameterASN1S (HMAC _)              = id+    parameterASN1S (KMAC_SHAKE128 p str) = kmacASN1S 256 p str+    parameterASN1S (KMAC_SHAKE256 p str) = kmacASN1S 512 p str++    parseParameter (TypeHMAC alg)    = getNextMaybe nullOrNothing >> return (HMAC alg)+    parseParameter TypeKMAC_SHAKE128 = parseKMAC p256 KMAC_SHAKE128+    parseParameter TypeKMAC_SHAKE256 = parseKMAC p512 KMAC_SHAKE256++kmacASN1S :: (KnownNat n, ASN1Elem e)+          => Integer -> proxy n -> ByteString -> ASN1Stream e+kmacASN1S n p str+    | B.null str && n == i = id+    | otherwise = asn1Container Sequence (gIntVal i . gOctetString str)+  where i = natVal p++parseKMAC :: (Monoid e, KnownNat n')+          => Proxy n'+          -> (forall n . KnownNat n => Proxy n -> ByteString -> MACAlgorithm)+          -> ParseASN1 e MACAlgorithm+parseKMAC p' fn = do+    b <- hasNext+    if b then parseParams else return (fn p' B.empty)+  where+    parseParams = onNextContainer Sequence $ parseBitLen $ \(SomeNat p) ->+        getNext >>= \(OctetString str) -> return (fn p str)+ instance HasKeySize MACAlgorithm where     getKeySizeSpecifier (HMAC a) = KeySizeFixed (digestSizeFromProxy a)-      where digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy+    getKeySizeSpecifier (KMAC_SHAKE128 p _) =+        KeySizeFixed (digestSizeFromProxy (SHAKE128 p))+    getKeySizeSpecifier (KMAC_SHAKE256 p _) =+        KeySizeFixed (digestSizeFromProxy (SHAKE256 p)) +digestSizeFromProxy :: Hash.HashAlgorithm a => proxy a -> Int+digestSizeFromProxy = Hash.hashDigestSize . hashFromProxy+ -- | Invoke the MAC function. mac :: (ByteArrayAccess key, ByteArrayAccess message)      => MACAlgorithm -> key -> message -> MessageAuthenticationCode@@ -441,7 +530,19 @@         => proxy a -> k -> m -> HMAC.HMAC a     runHMAC _ = HMAC.hmac +mac (KMAC_SHAKE128 p str) = \key -> AuthTag . B.convert . run128 p str key+  where+    run128 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE128 n)+    run128 _ = KMAC.kmac +mac (KMAC_SHAKE256 p str) = \key -> AuthTag . B.convert . run256 p str key+  where+    run256 :: (KnownNat n, ByteArrayAccess k, ByteArrayAccess m)+           => proxy n -> ByteString -> k -> m -> KMAC.KMAC (Hash.SHAKE256 n)+    run256 _ = KMAC.kmac++ -- Content encryption  -- | CMS content encryption cipher.@@ -1554,46 +1655,70 @@  -- Key agreement -data KeyAgreementType = TypeStdDH DigestAlgorithm-                      | TypeCofactorDH DigestAlgorithm+-- | Key derivation function used for key agreement.+data KeyAgreementKDF+    = forall hashAlg . Hash.HashAlgorithm hashAlg+      => KA_X963_KDF (DigestProxy hashAlg)+      -- ^ ANSI-X9.63-KDF key derivation function+    | forall hashAlg . Hash.HashAlgorithm hashAlg+       => KA_HKDF (DigestProxy hashAlg)+      -- ^ Extract-and-Expand HMAC-based key derivation function++deriving instance Show KeyAgreementKDF++instance Eq KeyAgreementKDF where+    KA_X963_KDF a == KA_X963_KDF b = DigestAlgorithm a == DigestAlgorithm b+    KA_HKDF a     == KA_HKDF b     = DigestAlgorithm a == DigestAlgorithm b+    _             == _             = False++data KeyAgreementType = TypeStdDH KeyAgreementKDF+                      | TypeCofactorDH KeyAgreementKDF                       deriving (Show,Eq)  instance Enumerable KeyAgreementType where-    values = [ TypeStdDH (DigestAlgorithm SHA1)-             , TypeStdDH (DigestAlgorithm SHA224)-             , TypeStdDH (DigestAlgorithm SHA256)-             , TypeStdDH (DigestAlgorithm SHA384)-             , TypeStdDH (DigestAlgorithm SHA512)+    values = [ TypeStdDH (KA_X963_KDF SHA1)+             , TypeStdDH (KA_X963_KDF SHA224)+             , TypeStdDH (KA_X963_KDF SHA256)+             , TypeStdDH (KA_X963_KDF SHA384)+             , TypeStdDH (KA_X963_KDF SHA512) -             , TypeCofactorDH (DigestAlgorithm SHA1)-             , TypeCofactorDH (DigestAlgorithm SHA224)-             , TypeCofactorDH (DigestAlgorithm SHA256)-             , TypeCofactorDH (DigestAlgorithm SHA384)-             , TypeCofactorDH (DigestAlgorithm SHA512)+             , TypeCofactorDH (KA_X963_KDF SHA1)+             , TypeCofactorDH (KA_X963_KDF SHA224)+             , TypeCofactorDH (KA_X963_KDF SHA256)+             , TypeCofactorDH (KA_X963_KDF SHA384)+             , TypeCofactorDH (KA_X963_KDF SHA512)++             , TypeStdDH (KA_HKDF SHA256)+             , TypeStdDH (KA_HKDF SHA384)+             , TypeStdDH (KA_HKDF SHA512)              ]  instance OIDable KeyAgreementType where-    getObjectID (TypeStdDH (DigestAlgorithm SHA1))        = [1,3,133,16,840,63,0,2]-    getObjectID (TypeStdDH (DigestAlgorithm SHA224))      = [1,3,132,1,11,0]-    getObjectID (TypeStdDH (DigestAlgorithm SHA256))      = [1,3,132,1,11,1]-    getObjectID (TypeStdDH (DigestAlgorithm SHA384))      = [1,3,132,1,11,2]-    getObjectID (TypeStdDH (DigestAlgorithm SHA512))      = [1,3,132,1,11,3]+    getObjectID (TypeStdDH (KA_X963_KDF SHA1))        = [1,3,133,16,840,63,0,2]+    getObjectID (TypeStdDH (KA_X963_KDF SHA224))      = [1,3,132,1,11,0]+    getObjectID (TypeStdDH (KA_X963_KDF SHA256))      = [1,3,132,1,11,1]+    getObjectID (TypeStdDH (KA_X963_KDF SHA384))      = [1,3,132,1,11,2]+    getObjectID (TypeStdDH (KA_X963_KDF SHA512))      = [1,3,132,1,11,3] -    getObjectID (TypeCofactorDH (DigestAlgorithm SHA1))   = [1,3,133,16,840,63,0,3]-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA224)) = [1,3,132,1,14,0]-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA256)) = [1,3,132,1,14,1]-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA384)) = [1,3,132,1,14,2]-    getObjectID (TypeCofactorDH (DigestAlgorithm SHA512)) = [1,3,132,1,14,3]+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA1))   = [1,3,133,16,840,63,0,3]+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA224)) = [1,3,132,1,14,0]+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA256)) = [1,3,132,1,14,1]+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA384)) = [1,3,132,1,14,2]+    getObjectID (TypeCofactorDH (KA_X963_KDF SHA512)) = [1,3,132,1,14,3] +    getObjectID (TypeStdDH (KA_HKDF SHA256))  = [1,2,840,113549,1,9,16,3,19]+    getObjectID (TypeStdDH (KA_HKDF SHA384))  = [1,2,840,113549,1,9,16,3,20]+    getObjectID (TypeStdDH (KA_HKDF SHA512))  = [1,2,840,113549,1,9,16,3,21]+     getObjectID ty = error ("Unsupported KeyAgreementType: " ++ show ty)  instance OIDNameable KeyAgreementType where     fromObjectID oid = unOIDNW <$> fromObjectID oid  -- | Key agreement algorithm with associated parameters.-data KeyAgreementParams = StdDH DigestAlgorithm KeyEncryptionParams+data KeyAgreementParams = StdDH KeyAgreementKDF KeyEncryptionParams                           -- ^ 1-Pass D-H with Stardard ECDH-                        | CofactorDH DigestAlgorithm KeyEncryptionParams+                        | CofactorDH KeyAgreementKDF KeyEncryptionParams                           -- ^ 1-Pass D-H with Cofactor ECDH                         deriving (Show,Eq) @@ -1611,21 +1736,31 @@     parseParameter (TypeCofactorDH d) = CofactorDH d <$> parseAlgorithm Sequence  ecdhKeyMaterial :: (ByteArrayAccess bin, ByteArray bout)-                => DigestAlgorithm -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout-ecdhKeyMaterial (DigestAlgorithm hashAlg) kep ukm zz-    | r == 0    = B.concat (map chunk [1..d])-    | otherwise = B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)-  where-    (d, r)   = outLen `divMod` Hash.hashDigestSize prx+                => KeyAgreementKDF -> KeyEncryptionParams -> Maybe ByteString -> bin -> bout+ecdhKeyMaterial kdf kep ukm zz =+    case kdf of+        KA_HKDF hashAlg ->+            HKDF.expand (extract hashAlg zz) otherInfo outLen+        KA_X963_KDF hashAlg+            | r == 0    -> B.concat (map chunk [1..d])+            | otherwise -> B.concat (map chunk [1..d]) `B.append` B.take r (chunk $ succ d)+          where+            (d, r)   = outLen `divMod` Hash.hashDigestSize prx+            prx      = hashFromProxy hashAlg -    prx      = hashFromProxy hashAlg+            chunk     = B.convert . Hash.hashFinalize . hashCtx+            hashCtx'  = Hash.hashUpdate (Hash.hashInitWith prx) zz+            hashCtx i = Hash.hashUpdate (Hash.hashUpdate hashCtx' $ toWord32 i) otherInfo++  where     outLen   = getMaximumKeySize kep     outBits  = 8 * outLen     toWord32 = i2ospOf_ 4 . fromIntegral -    chunk     = B.convert . Hash.hashFinalize . hashCtx-    hashCtx'  = Hash.hashInitWith prx-    hashCtx i = Hash.hashUpdate (Hash.hashUpdate (Hash.hashUpdate hashCtx' zz) (toWord32 i)) otherInfo+    extract :: (Hash.HashAlgorithm a, ByteArrayAccess ikm)+            => DigestProxy a -> ikm -> HKDF.PRK a+    extract _ = HKDF.extract (fromMaybe B.empty ukm)+     otherInfo =         let ki  = algorithmASN1S Sequence kep             eui = case ukm of
src/Crypto/Store/CMS/Attribute.hs view
@@ -25,12 +25,20 @@     , setContentTypeAttr     , getMessageDigestAttr     , setMessageDigestAttr+    , getSigningTimeAttr+    , setSigningTimeAttr+    , setSigningTimeAttrCurrent     ) where +import Control.Monad.IO.Class+ import Data.ASN1.Types import Data.ByteString (ByteString)+import Data.Hourglass import Data.Maybe (fromMaybe) +import System.Hourglass (dateCurrent)+ import Crypto.Store.ASN1.Generate import Crypto.Store.ASN1.Parse import Crypto.Store.CMS.Type@@ -124,3 +132,36 @@ -- | Add or replace the @messageDigest@ attribute in a list of attributes. setMessageDigestAttr :: ByteString -> [Attribute] -> [Attribute] setMessageDigestAttr d = setAttributeASN1S messageDigest (gOctetString d)+++-- Signing time++signingTime :: OID+signingTime = [1,2,840,113549,1,9,5]++-- | Return the value of the @signingTime@ attribute.+getSigningTimeAttr :: [Attribute] -> Maybe DateTime+getSigningTimeAttr attrs = runParseAttribute signingTime attrs $ do+    ASN1Time _ t offset <- getNext+    let validOffset = maybe True (== TimezoneOffset 0) offset+    if validOffset+        then return t+        else fail "getSigningTimeAttr: invalid timezone"++-- | Add or replace the @signingTime@ attribute in a list of attributes.+setSigningTimeAttr :: DateTime -> [Attribute] -> [Attribute]+setSigningTimeAttr t =+    let normalize val = val { dtTime = (dtTime val) { todNSec = 0 } }+        offset = Just (TimezoneOffset 0)+        ty | t >= timeConvert (Date 2050 January 1) = TimeGeneralized+           | t <  timeConvert (Date 1950 January 1) = TimeGeneralized+           | otherwise                              = TimeUTC+     in setAttributeASN1S signingTime (gASN1Time ty (normalize t) offset)++-- | Add or replace the @signingTime@ attribute in a list of attributes with the+-- current time.  This is equivalent to calling 'setSigningTimeAttr' with the+-- result of 'dateCurrent'.+setSigningTimeAttrCurrent :: MonadIO m => [Attribute] -> m [Attribute]+setSigningTimeAttrCurrent attrs = do+    t <- liftIO dateCurrent+    return (setSigningTimeAttr t attrs)
src/Crypto/Store/CMS/Encrypted.hs view
@@ -101,5 +101,3 @@     parseEncryptedContent = parseWrapped <|> parsePrimitive     parseWrapped  = onNextContainer (Container Context 0) parseOctetStrings     parsePrimitive = do Other Context 0 bs <- getNext; return bs-    parseOctetString = do OctetString bs <- getNext; return bs-    parseOctetStrings = B.concat <$> getMany parseOctetString
src/Crypto/Store/CMS/Enveloped.hs view
@@ -81,6 +81,10 @@ -- -- Some key-derivation functions add restrictions to what characters -- are supported.+--+-- Beware: 'Data.String.fromString' truncates multi-byte characters.+-- If the string may contain non-ASCII characters, prefer instead+-- @'Crypto.Store.PKCS5.fromProtectionPassword' . 'Data.String.fromString'@. type Password = ByteString  -- | Union type related to identification of the recipient.@@ -98,8 +102,7 @@     parse = parseIASN <|> parseSKI       where parseIASN = RecipientIASN <$> parse             parseSKI  = RecipientSKI  <$>-                onNextContainer (Container Context 0) parseBS-            parseBS = do { OctetString bs <- getNext; return bs }+                onNextContainer (Container Context 0) parseOctetStringPrim  getKTVersion :: RecipientIdentifier -> Integer getKTVersion (RecipientIASN _) = 0@@ -173,8 +176,7 @@     parse = parseIASN <|> parseSKI <|> parsePublic       where parseIASN = OriginatorIASN <$> parse             parseSKI  = OriginatorSKI  <$>-                onNextContainer (Container Context 0) parseBS-            parseBS = do { OctetString bs <- getNext; return bs }+                onNextContainer (Container Context 0) parseOctetStringPrim             parsePublic  = OriginatorPublic <$>                 parseOriginatorPublicKey (Container Context 1) @@ -371,7 +373,7 @@                 throwParseError ("RecipientInfo: parsed invalid KT version: " ++ show v)             rid <- parse             ktp <- parseAlgorithm Sequence-            (OctetString ek) <- getNext+            OctetString ek <- getNext             return KTRecipientInfo { ktRid = rid                                    , ktKeyTransportParams = ktp                                    , ktEncryptedKey = ek@@ -394,7 +396,7 @@             IntVal 4 <- getNext             kid <- parse             kep <- parseAlgorithm Sequence-            (OctetString ek) <- getNext+            OctetString ek <- getNext             return KEKRecipientInfo { kekId = kid                                     , kekKeyEncryptionParams = kep                                     , kekEncryptedKey = ek@@ -404,7 +406,7 @@             IntVal 0 <- getNext             kdf <- parseAlgorithm (Container Context 0)             kep <- parseAlgorithm Sequence-            (OctetString ek) <- getNext+            OctetString ek <- getNext             return PasswordRecipientInfo { priKeyDerivationFunc = kdf                                          , priKeyEncryptionParams = kep                                          , priEncryptedKey = ek
src/Crypto/Store/CMS/Info.hs view
@@ -105,12 +105,7 @@ dataASN1S = gOctetString  parseData :: Monoid e => ParseASN1 e ByteString-parseData = do-    next <- getNext-    case next of-        OctetString bs -> return bs-        _              -> throwParseError "Data: parsed unexpected content"-+parseData = parseOctetString  -- Encapsulation 
src/Crypto/Store/CMS/Signed.hs view
@@ -31,6 +31,7 @@ import           Data.ASN1.Types import           Data.ByteString (ByteString) import qualified Data.ByteString as B+import           Data.Hourglass import           Data.List import           Data.Maybe import           Data.X509@@ -90,7 +91,7 @@         dig <- parseAlgorithm Sequence         sAttrs <- parseAttributes (Container Context 0)         alg <- parseAlgorithm Sequence-        (OctetString sig) <- getNext+        OctetString sig <- getNext         uAttrs <- parseAttributes (Container Context 1)         return SignerInfo { siSignerId = sid                           , siDigestAlgorithm = dig@@ -123,8 +124,7 @@     parse = parseIASN <|> parseSKI       where parseIASN = SignerIASN <$> parse             parseSKI  = SignerSKI  <$>-                onNextContainer (Container Context 0) parseBS-            parseBS = do { OctetString bs <- getNext; return bs }+                onNextContainer (Container Context 0) parseOctetStringPrim  -- | Try to find a certificate with the specified identifier. findSigner :: SignerIdentifier@@ -226,16 +226,17 @@ -- valid.  All transmitted certificates are implicitely trusted and all CRLs are -- ignored. withSignerKey :: Applicative f => ConsumerOfSI f-withSignerKey = withSignerCertificate (\_ -> pure True)+withSignerKey = withSignerCertificate (\_ _ -> pure True)  -- | Verify that the signature is valid with one of the X.509 certificates -- contained in the signed data, and verify that the signer certificate is valid -- using the validation function supplied.  All CRLs are ignored. withSignerCertificate :: Applicative f-                      => (CertificateChain -> f Bool) -> ConsumerOfSI f+                      => (Maybe DateTime -> CertificateChain -> f Bool)+                      -> ConsumerOfSI f withSignerCertificate validate ct msg SignerInfo{..} certs crls =     case getCertificateChain of-        Just chain -> validate chain+        Just chain -> validate mSigningTime chain         Nothing    -> pure False   where     getCertificateChain = do@@ -245,6 +246,8 @@         guard validSignature         return $ CertificateChain (cert : others) +    mSigningTime = getSigningTimeAttr siSignedAttrs+     x509Certificates = mapMaybe asX509 certs      asX509 (CertificateCertificate c) = Just c@@ -322,16 +325,10 @@     onNextContainer Sequence $ do         OID oid <- getNext         withObjectID "content type" oid $ \ct ->-            wrap ct <$> onNextContainerMaybe (Container Context 0) parseInner+            wrap ct <$> onNextContainerMaybe (Container Context 0) parseOctetString   where     wrap ct Nothing  = (ct, Detached)     wrap ct (Just c) = (ct, Attached c)--    parseInner = parseContentSingle <|> parseContentChunks--    parseContentSingle = do { OctetString bs <- getNext; return bs }-    parseContentChunks = onNextContainer (Container Universal 4) $-        B.concat <$> getMany parseContentSingle  digestTypesASN1S :: ASN1Elem e => [DigestAlgorithm] -> ASN1Stream e digestTypesASN1S list cont = foldr (algorithmASN1S Sequence) cont list
src/Crypto/Store/CMS/Util.hs view
@@ -24,6 +24,10 @@     , Enumerable(..)     , OIDNameableWrapper(..)     , withObjectID+    -- * Parsing octet strings+    , parseOctetString+    , parseOctetStringPrim+    , parseOctetStrings     -- * Parsing and encoding ASN.1 objects     , ASN1Event     , ASN1ObjectExact(..)@@ -41,12 +45,15 @@     , orElse     ) where +import Control.Applicative+ import           Data.ASN1.BinaryEncoding import           Data.ASN1.BinaryEncoding.Raw import           Data.ASN1.Encoding import           Data.ASN1.OID import           Data.ASN1.Types import           Data.ByteString (ByteString)+import qualified Data.ByteString as B import           Data.List (find) import           Data.X509 @@ -231,3 +238,22 @@     case va of         Nothing -> pb         _       -> return va++-- | Parse an octet string, in primitive or constructed encodings.+parseOctetString :: Monoid e => ParseASN1 e ByteString+parseOctetString = parseOctetStringPrim <|> parseConstructed+  where+    parseConstructed = onNextContainer (Container Universal 4) parseOctetStrings++-- | Parse an octet string in primitive encoding.+parseOctetStringPrim :: Monoid e => ParseASN1 e ByteString+parseOctetStringPrim = do+    next <- getNext+    case next of+        OctetString bs -> return bs+        _ -> throwParseError "parseOctetStringPrim: parsed unexpected content"++-- | Parse some octet strings, in primitive or constructed encodings, and+-- concatenate the result.+parseOctetStrings :: Monoid e => ParseASN1 e ByteString+parseOctetStrings = B.concat <$> getMany parseOctetString
src/Crypto/Store/PKCS12.hs view
@@ -13,6 +13,7 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-} module Crypto.Store.PKCS12@@ -58,6 +59,12 @@     , toNamedCredential     -- * Password-based protection     , Password+    , OptAuthenticated(..)+    , recoverAuthenticated+    , ProtectionPassword+    , emptyNotTerminated+    , fromProtectionPassword+    , toProtectionPassword     , OptProtected(..)     , recover     , recoverA@@ -82,6 +89,7 @@ import Crypto.Store.CMS.Algorithms import Crypto.Store.CMS.Attribute import Crypto.Store.CMS.Encrypted+import Crypto.Store.CMS.Enveloped import Crypto.Store.CMS.Util import Crypto.Store.Error import Crypto.Store.PKCS5@@ -89,30 +97,63 @@ import Crypto.Store.PKCS8  +-- Password-based integrity++-- | Data type for objects that are possibly authenticated with a password.+--+-- Content is verified and retrieved by providing a 'Password' value.  When+-- verification is successful, a value of type 'ProtectionPassword' is also+-- returned and this value can be fed to an inner decryption layer that needs+-- the same password (usual case for PKCS #12).+data OptAuthenticated a = Unauthenticated a+                          -- ^ Value is not authenticated+                        | Authenticated (Password -> Either StoreError (ProtectionPassword, a))+                          -- ^ Value is authenticated with a password++instance Functor OptAuthenticated where+    fmap f (Unauthenticated x) = Unauthenticated (f x)+    fmap f (Authenticated g)   = Authenticated (fmap (fmap f) . g)++-- | Try to recover an 'OptAuthenticated' content using the specified password.+--+-- When successful, the content is returned, as well as the password converted+-- to type 'ProtectionPassword'.  This password value can then be fed to the+-- inner decryption layer when both passwords are known to be same (usual case+-- for PKCS #12).+recoverAuthenticated :: Password -> OptAuthenticated a -> Either StoreError (ProtectionPassword, a)+recoverAuthenticated pwd (Unauthenticated x) = Right (toProtectionPassword pwd, x)+recoverAuthenticated pwd (Authenticated f)   = f pwd++ -- Decoding and parsing  -- | Read a PKCS #12 file from disk.-readP12File :: FilePath -> IO (Either StoreError (OptProtected PKCS12))+readP12File :: FilePath -> IO (Either StoreError (OptAuthenticated PKCS12)) readP12File path = readP12FileFromMemory <$> BS.readFile path  -- | Read a PKCS #12 file from a bytearray in BER format.-readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptProtected PKCS12)+readP12FileFromMemory :: BS.ByteString -> Either StoreError (OptAuthenticated PKCS12) readP12FileFromMemory ber = decode ber >>= integrity   where     integrity PFX{..} =         case macData of-            Nothing -> Unprotected <$> decode authSafeData-            Just md -> return $ Protected (verify md authSafeData)+            Nothing -> Unauthenticated <$> decode authSafeData+            Just md -> return $ Authenticated (verify md authSafeData)      verify MacData{..} content pwdUTF8 =         case digAlg of-            DigestAlgorithm d ->-                let fn key macAlg bs-                        | not (securityAcceptable macAlg) =-                            Left (InvalidParameter "Integrity MAC too weak")-                        | macValue == mac macAlg key bs = decode bs-                        | otherwise = Left BadContentMAC-                 in pkcs12mac Left fn d macParams content pwdUTF8+            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) =+                                Left (InvalidParameter "Integrity MAC too weak")+                            | macValue == mac macAlg key bs = (pwd,) <$> decode bs+                            | otherwise = loop others+                     in pkcs12mac Left fn d macParams content pwd   -- Generating and encoding@@ -122,7 +163,7 @@  -- | Write a PKCS #12 file to disk. writeP12File :: FilePath-             -> IntegrityParams -> Password+             -> IntegrityParams -> ProtectionPassword              -> PKCS12              -> IO (Either StoreError ()) writeP12File path intp pw aSafe =@@ -131,7 +172,7 @@         Right bs -> Right <$> BS.writeFile path bs  -- | Write a PKCS #12 file to a bytearray in DER format.-writeP12FileToMemory :: IntegrityParams -> Password+writeP12FileToMemory :: IntegrityParams -> ProtectionPassword                      -> PKCS12                      -> Either StoreError BS.ByteString writeP12FileToMemory (alg@(DigestAlgorithm hashAlg), pbeParam) pwdUTF8 aSafe =@@ -263,7 +304,7 @@ unencrypted = PKCS12 . (:[]) . Unencrypted  -- | Build a PKCS #12 encrypted with the specified scheme and password.-encrypted :: EncryptionScheme -> Password -> SafeContents -> Either StoreError PKCS12+encrypted :: EncryptionScheme -> ProtectionPassword -> SafeContents -> Either StoreError PKCS12 encrypted alg pwd sc = PKCS12 . (:[]) . Encrypted <$> encrypt alg pwd bs   where bs = encodeASN1Object sc @@ -603,7 +644,7 @@ -- values so that the salt is not reused twice in the encryption process. fromCredential :: Maybe EncryptionScheme -- for certificates                -> EncryptionScheme       -- for private key-               -> Password+               -> ProtectionPassword                -> (X509.CertificateChain, X509.PrivKey)                -> Either StoreError PKCS12 fromCredential = fromCredential' id@@ -618,7 +659,7 @@ fromNamedCredential :: String                     -> Maybe EncryptionScheme -- for certificates                     -> EncryptionScheme       -- for private key-                    -> Password+                    -> ProtectionPassword                     -> (X509.CertificateChain, X509.PrivKey)                     -> Either StoreError PKCS12 fromNamedCredential name = fromCredential' (setFriendlyName name)@@ -626,7 +667,7 @@ fromCredential' :: ([Attribute] -> [Attribute])                 -> Maybe EncryptionScheme -- for certificates                 -> EncryptionScheme       -- for private key-                -> Password+                -> ProtectionPassword                 -> (X509.CertificateChain, X509.PrivKey)                 -> Either StoreError PKCS12 fromCredential' trans algChain algKey pwd (X509.CertificateChain certs, key)@@ -743,7 +784,7 @@ parseOctetStringObject :: (Monoid e, ParseASN1Object [ASN1Event] obj)                        => String -> ParseASN1 e obj parseOctetStringObject name = do-    OctetString bs <- getNext+    bs <- parseOctetString     case decode bs of         Left e  -> throwParseError (name ++ ": " ++ show e)         Right c -> return c
src/Crypto/Store/PKCS5.hs view
@@ -11,7 +11,10 @@ {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TypeFamilies #-} module Crypto.Store.PKCS5-    ( Password+    ( ProtectionPassword+    , emptyNotTerminated+    , fromProtectionPassword+    , toProtectionPassword     , EncryptedContent     -- * High-level API     , PKCS5(..)@@ -38,7 +41,6 @@     ) where  import           Data.ASN1.Types-import           Data.ByteArray (ByteArrayAccess) import           Data.ByteString (ByteString) import           Data.Maybe (fromMaybe) @@ -46,7 +48,6 @@ import Crypto.Store.ASN1.Generate import Crypto.Store.CMS.Algorithms import Crypto.Store.CMS.Encrypted-import Crypto.Store.CMS.Enveloped import Crypto.Store.CMS.Util import Crypto.Store.Error import Crypto.Store.PKCS5.PBES1@@ -192,20 +193,20 @@     fromASN1 = runParseASN1State parse  -- | Encrypt a bytestring with the specified encryption scheme and password.-encrypt :: EncryptionScheme -> Password -> ByteString -> Either StoreError PKCS5+encrypt :: EncryptionScheme -> ProtectionPassword -> ByteString -> Either StoreError PKCS5 encrypt alg pwd bs = build <$> pbEncrypt alg bs pwd   where     build ed = ed `seq` PKCS5 { encryptionAlgorithm = alg, encryptedData = ed }  -- | Decrypt the PKCS #5 content with the specified password.-decrypt :: PKCS5 -> Password -> Either StoreError ByteString+decrypt :: PKCS5 -> ProtectionPassword -> Either StoreError ByteString decrypt obj = pbDecrypt (encryptionAlgorithm obj) (encryptedData obj)   -- Encryption Schemes  -- | Encrypt a bytestring with the specified encryption scheme and password.-pbEncrypt :: EncryptionScheme -> ByteString -> Password+pbEncrypt :: EncryptionScheme -> ByteString -> ProtectionPassword           -> Either StoreError EncryptedContent pbEncrypt (PBES2 p)                 = pbes2  contentEncrypt p pbEncrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentEncrypt MD5  DES p@@ -219,7 +220,7 @@  -- | Decrypt an encrypted bytestring with the specified encryption scheme and -- password.-pbDecrypt :: EncryptionScheme -> EncryptedContent -> Password -> Either StoreError ByteString+pbDecrypt :: EncryptionScheme -> EncryptedContent -> ProtectionPassword -> Either StoreError ByteString pbDecrypt (PBES2 p)                 = pbes2  contentDecrypt p pbDecrypt (PBE_MD5_DES_CBC p)       = pkcs5  Left contentDecrypt MD5  DES p pbDecrypt (PBE_SHA1_DES_CBC p)      = pkcs5  Left contentDecrypt SHA1 DES p@@ -230,9 +231,8 @@ pbDecrypt (PBE_SHA1_RC2_128 p)      = pkcs12rc2 Left contentDecrypt SHA1 128 p pbDecrypt (PBE_SHA1_RC2_40 p)       = pkcs12rc2 Left contentDecrypt SHA1 40 p -pbes2 :: ByteArrayAccess password-      => (Key -> ContentEncryptionParams -> ByteString -> result)-      -> PBES2Parameter -> ByteString -> password -> result+pbes2 :: (Key -> ContentEncryptionParams -> ByteString -> result)+      -> PBES2Parameter -> ByteString -> ProtectionPassword -> result pbes2 encdec PBES2Parameter{..} bs pwd = encdec key pbes2EScheme bs-  where key = kdfDerive pbes2KDF len pwd :: Key+  where key = kdfDerive pbes2KDF len (fromProtectionPassword pwd) :: Key         len = fromMaybe (getMaximumKeySize pbes2EScheme) (kdfKeyLength pbes2KDF)
src/Crypto/Store/PKCS5/PBES1.hs view
@@ -14,6 +14,11 @@ module Crypto.Store.PKCS5.PBES1     ( PBEParameter(..)     , Key+    , ProtectionPassword+    , emptyNotTerminated+    , fromProtectionPassword+    , toProtectionPassword+    , toProtectionPasswords     , pkcs5     , pkcs12     , pkcs12rc2@@ -38,6 +43,7 @@ import           Data.ByteString (ByteString) import           Data.Maybe (fromMaybe) import           Data.Memory.PtrMethods+import           Data.String (IsString(..)) import           Data.Word  import           Foreign.Ptr (plusPtr)@@ -49,6 +55,64 @@ import Crypto.Store.CMS.Util import Crypto.Store.Error +-- | A password stored as a sequence of UTF-8 bytes.+--+-- Some key-derivation functions add restrictions to what characters+-- are supported.+--+-- The data type provides a special value 'emptyNotTerminated' that is used+-- as alternate representation of empty passwords on some systems and that+-- produces encryption results different than an empty bytearray.+--+-- Conversion to/from a regular sequence of bytes is possible with functions+-- 'toProtectionPassword' and 'fromProtectionPassword'.+--+-- Beware: the 'fromString' implementation correctly handles multi-byte+-- characters, so here is not equivalent to the 'ByteString' counterpart.+data ProtectionPassword = NullPassword | PasswordUTF8 ByteString+    deriving Eq++instance Show ProtectionPassword where+    showsPrec _ NullPassword     = showString "emptyNotTerminated"+    showsPrec d (PasswordUTF8 b) = showParen (d > 10) $+        showString "toProtectionPassword " . showsPrec 11 b++instance IsString ProtectionPassword where+    fromString = PasswordUTF8 . B.convert . S.toBytes S.UTF8 . fromString++instance ByteArrayAccess ProtectionPassword where+    length = applyPP 0 B.length+    withByteArray = B.withByteArray . fromProtectionPassword++applyPP :: a -> (ByteString -> a) -> ProtectionPassword -> a+applyPP d _ NullPassword     = d+applyPP _ f (PasswordUTF8 b) = f b++-- | A value denoting an empty password, but having a special encoding when+-- deriving a symmetric key on some systems, like the certificate export+-- wizard on Windows.+--+-- This value is different from @'toProtectionPassword ""'@ and can be tried+-- when decrypting content with a password known to be empty.+emptyNotTerminated :: ProtectionPassword+emptyNotTerminated = NullPassword++-- | Extract the UTF-8 bytes in a password value.+fromProtectionPassword :: ProtectionPassword -> ByteString+fromProtectionPassword = applyPP B.empty id++-- | Build a password value from a sequence of UTF-8 bytes.+--+-- When the password is empty, the special value 'emptyNotTerminated' may+-- be tried as well.+toProtectionPassword :: ByteString -> ProtectionPassword+toProtectionPassword = PasswordUTF8++toProtectionPasswords :: ByteString -> [ProtectionPassword]+toProtectionPasswords bs+    | B.null bs = [PasswordUTF8 B.empty, NullPassword]+    | otherwise = [PasswordUTF8 bs]+ -- | Secret key. type Key = B.ScrubbedBytes @@ -88,8 +152,9 @@ rc4Combine key = Right . snd . RC4.combine (RC4.initialize key)  -- | Conversion to UCS2 from UTF-8, ignoring non-BMP bits.-toUCS2 :: (ByteArrayAccess butf8, ByteArray bucs2) => butf8 -> Maybe bucs2-toUCS2 pwdUTF8+toUCS2 :: ByteArray bucs2 => ProtectionPassword -> Maybe bucs2+toUCS2 NullPassword = Just B.empty+toUCS2 (PasswordUTF8 pwdUTF8)     | B.null r  = Just pwdUCS2     | otherwise = Nothing   where@@ -105,19 +170,19 @@  -- | Apply PBKDF1 on the specified password and run an encryption or decryption -- function on some input using derived key and IV.-pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)+pkcs5 :: (Hash.HashAlgorithm hash, BlockCipher cipher)       => (StoreError -> result)       -> (Key -> ContentEncryptionParams -> ByteString -> result)       -> DigestProxy hash       -> ContentEncryptionCipher cipher       -> PBEParameter       -> ByteString-      -> password+      -> ProtectionPassword       -> result pkcs5 failure encdec hashAlg cec pbeParam bs pwd     | proxyBlockSize cec /= 8 = failure (InvalidParameter "Invalid cipher block size")     | otherwise =-        case pbkdf1 hashAlg pwd pbeParam 16 of+        case pbkdf1 hashAlg (fromProtectionPassword pwd) pbeParam 16 of             Left err -> failure err             Right dk ->                 let (key, iv) = B.splitAt 8 (dk :: Key)@@ -145,14 +210,14 @@  -- | Apply PKCS #12 derivation on the specified password and run an encryption -- or decryption function on some input using derived key and IV.-pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher, ByteArrayAccess password)+pkcs12 :: (Hash.HashAlgorithm hash, BlockCipher cipher)        => (StoreError -> result)        -> (Key -> ContentEncryptionParams -> ByteString -> result)        -> DigestProxy hash        -> ContentEncryptionCipher cipher        -> PBEParameter        -> ByteString-       -> password+       -> ProtectionPassword        -> result pkcs12 failure encdec hashAlg cec pbeParam bs pwdUTF8 =     case toUCS2 pwdUTF8 of@@ -168,14 +233,14 @@ -- | Apply PKCS #12 derivation on the specified password and run an encryption -- or decryption function on some input using derived key and IV.  This variant -- uses an RC2 cipher with the EKL specified (effective key length).-pkcs12rc2 :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+pkcs12rc2 :: Hash.HashAlgorithm hash           => (StoreError -> result)           -> (Key -> ContentEncryptionParams -> ByteString -> result)           -> DigestProxy hash           -> Int           -> PBEParameter           -> ByteString-          -> password+          -> ProtectionPassword           -> result pkcs12rc2 failure encdec hashAlg len pbeParam bs pwdUTF8 =     case toUCS2 pwdUTF8 of@@ -191,14 +256,14 @@ -- | Apply PKCS #12 derivation on the specified password and run an encryption -- or decryption function on some input using derived key.  This variant does -- not derive any IV and is required for RC4.-pkcs12stream :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+pkcs12stream :: Hash.HashAlgorithm hash              => (StoreError -> result)              -> (Key -> ByteString -> result)              -> DigestProxy hash              -> Int              -> PBEParameter              -> ByteString-             -> password+             -> ProtectionPassword              -> result pkcs12stream failure encdec hashAlg keyLen pbeParam bs pwdUTF8 =     case toUCS2 pwdUTF8 of@@ -209,13 +274,13 @@  -- | Apply PKCS #12 derivation on the specified password and run a MAC function -- on some input using derived key.-pkcs12mac :: (Hash.HashAlgorithm hash, ByteArrayAccess password)+pkcs12mac :: Hash.HashAlgorithm hash           => (StoreError -> result)           -> (Key -> MACAlgorithm -> ByteString -> result)           -> DigestProxy hash           -> PBEParameter           -> ByteString-          -> password+          -> ProtectionPassword           -> result pkcs12mac failure macFn hashAlg pbeParam bs pwdUTF8 =     case toUCS2 pwdUTF8 of
src/Crypto/Store/PKCS8.hs view
@@ -35,7 +35,10 @@     , PrivateKeyFormat(..)     , FormattedKey(..)     -- * Password-based protection-    , Password+    , ProtectionPassword+    , emptyNotTerminated+    , fromProtectionPassword+    , toProtectionPassword     , OptProtected(..)     , recover     , recoverA@@ -78,7 +81,7 @@ -- | Data type for objects that are possibly protected with a password. data OptProtected a = Unprotected a                       -- ^ Value is unprotected-                    | Protected (Password -> Either StoreError a)+                    | Protected (ProtectionPassword -> Either StoreError a)                       -- ^ Value is protected with a password  instance Functor OptProtected where@@ -86,7 +89,7 @@     fmap f (Protected g)   = Protected (fmap f . g)  -- | Try to recover an 'OptProtected' content using the specified password.-recover :: Password -> OptProtected a -> Either StoreError a+recover :: ProtectionPassword -> OptProtected a -> Either StoreError a recover _   (Unprotected x) = Right x recover pwd (Protected f)   = f pwd @@ -98,12 +101,14 @@ -- > -- > [encryptedKey] <- readKeyFile "privkey.pem" -- > let askForPassword = putStr "Please enter password: " >> B.getLine--- > result <- recoverA askForPassword encryptedKey+-- > result <- recoverA (toProtectionPassword <$> askForPassword) encryptedKey -- > case result of -- >     Left err  -> putStrLn $ "Unable to recover key: " ++ show err -- >     Right key -> print key recoverA :: Applicative f-         => f Password -> OptProtected a -> f (Either StoreError a)+         => f ProtectionPassword+         -> OptProtected a+         -> f (Either StoreError a) recoverA _   (Unprotected x) = pure (Right x) recoverA get (Protected f)   = fmap f get @@ -178,7 +183,7 @@ -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt. writeEncryptedKeyFile :: FilePath-                      -> EncryptionScheme -> Password-> X509.PrivKey+                      -> EncryptionScheme -> ProtectionPassword -> X509.PrivKey                       -> IO (Either StoreError ()) writeEncryptedKeyFile path alg pwd privKey =     let pem = encryptKeyToPEM alg pwd privKey@@ -191,8 +196,8 @@ -- -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt.-writeEncryptedKeyFileToMemory :: EncryptionScheme -> Password -> X509.PrivKey-                              -> Either StoreError B.ByteString+writeEncryptedKeyFileToMemory :: EncryptionScheme -> ProtectionPassword+                              -> X509.PrivKey -> Either StoreError B.ByteString writeEncryptedKeyFileToMemory alg pwd privKey =     pemWriteBS <$> encryptKeyToPEM alg pwd privKey @@ -241,7 +246,7 @@ -- -- Fresh 'EncryptionScheme' parameters should be generated for each key to -- encrypt.-encryptKeyToPEM :: EncryptionScheme -> Password -> X509.PrivKey+encryptKeyToPEM :: EncryptionScheme -> ProtectionPassword -> X509.PrivKey                 -> Either StoreError PEM encryptKeyToPEM alg pwd privKey = toPEM <$> encrypt alg pwd bs   where bs = pemContent (keyToModernPEM privKey)
tests/CMS/Instances.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -- | Orphan instances. module CMS.Instances@@ -154,7 +155,12 @@     ]  instance Arbitrary MACAlgorithm where-    arbitrary = (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest+    arbitrary = oneof+        [ (\(DigestAlgorithm alg) -> HMAC alg) <$> arbitraryIntegrityDigest+        , (\(SomeNat p) -> KMAC_SHAKE128 p) <$> arbitraryNat <*> arbitraryCtx+        , (\(SomeNat p) -> KMAC_SHAKE256 p) <$> arbitraryNat <*> arbitraryCtx+        ]+      where arbitraryCtx = elements [ B.empty , "ctx" , "custom string" ]  instance Arbitrary OAEPParams where     arbitrary = do@@ -377,19 +383,24 @@ arbitraryAgreeParams :: Bool -> KeyEncryptionParams -> Gen KeyAgreementParams arbitraryAgreeParams allowCofactorDH alg     | allowCofactorDH = oneof-        [ flip StdDH alg <$> arbitraryDigest-        , flip CofactorDH alg <$> arbitraryDigest+        [ flip StdDH alg <$> elements stdKDFs+        , flip CofactorDH alg <$> elements cofactorKDFs         ]-    | otherwise = flip StdDH alg <$> arbitraryDigest+    | otherwise = flip StdDH alg <$> elements stdKDFs   where-    arbitraryDigest =-        elements-            [ DigestAlgorithm SHA1-            , DigestAlgorithm SHA224-            , DigestAlgorithm SHA256-            , DigestAlgorithm SHA384-            , DigestAlgorithm SHA512-            ]+    cofactorKDFs =+        [ KA_X963_KDF SHA1+        , KA_X963_KDF SHA224+        , KA_X963_KDF SHA256+        , KA_X963_KDF SHA384+        , KA_X963_KDF SHA512+        ]++    stdKDFs = cofactorKDFs +++        [ KA_HKDF SHA256+        , KA_HKDF SHA384+        , KA_HKDF SHA512+        ]  arbitraryEnvDev :: ContentEncryptionKey                 -> Gen ([ProducerOfRI Gen], ConsumerOfRI Gen)
tests/PKCS12/Instances.hs view
@@ -29,7 +29,7 @@ arbitraryIntegrityParams :: Gen IntegrityParams arbitraryIntegrityParams = (,) <$> arbitraryIntegrityDigest <*> arbitrary -arbitraryPKCS12 :: Password -> Gen PKCS12+arbitraryPKCS12 :: ProtectionPassword -> Gen PKCS12 arbitraryPKCS12 pwd = do     p <- one     ps <- listOf one
tests/PKCS12/Tests.hs view
@@ -38,13 +38,12 @@         let r = readP12FileFromMemory (pemContent pem)          assertRight r $ \integrity ->-            assertRight (recover pwd integrity) $ \privacy ->-                assertRight (recover pwd $ unPKCS12 privacy) $ \scs -> do+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do                     step ("Testing " ++ name)-                    recover pwd (getAllSafeKeys scs) @?= Right [key]+                    recover ppwd (getAllSafeKeys scs) @?= Right [key]                     getAllSafeX509Certs scs @?= certs   where-    pwd :: Password     pwd = fromString "dontchangeme"      nameIntegrity n = "integrity with " ++ n@@ -67,19 +66,47 @@                      , "PBE-SHA1-RC2-40"                      ] +testEmptyPassword :: TestTree+testEmptyPassword = testCaseSteps "empty password" $ \step -> do+    step "Reading PKCS #12 files"+    pems <- readPEMs path+    length pems @?= length infos++    forM_ (zip infos pems) $ \((name, numKeys, numCerts), pem) -> do+        let r = readP12FileFromMemory (pemContent pem)++        assertRight r $ \integrity ->+            assertRight (recoverAuthenticated pwd integrity) $ \(ppwd, privacy) ->+                assertRight (recover ppwd $ unPKCS12 privacy) $ \scs -> do+                    step ("Testing " ++ name)+                    assertRight (recover ppwd $ getAllSafeKeys scs) $ \keys ->+                        length keys @?= numKeys+                    length (getAllSafeX509Certs scs) @?= numCerts+  where+    pwd = fromString ""++    path  = testFile "pkcs12-empty-password.pem"+    infos = [ ("Windows Certificate Export Wizard", 1, 2)+            , ("OpenSSL", 1, 1)+            , ("GnuTLS with --empty-password", 1, 1)+            , ("GnuTLS with --null-password", 1, 1)+            ]+ propertyTests :: TestTree propertyTests = localOption (QuickCheckMaxSize 5) $ testGroup "properties"     [ testProperty "marshalling" $ do-        pE <- arbitraryPassword+        pE <- arbitrary         c <- arbitraryPKCS12 pE         let r = readP12FileFromMemory $ writeUnprotectedP12FileToMemory c-        return $ Right (Right c) === (recover (fromString "not-used") <$> r)+            unused = fromString "not-used"+        return $ Right (Right c) === (fmap snd . recoverAuthenticated unused <$> r)     , testProperty "marshalling with authentication" $ do         params <- arbitraryIntegrityParams-        c <- arbitraryPassword >>= arbitraryPKCS12-        pI <- arbitraryPassword+        c <- arbitrary >>= arbitraryPKCS12+        pI <- arbitrary         let r = readP12FileFromMemory <$> writeP12FileToMemory params pI c-        return $ Right (Right (Right c)) === (fmap (recover pI) <$> r)+            p = fromProtectionPassword pI+        return $ Right (Right (Right (pI, c))) === (fmap (recoverAuthenticated p) <$> r)     , localOption (QuickCheckTests 20) $ testProperty "converting credentials" $         \pChain pKey privKey ->             testCredConv privKey toCredential (fromCredential pChain pKey)@@ -92,7 +119,7 @@     ]   where     testCredConv privKey to from = do-        pwd <- arbitraryPassword+        pwd <- arbitrary         chain <- arbitrary >>= arbitraryCertificateChain         chain' <- shuffleCertificateChain chain         let cred = (chain, privKey)@@ -104,5 +131,6 @@     testGroup "PKCS12"         [ testType "RSA"                        "rsa"         , testType "Ed25519"                    "ed25519"+        , testEmptyPassword         , propertyTests         ]
tests/PKCS8/Instances.hs view
@@ -15,6 +15,11 @@  import CMS.Instances +instance Arbitrary ProtectionPassword where+    arbitrary = oneof [ return emptyNotTerminated+                      , toProtectionPassword <$> arbitraryPassword+                      ]+ instance Arbitrary PBEParameter where     arbitrary = do         salt <- generateSalt 8
tests/PKCS8/Tests.hs view
@@ -11,7 +11,7 @@ import Test.Tasty.QuickCheck  import Util-import PKCS8.Instances+import PKCS8.Instances ()  keyTests :: String -> TestTree keyTests prefix =@@ -85,7 +85,7 @@         let r = readKeyFileFromMemory $ writeKeyFileToMemory fmt l         in map Right l === map (recover $ fromString "not-used") r     , testProperty "marshalling with encryption" $ \es k -> do-        p <- arbitraryPassword+        p <- arbitrary         let r = readKeyFileFromMemory <$> writeEncryptedKeyFileToMemory es p k         return $ Right [Right k] === (map (recover p) <$> r)     ]
+ tests/files/pkcs12-empty-password.pem view
@@ -0,0 +1,191 @@+The file from <https://bugzilla.mozilla.org/show_bug.cgi?id=265991>+encoded in base64:++    openssl base64 -in alice-nopassword.pfx++-----BEGIN PKCS12-----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+-----END PKCS12-----++A file generated with OpenSSL and encoded in base64:++    openssl pkcs12 -legacy -export -in tests/files/rsa-self-signed-cert.pem \+      -inkey tests/files/rsa-unencrypted-trad.pem -password 'pass:' \+        | openssl base64++-----BEGIN PKCS12-----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+-----END PKCS12-----++A file generated by GnuTLS with --empty-password:++gnutls-certtool --to-p12 --p12-name=demo \+    --load-privkey tests/files/rsa-unencrypted-trad.pem \+    --load-certificate tests/files/rsa-self-signed-cert.pem \+    --empty-password++-----BEGIN PKCS12-----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-----END PKCS12-----++A file generated by GnuTLS with --null-password:++gnutls-certtool --to-p12 --p12-name=demo \+    --load-privkey tests/files/rsa-unencrypted-trad.pem \+    --load-certificate tests/files/rsa-self-signed-cert.pem \+    --null-password++-----BEGIN PKCS12-----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-----END PKCS12-----