summaryrefslogtreecommitdiff
path: root/KeyRing.hs
diff options
context:
space:
mode:
Diffstat (limited to 'KeyRing.hs')
-rw-r--r--KeyRing.hs313
1 files changed, 303 insertions, 10 deletions
diff --git a/KeyRing.hs b/KeyRing.hs
index cdfcd34..2a80930 100644
--- a/KeyRing.hs
+++ b/KeyRing.hs
@@ -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 #-}
5module KeyRing where 6module KeyRing where
6 7
7import System.Environment 8import System.Environment
@@ -11,17 +12,32 @@ import Data.Char
11import Data.List 12import Data.List
12import Data.OpenPGP 13import Data.OpenPGP
13import Data.Functor 14import Data.Functor
14import Control.Applicative ( (<$>) ) 15import Data.Bits ( (.|.) )
16-- import Control.Applicative ( (<$>) )
15import System.Directory ( getHomeDirectory, doesFileExist ) 17import System.Directory ( getHomeDirectory, doesFileExist )
16import Control.Arrow ( first, second ) 18import Control.Arrow ( first, second )
17import Data.OpenPGP.Util ( fingerprint ) 19import Data.OpenPGP.Util ( fingerprint )
18import Data.ByteString.Lazy ( ByteString ) 20import Data.ByteString.Lazy ( ByteString )
19import Text.Show.Pretty as PP ( ppShow ) 21import Text.Show.Pretty as PP ( ppShow )
22import Data.Word ( Word8 )
23import Data.Binary ( decode )
24import ControlMaybe ( handleIO_ )
20import qualified Data.Map as Map 25import qualified Data.Map as Map
21 26import qualified Data.ByteString.Lazy as L ( null, readFile, ByteString )
27import qualified Data.ByteString.Lazy.Char8 as Char8 ( span, unpack, break )
28import qualified Crypto.Types.PubKey.ECC as ECC
29import System.Posix.Types (EpochTime)
30import System.Posix.Files ( modificationTime, getFileStatus )
31
32import qualified CryptoCoins as CryptoCoins
33import Base58
22import FunctorToMaybe 34import FunctorToMaybe
23import DotLock 35import DotLock
24 36
37-- DER-encoded elliptic curve ids
38nistp256_id = 0x2a8648ce3d030107
39secp256k1_id = 0x2b8104000a
40
25data HomeDir = 41data 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
56data KeyRingAction a = KeyRingAction a | RunTimeAction (KeyRingRuntime -> a) 73data 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
64filesToLock k secring pubring = do 81resolveInputFile 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
88filesToLock 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
124keyPacket (KeyData k _ _ _) = packet k
125
126usage (NotationDataPacket
127 { human_readable = True
128 , notation_name = "usage@"
129 , notation_value = u
130 }) = Just u
131usage _ = Nothing
132
133keyflags 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
145keyflags _ = Nothing
146
147
148data 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)
166usageString 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.
190matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp
191
192keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids)
193keyFlags0 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
251matchSpec (KeyGrip grip) (_,KeyData p _ _ _)
252 | matchpr grip (packet p)==grip = True
253 | otherwise = False
254
255matchSpec (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
267matchSpec (KeyUidMatch pat) (_,KeyData _ _ uids _) = not $ null us
268 where
269 us = filter (isInfixOf pat) $ Map.keys uids
270
271
272data KeySpec =
273 KeyGrip String
274 | KeyTag Packet String
275 | KeyUidMatch String
276 deriving Show
277
278
279buildKeyDB :: FilePath -> FilePath -> Maybe String -> KeyRingData
280 -> IO ((KeyDB,String),[(FilePath,KikiReportAction)])
281buildKeyDB 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
105runKeyRing :: KeyRingData -> (KeyRingRuntime -> a) -> IO (KikiResult a) 306runKeyRing :: KeyRingData -> (KeyRingRuntime -> a) -> IO (KikiResult a)
106runKeyRing keyring op = do 307runKeyRing 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
196isTrust (TrustPacket {}) = True 398isTrust (TrustPacket {}) = True
197isTrust _ = False 399isTrust _ = False
198 400
401slurpWIPKeys :: System.Posix.Types.EpochTime -> L.ByteString -> ( [(Word8,Packet)], [L.ByteString])
402slurpWIPKeys stamp "" = ([],[])
403slurpWIPKeys 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
414decode_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
457readPacketsFromWallet ::
458 Maybe Packet
459 -> FilePath
460 -> IO [(Packet,Packet,(Packet,Map.Map FilePath Packet))]
461readPacketsFromWallet 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
479readPacketsFromFile :: FilePath -> IO Message
480readPacketsFromFile 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
200data OriginFlags = OriginFlags { 493data OriginFlags = OriginFlags {
201 originallyPublic :: Bool, 494 originallyPublic :: Bool,