packages feed

hOpenPGP 0.9.1 → 0.10

raw patch · 16 files changed

+335/−73 lines, 16 filesdep +crypto-randomdep +errorsdep +tastydep −HUnitdep −test-frameworkdep −test-framework-hunitPVP ok

version bump matches the API change (PVP)

Dependencies added: crypto-random, errors, tasty, tasty-hunit, text

Dependencies removed: HUnit, test-framework, test-framework-hunit

API changes (from Hackage documentation)

+ Codec.Encryption.OpenPGP.CFB: decryptNoNonce :: SymmetricAlgorithm -> IV -> ByteString -> ByteString -> Either String ByteString
+ Codec.Encryption.OpenPGP.CFB: encryptNoNonce :: SymmetricAlgorithm -> S2K -> IV -> ByteString -> ByteString -> Either String ByteString
+ Codec.Encryption.OpenPGP.KeySelection: parseEightOctetKeyId :: Text -> Either String EightOctetKeyId
+ Codec.Encryption.OpenPGP.KeySelection: parseFingerprint :: Text -> Either String TwentyOctetFingerprint
+ Codec.Encryption.OpenPGP.SecretKey: decryptPrivateKey :: (PKPayload, SKAddendum) -> ByteString -> SKAddendum
+ Codec.Encryption.OpenPGP.SecretKey: encryptPrivateKey :: ByteString -> IV -> SKAddendum -> ByteString -> SKAddendum
+ Codec.Encryption.OpenPGP.SecretKey: encryptPrivateKeyIO :: SKAddendum -> ByteString -> IO SKAddendum
+ Codec.Encryption.OpenPGP.SecretKey: reencryptSecretKeyIO :: SecretKey -> ByteString -> IO SecretKey
+ Codec.Encryption.OpenPGP.Serialize: getSecretKey :: PKPayload -> Get SKey

Files

