summaryrefslogtreecommitdiff
path: root/Data/OpenPGP/Util/Sign.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2013-12-14 22:34:26 -0500
committerjoe <joe@jerkface.net>2013-12-14 22:34:26 -0500
commitce0d32ef83ccf15198bdd5248faa02abbcf2f769 (patch)
tree96155973d7af58bbb79a8f67a21f646af46cb47f /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.hs213
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 #-}
2module Data.OpenPGP.Util.Sign where
3
4import qualified Data.OpenPGP as OpenPGP
5import Data.Maybe
6import Data.Binary (encode)
7import qualified Data.ByteString as BS
8import qualified Data.ByteString.Lazy as LZ
9import Data.Bits ( (.|.), shiftL )
10import Control.Applicative ( (<$>) )
11import Data.Time.Clock.POSIX
12import Control.Exception as Exception (IOException(..),catch)
13
14import Data.OpenPGP.Util.Fingerprint (fingerprint)
15
16import qualified Crypto.Random as Vincent
17import qualified Crypto.PubKey.DSA as Vincent.DSA
18import qualified Crypto.PubKey.RSA as Vincent.RSA
19import qualified Crypto.PubKey.RSA.PKCS15 as Vincent.RSA
20import Crypto.PubKey.HashDescr as Vincent
21
22import Crypto.Hash.MD5 as MD5
23import Crypto.Hash.SHA1 as SHA1
24import Crypto.Hash.SHA256 as SHA256
25import Crypto.Hash.SHA384 as SHA384
26import Crypto.Hash.SHA512 as SHA512
27import Crypto.Hash.SHA224 as SHA224
28import Crypto.Hash.RIPEMD160 as RIPEMD160
29
30hashAlgoDesc OpenPGP.MD5 = Vincent.hashDescrMD5
31hashAlgoDesc OpenPGP.SHA1 = Vincent.hashDescrSHA1
32hashAlgoDesc OpenPGP.RIPEMD160 = Vincent.hashDescrRIPEMD160
33hashAlgoDesc OpenPGP.SHA256 = Vincent.hashDescrSHA256
34hashAlgoDesc OpenPGP.SHA384 = Vincent.hashDescrSHA384
35hashAlgoDesc OpenPGP.SHA512 = Vincent.hashDescrSHA512
36hashAlgoDesc OpenPGP.SHA224 = Vincent.hashDescrSHA224
37hashAlgoDesc _ =
38 error "Unsupported HashAlgorithm in hashAlgoDesc"
39
40find_key :: OpenPGP.Message -> String -> Maybe OpenPGP.Packet
41find_key = OpenPGP.find_key fingerprint
42
43
44privateDSAkey :: OpenPGP.Packet -> Vincent.DSA.PrivateKey
45privateDSAkey k = Vincent.DSA.PrivateKey
46 (Vincent.DSA.Params (keyParam 'p' k) (keyParam 'g' k) (keyParam 'q' k))
47 (keyParam 'x' k)
48privateRSAkey :: OpenPGP.Packet -> Vincent.RSA.PrivateKey
49privateRSAkey 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
61rsaKey :: OpenPGP.Packet -> Vincent.RSA.PublicKey
62rsaKey k =
63 Vincent.RSA.PublicKey (integerBytesize n) n (keyParam 'e' k)
64 where
65 n = keyParam 'n' k
66
67integerBytesize :: Integer -> Int
68integerBytesize i = fromIntegral $ LZ.length (encode (OpenPGP.MPI i)) - 2
69
70
71toStrictBS :: LZ.ByteString -> BS.ByteString
72toStrictBS = BS.concat . LZ.toChunks
73
74toLazyBS :: BS.ByteString -> LZ.ByteString
75toLazyBS = LZ.fromChunks . (:[])
76
77
78keyParam :: Char -> OpenPGP.Packet -> Integer
79keyParam c k = fromJustMPI $ lookup c (OpenPGP.key k)
80fromJustMPI :: Maybe OpenPGP.MPI -> Integer
81fromJustMPI (Just (OpenPGP.MPI x)) = x
82fromJustMPI _ = error "Not a Just MPI, Data.OpenPGP.CryptoAPI"
83
84hashBySymbol OpenPGP.MD5 = MD5.hashlazy
85hashBySymbol OpenPGP.SHA1 = SHA1.hashlazy
86hashBySymbol OpenPGP.SHA256 = SHA256.hashlazy
87hashBySymbol OpenPGP.SHA384 = SHA384.hashlazy
88hashBySymbol OpenPGP.SHA512 = SHA512.hashlazy
89hashBySymbol OpenPGP.SHA224 = SHA224.hashlazy
90hashBySymbol 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.
100unsafeSign :: (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)
108unsafeSign 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
172now = floor <$> Data.Time.Clock.POSIX.getPOSIXTime
173
174stampit 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.
188pgpSign ::
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)
194pgpSign 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
205catchIO_ :: IO a -> IO a -> IO a
206catchIO_ a h = Exception.catch a (\(_ :: IOException) -> h)
207
208catchIO :: IO a -> (IOException -> IO a) -> IO a
209catchIO body handler = Exception.catch body handler
210
211handleIO_ = flip catchIO_
212handleIO = flip catchIO
213