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 +23/−0
- LICENSE +1/−1
- README.md +37/−22
- cryptostore.cabal +1/−1
- src/Crypto/Store/CMS.hs +7/−1
- src/Crypto/Store/CMS/Algorithms.hs +197/−62
- src/Crypto/Store/CMS/Attribute.hs +41/−0
- src/Crypto/Store/CMS/Encrypted.hs +0/−2
- src/Crypto/Store/CMS/Enveloped.hs +9/−7
- src/Crypto/Store/CMS/Info.hs +1/−6
- src/Crypto/Store/CMS/Signed.hs +10/−13
- src/Crypto/Store/CMS/Util.hs +26/−0
- src/Crypto/Store/PKCS12.hs +59/−18
- src/Crypto/Store/PKCS5.hs +11/−11
- src/Crypto/Store/PKCS5/PBES1.hs +78/−13
- src/Crypto/Store/PKCS8.hs +14/−9
- tests/CMS/Instances.hs +23/−12
- tests/PKCS12/Instances.hs +1/−1
- tests/PKCS12/Tests.hs +38/−10
- tests/PKCS8/Instances.hs +5/−0
- tests/PKCS8/Tests.hs +2/−2
- tests/files/pkcs12-empty-password.pem +191/−0
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 -[](https://travis-ci.org/ocheron/cryptostore)-[](https://en.wikipedia.org/wiki/BSD_licenses)-[](https://haskell.org/)+[](https://github.com/ocheron/cryptostore/actions/workflows/tests.yml)+[](https://en.wikipedia.org/wiki/BSD_licenses)+[](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-----