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 /Data/OpenPGP/Util/Sign.hs |
New package openpgp-util as alternative to OpenPGP-CryptoAPI.
Diffstat (limited to 'Data/OpenPGP/Util/Sign.hs')
-rw-r--r-- | Data/OpenPGP/Util/Sign.hs | 213 |
1 files changed, 213 insertions, 0 deletions
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 | |||