diff options
Diffstat (limited to 'KeyRing.hs')
-rw-r--r-- | KeyRing.hs | 313 |
1 files changed, 303 insertions, 10 deletions
@@ -2,6 +2,7 @@ | |||
2 | {-# LANGUAGE TupleSections #-} | 2 | {-# LANGUAGE TupleSections #-} |
3 | {-# LANGUAGE ViewPatterns #-} | 3 | {-# LANGUAGE ViewPatterns #-} |
4 | {-# LANGUAGE ExistentialQuantification #-} | 4 | {-# LANGUAGE ExistentialQuantification #-} |
5 | {-# LANGUAGE OverloadedStrings #-} | ||
5 | module KeyRing where | 6 | module KeyRing where |
6 | 7 | ||
7 | import System.Environment | 8 | import System.Environment |
@@ -11,17 +12,32 @@ import Data.Char | |||
11 | import Data.List | 12 | import Data.List |
12 | import Data.OpenPGP | 13 | import Data.OpenPGP |
13 | import Data.Functor | 14 | import Data.Functor |
14 | import Control.Applicative ( (<$>) ) | 15 | import Data.Bits ( (.|.) ) |
16 | -- import Control.Applicative ( (<$>) ) | ||
15 | import System.Directory ( getHomeDirectory, doesFileExist ) | 17 | import System.Directory ( getHomeDirectory, doesFileExist ) |
16 | import Control.Arrow ( first, second ) | 18 | import Control.Arrow ( first, second ) |
17 | import Data.OpenPGP.Util ( fingerprint ) | 19 | import Data.OpenPGP.Util ( fingerprint ) |
18 | import Data.ByteString.Lazy ( ByteString ) | 20 | import Data.ByteString.Lazy ( ByteString ) |
19 | import Text.Show.Pretty as PP ( ppShow ) | 21 | import Text.Show.Pretty as PP ( ppShow ) |
22 | import Data.Word ( Word8 ) | ||
23 | import Data.Binary ( decode ) | ||
24 | import ControlMaybe ( handleIO_ ) | ||
20 | import qualified Data.Map as Map | 25 | import qualified Data.Map as Map |
21 | 26 | import qualified Data.ByteString.Lazy as L ( null, readFile, ByteString ) | |
27 | import qualified Data.ByteString.Lazy.Char8 as Char8 ( span, unpack, break ) | ||
28 | import qualified Crypto.Types.PubKey.ECC as ECC | ||
29 | import System.Posix.Types (EpochTime) | ||
30 | import System.Posix.Files ( modificationTime, getFileStatus ) | ||
31 | |||
32 | import qualified CryptoCoins as CryptoCoins | ||
33 | import Base58 | ||
22 | import FunctorToMaybe | 34 | import FunctorToMaybe |
23 | import DotLock | 35 | import DotLock |
24 | 36 | ||
37 | -- DER-encoded elliptic curve ids | ||
38 | nistp256_id = 0x2a8648ce3d030107 | ||
39 | secp256k1_id = 0x2b8104000a | ||
40 | |||
25 | data HomeDir = | 41 | data HomeDir = |
26 | HomeDir { homevar :: String | 42 | HomeDir { homevar :: String |
27 | , appdir :: String | 43 | , appdir :: String |
@@ -51,6 +67,7 @@ data KeyRingRuntime = KeyRingRuntime | |||
51 | , rtRings :: [FilePath] | 67 | , rtRings :: [FilePath] |
52 | , rtWallets :: [FilePath] | 68 | , rtWallets :: [FilePath] |
53 | , rtGrip :: Maybe String | 69 | , rtGrip :: Maybe String |
70 | , rtKeyDB :: KeyDB | ||
54 | } | 71 | } |
55 | 72 | ||
56 | data KeyRingAction a = KeyRingAction a | RunTimeAction (KeyRingRuntime -> a) | 73 | data KeyRingAction a = KeyRingAction a | RunTimeAction (KeyRingRuntime -> a) |
@@ -61,17 +78,19 @@ data KeyRingData = KeyRingData | |||
61 | , homeSpec :: Maybe String | 78 | , homeSpec :: Maybe String |
62 | } | 79 | } |
63 | 80 | ||
64 | filesToLock k secring pubring = do | 81 | resolveInputFile secring pubring = resolve |
65 | (f,(rtyp,ftyp)) <- Map.toList (kFiles k) | ||
66 | case rtyp of | ||
67 | ConstRef -> [] | ||
68 | MutableRef {} -> resolve f | ||
69 | where | 82 | where |
70 | resolve HomeSec = return secring | 83 | resolve HomeSec = return secring |
71 | resolve HomePub = return pubring | 84 | resolve HomePub = return pubring |
72 | resolve (ArgFile f) = return f | 85 | resolve (ArgFile f) = return f |
73 | resolve _ = [] | 86 | resolve _ = [] |
74 | 87 | ||
88 | filesToLock k secring pubring = do | ||
89 | (f,(rtyp,ftyp)) <- Map.toList (kFiles k) | ||
90 | case rtyp of | ||
91 | ConstRef -> [] | ||
92 | MutableRef {} -> resolveInputFile secring pubring f | ||
93 | |||
75 | 94 | ||
76 | -- kret :: a -> KeyRingData a | 95 | -- kret :: a -> KeyRingData a |
77 | -- kret x = KeyRingData Map.empty Nothing (KeyRingAction x) | 96 | -- kret x = KeyRingData Map.empty Nothing (KeyRingAction x) |
@@ -102,6 +121,188 @@ data KikiResult a = KikiResult | |||
102 | , kikiReport :: [ (FilePath, KikiReportAction) ] | 121 | , kikiReport :: [ (FilePath, KikiReportAction) ] |
103 | } | 122 | } |
104 | 123 | ||
124 | keyPacket (KeyData k _ _ _) = packet k | ||
125 | |||
126 | usage (NotationDataPacket | ||
127 | { human_readable = True | ||
128 | , notation_name = "usage@" | ||
129 | , notation_value = u | ||
130 | }) = Just u | ||
131 | usage _ = Nothing | ||
132 | |||
133 | keyflags flgs@(KeyFlagsPacket {}) = | ||
134 | Just . toEnum $ | ||
135 | ( bit 0x1 certify_keys | ||
136 | .|. bit 0x2 sign_data | ||
137 | .|. bit 0x4 encrypt_communication | ||
138 | .|. bit 0x8 encrypt_storage ) :: Maybe PGPKeyFlags | ||
139 | -- other flags: | ||
140 | -- split_key | ||
141 | -- authentication (ssh-client) | ||
142 | -- group_key | ||
143 | where | ||
144 | bit v f = if f flgs then v else 0 | ||
145 | keyflags _ = Nothing | ||
146 | |||
147 | |||
148 | data PGPKeyFlags = | ||
149 | Special | ||
150 | | Vouch -- Signkey | ||
151 | | Sign | ||
152 | | VouchSign | ||
153 | | Communication | ||
154 | | VouchCommunication | ||
155 | | SignCommunication | ||
156 | | VouchSignCommunication | ||
157 | | Storage | ||
158 | | VouchStorage | ||
159 | | SignStorage | ||
160 | | VouchSignStorage | ||
161 | | Encrypt | ||
162 | | VouchEncrypt | ||
163 | | SignEncrypt | ||
164 | | VouchSignEncrypt | ||
165 | deriving (Eq,Show,Read,Enum) | ||
166 | usageString flgs = | ||
167 | case flgs of | ||
168 | Special -> "special" | ||
169 | Vouch -> "vouch" -- signkey | ||
170 | Sign -> "sign" | ||
171 | VouchSign -> "vouch-sign" | ||
172 | Communication -> "communication" | ||
173 | VouchCommunication -> "vouch-communication" | ||
174 | SignCommunication -> "sign-communication" | ||
175 | VouchSignCommunication -> "vouch-sign-communication" | ||
176 | Storage -> "storage" | ||
177 | VouchStorage -> "vouch-storage" | ||
178 | SignStorage -> "sign-storage" | ||
179 | VouchSignStorage -> "vouch-sign-storage" | ||
180 | Encrypt -> "encrypt" | ||
181 | VouchEncrypt -> "vouch-encrypt" | ||
182 | SignEncrypt -> "sign-encrypt" | ||
183 | VouchSignEncrypt -> "vouch-sign-encrypt" | ||
184 | |||
185 | |||
186 | |||
187 | |||
188 | -- matchpr computes the fingerprint of the given key truncated to | ||
189 | -- be the same lenght as the given fingerprint for comparison. | ||
190 | matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp | ||
191 | |||
192 | keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids) | ||
193 | keyFlags0 wkun uidsigs = concat | ||
194 | [ keyflags | ||
195 | , preferredsym | ||
196 | , preferredhash | ||
197 | , preferredcomp | ||
198 | , features ] | ||
199 | |||
200 | where | ||
201 | subs = concatMap hashed_subpackets uidsigs | ||
202 | keyflags = filterOr isflags subs $ | ||
203 | KeyFlagsPacket { certify_keys = True | ||
204 | , sign_data = True | ||
205 | , encrypt_communication = False | ||
206 | , encrypt_storage = False | ||
207 | , split_key = False | ||
208 | , authentication = False | ||
209 | , group_key = False | ||
210 | } | ||
211 | preferredsym = filterOr ispreferedsym subs $ | ||
212 | PreferredSymmetricAlgorithmsPacket | ||
213 | [ AES256 | ||
214 | , AES192 | ||
215 | , AES128 | ||
216 | , CAST5 | ||
217 | , TripleDES | ||
218 | ] | ||
219 | preferredhash = filterOr ispreferedhash subs $ | ||
220 | PreferredHashAlgorithmsPacket | ||
221 | [ SHA256 | ||
222 | , SHA1 | ||
223 | , SHA384 | ||
224 | , SHA512 | ||
225 | , SHA224 | ||
226 | ] | ||
227 | preferredcomp = filterOr ispreferedcomp subs $ | ||
228 | PreferredCompressionAlgorithmsPacket | ||
229 | [ ZLIB | ||
230 | , BZip2 | ||
231 | , ZIP | ||
232 | ] | ||
233 | features = filterOr isfeatures subs $ | ||
234 | FeaturesPacket { supports_mdc = True | ||
235 | } | ||
236 | |||
237 | filterOr pred xs def = if null rs then [def] else rs where rs=filter pred xs | ||
238 | |||
239 | isflags (KeyFlagsPacket {}) = True | ||
240 | isflags _ = False | ||
241 | ispreferedsym (PreferredSymmetricAlgorithmsPacket {}) = True | ||
242 | ispreferedsym _ = False | ||
243 | ispreferedhash (PreferredHashAlgorithmsPacket {}) = True | ||
244 | ispreferedhash _ = False | ||
245 | ispreferedcomp (PreferredCompressionAlgorithmsPacket {}) = True | ||
246 | ispreferedcomp _ = False | ||
247 | isfeatures (FeaturesPacket {}) = True | ||
248 | isfeatures _ = False | ||
249 | |||
250 | |||
251 | matchSpec (KeyGrip grip) (_,KeyData p _ _ _) | ||
252 | | matchpr grip (packet p)==grip = True | ||
253 | | otherwise = False | ||
254 | |||
255 | matchSpec (KeyTag key tag) (_,KeyData _ sigs _ _) = not . null $ filter match ps | ||
256 | where | ||
257 | ps = map (packet .fst) sigs | ||
258 | match p = isSignaturePacket p | ||
259 | && has_tag tag p | ||
260 | && has_issuer key p | ||
261 | has_issuer key p = isJust $ do | ||
262 | issuer <- signature_issuer p | ||
263 | guard $ matchpr issuer key == issuer | ||
264 | has_tag tag p = tag `elem` mapMaybe usage (hashed_subpackets p) | ||
265 | || tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p)) | ||
266 | |||
267 | matchSpec (KeyUidMatch pat) (_,KeyData _ _ uids _) = not $ null us | ||
268 | where | ||
269 | us = filter (isInfixOf pat) $ Map.keys uids | ||
270 | |||
271 | |||
272 | data KeySpec = | ||
273 | KeyGrip String | ||
274 | | KeyTag Packet String | ||
275 | | KeyUidMatch String | ||
276 | deriving Show | ||
277 | |||
278 | |||
279 | buildKeyDB :: FilePath -> FilePath -> Maybe String -> KeyRingData | ||
280 | -> IO ((KeyDB,String),[(FilePath,KikiReportAction)]) | ||
281 | buildKeyDB secring pubring grip0 keyring = do | ||
282 | let rings = do | ||
283 | (f,(rtyp,ftyp)) <- Map.toList (kFiles keyring) | ||
284 | let isring (KeyRingFile {}) = True | ||
285 | isring _ = False | ||
286 | guard (isring ftyp) | ||
287 | resolveInputFile secring pubring f | ||
288 | readp n = fmap (n,) (readPacketsFromFile n) | ||
289 | readw wk n = fmap (n,) (readPacketsFromWallet wk n) | ||
290 | ms <- mapM readp rings | ||
291 | let grip = grip0 `mplus` (fingerprint <$> fstkey) | ||
292 | where | ||
293 | fstkey = listToMaybe $ mapMaybe isSecringKey ms | ||
294 | where isSecringKey (fn,Message ps) | ||
295 | | fn==secring = listToMaybe ps | ||
296 | isSecringKey _ = Nothing | ||
297 | wk = listToMaybe $ do | ||
298 | fp <- maybeToList grip | ||
299 | elm <- Map.toList db0 | ||
300 | guard $ matchSpec (KeyGrip fp) elm | ||
301 | return $ keyPacket (snd elm) | ||
302 | db0 = foldl' (uncurry . merge) Map.empty ms | ||
303 | db <- return db0 -- todo | ||
304 | return ( (db, todo), todo ) | ||
305 | |||
105 | runKeyRing :: KeyRingData -> (KeyRingRuntime -> a) -> IO (KikiResult a) | 306 | runKeyRing :: KeyRingData -> (KeyRingRuntime -> a) -> IO (KikiResult a) |
106 | runKeyRing keyring op = do | 307 | runKeyRing keyring op = do |
107 | (homedir,secring,pubring,grip0) <- getHomeDir (homeSpec keyring) | 308 | (homedir,secring,pubring,grip0) <- getHomeDir (homeSpec keyring) |
@@ -117,7 +318,7 @@ runKeyRing keyring op = do | |||
117 | ret = if null failed then KikiSuccess () else FailedToLock failed | 318 | ret = if null failed then KikiSuccess () else FailedToLock failed |
118 | ret <- case functorToEither ret of | 319 | ret <- case functorToEither ret of |
119 | Right {} -> do | 320 | Right {} -> do |
120 | report <- todo -- build db | 321 | ((db,grip), report1) <- buildKeyDB secring pubring grip0 keyring -- build db |
121 | 322 | ||
122 | a <- return $ op KeyRingRuntime | 323 | a <- return $ op KeyRingRuntime |
123 | { rtPubring = pubring | 324 | { rtPubring = pubring |
@@ -125,10 +326,11 @@ runKeyRing keyring op = do | |||
125 | , rtRings = [] -- todo secring:pubring:keyringFiles keyring | 326 | , rtRings = [] -- todo secring:pubring:keyringFiles keyring |
126 | , rtWallets = [] -- todo walletFiles keyring | 327 | , rtWallets = [] -- todo walletFiles keyring |
127 | , rtGrip = grip0 | 328 | , rtGrip = grip0 |
329 | , rtKeyDB = db | ||
128 | } | 330 | } |
129 | report <- todo report -- write files | 331 | report2 <- todo -- write files |
130 | 332 | ||
131 | return $ KikiResult (KikiSuccess a) report | 333 | return $ KikiResult (KikiSuccess a) (report1 ++ report2) |
132 | Left err -> return $ KikiResult err [] | 334 | Left err -> return $ KikiResult err [] |
133 | 335 | ||
134 | forM_ lked $ \(Just lk, fname) -> do dotlock_release lk | 336 | forM_ lked $ \(Just lk, fname) -> do dotlock_release lk |
@@ -196,6 +398,97 @@ isUserID _ = False | |||
196 | isTrust (TrustPacket {}) = True | 398 | isTrust (TrustPacket {}) = True |
197 | isTrust _ = False | 399 | isTrust _ = False |
198 | 400 | ||
401 | slurpWIPKeys :: System.Posix.Types.EpochTime -> L.ByteString -> ( [(Word8,Packet)], [L.ByteString]) | ||
402 | slurpWIPKeys stamp "" = ([],[]) | ||
403 | slurpWIPKeys stamp cs = | ||
404 | let (b58,xs) = Char8.span (\x -> elem x base58chars) cs | ||
405 | mb = decode_btc_key stamp (Char8.unpack b58) | ||
406 | in if L.null b58 | ||
407 | then let (ys,xs') = Char8.break (\x -> elem x base58chars) cs | ||
408 | (ks,js) = slurpWIPKeys stamp xs' | ||
409 | in (ks,ys:js) | ||
410 | else let (ks,js) = slurpWIPKeys stamp xs | ||
411 | in maybe (ks,b58:js) (\(net,Message [k])->((net,k):ks,js)) mb | ||
412 | |||
413 | |||
414 | decode_btc_key timestamp str = do | ||
415 | (network_id,us) <- base58_decode str | ||
416 | return . (network_id,) $ Message $ do | ||
417 | let d = foldl' (\a b->a*256+b) 0 (map fromIntegral us :: [Integer]) | ||
418 | {- | ||
419 | xy = secp256k1_G `pmul` d | ||
420 | x = getx xy | ||
421 | y = gety xy | ||
422 | -- y² = x³ + 7 (mod p) | ||
423 | y' = sqrtModP' (applyCurve secp256k1_curve x) (getp secp256k1_curve) | ||
424 | y'' = sqrtModPList (applyCurve secp256k1_curve x) (getp secp256k1_curve) | ||
425 | -} | ||
426 | secp256k1 = ECC.getCurveByName ECC.SEC_p256k1 | ||
427 | ECC.Point x y = ECC.ecc_g $ ECC.common_curve secp256k1 | ||
428 | -- pub = cannonical_eckey x y | ||
429 | -- hash = S.cons network_id . RIPEMD160.hash . SHA256.hash . S.pack $ pub | ||
430 | -- address = base58_encode hash | ||
431 | -- pubstr = concatMap (printf "%02x") $ pub | ||
432 | -- _ = pubstr :: String | ||
433 | return $ {- trace (unlines ["pub="++show pubstr | ||
434 | ,"add="++show address | ||
435 | ,"y ="++show y | ||
436 | ,"y' ="++show y' | ||
437 | ,"y''="++show y'']) -} | ||
438 | SecretKeyPacket | ||
439 | { version = 4 | ||
440 | , timestamp = toEnum (fromEnum timestamp) | ||
441 | , key_algorithm = ECDSA | ||
442 | , key = [ -- public fields... | ||
443 | ('c',MPI secp256k1_id) -- secp256k1 (bitcoin curve) | ||
444 | ,('l',MPI 256) | ||
445 | ,('x',MPI x) | ||
446 | ,('y',MPI y) | ||
447 | -- secret fields | ||
448 | ,('d',MPI d) | ||
449 | ] | ||
450 | , s2k_useage = 0 | ||
451 | , s2k = S2K 100 "" | ||
452 | , symmetric_algorithm = Unencrypted | ||
453 | , encrypted_data = "" | ||
454 | , is_subkey = True | ||
455 | } | ||
456 | |||
457 | readPacketsFromWallet :: | ||
458 | Maybe Packet | ||
459 | -> FilePath | ||
460 | -> IO [(Packet,Packet,(Packet,Map.Map FilePath Packet))] | ||
461 | readPacketsFromWallet wk fname = do | ||
462 | timestamp <- handleIO_ (error $ fname++": modificaiton time?") $ | ||
463 | modificationTime <$> getFileStatus fname | ||
464 | input <- L.readFile fname | ||
465 | let (ks,_) = slurpWIPKeys timestamp input | ||
466 | when (not (null ks)) $ do | ||
467 | -- decrypt wk | ||
468 | -- create sigs | ||
469 | -- return key/sig pairs | ||
470 | return () | ||
471 | return $ do | ||
472 | wk <- maybeToList wk | ||
473 | guard (not $ null ks) | ||
474 | let prep (tagbyte,k) = (wk,k,(k,Map.singleton tag wk)) | ||
475 | where tag = CryptoCoins.nameFromSecretByte tagbyte | ||
476 | (wk,MarkerPacket,(MarkerPacket,Map.empty)) | ||
477 | :map prep ks | ||
478 | |||
479 | readPacketsFromFile :: FilePath -> IO Message | ||
480 | readPacketsFromFile fname = do | ||
481 | -- warn $ fname ++ ": reading..." | ||
482 | input <- L.readFile fname | ||
483 | #if MIN_VERSION_binary(0,6,4) | ||
484 | return $ | ||
485 | case decodeOrFail input of | ||
486 | Right (_,_,msg ) -> msg | ||
487 | Left (_,_,_) -> trace (fname++": read fail") $ Message [] | ||
488 | #else | ||
489 | return $ decode input | ||
490 | #endif | ||
491 | |||
199 | 492 | ||
200 | data OriginFlags = OriginFlags { | 493 | data OriginFlags = OriginFlags { |
201 | originallyPublic :: Bool, | 494 | originallyPublic :: Bool, |