openpgp-crypto-api 0.4 → 0.6
raw patch · 15 files changed
+645/−129 lines, 15 filesdep +quickcheck-instancesdep +taggeddep +transformersdep ~binarydep ~crypto-apidep ~cryptocipherbinary-added
Dependencies added: quickcheck-instances, tagged, transformers
Dependency ranges changed: binary, crypto-api, cryptocipher, openpgp
Files
- Data/OpenPGP/CryptoAPI.hs +418/−103
- Data/OpenPGP/CryptoAPI/Blowfish128.hs +20/−0
- Data/OpenPGP/CryptoAPI/Util.hs +80/−0
- README +2/−1
- openpgp-crypto-api.cabal +35/−16
- tests/data/encryptedSecretKey.gpg binary
- tests/data/hello.gpg binary
- tests/data/helloKey.gpg binary
- tests/data/pubring.gpg binary
- tests/data/secring.gpg binary
- tests/data/symmetric-aes.gpg +1/−0
- tests/data/symmetric-blowfish.gpg +1/−0
- tests/data/symmetric-no-mdc.gpg +2/−0
- tests/data/symmetric-with-session-key.gpg binary
- tests/suite.hs +86/−9
Data/OpenPGP/CryptoAPI.hs view
@@ -1,13 +1,19 @@-module Data.OpenPGP.CryptoAPI (fingerprint, sign, verify) where+module Data.OpenPGP.CryptoAPI (fingerprint, sign, verify, encrypt, decryptAsymmetric, decryptSymmetric, decryptSecretKey) where -import Numeric-import Data.Word import Data.Char import Data.Bits import Data.List (find)-import Data.Binary+import Data.Maybe (mapMaybe, catMaybes, listToMaybe)+import Control.Arrow+import Control.Applicative+import Control.Monad+import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.State (StateT(..), runStateT)+import Data.Binary (encode, decode, get, Word16) import Crypto.Classes hiding (hash,sign,verify,encode)-import Crypto.Random (CryptoRandomGen)+import Data.Tagged (untag, asTaggedTypeOf, Tagged(..))+import Crypto.Modes (cfb, unCfb, IV, zeroIV)+import Crypto.Random (CryptoRandomGen, GenError(GenErrorOther), genBytes) import Crypto.Hash.MD5 (MD5) import Crypto.Hash.SHA1 (SHA1) import Crypto.Hash.RIPEMD160 (RIPEMD160)@@ -15,25 +21,41 @@ import Crypto.Hash.SHA384 (SHA384) import Crypto.Hash.SHA512 (SHA512) import Crypto.Hash.SHA224 (SHA224)-import qualified Crypto.Classes as Serialize (encode)+import Crypto.Cipher.AES (AES128,AES192,AES256)+import qualified Data.Serialize as Serialize import qualified Crypto.Cipher.RSA as RSA import qualified Crypto.Cipher.DSA as DSA import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as LZ-import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString) import qualified Data.OpenPGP as OpenPGP+import Data.OpenPGP.CryptoAPI.Util+import Data.OpenPGP.CryptoAPI.Blowfish128 --- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket--- <http://tools.ietf.org/html/rfc4880#section-12.2>-fingerprint :: OpenPGP.Packet -> String-fingerprint p- | OpenPGP.version p == 4 = snd $ hash OpenPGP.SHA1 material- | OpenPGP.version p `elem` [2, 3] = snd $ hash OpenPGP.MD5 material- | otherwise = error "Unsupported Packet version or type in fingerprint"- where- material = LZ.concat $ OpenPGP.fingerprint_material p+-- | An encryption routine+type Encrypt g = (LZ.ByteString -> g -> (LZ.ByteString, g)) +-- | A decryption routine, Bool=True to do resynchronization+type Decrypt = (Bool -> LZ.ByteString -> (LZ.ByteString, LZ.ByteString))++-- Start differently-formatted section+-- | This should be in Crypto.Classes and is based on buildKeyIO+buildKeyGen :: (BlockCipher k, CryptoRandomGen g) => g -> Either GenError (k, g)+buildKeyGen = runStateT (go (0::Int))+ where+ go 1000 = lift $ Left $ GenErrorOther+ "Tried 1000 times to generate a key from the system entropy.\+ \ No keys were returned! Perhaps the system entropy is broken\+ \ or perhaps the BlockCipher instance being used has a non-flat\+ \ keyspace."+ go i = do+ let bs = keyLength+ kd <- StateT $ genBytes ((7 + untag bs) `div` 8)+ case buildKey kd of+ Nothing -> go (i+1)+ Just k -> return $ k `asTaggedTypeOf` bs+-- End differently-formatted section+ find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet find_key = OpenPGP.find_key fingerprint @@ -54,13 +76,6 @@ pad s = replicate (len - length s) '0' ++ s len = (outputLength `for` d) `div` 8 -hexString :: [Word8] -> String-hexString = foldr (pad `oo` showHex) ""- where- oo = (.) . (.)- pad s | odd $ length s = '0':s- | otherwise = s- -- http://tools.ietf.org/html/rfc3447#page-43 -- http://tools.ietf.org/html/rfc4880#section-5.2.2 emsa_pkcs1_v1_5_hash_padding :: OpenPGP.HashAlgorithm -> BS.ByteString@@ -74,22 +89,80 @@ emsa_pkcs1_v1_5_hash_padding _ = error "Unsupported HashAlgorithm in emsa_pkcs1_v1_5_hash_padding." -toStrictBS :: LZ.ByteString -> BS.ByteString-toStrictBS = BS.concat . LZ.toChunks+blockBytes :: (BlockCipher k, Num n) => k -> n+blockBytes k = fromIntegral $ blockSizeBytes `for` k -toLazyBS :: BS.ByteString -> LZ.ByteString-toLazyBS = LZ.fromChunks . (:[])+pgpCFBPrefix :: (BlockCipher k, CryptoRandomGen g) => k -> g -> (LZ.ByteString, g)+pgpCFBPrefix k g =+ (toLazyBS $ str `BS.append` BS.reverse (BS.take 2 $ BS.reverse str), g')+ where+ Right (str,g') = genBytes (blockSizeBytes `for` k) g -fromJustMPI :: Maybe OpenPGP.MPI -> Integer-fromJustMPI (Just (OpenPGP.MPI x)) = x-fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI"+pgpCFB :: (BlockCipher k, CryptoRandomGen g) => k -> (LZ.ByteString -> LZ.ByteString -> LZ.ByteString) -> Encrypt g+pgpCFB k sufGen bs g =+ (simpleCFB k zeroIV (LZ.concat [p, bs, sufGen p bs]), g')+ where+ (p,g') = pgpCFBPrefix k g +simpleCFB :: (BlockCipher k) => k -> IV k -> LZ.ByteString -> LZ.ByteString+simpleCFB k iv = padThenUnpad k (fst . cfb k iv)++pgpUnCFB :: (BlockCipher k) => k -> Decrypt+pgpUnCFB k False s = LZ.splitAt (2 + blockBytes k) $ simpleUnCFB k zeroIV s+pgpUnCFB k True s = (simpleUnCFB k zeroIV prefix, simpleUnCFB k iv content)+ where+ Just iv = sDecode $ toStrictBS $ LZ.drop 2 prefix+ (prefix, content) = LZ.splitAt (2 + blockBytes k) s++simpleUnCFB :: (BlockCipher k) => k -> IV k -> LZ.ByteString -> LZ.ByteString+simpleUnCFB k iv = padThenUnpad k (fst . unCfb k iv)++padThenUnpad :: (BlockCipher k) => k -> (LZ.ByteString -> LZ.ByteString) -> LZ.ByteString -> LZ.ByteString+padThenUnpad k f s = dropPadEnd (f padded)+ where+ dropPadEnd s = LZ.take (LZ.length s - padAmount) s+ padded = s `LZ.append` LZ.replicate padAmount 0+ padAmount = blockBytes k - (LZ.length s `mod` blockBytes k)++addBitLen :: LZ.ByteString -> LZ.ByteString+addBitLen bytes = encode (bitLen bytes :: Word16) `LZ.append` bytes+ where+ bitLen bytes = (fromIntegral (LZ.length bytes) - 1) * 8 + sigBit bytes+ sigBit bytes = fst $ until ((==0) . snd)+ (first (+1) . second (`shiftR` 1)) (0,LZ.index bytes 0)++-- Drops 2 because the value is an MPI+rsaDecrypt :: RSA.PrivateKey -> BS.ByteString -> Maybe BS.ByteString+rsaDecrypt pk = hush . RSA.decrypt pk . BS.drop 2++rsaEncrypt :: (CryptoRandomGen g) => RSA.PublicKey -> BS.ByteString -> StateT g (Either GenError) BS.ByteString+rsaEncrypt pk bs = StateT (\g ->+ case RSA.encrypt g pk bs of+ (Left (RSA.RandomGenFailure e)) -> Left e+ (Left e) -> Left (GenErrorOther $ show e)+ (Right v) -> Right v+ )+ integerBytesize :: Integer -> Int-integerBytesize i = length (LZ.unpack $ encode (OpenPGP.MPI i)) - 2+integerBytesize i = fromIntegral $ LZ.length (encode (OpenPGP.MPI i)) - 2 keyParam :: Char -> OpenPGP.Packet -> Integer keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k) +keyAlgorithmIs :: OpenPGP.KeyAlgorithm -> OpenPGP.Packet -> Bool+keyAlgorithmIs algo p = OpenPGP.key_algorithm p == algo++secretKeys :: OpenPGP.Message -> ([(String, RSA.PrivateKey)], [(String, DSA.PrivateKey)])+secretKeys (OpenPGP.Message keys) =+ (+ map (fingerprint &&& privateRSAkey) rsa,+ map (fingerprint &&& privateDSAkey) dsa+ )+ where+ dsa = secrets OpenPGP.DSA+ rsa = secrets OpenPGP.RSA+ secrets algo = filter (swing all [isSecretKey, keyAlgorithmIs algo]) keys+ privateRSAkey :: OpenPGP.Packet -> RSA.PrivateKey privateRSAkey k = -- Invert p and q because u is pinv not qinv@@ -117,80 +190,77 @@ dsaKey k = DSA.PublicKey (keyParam 'p' k, keyParam 'g' k, keyParam 'q' k) (keyParam 'y' k) +-- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket+-- <http://tools.ietf.org/html/rfc4880#section-12.2>+fingerprint :: OpenPGP.Packet -> String+fingerprint p+ | OpenPGP.version p == 4 = snd $ hash OpenPGP.SHA1 material+ | OpenPGP.version p `elem` [2, 3] = snd $ hash OpenPGP.MD5 material+ | otherwise = error "Unsupported Packet version or type in fingerprint"+ where+ material = LZ.concat $ OpenPGP.fingerprint_material p+ -- | Verify a message signature-verify :: OpenPGP.Message -- ^ Keys that may have made the signature- -> OpenPGP.Message -- ^ LiteralData message to verify- -> Int -- ^ Index of signature to verify (0th, 1st, etc)- -> Bool-verify keys message sigidx =+verify ::+ OpenPGP.Message -- ^ Keys that may have made the signature+ -> OpenPGP.SignatureOver -- ^ Signatures to verify+ -> OpenPGP.SignatureOver -- ^ Will only contain signatures that passed+verify keys over =+ over {OpenPGP.signatures_over = mapMaybe (uncurry $ verifyOne keys) sigs}+ where+ sigs = map (\s -> (s, toStrictBS $ encode over `LZ.append` OpenPGP.trailer s))+ (OpenPGP.signatures_over over)++verifyOne :: OpenPGP.Message -> OpenPGP.Packet -> BS.ByteString -> Maybe OpenPGP.Packet+verifyOne keys sig over = fmap (const sig) $ maybeKey >>= case OpenPGP.key_algorithm sig of OpenPGP.DSA -> dsaVerify alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify- | otherwise -> error ("Unsupported key algorithm " ++ show alg)+ | otherwise -> const Nothing where- dsaVerify = let k' = dsaKey k in- case DSA.verify dsaSig (dsaTruncate k' . bhash) k' signature_over of- Left _ -> False- Right v -> v- rsaVerify =- case RSA.verify bhash padding (rsaKey k) signature_over rsaSig of- Left _ -> False- Right v -> v- rsaSig = toStrictBS $ LZ.drop 2 $ encode (head $ OpenPGP.signature sig)+ dsaVerify k = let k' = dsaKey k in+ hush $ DSA.verify dsaSig (dsaTruncate k' . bhash) k' over+ rsaVerify k = hush $ RSA.verify bhash padding (rsaKey k) over rsaSig+ [rsaSig] = map (toStrictBS . LZ.drop 2 . encode) (OpenPGP.signature sig) dsaSig = let [OpenPGP.MPI r, OpenPGP.MPI s] = OpenPGP.signature sig in (r, s) dsaTruncate (DSA.PublicKey (_,_,q) _) = BS.take (integerBytesize q) bhash = fst . hash hash_algo . toLazyBS padding = emsa_pkcs1_v1_5_hash_padding hash_algo hash_algo = OpenPGP.hash_algorithm sig- signature_over = toStrictBS $ dta `LZ.append` OpenPGP.trailer sig- Just k = OpenPGP.signature_issuer sig >>= find_key keys- sig = sigs !! sigidx- (sigs, (OpenPGP.LiteralDataPacket {OpenPGP.content = dta}):_) =- OpenPGP.signatures_and_data message+ maybeKey = OpenPGP.signature_issuer sig >>= find_key keys --- | Sign data or key/userID pair.+-- | Make a signature+--+-- In order to set more options on a signature, pass in a signature packet. sign :: (CryptoRandomGen g) =>- OpenPGP.Message -- ^ SecretKeys, one of which will be used- -> OpenPGP.Message -- ^ Message containing data or key to sign, and optional signature packet- -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature- -> String -- ^ KeyID of key to choose or @[]@ for first- -> Integer -- ^ Timestamp for signature (unless sig supplied)- -> g -- ^ Random number generator- -> OpenPGP.Packet-sign keys message hsh keyid timestamp g =- -- WARNING: this style of update is unsafe on most fields- -- it is safe on signature and hash_head, though- sig {- OpenPGP.signature = map OpenPGP.MPI final,- OpenPGP.hash_head = 0 -- TODO- }+ OpenPGP.Message -- ^ SecretKeys, one of which will be used+ -> OpenPGP.SignatureOver -- ^ Data to sign, and optional signature packet+ -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature+ -> String -- ^ KeyID of key to choose+ -> Integer -- ^ Timestamp for signature (unless sig supplied)+ -> g -- ^ Random number generator+ -> (OpenPGP.SignatureOver, g)+sign keys over hsh keyid timestamp g = (over {OpenPGP.signatures_over = [sig]}, g') where- final = case OpenPGP.key_algorithm sig of- OpenPGP.DSA -> [dsaR, dsaS]- kalgo | kalgo `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> [toNum rsaFinal]+ (final, g') = case OpenPGP.key_algorithm sig of+ OpenPGP.DSA -> ([dsaR, dsaS], dsaG)+ kalgo | kalgo `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> ([toNum rsaFinal], g) | otherwise -> error ("Unsupported key algorithm " ++ show kalgo ++ "in sign")- Right ((dsaR,dsaS),_) = let k' = privateDSAkey k in+ Right ((dsaR,dsaS),dsaG) = let k' = privateDSAkey k in DSA.sign g (dsaTruncate k' . bhash) k' dta Right rsaFinal = RSA.sign bhash padding (privateRSAkey k) dta dsaTruncate (DSA.PrivateKey (_,_,q) _) = BS.take (integerBytesize q)- dta = toStrictBS $ case signOver of {- OpenPGP.LiteralDataPacket {OpenPGP.content = c} -> c;- _ -> LZ.concat $ OpenPGP.fingerprint_material signOver ++ [- LZ.singleton 0xB4,- encode (fromIntegral (length firstUserID) :: Word32),- LZ.fromString firstUserID- ]- } `LZ.append` OpenPGP.trailer sig- sig = findSigOrDefault (find OpenPGP.isSignaturePacket m)+ dta = toStrictBS $ encode over `LZ.append` OpenPGP.trailer sig+ sig = findSigOrDefault (listToMaybe $ OpenPGP.signatures_over over) padding = emsa_pkcs1_v1_5_hash_padding hsh bhash = fst . hash hsh . toLazyBS toNum = BS.foldl (\a b -> a `shiftL` 8 .|. fromIntegral b) 0+ Just k = find_key keys keyid -- Either a SignaturePacket was found, or we need to make one- findSigOrDefault (Just s) =- OpenPGP.signaturePacket+ findSigOrDefault (Just s) = OpenPGP.signaturePacket (OpenPGP.version s) (OpenPGP.signature_type s) (OpenPGP.key_algorithm k) -- force to algo of key@@ -198,7 +268,7 @@ (OpenPGP.hashed_subpackets s) (OpenPGP.unhashed_subpackets s) (OpenPGP.hash_head s)- (OpenPGP.signature s)+ (map OpenPGP.MPI final) findSigOrDefault Nothing = OpenPGP.signaturePacket 4 defaultStype@@ -207,32 +277,277 @@ ([ -- Do we really need to pass in timestamp just for the default? OpenPGP.SignatureCreationTimePacket $ fromIntegral timestamp,- OpenPGP.IssuerPacket keyid'- ] ++ (case signOver of- OpenPGP.LiteralDataPacket {} -> []- _ -> [] -- TODO: OpenPGP.KeyFlagsPacket [0x01, 0x02]+ OpenPGP.IssuerPacket $ fingerprint k+ ] ++ (case over of+ OpenPGP.KeySignature {} -> [OpenPGP.KeyFlagsPacket {+ OpenPGP.certify_keys = True,+ OpenPGP.sign_data = True,+ OpenPGP.encrypt_communication = False,+ OpenPGP.encrypt_storage = False,+ OpenPGP.split_key = False,+ OpenPGP.authentication = False,+ OpenPGP.group_key = False+ }]+ _ -> [] )) []- undefined- undefined+ 0 -- TODO+ (map OpenPGP.MPI final) - keyid' = reverse $ take 16 $ reverse $ fingerprint k- Just k = find_key keys keyid+ defaultStype = case over of+ OpenPGP.DataSignature ld _+ | OpenPGP.format ld == 'b' -> 0x00+ | otherwise -> 0x01+ OpenPGP.KeySignature {} -> 0x1F+ OpenPGP.SubkeySignature {} -> 0x18+ OpenPGP.CertificationSignature {} -> 0x13 - Just (OpenPGP.UserIDPacket firstUserID) = find isUserID m+encrypt :: (CryptoRandomGen g) =>+ [BS.ByteString] -- ^ Passphrases, all of which will be used+ -> OpenPGP.Message -- ^ PublicKeys, all of which will be used+ -> OpenPGP.SymmetricAlgorithm -- ^ Cipher to use+ -> OpenPGP.Message -- ^ The 'OpenPGP.Message' to encrypt+ -> g -- ^ Random number generator+ -> Either GenError (OpenPGP.Message, g)+encrypt pass (OpenPGP.Message keys) algo msg = runStateT $ do+ (sk, encP) <- sessionFor algo msg+ OpenPGP.Message . (++[encP]) <$> liftA2 (++)+ (mapM (encryptSessionKeyAsymmetric sk) (filter isKey keys))+ (mapM (encryptSessionKeySymmetric (LZ.take (LZ.length sk - 2) sk) algo) pass) - defaultStype = case signOver of- OpenPGP.LiteralDataPacket {OpenPGP.format = f} ->- if f == 'b' then 0x00 else 0x01- _ -> 0x13+encryptSessionKeyAsymmetric :: (CryptoRandomGen g) => LZ.ByteString -> OpenPGP.Packet -> StateT g (Either GenError) OpenPGP.Packet+encryptSessionKeyAsymmetric sk pk = OpenPGP.AsymmetricSessionKeyPacket 3+ (fingerprint pk)+ (OpenPGP.key_algorithm pk)+ . addBitLen <$> encd (OpenPGP.key_algorithm pk)+ where+ encd OpenPGP.RSA = toLazyBS <$> rsaEncrypt (rsaKey pk) (toStrictBS sk)+ encd _ = lift $ Left $ GenErrorOther $ "Unsupported PublicKey: " ++ show pk - Just signOver = find isSignable m- OpenPGP.Message m = message+encryptSessionKeySymmetric :: (CryptoRandomGen g) => LZ.ByteString -> OpenPGP.SymmetricAlgorithm -> BS.ByteString -> StateT g (Either GenError) OpenPGP.Packet+encryptSessionKeySymmetric sk salgo pass = do+ s2k <- s2k+ return $ OpenPGP.SymmetricSessionKeyPacket 4 salgo s2k+ (string2sencrypt salgo s2k (toLazyBS pass) sk)+ where+ halgo = s2kHashAlgorithmFor salgo+ s2k = OpenPGP.IteratedSaltedS2K halgo . decode . toLazyBS <$>+ (StateT $ genBytes 8) <*> pure 65536 - isSignable (OpenPGP.LiteralDataPacket {}) = True- isSignable (OpenPGP.PublicKeyPacket {}) = True- isSignable (OpenPGP.SecretKeyPacket {}) = True- isSignable _ = False+s2kHashAlgorithmFor :: OpenPGP.SymmetricAlgorithm -> OpenPGP.HashAlgorithm+s2kHashAlgorithmFor OpenPGP.AES128 = s2kHashAlgorithm `for` (undefined :: AES128)+s2kHashAlgorithmFor OpenPGP.AES192 = s2kHashAlgorithm `for` (undefined :: AES192)+s2kHashAlgorithmFor OpenPGP.AES256 = s2kHashAlgorithm `for` (undefined :: AES256)+s2kHashAlgorithmFor OpenPGP.Blowfish = s2kHashAlgorithm `for` (undefined :: Blowfish128)+s2kHashAlgorithmFor algo = error $ "Unsupported SymmetricAlgorithm " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.s2kHashAlgorithmFor" - isUserID (OpenPGP.UserIDPacket {}) = True- isUserID _ = False+s2kHashAlgorithm :: (BlockCipher k) => Tagged k OpenPGP.HashAlgorithm+s2kHashAlgorithm = v+ where+ v = Tagged $ case () of+ _ | ksize <= 160 -> OpenPGP.SHA1+ | ksize <= 256 -> OpenPGP.SHA256+ | otherwise -> OpenPGP.SHA512+ ksize = keyLength `tagOfTag` v++tagOfTag :: Tagged a c -> Tagged a b -> c+tagOfTag a b = a `for` (undefined `asTaggedTypeOf` b)++sessionFor :: (CryptoRandomGen g) => OpenPGP.SymmetricAlgorithm -> OpenPGP.Message -> StateT g (Either GenError) (LZ.ByteString, OpenPGP.Packet)+sessionFor algo@OpenPGP.AES128 msg = do+ sk <- StateT buildKeyGen+ encP <- newSession (sk :: AES128) msg+ return (sessionKeyEncode sk algo, encP)+sessionFor algo@OpenPGP.AES192 msg = do+ sk <- StateT buildKeyGen+ encP <- newSession (sk :: AES192) msg+ return (sessionKeyEncode sk algo, encP)+sessionFor algo@OpenPGP.AES256 msg = do+ sk <- StateT buildKeyGen+ encP <- newSession (sk :: AES256) msg+ return (sessionKeyEncode sk algo, encP)+sessionFor algo@OpenPGP.Blowfish msg = do+ sk <- StateT buildKeyGen+ encP <- newSession (sk :: Blowfish128) msg+ return (sessionKeyEncode sk algo, encP)+sessionFor algo _ = lift $ Left $ GenErrorOther $ "Unsupported cipher: " ++ show algo++sessionKeyEncode :: (BlockCipher k) => k -> OpenPGP.SymmetricAlgorithm -> LZ.ByteString+sessionKeyEncode sk algo =+ LZ.concat [encode algo, toLazyBS bs, encode $ checksum bs]+ where+ bs = Serialize.encode sk++newSession :: (BlockCipher k, CryptoRandomGen g, Monad m) => k -> OpenPGP.Message -> StateT g m OpenPGP.Packet+newSession sk msg = do+ encd <- StateT $ return . pgpCFB sk (encode `oo` mkMDC) (encode msg)+ return $ OpenPGP.EncryptedDataPacket 1 encd++mkMDC :: LZ.ByteString -> LZ.ByteString -> OpenPGP.Packet+mkMDC prefix msg = OpenPGP.ModificationDetectionCodePacket $ toLazyBS $ fst $+ hash OpenPGP.SHA1 $ LZ.concat [prefix, msg, LZ.pack [0xD3, 0x14]]++checksum :: BS.ByteString -> Word16+checksum key = fromIntegral $+ BS.foldl' (\x y -> x + fromIntegral y) (0::Integer) key `mod` 65536++decryptSecretKey ::+ BS.ByteString -- ^ Passphrase+ -> OpenPGP.Packet -- ^ Encrypted SecretKeyPacket+ -> Maybe OpenPGP.Packet -- ^ Decrypted SecretKeyPacket+decryptSecretKey pass k@(OpenPGP.SecretKeyPacket {+ OpenPGP.version = 4, OpenPGP.key_algorithm = kalgo,+ OpenPGP.s2k = s2k, OpenPGP.symmetric_algorithm = salgo,+ OpenPGP.key = existing, OpenPGP.encrypted_data = encd+ }) | chkF material == toStrictBS chk =+ fmap (\m -> k {+ OpenPGP.s2k_useage = 0,+ OpenPGP.symmetric_algorithm = OpenPGP.Unencrypted,+ OpenPGP.encrypted_data = LZ.empty,+ OpenPGP.key = m+ }) parseMaterial+ | otherwise = Nothing+ where+ parseMaterial = maybeGet+ (foldM (\m f -> do {mpi <- get; return $ (f,mpi):m}) existing+ (OpenPGP.secret_key_fields kalgo)) material+ (material, chk) = LZ.splitAt (LZ.length decd - chkSize) decd+ (chkSize, chkF)+ | OpenPGP.s2k_useage k == 254 = (20, fst . hash OpenPGP.SHA1)+ | otherwise = (2, Serialize.encode . checksum . toStrictBS)+ decd = string2sdecrypt salgo s2k (toLazyBS pass) (EncipheredWithIV encd)+decryptSecretKey _ _ = Nothing++-- | Decrypt an OpenPGP message using secret key+decryptAsymmetric ::+ OpenPGP.Message -- ^ SecretKeys, one of which will be used+ -> OpenPGP.Message -- ^ A 'OpenPGP.Message' containing AsymmetricSessionKey and EncryptedData+ -> Maybe OpenPGP.Message+decryptAsymmetric keys msg@(OpenPGP.Message pkts) = do+ (_, d) <- getAsymmetricSessionKey keys msg+ pkt <- find isEncryptedData pkts+ decryptPacket d pkt++-- | Decrypt an OpenPGP message using passphrase+decryptSymmetric ::+ [BS.ByteString] -- ^ Passphrases, one of which will be used+ -> OpenPGP.Message -- ^ A 'OpenPGP.Message' containing SymetricSessionKey and EncryptedData+ -> Maybe OpenPGP.Message+decryptSymmetric pass msg@(OpenPGP.Message pkts) = do+ let ds = map snd $ getSymmetricSessionKey pass msg+ pkt <- find isEncryptedData pkts+ listToMaybe $ mapMaybe (`decryptPacket` pkt) ds++-- | Decrypt a single packet, given the decryptor+decryptPacket :: Decrypt -> OpenPGP.Packet -> Maybe OpenPGP.Message+decryptPacket d (OpenPGP.EncryptedDataPacket {+ OpenPGP.version = 1,+ OpenPGP.encrypted_data = encd+ }) | Just (mkMDC prefix msg) == maybeDecode mdc = maybeDecode msg+ | otherwise = Nothing+ where+ (msg,mdc) = LZ.splitAt (LZ.length content - 22) content+ (prefix, content) = d False encd+decryptPacket d (OpenPGP.EncryptedDataPacket {+ OpenPGP.version = 0,+ OpenPGP.encrypted_data = encd+ }) = maybeDecode (snd $ d True encd)+decryptPacket _ _ = error "Can only decrypt EncryptedDataPacket in Data.OpenPGP.CryptoAPI.decryptPacket"++getSymmetricSessionKey ::+ [BS.ByteString] -- ^ Passphrases, one of which will be used+ -> OpenPGP.Message -- ^ An OpenPGP Message containing SymmetricSessionKey+ -> [(OpenPGP.SymmetricAlgorithm, Decrypt)]+getSymmetricSessionKey pass (OpenPGP.Message ps) =+ concatMap (\OpenPGP.SymmetricSessionKeyPacket {+ OpenPGP.s2k = s2k, OpenPGP.symmetric_algorithm = algo,+ OpenPGP.encrypted_data = encd+ } ->+ if LZ.null encd then+ map ((,) algo . string2decrypt algo s2k) pass'+ else+ mapMaybe (\p -> decodeSess $ string2sdecrypt algo s2k p (EncipheredZeroIV encd)) pass'+ ) sessionKeys+ where+ decodeSess s = let (a, k) = LZ.splitAt 1 s in+ (,) (decode a) <$> decodeSymKey (decode a) (toStrictBS k)+ sessionKeys = filter isSymmetricSessionKey ps+ pass' = map toLazyBS pass++-- | Decrypt an asymmetrically encrypted symmetric session key+getAsymmetricSessionKey ::+ OpenPGP.Message -- ^ SecretKeys, one of which will be used+ -> OpenPGP.Message -- ^ An OpenPGP Message containing AssymetricSessionKey+ -> Maybe (OpenPGP.SymmetricAlgorithm, Decrypt)+getAsymmetricSessionKey keys (OpenPGP.Message ps) =+ listToMaybe $ mapMaybe decodeSessionKey $ catMaybes $+ concatMap (\(sk,ks) ->+ map ($ toStrictBS $ OpenPGP.encrypted_data sk) ks+ ) toTry+ where+ toTry = map (id &&& lookupKey) sessionKeys++ lookupKey (OpenPGP.AsymmetricSessionKeyPacket {+ OpenPGP.key_algorithm = OpenPGP.RSA,+ OpenPGP.key_id = key_id+ }) | all (=='0') key_id = map (rsaDecrypt . snd) rsa+ | otherwise = map (rsaDecrypt . snd) $+ filter (keyIdMatch key_id . fst) rsa+ lookupKey _ = []++ sessionKeys = filter isAsymmetricSessionKey ps+ (rsa, _) = secretKeys keys++decodeSessionKey :: BS.ByteString -> Maybe (OpenPGP.SymmetricAlgorithm, Decrypt)+decodeSessionKey sk+ | checksum key == (decode (toLazyBS chk) :: Word16) = do+ algo <- maybeDecode (toLazyBS algoByte)+ decrypt <- decodeSymKey algo key+ return (algo, decrypt)+ | otherwise = Nothing+ where+ (key, chk) = BS.splitAt (BS.length rest - 2) rest+ (algoByte, rest) = BS.splitAt 1 sk++decodeSymKey :: OpenPGP.SymmetricAlgorithm -> BS.ByteString -> Maybe Decrypt+decodeSymKey OpenPGP.AES128 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES128)) <$> sDecode k+decodeSymKey OpenPGP.AES192 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES192)) <$> sDecode k+decodeSymKey OpenPGP.AES256 k = pgpUnCFB <$> (`asTypeOf` (undefined :: AES256)) <$> sDecode k+decodeSymKey OpenPGP.Blowfish k = pgpUnCFB <$> (`asTypeOf` (undefined :: Blowfish128)) <$> sDecode k+decodeSymKey _ _ = Nothing++string2sencrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> LZ.ByteString -> LZ.ByteString+string2sencrypt OpenPGP.AES128 s2k s = simpleCFB (string2key s2k s :: AES128) zeroIV+string2sencrypt OpenPGP.AES192 s2k s = simpleCFB (string2key s2k s :: AES192) zeroIV+string2sencrypt OpenPGP.AES256 s2k s = simpleCFB (string2key s2k s :: AES256) zeroIV+string2sencrypt OpenPGP.Blowfish s2k s = simpleCFB (string2key s2k s :: Blowfish128) zeroIV+string2sencrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2sencrypt"++string2decrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> Decrypt+string2decrypt OpenPGP.AES128 s2k s = pgpUnCFB (string2key s2k s :: AES128)+string2decrypt OpenPGP.AES192 s2k s = pgpUnCFB (string2key s2k s :: AES192)+string2decrypt OpenPGP.AES256 s2k s = pgpUnCFB (string2key s2k s :: AES256)+string2decrypt OpenPGP.Blowfish s2k s = pgpUnCFB (string2key s2k s :: Blowfish128)+string2decrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2decrypt"++string2sdecrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> Enciphered -> LZ.ByteString+string2sdecrypt OpenPGP.AES128 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES128)+string2sdecrypt OpenPGP.AES192 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES192)+string2sdecrypt OpenPGP.AES256 s2k s = withIV $ simpleUnCFB (string2key s2k s :: AES256)+string2sdecrypt OpenPGP.Blowfish s2k s = withIV $ simpleUnCFB (string2key s2k s :: Blowfish128)+string2sdecrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2sdecrypt"++data Enciphered = EncipheredWithIV !LZ.ByteString | EncipheredZeroIV !LZ.ByteString++withIV :: (BlockCipher k) => (IV k -> LZ.ByteString -> LZ.ByteString) -> Enciphered -> LZ.ByteString+withIV f (EncipheredWithIV s) = f iv $ LZ.drop (fromIntegral $ BS.length $ Serialize.encode iv) s+ where+ iv = let Right x = Serialize.decode (toStrictBS s) in x+withIV f (EncipheredZeroIV s) = f zeroIV s++string2key :: (BlockCipher k) => OpenPGP.S2K -> LZ.ByteString -> k+string2key s2k s = k+ where+ Just k = buildKey $ toStrictBS $+ LZ.take ksize $ OpenPGP.string2key (fst `oo` hash) s2k s+ ksize = fromIntegral (keyLength `for` k) `div` 8
+ Data/OpenPGP/CryptoAPI/Blowfish128.hs view
@@ -0,0 +1,20 @@+module Data.OpenPGP.CryptoAPI.Blowfish128 (Blowfish128) where++import Crypto.Classes (BlockCipher(..))+import Crypto.Types (BitLength)+import Crypto.Cipher.Blowfish (Blowfish)+import Data.Tagged (retag, Tagged(..))+import qualified Data.Serialize as Serialize++newtype Blowfish128 = Blowfish128 Blowfish++instance Serialize.Serialize Blowfish128 where+ put (Blowfish128 b) = Serialize.put b+ get = fmap Blowfish128 Serialize.get++instance BlockCipher Blowfish128 where+ blockSize = retag (blockSize :: Tagged Blowfish BitLength)+ encryptBlock (Blowfish128 k) = encryptBlock k+ decryptBlock (Blowfish128 k) = decryptBlock k+ buildKey = fmap Blowfish128 . buildKey+ keyLength = Tagged 128
+ Data/OpenPGP/CryptoAPI/Util.hs view
@@ -0,0 +1,80 @@+module Data.OpenPGP.CryptoAPI.Util where++import Numeric+import Control.Applicative+import Data.Binary+import Data.Binary.Get (runGetOrFail)+import qualified Data.Serialize as Serialize+import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as LZ+import qualified Data.OpenPGP as OpenPGP++swing :: (((a -> b) -> b) -> c -> d) -> c -> a -> d+swing f c a = f ($ a) c++hush :: Either a b -> Maybe b+hush (Left _) = Nothing+hush (Right x) = Just x++toStrictBS :: LZ.ByteString -> BS.ByteString+toStrictBS = BS.concat . LZ.toChunks++toLazyBS :: BS.ByteString -> LZ.ByteString+toLazyBS = LZ.fromChunks . (:[])++oo :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c+oo = (.) . (.)++hexString :: [Word8] -> String+hexString = foldr (pad `oo` showHex) ""+ where+ pad s | odd $ length s = '0':s+ | otherwise = s++maybeDecode :: (Binary a) => LZ.ByteString -> Maybe a+maybeDecode = maybeGet get++maybeGet :: (Binary a) => Get a -> LZ.ByteString -> Maybe a+maybeGet g bs = (\(_,_,x) -> x) <$> hush (runGetOrFail g bs)++sDecode :: (Serialize.Serialize a) => BS.ByteString -> Maybe a+sDecode = hush . Serialize.decode++keyIdMatch :: String -> String -> Bool+keyIdMatch x y = all (uncurry (==)) $ uncurry (flip zip) $+ unzip $ zip (reverse x) (reverse y)++fromJustMPI :: Maybe OpenPGP.MPI -> Integer+fromJustMPI (Just (OpenPGP.MPI x)) = x+fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI"++isSecretKey :: OpenPGP.Packet -> Bool+isSecretKey (OpenPGP.SecretKeyPacket {}) = True+isSecretKey _ = False++isAsymmetricSessionKey :: OpenPGP.Packet -> Bool+isAsymmetricSessionKey (OpenPGP.AsymmetricSessionKeyPacket {}) = True+isAsymmetricSessionKey _ = False++isSymmetricSessionKey :: OpenPGP.Packet -> Bool+isSymmetricSessionKey (OpenPGP.SymmetricSessionKeyPacket {}) = True+isSymmetricSessionKey _ = False++isEncryptedData :: OpenPGP.Packet -> Bool+isEncryptedData (OpenPGP.EncryptedDataPacket {}) = True+isEncryptedData _ = False++isUserID :: OpenPGP.Packet -> Bool+isUserID (OpenPGP.UserIDPacket {}) = True+isUserID _ = False++isSignable :: OpenPGP.Packet -> Bool+isSignable (OpenPGP.LiteralDataPacket {}) = True+isSignable (OpenPGP.PublicKeyPacket {}) = True+isSignable (OpenPGP.SecretKeyPacket {}) = True+isSignable _ = False++isKey :: OpenPGP.Packet -> Bool+isKey (OpenPGP.SecretKeyPacket {}) = True+isKey (OpenPGP.PublicKeyPacket {}) = True+isKey _ = False
README view
@@ -1,6 +1,7 @@ This is a wrapper around <http://hackage.haskell.org/package/crypto-api> and related libraries that currently does fingerprint generation, signature-generation, and signature verification (for RSA and DSA keys).+generation, signature verification, and both asymmetric and symmetric+encryption and decryption. It is indended to be used with <http://hackage.haskell.org/package/openpgp>
openpgp-crypto-api.cabal view
@@ -1,5 +1,5 @@ name: openpgp-crypto-api-version: 0.4+version: 0.6 cabal-version: >= 1.8 license: OtherLicense license-file: COPYING@@ -11,12 +11,13 @@ tested-with: GHC == 7.0.3 synopsis: Implement cryptography for OpenPGP using crypto-api compatible libraries homepage: http://github.com/singpolyma/OpenPGP-CryptoAPI-bug-reports: http://github.com/singpolyma/OpenPGP-Haskell/issues+bug-reports: http://github.com/singpolyma/OpenPGP-CryptoAPI/issues build-type: Simple description: This is a wrapper around <http://hackage.haskell.org/package/crypto-api> and related libraries that currently does fingerprint generation, signature- generation, and signature verification (for RSA and DSA keys).+ generation, signature verification, and both asymmetric and symmetric+ encryption and decryption. . It is indended to be used with <http://hackage.haskell.org/package/openpgp> .@@ -31,25 +32,39 @@ tests/data/000016-006.public_key, tests/data/000027-006.public_key, tests/data/000035-006.public_key,- tests/data/uncompressed-ops-dsa.gpg- tests/data/uncompressed-ops-dsa-sha384.txt.gpg+ tests/data/compressedsig-bzip2.gpg,+ tests/data/compressedsig.gpg,+ tests/data/compressedsig-zlib.gpg,+ tests/data/encryptedSecretKey.gpg,+ tests/data/hello.gpg,+ tests/data/helloKey.gpg,+ tests/data/pubring.gpg,+ tests/data/secring.gpg,+ tests/data/symmetric-aes.gpg,+ tests/data/symmetric-blowfish.gpg,+ tests/data/symmetric-no-mdc.gpg,+ tests/data/symmetric-with-session-key.gpg,+ tests/data/uncompressed-ops-dsa.gpg,+ tests/data/uncompressed-ops-dsa-sha384.txt.gpg, tests/data/uncompressed-ops-rsa.gpg- tests/data/compressedsig.gpg- tests/data/compressedsig-zlib.gpg- tests/data/compressedsig-bzip2.gpg library exposed-modules: Data.OpenPGP.CryptoAPI + other-modules:+ Data.OpenPGP.CryptoAPI.Util+ Data.OpenPGP.CryptoAPI.Blowfish128+ build-depends: base == 4.*,+ transformers, bytestring,- utf8-string,- binary,- openpgp,- crypto-api,- cryptocipher >= 0.3.7,+ binary >= 0.6.4.0,+ openpgp >= 0.6,+ crypto-api >= 0.9,+ tagged,+ cryptocipher >= 0.3.6, cryptohash, cereal @@ -59,15 +74,19 @@ build-depends: base == 4.*,+ transformers, bytestring,+ binary >= 0.6.4.0, utf8-string,- binary,- openpgp,+ openpgp >= 0.6, crypto-api >= 0.9,- cryptocipher >= 0.3.7,+ tagged,+ cryptocipher >= 0.3.6, cryptohash,+ cereal, HUnit, QuickCheck >= 2.4.1.1,+ quickcheck-instances, test-framework, test-framework-hunit, test-framework-quickcheck2
+ tests/data/encryptedSecretKey.gpg view
binary file changed (absent → 1291 bytes)
+ tests/data/hello.gpg view
binary file changed (absent → 238 bytes)
+ tests/data/helloKey.gpg view
binary file changed (absent → 1598 bytes)
+ tests/data/pubring.gpg view
binary file changed (absent → 7368 bytes)
+ tests/data/secring.gpg view
binary file changed (absent → 10573 bytes)
+ tests/data/symmetric-aes.gpg view
@@ -0,0 +1,1 @@+ »FÝyDã`Ò>ì~iXaMé©MÂÛ7*W£ÛK1®IºÉ&¢·UÞ]-axósön´ìíÖì©ó§jUÄûÇ%ÀøÔ©eÚ
+ tests/data/symmetric-blowfish.gpg view
@@ -0,0 +1,1 @@+ B|LÉ©<îâ`Ò2ÆïG5ðÌzì÷g¶çóý¬î%dõíú`~JXêMÂÄľm˦4Ü
+ tests/data/symmetric-no-mdc.gpg view
@@ -0,0 +1,2 @@+ [¨¡JH×BÉ`É#¥{Õ%íû|©6buâC» gÝ++HÞÄè
+ tests/data/symmetric-with-session-key.gpg view
binary file changed (absent → 641 bytes)
tests/suite.hs view
@@ -2,18 +2,31 @@ import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2 import Test.QuickCheck+import Test.QuickCheck.Instances () import Test.HUnit hiding (Test) +import Data.Maybe+import Data.Monoid+import Data.List (find) import Crypto.Random import Data.Binary import qualified Data.OpenPGP as OpenPGP import qualified Data.OpenPGP.CryptoAPI as OpenPGP import qualified Data.ByteString.Lazy as LZ-import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString)+import qualified Data.ByteString.Lazy.UTF8 as LZ (fromString, toString)+import qualified Data.ByteString as BS+import qualified Data.ByteString.UTF8 as BS (fromString) instance Arbitrary OpenPGP.HashAlgorithm where arbitrary = elements [OpenPGP.MD5, OpenPGP.SHA1, OpenPGP.RIPEMD160, OpenPGP.SHA256, OpenPGP.SHA384, OpenPGP.SHA512, OpenPGP.SHA224] +instance Arbitrary OpenPGP.SymmetricAlgorithm where+ arbitrary = elements [OpenPGP.AES128, OpenPGP.AES192, OpenPGP.AES256, OpenPGP.Blowfish]++isLiteral :: OpenPGP.Packet -> Bool+isLiteral (OpenPGP.LiteralDataPacket {}) = True+isLiteral _ = False+ testFingerprint :: FilePath -> String -> Assertion testFingerprint fp kf = do bs <- LZ.readFile $ "tests/data/" ++ fp@@ -24,9 +37,41 @@ testVerifyMessage keyring message = do keys <- fmap decode $ LZ.readFile $ "tests/data/" ++ keyring m <- fmap decode $ LZ.readFile $ "tests/data/" ++ message- let verification = OpenPGP.verify keys m 0- assertEqual (keyring ++ " for " ++ message) True verification+ let OpenPGP.DataSignature _ ss =+ OpenPGP.verify keys (head $ OpenPGP.signatures m)+ assertEqual (keyring ++ " for " ++ message) 1 (length ss) +testVerifyKey :: FilePath -> Int -> Assertion+testVerifyKey keyring count = do+ keys <- fmap decode $ LZ.readFile $ "tests/data/" ++ keyring+ let out = OpenPGP.verify keys (OpenPGP.signatures keys !! 1)+ assertEqual keyring count (length $ OpenPGP.signatures_over out)++testDecryptHello :: Assertion+testDecryptHello = do+ keys <- fmap decode $ LZ.readFile "tests/data/helloKey.gpg"+ m <- fmap decode $ LZ.readFile "tests/data/hello.gpg"+ let Just (OpenPGP.Message [OpenPGP.CompressedDataPacket {+ OpenPGP.message = OpenPGP.Message msg+ }]) = OpenPGP.decryptAsymmetric keys m+ let content = fmap (LZ.toString . OpenPGP.content) (find isLiteral msg)+ assertEqual "Decrypt hello" (Just "hello\n") content++testDecryptSymmetric :: String -> String -> FilePath -> Assertion+testDecryptSymmetric pass cnt file = do+ m <- fmap decode $ LZ.readFile $ "tests/data/" ++ file+ let Just (OpenPGP.Message [OpenPGP.CompressedDataPacket {+ OpenPGP.message = OpenPGP.Message msg+ }]) = OpenPGP.decryptSymmetric [BS.fromString pass] m+ let content = fmap (LZ.toString . OpenPGP.content) (find isLiteral msg)+ assertEqual "Decrypt symmetric" (Just cnt) content++testDecryptSecretKey :: String -> FilePath -> Assertion+testDecryptSecretKey pass file = do+ m <- fmap decode $ LZ.readFile $ "tests/data/" ++ file+ let d = OpenPGP.decryptSecretKey (BS.fromString pass) m+ assertEqual "Decrypt secret key" True (isJust d)+ prop_sign_and_verify :: (CryptoRandomGen g) => OpenPGP.Message -> g -> OpenPGP.HashAlgorithm -> String -> String -> Gen Bool prop_sign_and_verify secring g halgo filename msg = do keyid <- elements ["FEF8AFA0F661C3EE","7F69FA376B020509"]@@ -36,12 +81,29 @@ OpenPGP.timestamp = 12341234, OpenPGP.content = LZ.fromString msg }- let sig = OpenPGP.sign secring (OpenPGP.Message [m])+ let (sig,_) = OpenPGP.sign secring (OpenPGP.DataSignature m []) halgo keyid 12341234 g- return $ OpenPGP.verify secring (OpenPGP.Message [sig,m]) 0+ let OpenPGP.DataSignature _ ss = OpenPGP.verify secring sig+ return (length ss == 1) -tests :: (CryptoRandomGen g) => OpenPGP.Message -> g -> [Test]-tests secring rng =+prop_encrypt_and_decrypt :: (CryptoRandomGen g) => OpenPGP.Message -> g -> BS.ByteString -> OpenPGP.SymmetricAlgorithm -> String -> String -> Bool+prop_encrypt_and_decrypt secring g pass algo filename msg =+ case (OpenPGP.encrypt [] secring algo m g, OpenPGP.encrypt [pass] mempty algo m g) of+ (Left _, _) -> False+ (_, Left _) -> False+ (Right (encA, _), Right (encB, _)) ->+ (OpenPGP.decryptAsymmetric secring encA == Just m) &&+ (OpenPGP.decryptSymmetric [pass] encB == Just m)+ where+ m = OpenPGP.Message [OpenPGP.LiteralDataPacket {+ OpenPGP.format = 'u',+ OpenPGP.filename = filename,+ OpenPGP.timestamp = 12341234,+ OpenPGP.content = LZ.fromString msg+ }]++tests :: (CryptoRandomGen g) => OpenPGP.Message -> OpenPGP.Message -> g -> [Test]+tests secring oneKey rng = [ testGroup "Fingerprint" [ testCase "000001-006.public_key" (testFingerprint "000001-006.public_key" "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"),@@ -57,8 +119,22 @@ testCase "compressedsig-zlib" (testVerifyMessage "pubring.gpg" "compressedsig-zlib.gpg"), testCase "compressedsig-bzip2" (testVerifyMessage "pubring.gpg" "compressedsig-bzip2.gpg") ],+ testGroup "Key verification" [+ testCase "helloKey" (testVerifyKey "helloKey.gpg" 1)+ ], testGroup "Signing" [- testProperty "Crypto signatures verify" (prop_sign_and_verify secring rng)+ testProperty "Signatures verify" (prop_sign_and_verify secring rng)+ ],+ testGroup "Decryption" [+ testCase "decrypt hello" testDecryptHello,+ testCase "decrypt AES" (testDecryptSymmetric "hello" "PGP\n" "symmetric-aes.gpg"),+ testCase "decrypt session key" (testDecryptSymmetric "hello" "PGP\n" "symmetric-with-session-key.gpg"),+ testCase "decrypt Blowfish" (testDecryptSymmetric "hello" "PGP\n" "symmetric-blowfish.gpg"),+ testCase "decrypt no MDC" (testDecryptSymmetric "hello" "PGP\n" "symmetric-no-mdc.gpg"),+ testCase "decrypt secret key" (testDecryptSecretKey "hello" "encryptedSecretKey.gpg")+ ],+ testGroup "Encryption" [+ testProperty "Encrypted messages decrypt" (prop_encrypt_and_decrypt oneKey rng) ] ] @@ -66,4 +142,5 @@ main = do rng <- newGenIO :: IO SystemRandom secring <- fmap decode $ LZ.readFile "tests/data/secring.gpg"- defaultMain (tests secring rng)+ oneKey <- fmap decode $ LZ.readFile "tests/data/helloKey.gpg"+ defaultMain (tests secring oneKey rng)