diff options
author | joe <joe@jerkface.net> | 2013-12-14 22:34:26 -0500 |
---|---|---|
committer | joe <joe@jerkface.net> | 2013-12-14 22:34:26 -0500 |
commit | ce0d32ef83ccf15198bdd5248faa02abbcf2f769 (patch) | |
tree | 96155973d7af58bbb79a8f67a21f646af46cb47f |
New package openpgp-util as alternative to OpenPGP-CryptoAPI.
-rw-r--r-- | Data/OpenPGP/Util.hs | 12 | ||||
-rw-r--r-- | Data/OpenPGP/Util/DecryptSecretKey.hs | 134 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Fingerprint.hs | 33 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Sign.hs | 213 | ||||
-rw-r--r-- | Data/OpenPGP/Util/Verify.hs | 115 |
5 files changed, 507 insertions, 0 deletions
diff --git a/Data/OpenPGP/Util.hs b/Data/OpenPGP/Util.hs new file mode 100644 index 0000000..c675ec2 --- /dev/null +++ b/Data/OpenPGP/Util.hs | |||
@@ -0,0 +1,12 @@ | |||
1 | module Data.OpenPGP.Util | ||
2 | ( fingerprint | ||
3 | , decryptSecretKey | ||
4 | , verify | ||
5 | , pgpSign | ||
6 | ) where | ||
7 | |||
8 | import Data.OpenPGP.Util.Fingerprint | ||
9 | import Data.OpenPGP.Util.Sign | ||
10 | import Data.OpenPGP.Util.Verify | ||
11 | import Data.OpenPGP.Util.DecryptSecretKey | ||
12 | |||
diff --git a/Data/OpenPGP/Util/DecryptSecretKey.hs b/Data/OpenPGP/Util/DecryptSecretKey.hs new file mode 100644 index 0000000..0ba89d2 --- /dev/null +++ b/Data/OpenPGP/Util/DecryptSecretKey.hs | |||
@@ -0,0 +1,134 @@ | |||
1 | module Data.OpenPGP.Util.DecryptSecretKey where | ||
2 | |||
3 | import qualified Data.OpenPGP as OpenPGP | ||
4 | import qualified Data.ByteString as BS | ||
5 | import qualified Data.ByteString.Lazy as LZ | ||
6 | import Data.Word (Word16,Word8) | ||
7 | import Data.Char (toUpper) | ||
8 | import Control.Monad (foldM) | ||
9 | import Numeric (showHex) | ||
10 | import Data.Binary (get,Binary,Get) | ||
11 | import Data.Binary.Get (runGetOrFail) | ||
12 | import qualified Data.Serialize as Serialize | ||
13 | import Control.Applicative ( (<$>) ) | ||
14 | |||
15 | import Crypto.Hash.MD5 as MD5 | ||
16 | import Crypto.Hash.SHA1 as SHA1 | ||
17 | import Crypto.Hash.SHA256 as SHA256 | ||
18 | import Crypto.Hash.SHA384 as SHA384 | ||
19 | import Crypto.Hash.SHA512 as SHA512 | ||
20 | import Crypto.Hash.SHA224 as SHA224 | ||
21 | import Crypto.Hash.RIPEMD160 as RIPEMD160 | ||
22 | |||
23 | import qualified Crypto.Cipher.AES as Vincent | ||
24 | import qualified Crypto.Cipher.Blowfish as Vincent | ||
25 | |||
26 | import qualified Crypto.Cipher.Types as Vincent | ||
27 | import qualified Data.Byteable as Vincent | ||
28 | |||
29 | import Crypto.Cipher.Cast5 (CAST5_128) | ||
30 | import Crypto.Cipher.ThomasToVincent | ||
31 | |||
32 | |||
33 | hashBySymbol OpenPGP.MD5 = MD5.hashlazy | ||
34 | hashBySymbol OpenPGP.SHA1 = SHA1.hashlazy | ||
35 | hashBySymbol OpenPGP.SHA256 = SHA256.hashlazy | ||
36 | hashBySymbol OpenPGP.SHA384 = SHA384.hashlazy | ||
37 | hashBySymbol OpenPGP.SHA512 = SHA512.hashlazy | ||
38 | hashBySymbol OpenPGP.SHA224 = SHA224.hashlazy | ||
39 | hashBySymbol OpenPGP.RIPEMD160 = RIPEMD160.hashlazy | ||
40 | |||
41 | |||
42 | |||
43 | data Enciphered = | ||
44 | EncipheredWithIV !LZ.ByteString -- initial vector is appended to front of ByteString | ||
45 | | EncipheredZeroIV !LZ.ByteString -- initial vector is zero, ByteString contains only the block | ||
46 | |||
47 | withIV :: (Vincent.BlockCipher k) => (Vincent.IV k -> LZ.ByteString -> LZ.ByteString) -> Enciphered -> LZ.ByteString | ||
48 | withIV f (EncipheredWithIV s) = f iv bs | ||
49 | where | ||
50 | Just iv = Vincent.makeIV (toStrictBS ivbs) | ||
51 | (ivbs,bs) = LZ.splitAt (fromIntegral ivlen) s | ||
52 | ivlen = Vincent.byteableLength z | ||
53 | z = Vincent.nullIV | ||
54 | _ = Vincent.constEqBytes z iv | ||
55 | withIV f (EncipheredZeroIV s) = f Vincent.nullIV s | ||
56 | |||
57 | decryptSecretKey :: | ||
58 | BS.ByteString -- ^ Passphrase | ||
59 | -> OpenPGP.Packet -- ^ Encrypted SecretKeyPacket | ||
60 | -> Maybe OpenPGP.Packet -- ^ Decrypted SecretKeyPacket | ||
61 | decryptSecretKey pass k@(OpenPGP.SecretKeyPacket { | ||
62 | OpenPGP.version = 4, OpenPGP.key_algorithm = kalgo, | ||
63 | OpenPGP.s2k = s2k, OpenPGP.symmetric_algorithm = salgo, | ||
64 | OpenPGP.key = existing, OpenPGP.encrypted_data = encd | ||
65 | }) | chkF material == toStrictBS chk = | ||
66 | fmap (\m -> k { | ||
67 | OpenPGP.s2k_useage = 0, | ||
68 | OpenPGP.symmetric_algorithm = OpenPGP.Unencrypted, | ||
69 | OpenPGP.encrypted_data = LZ.empty, | ||
70 | OpenPGP.key = m | ||
71 | }) parseMaterial | ||
72 | | otherwise = Nothing | ||
73 | where | ||
74 | parseMaterial = maybeGet | ||
75 | (foldM (\m f -> do {mpi <- get; return $ (f,mpi):m}) existing | ||
76 | (OpenPGP.secret_key_fields kalgo)) material | ||
77 | (material, chk) = LZ.splitAt (LZ.length decd - chkSize) decd | ||
78 | (chkSize, chkF) | ||
79 | | OpenPGP.s2k_useage k == 254 = (20, SHA1.hash . toStrictBS) | ||
80 | | otherwise = (2, Serialize.encode . checksum . toStrictBS) | ||
81 | decd = string2sdecrypt salgo s2k (toLazyBS pass) (EncipheredWithIV encd) | ||
82 | |||
83 | checksum :: BS.ByteString -> Word16 | ||
84 | checksum key = fromIntegral $ | ||
85 | BS.foldl' (\x y -> x + fromIntegral y) (0::Integer) key `mod` 65536 | ||
86 | |||
87 | maybeGet :: (Binary a) => Get a -> LZ.ByteString -> Maybe a | ||
88 | maybeGet g bs = (\(_,_,x) -> x) <$> hush (runGetOrFail g bs) | ||
89 | |||
90 | hush :: Either a b -> Maybe b | ||
91 | hush (Left _) = Nothing | ||
92 | hush (Right x) = Just x | ||
93 | |||
94 | |||
95 | decryptSecretKey _ _ = Nothing | ||
96 | |||
97 | toStrictBS :: LZ.ByteString -> BS.ByteString | ||
98 | toStrictBS = BS.concat . LZ.toChunks | ||
99 | |||
100 | toLazyBS :: BS.ByteString -> LZ.ByteString | ||
101 | toLazyBS = LZ.fromChunks . (:[]) | ||
102 | |||
103 | |||
104 | |||
105 | string2sdecrypt :: OpenPGP.SymmetricAlgorithm -> OpenPGP.S2K -> LZ.ByteString -> Enciphered -> LZ.ByteString | ||
106 | string2sdecrypt OpenPGP.AES128 s2k s = withIV $ simpleUnCFB (string2key s2k s :: Vincent.AES128) | ||
107 | string2sdecrypt OpenPGP.AES192 s2k s = withIV $ simpleUnCFB (string2key s2k s :: Vincent.AES192) | ||
108 | string2sdecrypt OpenPGP.AES256 s2k s = withIV $ simpleUnCFB (string2key s2k s :: Vincent.AES256) | ||
109 | string2sdecrypt OpenPGP.Blowfish s2k s = withIV $ simpleUnCFB (string2key s2k s :: Vincent.Blowfish128) | ||
110 | string2sdecrypt OpenPGP.CAST5 s2k s = withIV $ simpleUnCFB (string2key s2k s :: ThomasToVincent CAST5_128) | ||
111 | string2sdecrypt algo _ _ = error $ "Unsupported symmetric algorithm : " ++ show algo ++ " in Data.OpenPGP.CryptoAPI.string2sdecrypt" | ||
112 | |||
113 | simpleUnCFB :: (Vincent.BlockCipher k) => k -> Vincent.IV k -> LZ.ByteString -> LZ.ByteString | ||
114 | simpleUnCFB k iv = padThenUnpad k (toLazyBS . Vincent.cfbDecrypt k iv . toStrictBS) | ||
115 | where | ||
116 | padThenUnpad :: (Vincent.BlockCipher k) => k -> (LZ.ByteString -> LZ.ByteString) -> LZ.ByteString -> LZ.ByteString | ||
117 | padThenUnpad k f s = dropPadEnd (f padded) | ||
118 | where | ||
119 | dropPadEnd s = LZ.take (LZ.length s - padAmount) s | ||
120 | padded = s `LZ.append` LZ.replicate padAmount 0 | ||
121 | padAmount = blksize - (LZ.length s `mod` blksize) | ||
122 | blksize = fromIntegral $ Vincent.blockSize k | ||
123 | |||
124 | string2key :: (Vincent.BlockCipher k) => OpenPGP.S2K -> LZ.ByteString -> k | ||
125 | string2key s2k s = cipher | ||
126 | where | ||
127 | cipher = Vincent.cipherInit k | ||
128 | Right k = Vincent.makeKey $ toStrictBS $ | ||
129 | LZ.take ksize $ OpenPGP.string2key hashBySymbol s2k s | ||
130 | ksize = case Vincent.cipherKeySize cipher of | ||
131 | Vincent.KeySizeFixed n -> fromIntegral n | ||
132 | Vincent.KeySizeEnum xs -> error $ "Unknown key size in string2key" | ||
133 | Vincent.KeySizeRange min max -> error $ "Unknown key size range in string2key" | ||
134 | |||
diff --git a/Data/OpenPGP/Util/Fingerprint.hs b/Data/OpenPGP/Util/Fingerprint.hs new file mode 100644 index 0000000..538688b --- /dev/null +++ b/Data/OpenPGP/Util/Fingerprint.hs | |||
@@ -0,0 +1,33 @@ | |||
1 | module Data.OpenPGP.Util.Fingerprint (fingerprint) where | ||
2 | |||
3 | import qualified Data.OpenPGP as OpenPGP | ||
4 | import qualified Data.ByteString as BS | ||
5 | import qualified Data.ByteString.Lazy as LZ | ||
6 | import Data.Char (toUpper) | ||
7 | import Data.Word (Word8) | ||
8 | import Numeric (showHex) | ||
9 | |||
10 | import Crypto.Hash.MD5 as MD5 | ||
11 | import Crypto.Hash.SHA1 as SHA1 | ||
12 | |||
13 | -- | Generate a key fingerprint from a PublicKeyPacket or SecretKeyPacket | ||
14 | -- <http://tools.ietf.org/html/rfc4880#section-12.2> | ||
15 | fingerprint :: OpenPGP.Packet -> String | ||
16 | fingerprint p | ||
17 | | OpenPGP.version p == 4 = hexify $ SHA1.hashlazy material | ||
18 | | OpenPGP.version p `elem` [2, 3] = hexify $ MD5.hashlazy material | ||
19 | | otherwise = error "Unsupported Packet version or type in fingerprint" | ||
20 | where | ||
21 | material = LZ.concat $ OpenPGP.fingerprint_material p | ||
22 | |||
23 | hexify = map toUpper . hexString . BS.unpack | ||
24 | |||
25 | hexString :: [Word8] -> String | ||
26 | hexString = foldr (pad `oo` showHex) "" | ||
27 | where | ||
28 | pad s | odd $ length s = '0':s | ||
29 | | otherwise = s | ||
30 | |||
31 | oo :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c | ||
32 | oo = (.) . (.) | ||
33 | |||
diff --git a/Data/OpenPGP/Util/Sign.hs b/Data/OpenPGP/Util/Sign.hs new file mode 100644 index 0000000..ef7d16b --- /dev/null +++ b/Data/OpenPGP/Util/Sign.hs | |||
@@ -0,0 +1,213 @@ | |||
1 | {-# LANGUAGE ScopedTypeVariables #-} | ||
2 | module Data.OpenPGP.Util.Sign where | ||
3 | |||
4 | import qualified Data.OpenPGP as OpenPGP | ||
5 | import Data.Maybe | ||
6 | import Data.Binary (encode) | ||
7 | import qualified Data.ByteString as BS | ||
8 | import qualified Data.ByteString.Lazy as LZ | ||
9 | import Data.Bits ( (.|.), shiftL ) | ||
10 | import Control.Applicative ( (<$>) ) | ||
11 | import Data.Time.Clock.POSIX | ||
12 | import Control.Exception as Exception (IOException(..),catch) | ||
13 | |||
14 | import Data.OpenPGP.Util.Fingerprint (fingerprint) | ||
15 | |||
16 | import qualified Crypto.Random as Vincent | ||
17 | import qualified Crypto.PubKey.DSA as Vincent.DSA | ||
18 | import qualified Crypto.PubKey.RSA as Vincent.RSA | ||
19 | import qualified Crypto.PubKey.RSA.PKCS15 as Vincent.RSA | ||
20 | import Crypto.PubKey.HashDescr as Vincent | ||
21 | |||
22 | import Crypto.Hash.MD5 as MD5 | ||
23 | import Crypto.Hash.SHA1 as SHA1 | ||
24 | import Crypto.Hash.SHA256 as SHA256 | ||
25 | import Crypto.Hash.SHA384 as SHA384 | ||
26 | import Crypto.Hash.SHA512 as SHA512 | ||
27 | import Crypto.Hash.SHA224 as SHA224 | ||
28 | import Crypto.Hash.RIPEMD160 as RIPEMD160 | ||
29 | |||
30 | hashAlgoDesc OpenPGP.MD5 = Vincent.hashDescrMD5 | ||
31 | hashAlgoDesc OpenPGP.SHA1 = Vincent.hashDescrSHA1 | ||
32 | hashAlgoDesc OpenPGP.RIPEMD160 = Vincent.hashDescrRIPEMD160 | ||
33 | hashAlgoDesc OpenPGP.SHA256 = Vincent.hashDescrSHA256 | ||
34 | hashAlgoDesc OpenPGP.SHA384 = Vincent.hashDescrSHA384 | ||
35 | hashAlgoDesc OpenPGP.SHA512 = Vincent.hashDescrSHA512 | ||
36 | hashAlgoDesc OpenPGP.SHA224 = Vincent.hashDescrSHA224 | ||
37 | hashAlgoDesc _ = | ||
38 | error "Unsupported HashAlgorithm in hashAlgoDesc" | ||
39 | |||
40 | find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet | ||
41 | find_key = OpenPGP.find_key fingerprint | ||
42 | |||
43 | |||
44 | privateDSAkey :: OpenPGP.Packet -> Vincent.DSA.PrivateKey | ||
45 | privateDSAkey k = Vincent.DSA.PrivateKey | ||
46 | (Vincent.DSA.Params (keyParam 'p' k) (keyParam 'g' k) (keyParam 'q' k)) | ||
47 | (keyParam 'x' k) | ||
48 | privateRSAkey :: OpenPGP.Packet -> Vincent.RSA.PrivateKey | ||
49 | privateRSAkey k = | ||
50 | -- Invert p and q because u is pinv not qinv | ||
51 | Vincent.RSA.PrivateKey pubkey d q p | ||
52 | (d `mod` (q-1)) | ||
53 | (d `mod` (p-1)) | ||
54 | (keyParam 'u' k) | ||
55 | where | ||
56 | d = keyParam 'd' k | ||
57 | p = keyParam 'p' k | ||
58 | q = keyParam 'q' k | ||
59 | pubkey = rsaKey k | ||
60 | |||
61 | rsaKey :: OpenPGP.Packet -> Vincent.RSA.PublicKey | ||
62 | rsaKey k = | ||
63 | Vincent.RSA.PublicKey (integerBytesize n) n (keyParam 'e' k) | ||
64 | where | ||
65 | n = keyParam 'n' k | ||
66 | |||
67 | integerBytesize :: Integer -> Int | ||
68 | integerBytesize i = fromIntegral $ LZ.length (encode (OpenPGP.MPI i)) - 2 | ||
69 | |||
70 | |||
71 | toStrictBS :: LZ.ByteString -> BS.ByteString | ||
72 | toStrictBS = BS.concat . LZ.toChunks | ||
73 | |||
74 | toLazyBS :: BS.ByteString -> LZ.ByteString | ||
75 | toLazyBS = LZ.fromChunks . (:[]) | ||
76 | |||
77 | |||
78 | keyParam :: Char -> OpenPGP.Packet -> Integer | ||
79 | keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k) | ||
80 | fromJustMPI :: Maybe OpenPGP.MPI -> Integer | ||
81 | fromJustMPI (Just (OpenPGP.MPI x)) = x | ||
82 | fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI" | ||
83 | |||
84 | hashBySymbol OpenPGP.MD5 = MD5.hashlazy | ||
85 | hashBySymbol OpenPGP.SHA1 = SHA1.hashlazy | ||
86 | hashBySymbol OpenPGP.SHA256 = SHA256.hashlazy | ||
87 | hashBySymbol OpenPGP.SHA384 = SHA384.hashlazy | ||
88 | hashBySymbol OpenPGP.SHA512 = SHA512.hashlazy | ||
89 | hashBySymbol OpenPGP.SHA224 = SHA224.hashlazy | ||
90 | hashBySymbol OpenPGP.RIPEMD160 = RIPEMD160.hashlazy | ||
91 | |||
92 | |||
93 | |||
94 | |||
95 | -- | Make a signature | ||
96 | -- | ||
97 | -- In order to set more options on a signature, pass in a signature packet. | ||
98 | -- Operation is unsafe in that it silently re-uses "random" bytes when | ||
99 | -- entropy runs out. Use pgpSign for a safer interface. | ||
100 | unsafeSign :: (Vincent.CPRG g) => -- CryptoRandomGen g) => | ||
101 | OpenPGP.Message -- ^ SecretKeys, one of which will be used | ||
102 | -> OpenPGP.SignatureOver -- ^ Data to sign, and optional signature packet | ||
103 | -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature | ||
104 | -> String -- ^ KeyID of key to choose | ||
105 | -> Integer -- ^ Timestamp for signature (unless sig supplied) | ||
106 | -> g -- ^ Random number generator | ||
107 | -> (OpenPGP.SignatureOver, g) | ||
108 | unsafeSign keys over hsh keyid timestamp g = (over {OpenPGP.signatures_over = [sig]}, g') | ||
109 | where | ||
110 | (final, g') = case OpenPGP.key_algorithm sig of | ||
111 | OpenPGP.DSA -> ([dsaR, dsaS], dsaG) | ||
112 | kalgo | kalgo `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> ([toNum rsaFinal], g) | ||
113 | | otherwise -> | ||
114 | error ("Unsupported key algorithm " ++ show kalgo ++ "in sign") | ||
115 | (Vincent.DSA.Signature dsaR dsaS,dsaG) = let k' = privateDSAkey k in | ||
116 | Vincent.DSA.sign g k' (dsaTruncate k' . bhash) dta | ||
117 | (Right rsaFinal,_) = Vincent.RSA.signSafer g desc (privateRSAkey k) dta | ||
118 | dsaTruncate (Vincent.DSA.PrivateKey (Vincent.DSA.Params _ _ q) _) = BS.take (integerBytesize q) | ||
119 | dta = toStrictBS $ encode over `LZ.append` OpenPGP.trailer sig | ||
120 | sig = findSigOrDefault (listToMaybe $ OpenPGP.signatures_over over) | ||
121 | -- padding = emsa_pkcs1_v1_5_hash_padding hsh | ||
122 | desc = hashAlgoDesc hsh | ||
123 | bhash = hashBySymbol hsh . toLazyBS | ||
124 | toNum = BS.foldl (\a b -> a `shiftL` 8 .|. fromIntegral b) 0 | ||
125 | Just k = find_key keys keyid | ||
126 | |||
127 | -- Either a SignaturePacket was found, or we need to make one | ||
128 | findSigOrDefault (Just s) = OpenPGP.signaturePacket | ||
129 | (OpenPGP.version s) | ||
130 | (OpenPGP.signature_type s) | ||
131 | (OpenPGP.key_algorithm k) -- force to algo of key | ||
132 | hsh -- force hash algorithm | ||
133 | (OpenPGP.hashed_subpackets s) | ||
134 | (OpenPGP.unhashed_subpackets s) | ||
135 | (OpenPGP.hash_head s) | ||
136 | (map OpenPGP.MPI final) | ||
137 | findSigOrDefault Nothing = OpenPGP.signaturePacket | ||
138 | 4 | ||
139 | defaultStype | ||
140 | (OpenPGP.key_algorithm k) -- force to algo of key | ||
141 | hsh | ||
142 | ([ | ||
143 | -- Do we really need to pass in timestamp just for the default? | ||
144 | OpenPGP.SignatureCreationTimePacket $ fromIntegral timestamp, | ||
145 | OpenPGP.IssuerPacket $ fingerprint k | ||
146 | ] ++ (case over of | ||
147 | OpenPGP.KeySignature {} -> [OpenPGP.KeyFlagsPacket { | ||
148 | OpenPGP.certify_keys = True, | ||
149 | OpenPGP.sign_data = True, | ||
150 | OpenPGP.encrypt_communication = False, | ||
151 | OpenPGP.encrypt_storage = False, | ||
152 | OpenPGP.split_key = False, | ||
153 | OpenPGP.authentication = False, | ||
154 | OpenPGP.group_key = False | ||
155 | }] | ||
156 | _ -> [] | ||
157 | )) | ||
158 | [] | ||
159 | 0 -- TODO | ||
160 | (map OpenPGP.MPI final) | ||
161 | |||
162 | defaultStype = case over of | ||
163 | OpenPGP.DataSignature ld _ | ||
164 | | OpenPGP.format ld == 'b' -> 0x00 | ||
165 | | otherwise -> 0x01 | ||
166 | OpenPGP.KeySignature {} -> 0x1F | ||
167 | OpenPGP.SubkeySignature {} -> 0x18 | ||
168 | OpenPGP.CertificationSignature {} -> 0x13 | ||
169 | |||
170 | |||
171 | |||
172 | now = floor <$> Data.Time.Clock.POSIX.getPOSIXTime | ||
173 | |||
174 | stampit timestamp sig = sig { OpenPGP.hashed_subpackets = hashed' } | ||
175 | where | ||
176 | hashed_stamps = filter isStamp (OpenPGP.hashed_subpackets sig) | ||
177 | unhashed_stamps = filter isStamp (OpenPGP.unhashed_subpackets sig) | ||
178 | hashed' = case hashed_stamps ++ unhashed_stamps of | ||
179 | [] -> OpenPGP.SignatureCreationTimePacket (fromIntegral timestamp) | ||
180 | : OpenPGP.hashed_subpackets sig | ||
181 | _ -> OpenPGP.hashed_subpackets sig | ||
182 | isStamp (OpenPGP.SignatureCreationTimePacket {}) = True | ||
183 | isStamp _ = False | ||
184 | |||
185 | -- | Make a signature | ||
186 | -- | ||
187 | -- In order to set more options on a signature, pass in a signature packet. | ||
188 | pgpSign :: | ||
189 | OpenPGP.Message -- ^ SecretKeys, one of which will be used | ||
190 | -> OpenPGP.SignatureOver -- ^ Data to sign, and optional signature packet | ||
191 | -> OpenPGP.HashAlgorithm -- ^ HashAlgorithm to use in signature | ||
192 | -> String -- ^ KeyID of key to choose | ||
193 | -> IO (Maybe OpenPGP.SignatureOver) | ||
194 | pgpSign seckeys dta hash_algo keyid = | ||
195 | handleIO_ (return Nothing) $ do | ||
196 | timestamp <- now | ||
197 | -- g <- Thomas.newGenIO :: IO Thomas.SystemRandom | ||
198 | g <- fmap Vincent.cprgCreate $ Vincent.createEntropyPool | ||
199 | let _ = g :: Vincent.SystemRNG | ||
200 | let sigs = map (stampit timestamp) $ OpenPGP.signatures_over dta | ||
201 | dta' = dta { OpenPGP.signatures_over = sigs } | ||
202 | let (r,g') = unsafeSign seckeys dta' hash_algo keyid timestamp g | ||
203 | return (Just r) | ||
204 | |||
205 | catchIO_ :: IO a -> IO a -> IO a | ||
206 | catchIO_ a h = Exception.catch a (\(_ :: IOException) -> h) | ||
207 | |||
208 | catchIO :: IO a -> (IOException -> IO a) -> IO a | ||
209 | catchIO body handler = Exception.catch body handler | ||
210 | |||
211 | handleIO_ = flip catchIO_ | ||
212 | handleIO = flip catchIO | ||
213 | |||
diff --git a/Data/OpenPGP/Util/Verify.hs b/Data/OpenPGP/Util/Verify.hs new file mode 100644 index 0000000..137c00f --- /dev/null +++ b/Data/OpenPGP/Util/Verify.hs | |||
@@ -0,0 +1,115 @@ | |||
1 | module Data.OpenPGP.Util.Verify where | ||
2 | |||
3 | import qualified Data.OpenPGP as OpenPGP | ||
4 | import Data.Maybe | ||
5 | import Data.Binary (encode) | ||
6 | import Control.Monad | ||
7 | import qualified Data.ByteString as BS | ||
8 | import qualified Data.ByteString.Lazy as LZ | ||
9 | import Data.Monoid ( (<>) ) | ||
10 | |||
11 | import Data.OpenPGP.Util.Fingerprint (fingerprint) | ||
12 | |||
13 | import qualified Crypto.PubKey.DSA as Vincent.DSA | ||
14 | import qualified Crypto.PubKey.RSA as Vincent.RSA | ||
15 | import qualified Crypto.PubKey.RSA.PKCS15 as Vincent.RSA | ||
16 | import Crypto.PubKey.HashDescr as Vincent | ||
17 | |||
18 | import Crypto.Hash.MD5 as MD5 | ||
19 | import Crypto.Hash.SHA1 as SHA1 | ||
20 | import Crypto.Hash.SHA256 as SHA256 | ||
21 | import Crypto.Hash.SHA384 as SHA384 | ||
22 | import Crypto.Hash.SHA512 as SHA512 | ||
23 | import Crypto.Hash.SHA224 as SHA224 | ||
24 | import Crypto.Hash.RIPEMD160 as RIPEMD160 | ||
25 | |||
26 | hashBySymbol OpenPGP.MD5 = MD5.hashlazy | ||
27 | hashBySymbol OpenPGP.SHA1 = SHA1.hashlazy | ||
28 | hashBySymbol OpenPGP.SHA256 = SHA256.hashlazy | ||
29 | hashBySymbol OpenPGP.SHA384 = SHA384.hashlazy | ||
30 | hashBySymbol OpenPGP.SHA512 = SHA512.hashlazy | ||
31 | hashBySymbol OpenPGP.SHA224 = SHA224.hashlazy | ||
32 | hashBySymbol OpenPGP.RIPEMD160 = RIPEMD160.hashlazy | ||
33 | |||
34 | |||
35 | toStrictBS :: LZ.ByteString -> BS.ByteString | ||
36 | toStrictBS = BS.concat . LZ.toChunks | ||
37 | |||
38 | toLazyBS :: BS.ByteString -> LZ.ByteString | ||
39 | toLazyBS = LZ.fromChunks . (:[]) | ||
40 | |||
41 | hush :: Either a b -> Maybe b | ||
42 | hush (Left _) = Nothing | ||
43 | hush (Right x) = Just x | ||
44 | |||
45 | fromJustMPI :: Maybe OpenPGP.MPI -> Integer | ||
46 | fromJustMPI (Just (OpenPGP.MPI x)) = x | ||
47 | fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI" | ||
48 | |||
49 | |||
50 | |||
51 | find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet | ||
52 | find_key = OpenPGP.find_key fingerprint | ||
53 | |||
54 | integerBytesize :: Integer -> Int | ||
55 | integerBytesize i = fromIntegral $ LZ.length (encode (OpenPGP.MPI i)) - 2 | ||
56 | |||
57 | dsaKey :: OpenPGP.Packet -> Vincent.DSA.PublicKey | ||
58 | dsaKey k = Vincent.DSA.PublicKey | ||
59 | (Vincent.DSA.Params (keyParam 'p' k) (keyParam 'g' k) (keyParam 'q' k)) | ||
60 | (keyParam 'y' k) | ||
61 | |||
62 | rsaKey :: OpenPGP.Packet -> Vincent.RSA.PublicKey | ||
63 | rsaKey k = | ||
64 | Vincent.RSA.PublicKey (integerBytesize n) n (keyParam 'e' k) | ||
65 | where | ||
66 | n = keyParam 'n' k | ||
67 | |||
68 | |||
69 | keyParam :: Char -> OpenPGP.Packet -> Integer | ||
70 | keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k) | ||
71 | |||
72 | |||
73 | -- | Verify a message signature | ||
74 | verify :: | ||
75 | OpenPGP.Message -- ^ Keys that may have made the signature | ||
76 | -> OpenPGP.SignatureOver -- ^ Signatures to verify | ||
77 | -> OpenPGP.SignatureOver -- ^ Will only contain signatures that passed | ||
78 | verify keys over = | ||
79 | over {OpenPGP.signatures_over = mapMaybe (uncurry $ verifyOne keys) sigs} | ||
80 | where | ||
81 | sigs = map (\s -> (s, toStrictBS $ encode over `LZ.append` OpenPGP.trailer s)) | ||
82 | (OpenPGP.signatures_over over) | ||
83 | |||
84 | verifyOne :: OpenPGP.Message -> OpenPGP.Packet -> BS.ByteString -> Maybe OpenPGP.Packet | ||
85 | verifyOne keys sig over = fmap (const sig) $ maybeKey >>= verification >>= guard | ||
86 | where | ||
87 | verification = case OpenPGP.key_algorithm sig of | ||
88 | OpenPGP.DSA -> dsaVerify | ||
89 | alg | alg `elem` [OpenPGP.RSA,OpenPGP.RSA_S] -> rsaVerify | ||
90 | | otherwise -> const Nothing | ||
91 | dsaVerify k = let k' = dsaKey k in | ||
92 | Just $ Vincent.DSA.verify (dsaTruncate k' . bhash) k' dsaSig over | ||
93 | rsaVerify k = Just $ Vincent.RSA.verify desc (rsaKey k) over rsaSig | ||
94 | [rsaSig] = map (toStrictBS . LZ.drop 2 . encode) (OpenPGP.signature sig) | ||
95 | dsaSig = let [OpenPGP.MPI r, OpenPGP.MPI s] = OpenPGP.signature sig in | ||
96 | Vincent.DSA.Signature r s | ||
97 | dsaTruncate (Vincent.DSA.PublicKey (Vincent.DSA.Params _ _ q) _) = BS.take (integerBytesize q) | ||
98 | bhash = hashBySymbol hash_algo . toLazyBS | ||
99 | desc = hashAlgoDesc hash_algo | ||
100 | hash_algo = OpenPGP.hash_algorithm sig | ||
101 | maybeKey = OpenPGP.signature_issuer sig >>= find_key keys | ||
102 | |||
103 | -- http://tools.ietf.org/html/rfc3447#page-43 | ||
104 | -- http://tools.ietf.org/html/rfc4880#section-5.2.2 | ||
105 | hashAlgoDesc OpenPGP.MD5 = Vincent.hashDescrMD5 | ||
106 | hashAlgoDesc OpenPGP.SHA1 = Vincent.hashDescrSHA1 | ||
107 | hashAlgoDesc OpenPGP.RIPEMD160 = Vincent.hashDescrRIPEMD160 | ||
108 | hashAlgoDesc OpenPGP.SHA256 = Vincent.hashDescrSHA256 | ||
109 | hashAlgoDesc OpenPGP.SHA384 = Vincent.hashDescrSHA384 | ||
110 | hashAlgoDesc OpenPGP.SHA512 = Vincent.hashDescrSHA512 | ||
111 | hashAlgoDesc OpenPGP.SHA224 = Vincent.hashDescrSHA224 | ||
112 | hashAlgoDesc _ = | ||
113 | error "Unsupported HashAlgorithm in hashAlgoDesc" | ||
114 | |||
115 | |||