+ Codec/Encryption/OpenPGP/BlockCipher.hs view
@@ -0,0 +1,56 @@+-- BlockCipher.hs: OpenPGP (RFC4880) block cipher stuff+-- Copyright © 2013  Clint Adams+-- This software is released under the terms of the Expat license.+-- (See the LICENSE file).++{-# LANGUAGE ExistentialQuantification #-}++module Codec.Encryption.OpenPGP.BlockCipher (+    BCipher(..)+  , bcBlockSize+  , saBlockSize+  , keySize+) where++import Codec.Encryption.OpenPGP.Types++import Crypto.Cipher.Types (BlockCipher(..))+import qualified Crypto.Cipher as CC+import qualified Crypto.Nettle.Ciphers as CNC++data BCipher = forall a. (BlockCipher a) => BCipher a++bcBlockSize :: BCipher -> Int+bcBlockSize (BCipher bc) = blockSize bc++saBlockSize :: SymmetricAlgorithm -> Int+saBlockSize = bcBlockSize . saToBCipher++saToBCipher :: SymmetricAlgorithm -> BCipher+saToBCipher Plaintext = error "this shouldn't have happened" -- FIXME: orphan instance?+saToBCipher IDEA = error "IDEA not yet implemented" -- FIXME: IDEA+saToBCipher ReservedSAFER = error "SAFER not implemented" -- FIXME: or not?+saToBCipher ReservedDES = error "DES not implemented" -- FIXME: or not?+saToBCipher (OtherSA _) = error "Unknown, unimplemented symmetric algorithm"+saToBCipher CAST5 = BCipher (undefined :: CNC.CAST128)+saToBCipher Twofish = BCipher (undefined :: CNC.TWOFISH)+saToBCipher TripleDES = BCipher (undefined :: CC.DES_EDE3)+saToBCipher Blowfish = BCipher (undefined :: CC.Blowfish128)+saToBCipher AES128 = BCipher (undefined :: CC.AES128)+saToBCipher AES192 = BCipher (undefined :: CC.AES192)+saToBCipher AES256 = BCipher (undefined :: CC.AES256)++-- in octets; FIXME: co-opt Cipher's cipherKeySize or not?+keySize :: SymmetricAlgorithm -> Int+keySize Plaintext = 0+keySize IDEA = 16 -- according to https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm+keySize TripleDES = 24 -- RFC 4880 says 168 bits (derived from 192 bits) but we don't know who does the derivation+keySize CAST5 = 16+keySize Blowfish = 16+keySize ReservedSAFER = undefined+keySize ReservedDES = undefined+keySize AES128 = 16+keySize AES192 = 24+keySize AES256 = 32+keySize Twofish = 32+keySize (OtherSA _) = undefined
Codec/Encryption/OpenPGP/CFB.hs view
@@ -3,51 +3,63 @@ -- This software is released under the terms of the Expat license. -- (See the LICENSE file). -{-# LANGUAGE ExistentialQuantification #-}- module Codec.Encryption.OpenPGP.CFB (    decrypt+ , decryptNoNonce  , decryptOpenPGPCfb+ , encryptNoNonce ) where +import Codec.Encryption.OpenPGP.BlockCipher (BCipher(..), bcBlockSize) import Codec.Encryption.OpenPGP.Types+import Control.Applicative ((<$>), (<*>))+import Control.Error.Util (note) import Crypto.Cipher.Types (makeKey, nullIV, BlockCipher(..), Cipher(..)) import qualified Crypto.Cipher as CC import qualified Crypto.Nettle.Ciphers as CNC import qualified Data.ByteString as B-import Data.Maybe (fromJust) import Data.SecureMem (ToSecureMem) -data BCipher = forall a. (BlockCipher a) => BCipher a- decryptOpenPGPCfb :: SymmetricAlgorithm -> B.ByteString -> B.ByteString -> Either String B.ByteString decryptOpenPGPCfb Plaintext ciphertext _ = return ciphertext decryptOpenPGPCfb sa ciphertext keydata = do     bc <- mkBCipher sa keydata-    return $ B.drop (bsize bc + 2) (decrypt1 ciphertext bc `B.append` decrypt2 ciphertext bc) -- FIXME: verify the code+    let nonce = decrypt1 ciphertext bc+    cleartext <- decrypt2 ciphertext bc+    if nonceCheck bc nonce then return cleartext else fail "Session key quickcheck failed"     where         decrypt1 :: B.ByteString -> BCipher -> B.ByteString         decrypt1 ct (BCipher cipher) = cdecrypt sa cipher nullIV (B.take (blockSize cipher + 2) ct)-        decrypt2 :: B.ByteString -> BCipher -> B.ByteString-        decrypt2 ct (BCipher cipher) = cdecrypt sa cipher (fromJust (CC.makeIV (B.take (blockSize cipher) (B.drop 2 ct)))) (B.drop (blockSize cipher + 2) ct)+        decrypt2 :: B.ByteString -> BCipher -> Either String B.ByteString+        decrypt2 ct (BCipher cipher) = note "unexpected CFB-resync failure" (CC.makeIV (B.take (blockSize cipher) (B.drop 2 ct))) >>= \i -> return (cdecrypt sa cipher i (B.drop (blockSize cipher + 2) ct))  decrypt :: SymmetricAlgorithm -> B.ByteString -> B.ByteString -> Either String B.ByteString decrypt Plaintext ciphertext _ = return ciphertext decrypt sa ciphertext keydata = do     bc <- mkBCipher sa keydata-    return $ B.drop (bsize bc + 2) (decrypt' ciphertext bc)  -- FIXME: verify the code+    let (nonce, cleartext) = B.splitAt (bcBlockSize bc + 2) (decrypt' ciphertext bc)+    if nonceCheck bc nonce then return cleartext else fail "Session key quickcheck failed"     where         decrypt' :: B.ByteString -> BCipher -> B.ByteString         decrypt' ct (BCipher cipher) = cdecrypt sa cipher nullIV ct -bsize :: BCipher -> Int-bsize (BCipher bc) = blockSize bc+decryptNoNonce :: SymmetricAlgorithm -> IV -> B.ByteString -> B.ByteString -> Either String B.ByteString+decryptNoNonce Plaintext _ ciphertext _ = return ciphertext+decryptNoNonce sa iv ciphertext keydata = do+    bc <- mkBCipher sa keydata+    decrypt' ciphertext bc+    where+        decrypt' :: B.ByteString -> BCipher -> Either String B.ByteString+        decrypt' ct (BCipher cipher) = note "Bad IV" (CC.makeIV iv) >>= \i -> return (cdecrypt sa cipher i ct)  cdecrypt :: BlockCipher cipher => SymmetricAlgorithm -> cipher -> CC.IV cipher -> B.ByteString -> B.ByteString cdecrypt sa     | sa `elem` [CAST5, Twofish] = paddedCfbDecrypt     | otherwise = cfbDecrypt +nonceCheck :: BCipher -> B.ByteString -> Bool+nonceCheck bc = (==) <$> B.take 2 . B.drop (bcBlockSize bc - 2) <*> B.drop (bcBlockSize bc)+ paddedCfbDecrypt :: BlockCipher cipher => cipher -> CC.IV cipher -> B.ByteString -> B.ByteString paddedCfbDecrypt cipher iv ciphertext = B.take (B.length ciphertext) (cfbDecrypt cipher iv padded)     where@@ -73,3 +85,17 @@         ekey = case makeKey keydata of           Left _ -> error "bad cipher parameters"           Right key -> key++encryptNoNonce :: SymmetricAlgorithm -> S2K -> IV -> B.ByteString -> B.ByteString -> Either String B.ByteString+encryptNoNonce Plaintext _ _ payload keydata = return payload+encryptNoNonce sa s2k iv payload keydata = do+    bc <- mkBCipher sa keydata+    encrypt' payload bc+    where+        encrypt' :: B.ByteString -> BCipher -> Either String B.ByteString+        encrypt' ct (BCipher cipher) = note "Bad IV" (CC.makeIV iv) >>= \i -> return (cencrypt sa cipher i ct)++cencrypt :: BlockCipher cipher => SymmetricAlgorithm -> cipher -> CC.IV cipher -> B.ByteString -> B.ByteString+cencrypt sa+    | sa `elem` [CAST5, Twofish] = error "padding for nettle-encryption not implemented yet"+    | otherwise = cfbEncrypt
Codec/Encryption/OpenPGP/Internal.hs view
@@ -12,6 +12,7 @@  , issuer  , emptyPSC  , pubkeyToMPIs+ , multiplicativeInverse ) where  import Crypto.PubKey.HashDescr (HashDescr(..), hashDescrMD5, hashDescrSHA1, hashDescrSHA224, hashDescrSHA256, hashDescrSHA384, hashDescrSHA512, hashDescrRIPEMD160)@@ -77,3 +78,8 @@   where pkParams f = MPI . f . DSA.public_params $ k  pubkeyToMPIs (ElGamalPubKey k) = fmap MPI k++multiplicativeInverse :: Integral a => a -> a -> a+multiplicativeInverse _ 1 = 1+multiplicativeInverse q p = (n * q + 1) `div` p+    where n = p - multiplicativeInverse p (q `mod` p)
Codec/Encryption/OpenPGP/KeyInfo.hs view
@@ -20,7 +20,7 @@  keySize (RSAPubKey x) = RSA.public_size x * 8 keySize (DSAPubKey x) = bitcount . DSA.params_p . DSA.public_params $ x-keySize (ElGamalPubKey x) = bitcount $ x !! 0+keySize (ElGamalPubKey x) = bitcount $ head x  bitcount = (*8) . length . unfoldr (\x -> if x == 0 then Nothing else Just (True, x `shiftR` 8)) 
+ Codec/Encryption/OpenPGP/KeySelection.hs view
@@ -0,0 +1,35 @@+-- KeySelection.hs: OpenPGP (RFC4880) ways to ask for keys+-- Copyright © 2014  Clint Adams+-- This software is released under the terms of the Expat license.+-- (See the LICENSE file).++{-# LANGUAGE OverloadedStrings #-}++module Codec.Encryption.OpenPGP.KeySelection (+   parseEightOctetKeyId+ , parseFingerprint+) where++import qualified Data.ByteString as B+import Codec.Encryption.OpenPGP.Internal (integerToBEBS)+import Codec.Encryption.OpenPGP.Types+import Control.Applicative (optional, (<$>), (*>))+import Control.Monad ((<=<))+import Data.Attoparsec.Text (asciiCI, count, hexadecimal, inClass, parseOnly, Parser, satisfy)+import Data.Text (Text, toUpper)+import qualified Data.Text as T++parseEightOctetKeyId :: Text -> Either String EightOctetKeyId+parseEightOctetKeyId = fmap EightOctetKeyId . (parseOnly hexes <=< parseOnly (hexPrefix *> hexen 16)) . toUpper++parseFingerprint :: Text -> Either String TwentyOctetFingerprint+parseFingerprint = fmap TwentyOctetFingerprint . (parseOnly hexes <=< parseOnly (hexen 40)) . toUpper . T.filter (/=' ')++hexPrefix :: Parser (Maybe Text)+hexPrefix = optional (asciiCI "0x")++hexen :: Int -> Parser Text+hexen n = T.pack <$> count n (satisfy (inClass "A-F0-9"))++hexes :: Parser B.ByteString+hexes = integerToBEBS <$> hexadecimal
Codec/Encryption/OpenPGP/S2K.hs view
@@ -8,33 +8,25 @@   ,skesk2Key ) where +import Codec.Encryption.OpenPGP.BlockCipher (keySize) import Codec.Encryption.OpenPGP.Types import Control.Monad.Loops (untilM_) import Control.Monad.Trans.State.Lazy (execState, get, put) import qualified Crypto.Hash.SHA1 as SHA1+import qualified Crypto.Hash.SHA512 as SHA512 import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL  string2Key :: S2K -> Int -> BL.ByteString -> B.ByteString-string2Key (Simple SHA1) ksz bs = B.take (fromIntegral ksz) $ hashpp SHA1.hashlazy ksz bs-string2Key (Salted SHA1 salt) ksz bs = string2Key (Simple SHA1) ksz (BL.append (BL.fromChunks [salt]) bs)-string2Key (IteratedSalted SHA1 salt cnt) ksz bs = string2Key (Simple SHA1) ksz (BL.take (fromIntegral cnt) . BL.cycle $ BL.append (BL.fromChunks [salt]) bs)-string2Key _ _ _ = error "FIXME: unimplemented S2K hash"+string2Key (Simple ha) ksz bs = B.take (fromIntegral ksz) $ hashpp (hf ha) ksz bs+string2Key (Salted ha salt) ksz bs = string2Key (Simple ha) ksz (BL.append (BL.fromChunks [salt]) bs)+string2Key (IteratedSalted ha salt cnt) ksz bs = string2Key (Simple ha) ksz (BL.take (fromIntegral cnt) . BL.cycle $ BL.append (BL.fromStrict salt) bs)+string2Key _ _ _ = error "FIXME: unimplemented S2K type" --- in bytes-keySize :: SymmetricAlgorithm -> Int-keySize Plaintext = 0-keySize IDEA = 16 -- according to https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm-keySize TripleDES = 24 -- RFC 4880 says 168 bits (derived from 192 bits) but we don't know who does the derivation-keySize CAST5 = 16-keySize Blowfish = 16-keySize ReservedSAFER = undefined-keySize ReservedDES = undefined-keySize AES128 = 16-keySize AES192 = 24-keySize AES256 = 32-keySize Twofish = 32-keySize (OtherSA _) = undefined+hf :: HashAlgorithm -> BL.ByteString -> B.ByteString+hf SHA1 = SHA1.hashlazy+hf SHA512 = SHA512.hashlazy+hf _ = error "FIXME: unimplemented S2K hash"  skesk2Key :: SKESK -> BL.ByteString -> B.ByteString skesk2Key (SKESK 4 sa s2k Nothing) pass = string2Key s2k (keySize sa) pass
+ Codec/Encryption/OpenPGP/SecretKey.hs view
@@ -0,0 +1,83 @@+-- SecretKey.hs: OpenPGP (RFC4880) secret key decryption+-- Copyright © 2013  Clint Adams+-- This software is released under the terms of the Expat license.+-- (See the LICENSE file).++module Codec.Encryption.OpenPGP.SecretKey (+   decryptPrivateKey+ , encryptPrivateKey+ , encryptPrivateKeyIO+ , reencryptSecretKeyIO+) where++import Codec.Encryption.OpenPGP.Types+import Codec.Encryption.OpenPGP.BlockCipher (saBlockSize, keySize)+import Codec.Encryption.OpenPGP.CFB (decryptNoNonce, encryptNoNonce)+import Codec.Encryption.OpenPGP.Serialize (getSecretKey)+import Codec.Encryption.OpenPGP.S2K (skesk2Key, string2Key)+import Control.Monad ((>=>))+import qualified Crypto.Hash.SHA1 as SHA1+import Crypto.Random (createEntropyPool, cprgCreate, cprgGenerateWithEntropy, SystemRNG)+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy as BL+import Data.Serialize (runGet, runPut, put)+import Data.Serialize.Get (getBytes, remaining, getWord16be)+import qualified Crypto.PubKey.RSA as R++decryptPrivateKey :: (PKPayload, SKAddendum) -> BL.ByteString -> SKAddendum+decryptPrivateKey (pkp, ska@(SUS16bit {})) pp = either (error "could not decrypt SUS16bit") id (decryptSKA (pkp, ska) pp)+decryptPrivateKey (pkp, ska@(SUSSHA1 {})) pp = either (error "could not decrypt SUSSHA1") id (decryptSKA (pkp, ska) pp)+decryptPrivateKey (_, SUSym {}) _ = error "SUSym key decryption not implemented"+decryptPrivateKey (_, ska@(SUUnencrypted {})) _ = ska++decryptSKA :: (PKPayload, SKAddendum) -> BL.ByteString -> Either String SKAddendum+decryptSKA (pkp, SUS16bit sa s2k iv payload) pp = do+    let key = skesk2Key (SKESK 4 sa s2k Nothing) pp+    p <- decryptNoNonce sa iv payload key+    (s, cksum) <- runGet (getSecretKey pkp >>= \sk -> getWord16be >>= \csum -> return (sk, csum)) p  -- FIXME: check the 16bit hash+    let checksum = cksum+    return $ SUUnencrypted s checksum  -- FIXME: is this the correct checksum?+decryptSKA (pkp, SUSSHA1 sa s2k iv payload) pp = do+    let key = skesk2Key (SKESK 4 sa s2k Nothing) pp+    p <- decryptNoNonce sa iv payload key+    (s, cksum) <- runGet (getSecretKey pkp >>= \sk -> remaining >>= (getBytes >=> \csum -> return (sk, csum))) p  -- FIXME: check the SHA1 hash+    let checksum = sum . map fromIntegral . B.unpack . B.take (B.length p - 20) $ p+    return $ SUUnencrypted s checksum  -- FIXME: is this the correct checksum?+decryptSKA _ _ = fail "Unexpected codepath"++-- |generates pseudo-random salt and IV+encryptPrivateKeyIO :: SKAddendum -> BL.ByteString -> IO SKAddendum+encryptPrivateKeyIO ska pp = saltiv >>= \(s,i) -> return (encryptPrivateKey s i ska pp)+    where+        saltiv = do+                    ep <- createEntropyPool+                    let gen = cprgCreate ep :: SystemRNG+		        bb = fst (cprgGenerateWithEntropy (8 + saBlockSize AES256) gen)+		    return $ B.splitAt 8 bb++-- |8-octet salt, IV must be length of cipher blocksize+encryptPrivateKey :: B.ByteString -> IV -> SKAddendum -> BL.ByteString -> SKAddendum+encryptPrivateKey _ _ ska@(SUS16bit {}) _ = ska+encryptPrivateKey _ _ ska@(SUSSHA1 {}) _ = ska+encryptPrivateKey _ _ ska@(SUSym {}) _ = ska+encryptPrivateKey salt iv (SUUnencrypted skey _) pp = SUSSHA1 AES256 s2k iv (encryptSKey skey s2k iv pp)+    where+       s2k = IteratedSalted SHA512 salt 12058624++encryptSKey :: SKey -> S2K -> IV -> BL.ByteString -> B.ByteString+encryptSKey (RSAPrivateKey (R.PrivateKey _ d p q _ _ _)) s2k iv pp = either error id (encryptNoNonce AES256 s2k iv payload key)+    where+        key = string2Key s2k (keySize AES256) pp+        algospecific = runPut $ put (MPI d) >> put (MPI p) >> put (MPI q) >> put (MPI u)+	cksum = SHA1.hash algospecific+	payload = algospecific `B.append` cksum+	u = inverse q p+encryptSKey _ _ _ _ = error "Non-RSA keytypes not handled yet" -- FIXME: do DSA and ElGamal++inverse :: Integral a => a -> a -> a+inverse _ 1 = 1+inverse q p = (n * q + 1) `div` p+    where n = p - inverse p (q `mod` p)++reencryptSecretKeyIO :: SecretKey -> BL.ByteString -> IO SecretKey+reencryptSecretKeyIO sk pp = encryptPrivateKeyIO (_secretKeySKAddendum sk) pp >>= \n -> return sk { _secretKeySKAddendum = n }
Codec/Encryption/OpenPGP/Serialize.hs view
@@ -5,11 +5,12 @@  module Codec.Encryption.OpenPGP.Serialize (    putSKAddendum+ , getSecretKey ) where  import Control.Applicative ((<$>),(<*>)) import Control.Lens ((^.))-import Control.Monad (guard, mplus, replicateM)+import Control.Monad (guard, liftM, mplus, replicateM, replicateM_) import qualified Crypto.PubKey.RSA as R import qualified Crypto.PubKey.DSA as D import Data.Bits ((.&.), (.|.), shiftL, shiftR, testBit)@@ -25,7 +26,7 @@ import Data.Word (Word8, Word32) import Data.Maybe (fromMaybe) -import Codec.Encryption.OpenPGP.Internal (countBits, beBSToInteger, integerToBEBS, pubkeyToMPIs)+import Codec.Encryption.OpenPGP.Internal (countBits, beBSToInteger, integerToBEBS, pubkeyToMPIs, multiplicativeInverse) import Codec.Encryption.OpenPGP.Types  instance Serialize SigSubPacket where@@ -36,19 +37,19 @@ --     put = putNotationFlagSet  instance Serialize CompressionAlgorithm where-    get = getWord8 >>= return . toFVal+    get = liftM toFVal getWord8     put = putWord8 . fromFVal  instance Serialize PubKeyAlgorithm where-    get = getWord8 >>= return . toFVal+    get = liftM toFVal getWord8     put = putWord8 . fromFVal  instance Serialize HashAlgorithm where-    get = getWord8 >>= return . toFVal+    get = liftM toFVal getWord8     put = putWord8 . fromFVal  instance Serialize SymmetricAlgorithm where-    get = getWord8 >>= return . toFVal+    get = liftM toFVal getWord8     put = putWord8 . fromFVal  instance Serialize MPI where@@ -56,7 +57,7 @@     put = putMPI  instance Serialize SigType where-    get = getWord8 >>= return . toFVal+    get = liftM toFVal getWord8     put = putWord8 . fromFVal  instance Serialize UserAttrSubPacket where@@ -391,14 +392,14 @@ getSigSubPacketType :: Get (Bool, Word8) getSigSubPacketType = do                          x <- getWord8-                         if x .&. 0x80 == 0x80 then return (True, x .&. 0x7f) else return (False, x)+                         return (if x .&. 128 == 128 then (True, x .&. 127) else (False, x))  putSigSubPacketType :: Bool -> Word8 -> Put putSigSubPacketType False sst = putWord8 sst putSigSubPacketType True sst = putWord8 (sst .|. 0x80)  bsToFFSet :: FutureFlag a => ByteString -> Set a-bsToFFSet bs = Set.fromAscList .  concat . snd $ mapAccumL (\acc y -> (acc+8, concatMap (\x -> if y .&. (shiftR 128 x) == (shiftR 128 x) then [toFFlag (acc + x)] else []) [0..7])) 0 (B.unpack bs)+bsToFFSet bs = Set.fromAscList .  concat . snd $ mapAccumL (\acc y -> (acc+8, concatMap (\x -> if y .&. shiftR 128 x == shiftR 128 x then [toFFlag (acc + x)] else []) [0..7])) 0 (B.unpack bs)  ffSetToFixedLengthBS :: (Integral a, FutureFlag b) => a -> Set b -> ByteString ffSetToFixedLengthBS len ffs = B.take (fromIntegral len) (B.append (ffSetToBS ffs) (B.pack (replicate 5 0)))@@ -407,7 +408,7 @@ ffSetToBS = B.pack . ffSetToBS'     where         ffSetToBS' :: FutureFlag a => Set a -> [Word8]-        ffSetToBS' ks = map (foldl (.|.) 0 . map (shiftR 128 . flip mod 8 . fromFFlag) . Set.toAscList) (map (\x -> Set.filter (\y -> fromFFlag y `div` 8 == x) ks) [0..(fromFFlag $ Set.findMax ks) `div` 8])+        ffSetToBS' ks = map ((foldl (.|.) 0 .  map (shiftR 128 . flip mod 8 . fromFFlag) . Set.toAscList) . (\ x -> Set.filter (\ y -> fromFFlag y `div` 8 == x) ks)) [0 .. fromFFlag (Set.findMax ks) `div` 8]  fromS2K :: S2K -> ByteString fromS2K (Simple hashalgo) = B.pack [0, fromIntegral . fromFVal $ hashalgo]@@ -415,7 +416,7 @@     | B.length salt == 8 = B.pack [1, fromIntegral . fromFVal $ hashalgo] `B.append` salt     | otherwise = error "Confusing salt size" fromS2K (IteratedSalted hashalgo salt count)-    | B.length salt == 8 = B.pack [3, fromIntegral . fromFVal $ hashalgo] `B.append` salt `B.snoc` (encodeIterationCount count)+    | B.length salt == 8 = B.pack [3, fromIntegral . fromFVal $ hashalgo] `B.append` salt `B.snoc` encodeIterationCount count     | otherwise = error "Confusing salt size" fromS2K (OtherS2K _ bs) = bs @@ -571,7 +572,7 @@                           flen <- getWord8                           fn <- getByteString (fromIntegral flen)                           ts <- getWord32be-                          ldata <- getByteString (len - (6 + (fromIntegral flen)))+                          ldata <- getByteString (len - (6 + fromIntegral flen))                           return $ LiteralDataPkt (toFVal dt) fn ts ldata             | t == 12 = do                           tdata <- getByteString len@@ -612,7 +613,7 @@                               iformat <- getWord8                               nuls <- getBytes 12 -- should be NULs                               bs <- getByteString (l - 17)-                              if hver /= 1 || nuls /= (B.pack (replicate 12 0)) then fail "Corrupt UAt subpacket" else return $ ImageAttribute (ImageHV1 (toFVal iformat)) bs+                              if hver /= 1 || nuls /= B.pack (replicate 12 0) then fail "Corrupt UAt subpacket" else return $ ImageAttribute (ImageHV1 (toFVal iformat)) bs                 | otherwise = do                                  bs <- getByteString (l - 1)                                  return $ OtherUASub t bs@@ -628,7 +629,7 @@             putWord16le 16             putWord8 1             putWord8 (fromFVal iformat)-            mapM_ putWord8 $ replicate 12 0+            replicateM_ 12 $ putWord8 0             putByteString idata         putUserAttrSubPacket' (OtherUASub t bs) = do             putWord8 t@@ -638,7 +639,7 @@ putPkt (PKESKPkt pv eokeyid pkalgo mpis) = do     putWord8 (0xc0 .|. 1)     let bsk = runPut $ mapM_ put mpis-    putPacketLength . fromIntegral $ 10 + (B.length bsk)+    putPacketLength . fromIntegral $ 10 + B.length bsk     putWord8 pv -- must be 3     putByteString (unEOKI eokeyid) -- must be 8 octets     putWord8 $ fromIntegral . fromFVal $ pkalgo@@ -652,7 +653,7 @@     putWord8 (0xc0 .|. 3)     let bs2k = fromS2K s2k     let bsk = fromMaybe B.empty mesk-    putPacketLength . fromIntegral $ 2 + (B.length bs2k) + (B.length bsk)+    putPacketLength . fromIntegral $ 2 + B.length bs2k + B.length bsk     putWord8 pv -- should be 4     putWord8 $ fromIntegral . fromFVal $ symalgo     putByteString bs2k@@ -729,7 +730,7 @@     putByteString bs putPkt (SymEncIntegrityProtectedDataPkt pv b) = do     putWord8 (0xc0 .|. 18)-    putPacketLength . fromIntegral $ (B.length b) + 1+    putPacketLength . fromIntegral $ B.length b + 1     putWord8 pv -- should be 1     putByteString b putPkt (ModificationDetectionCodePkt hash) = do@@ -786,6 +787,11 @@         do MPI x <- get            return $ ElGamalPrivateKey [x] +putSKey :: SKey -> Put+putSKey (RSAPrivateKey (R.PrivateKey _ d p q _ _ _)) = put (MPI d) >> put (MPI p) >> put (MPI q) >> put (MPI u)+    where+        u = multiplicativeInverse q p+ putMPI :: MPI -> Put putMPI (MPI i) = do let bs = integerToBEBS i                     putWord16be . countBits $ bs@@ -849,6 +855,10 @@     put s2k     putByteString iv     putByteString encryptedblock+putSKAddendum (SUUnencrypted sk checksum) = do+    putWord8 0+    putSKey sk+    putWord16be checksum putSKAddendum _ = fail "Type not supported"  symEncBlockSize :: SymmetricAlgorithm -> Int
Codec/Encryption/OpenPGP/SerializeForSigs.hs view
@@ -101,7 +101,7 @@     putByteString bs  payloadForSig :: SigType -> PktStreamContext -> ByteString-payloadForSig BinarySig state = (fromPkt (lastLD state))^.literalDataPayload+payloadForSig BinarySig state = fromPkt (lastLD state)^.literalDataPayload payloadForSig CanonicalTextSig state = payloadForSig BinarySig state payloadForSig StandaloneSig _ = B.empty payloadForSig GenericCert state = kandUPayload (lastPrimaryKey state) (lastUIDorUAt state)
Codec/Encryption/OpenPGP/Signatures.hs view
@@ -116,9 +116,9 @@         verify'' (RSA,mpis) ha pub (RSAPubKey pkey) bs = verify''' (rsaVerify mpis ha pkey bs) pub         verify'' _ _ _ _ _ = Left "unimplemented key type" 	verify''' f pub = if f then Right pub else Left "verification failed"-	dsaVerify mpis ha pkey bs = DSA.verify (dsaTruncate pkey . hashFunction (hashDescr ha)) pkey (dsaMPIsToSig mpis) bs+	dsaVerify mpis ha pkey = DSA.verify (dsaTruncate pkey . hashFunction (hashDescr ha)) pkey (dsaMPIsToSig mpis) 	rsaVerify mpis ha pkey bs = P15.verify (hashDescr ha) pkey bs (rsaMPItoSig mpis)-        dsaMPIsToSig mpis = (DSA.Signature (unMPI (mpis !! 0)) (unMPI (mpis !! 1)))+        dsaMPIsToSig mpis = DSA.Signature (unMPI (head mpis)) (unMPI (mpis !! 1))         rsaMPItoSig mpis = integerToBEBS (unMPI (head mpis))         hashalgo :: Pkt -> HashAlgorithm         hashalgo (SignaturePkt (SigV4 _ _ ha _ _ _ _)) = ha
Data/Conduit/OpenPGP/Decrypt.hs view
@@ -9,7 +9,10 @@    conduitDecrypt ) where +import Control.Monad (when) import Control.Monad.IO.Class (MonadIO(..))+import qualified Control.Monad.Trans.State.Lazy as S+import qualified Crypto.Hash.SHA1 as SHA1 import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL import Data.Conduit@@ -18,7 +21,7 @@ import Data.Conduit.OpenPGP.Compression (conduitDecompress) import qualified Data.Conduit.List as CL import Data.Default (Default, def)-import Data.Maybe (fromJust)+import Data.Maybe (fromJust, isNothing) import Data.Serialize (get)  import Codec.Encryption.OpenPGP.S2K (skesk2Key)@@ -29,10 +32,11 @@      _depth    :: Int   , _lastPKESK :: Maybe PKESK   , _lastSKESK :: Maybe SKESK+  , _lastLDP   :: Maybe LiteralData } deriving (Eq, Show)  instance Default RecursorState where-    def = RecursorState 0 Nothing Nothing+    def = RecursorState 0 Nothing Nothing Nothing  type InputCallback m = String -> m BL.ByteString @@ -48,10 +52,16 @@ 	    | otherwise = case i of                        (SKESKPkt {}) -> return (s { _lastSKESK = Just (fromPkt i) }, [])                        (SymEncDataPkt bs) -> do d <- decryptSEDP (_depth s) cb (fromJust . _lastSKESK $ s) bs-                                                return (s, d)+                                                return (processLDPs s d, d)                        (SymEncIntegrityProtectedDataPkt _ bs) -> do d <- decryptSEIPDP (_depth s) cb (fromJust . _lastSKESK $ s) bs-                                                                    return (s, d)+                                                                    return (processLDPs s d, d)+                       m@(ModificationDetectionCodePkt mdc) -> do when (isNothing (_lastLDP s)) $ fail "MDC with no referent"+                                                                  when (fmap (SHA1.hash . _literalDataPayload) (_lastLDP s) /= Just mdc) $ fail "MDC indicates tampering"+                                                                  return (s, [m])                        p -> return (s, [p])+        processLDPs s ds = S.execState (mapM_ ldpCheck ds) s+        ldpCheck l@(LiteralDataPkt {}) = S.get >>= \o -> S.put o { _lastLDP = Just . fromPkt $ l }+        ldpCheck _ = return ()  decryptSEDP :: (MonadBaseControl IO m, MonadIO m, MonadThrow m, MonadUnsafeIO m) => Int -> InputCallback IO -> SKESK -> B.ByteString -> m [Pkt] decryptSEDP depth cb skesk bs = do -- FIXME: this shouldn't pass the whole SKESK
Data/Conduit/OpenPGP/Keyring/Instances.hs view
@@ -1,5 +1,5 @@ -- Instances.hs: OpenPGP (RFC4880) additional types for transferable keys--- Copyright © 2012-2013  Clint Adams+-- Copyright © 2012-2014  Clint Adams -- This software is released under the terms of the Expat license. -- (See the LICENSE file). @@ -11,7 +11,7 @@ import Codec.Encryption.OpenPGP.Fingerprint (eightOctetKeyID, fingerprint) import Codec.Encryption.OpenPGP.Types -import Control.Lens ((^..))+import Control.Lens ((^.), (^..), _1, folded) import Data.Data.Lens (biplate)  instance Indexable TK where@@ -19,10 +19,12 @@                 [ ixGen (Proxy :: Proxy PKPayload)                 , ixFun getEOKIs                 , ixFun getTOFs+                , ixFun getUIDs                 ]  getEOKIs :: TK -> [EightOctetKeyId] getEOKIs tk = map eightOctetKeyID (tk ^.. biplate :: [PKPayload]) getTOFs :: TK -> [TwentyOctetFingerprint] getTOFs tk = map fingerprint (tk ^.. biplate :: [PKPayload])-+getUIDs :: TK -> [String]+getUIDs tk = (tk^.tkUIDs)^..folded._1
LICENSE view
@@ -1,4 +1,4 @@-Copyright © 2012-2013 Clint Adams <clint@debian.org>+Copyright © 2012-2014 Clint Adams <clint@debian.org>  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the
hOpenPGP.cabal view
@@ -1,5 +1,5 @@ Name:                hOpenPGP-Version:             0.9.1+Version:             0.10 Synopsis:            native Haskell implementation of OpenPGP (RFC4880) Description:         native Haskell implementation of OpenPGP (RFC4880) Homepage:            http://floss.scru.org/hOpenPGP/@@ -7,7 +7,7 @@ License-file:        LICENSE Author:              Clint Adams Maintainer:          Clint Adams <clint@debian.org>-Copyright:           2012-2013  Clint Adams+Copyright:           2012-2014  Clint Adams Category:            Codec, Data Build-type:          Simple Extra-source-files: tests/suite.hs@@ -111,6 +111,7 @@   , tests/data/6F87040E-cr.pubkey   , tests/data/v3.key   , tests/data/primary-binding.gpg+  , tests/data/pki-password.txt   , tests/data/symmetric-password.txt   , tests/data/encryption-sym-aes256-s2k0.gpg   , tests/data/encryption-sym-aes128-s2k0.gpg@@ -142,8 +143,10 @@                      , Codec.Encryption.OpenPGP.Compression                      , Codec.Encryption.OpenPGP.Fingerprint                      , Codec.Encryption.OpenPGP.KeyInfo+                     , Codec.Encryption.OpenPGP.KeySelection                      , Codec.Encryption.OpenPGP.S2K                      , Codec.Encryption.OpenPGP.CFB+                     , Codec.Encryption.OpenPGP.SecretKey                      , Codec.Encryption.OpenPGP.Signatures                      , Data.Conduit.OpenPGP.Compression                      , Data.Conduit.OpenPGP.Decrypt@@ -152,6 +155,7 @@                      , Data.Conduit.OpenPGP.Verify   Other-Modules: Codec.Encryption.OpenPGP.Internal                , Codec.Encryption.OpenPGP.SerializeForSigs+               , Codec.Encryption.OpenPGP.BlockCipher   Build-depends: attoparsec                , base                  > 4       && < 5                , base64-bytestring@@ -163,9 +167,11 @@                , containers                , crypto-cipher-types                , crypto-pubkey         >= 0.1.4+               , crypto-random                , cryptocipher                , cryptohash                , data-default+               , errors                , ixset                 >= 1.0                , lens                  >= 3.0                , monad-loops@@ -174,6 +180,7 @@                , openpgp-asciiarmor                 >= 0.1                , securemem                , split+               , text                , time                               >= 1.1                , transformers                , zlib@@ -186,6 +193,7 @@   Ghc-options: -Wall   Build-depends: hOpenPGP                , base                  > 4       && < 5+               , attoparsec                , bytestring                , bzlib                , cereal@@ -194,9 +202,11 @@                , containers                , crypto-cipher-types                , crypto-pubkey         >= 0.1.4+               , crypto-random                , cryptocipher                , cryptohash                , data-default+               , errors                , ixset                 >= 1.0                , lens                  >= 3.0                , monad-loops@@ -204,12 +214,12 @@                , nettle                , securemem                , split+               , text                , time                               >= 1.1                , transformers                , zlib-               , HUnit-               , test-framework-               , test-framework-hunit+               , tasty+               , tasty-hunit                , resourcet              > 0.4 && < 0.5   default-language: Haskell2010 @@ -220,4 +230,4 @@ source-repository this   type:     git   location: git://git.debian.org/users/clint/hOpenPGP.git-  tag:      v0.9.1+  tag:      v0.10
+ tests/data/pki-password.txt view
@@ -0,0 +1,1 @@+test
tests/suite.hs view
@@ -1,24 +1,26 @@ {-# LANGUAGE OverloadedStrings #-}  -- suite.hs: hOpenPGP test suite--- Copyright © 2012-2013  Clint Adams+-- Copyright © 2012-2014  Clint Adams -- This software is released under the terms of the Expat license. -- (See the LICENSE file). -import Test.Framework (defaultMain, testGroup, Test)-import Test.Framework.Providers.HUnit--import Test.HUnit (Assertion, assertFailure, assertEqual)+import Test.Tasty (defaultMain, testGroup, TestTree)+import Test.Tasty.HUnit (testCase, Assertion, assertFailure, assertEqual)  import Codec.Encryption.OpenPGP.Serialize () import Codec.Encryption.OpenPGP.Compression (decompressPkt, compressPkts) import Codec.Encryption.OpenPGP.Fingerprint (eightOctetKeyID, fingerprint)+import Codec.Encryption.OpenPGP.KeySelection (parseFingerprint)+import Codec.Encryption.OpenPGP.SecretKey (decryptPrivateKey, encryptPrivateKey) import Codec.Encryption.OpenPGP.Types+import Crypto.PubKey.RSA (PrivateKey(private_pub)) import Data.Conduit.Cereal (conduitGet) import Data.Conduit.OpenPGP.Compression (conduitCompress, conduitDecompress) import Data.Conduit.OpenPGP.Decrypt (conduitDecrypt) import Data.Conduit.OpenPGP.Keyring (conduitToTKs, conduitToTKsDropping, sinkKeyringMap) import Data.Conduit.OpenPGP.Verify (conduitVerify)+import Data.Text (Text) import Codec.Encryption.OpenPGP.Serialize ()  import qualified Data.ByteString as B@@ -112,8 +114,29 @@           fakeCallback :: BL.ByteString -> String -> IO BL.ByteString           fakeCallback = const . return -tests :: [Test]-tests = [+testSecretKeyDecryption :: FilePath -> FilePath -> Assertion+testSecretKeyDecryption keyfile passfile = do+  passphrase <- BL.readFile $ "tests/data/" ++ passfile+  kr <- DC.runResourceT $ CB.sourceFile ("tests/data/" ++ keyfile) DC.$= conduitGet get DC.$$ CL.consume+  let SecretKey pkp ska = fromPkt . head $ kr+      SUUnencrypted (RSAPrivateKey dpk) _ = decryptPrivateKey (pkp, ska) passphrase  -- FIXME: better API for multiple keytypes+      RSAPubKey pk = _pubkey pkp+  assertEqual "private key matches public key" pk (private_pub dpk)++-- FIXME: this should be reworked either with tasty-golden or some other form of sanity+testSecretKeyEncryption :: FilePath -> FilePath -> Assertion+testSecretKeyEncryption keyfile passfile = do+  passphrase <- BL.readFile $ "tests/data/" ++ passfile+  kr <- DC.runResourceT $ CB.sourceFile ("tests/data/" ++ keyfile) DC.$= conduitGet get DC.$$ CL.consume+  gkr <- DC.runResourceT $ CB.sourceFile ("tests/data/" ++ "aes256-sha512.seckey") DC.$= conduitGet get DC.$$ CL.consume+  let SecretKey pkp ska = fromPkt . head $ kr+      newska = encryptPrivateKey "\226~\197\a\202#\"G" "\187\219\253I\236\204\t5D\196\NAK>;\202\185\t" ska passphrase+      newtruck = toPkt (SecretKey pkp newska):tail kr+  assertEqual "encrypted private key matches golden file" gkr newtruck+++tests :: TestTree+tests = testGroup "unit tests" [   testGroup "Serialization group" [      testCase "000001-006.public_key" (testSerialization "000001-006.public_key")    , testCase "000002-013.user_id" (testSerialization "000002-013.user_id")@@ -268,14 +291,22 @@    , testCase "Symmetric Encryption iterated-salted S2K SHA1 AES256" (testSymmetricEncryption "encryption-sym-aes256.gpg" "symmetric-password.txt" "test\n")    , testCase "Symmetric Encryption simple S2K SHA1 Twofish" (testSymmetricEncryption "encryption-sym-twofish-s2k0.gpg" "symmetric-password.txt" "test\n")    , testCase "Symmetric Encryption iterated-salted S2K SHA1 Twofish" (testSymmetricEncryption "encryption-sym-twofish.gpg" "symmetric-password.txt" "test\n")+   ],+  testGroup "Encrypted secret keys" [+     testCase "SUSSHA1 CAST5 IteratedSalted SHA1 RSA" (testSecretKeyDecryption "simple.seckey" "pki-password.txt")+   , testCase "SUS16bit CAST5 IteratedSalted SHA1 RSA" (testSecretKeyDecryption "16bitcksum.seckey" "pki-password.txt")+   , testCase "SUSSHA1 AES256 IteratedSalted SHA512 RSA" (testSecretKeyDecryption "aes256-sha512.seckey" "pki-password.txt")+   ],+  testGroup "Encrypting secret keys" [+     testCase "SUSSHA1 AES256 IteratedSalted SHA512 RSA" (testSecretKeyEncryption "unencrypted.seckey" "pki-password.txt")    ]  ]  cgp :: DC.Conduit B.ByteString (DC.ResourceT IO) Pkt cgp = conduitGet (get :: Get Pkt) -fp :: String -> TwentyOctetFingerprint-fp = read+fp :: Text -> TwentyOctetFingerprint+fp = either error id . parseFingerprint  main :: IO () main = defaultMain tests