diff options
Diffstat (limited to 'kiki.hs')
-rw-r--r-- | kiki.hs | 1195 |
1 files changed, 1195 insertions, 0 deletions
@@ -0,0 +1,1195 @@ | |||
1 | {-# LANGUAGE ViewPatterns #-} | ||
2 | {-# LANGUAGE TupleSections #-} | ||
3 | {-# LANGUAGE OverloadedStrings #-} | ||
4 | {-# LANGUAGE RankNTypes #-} | ||
5 | {-# LANGUAGE FlexibleInstances #-} | ||
6 | {-# LANGUAGE DeriveDataTypeable #-} | ||
7 | {-# LANGUAGE CPP #-} | ||
8 | module Main where | ||
9 | |||
10 | import Debug.Trace | ||
11 | import Data.Binary | ||
12 | import Data.OpenPGP | ||
13 | import qualified Data.ByteString.Lazy as L | ||
14 | import qualified Data.ByteString.Lazy.Char8 as Char8 | ||
15 | import qualified Data.ByteString as S | ||
16 | import qualified Data.ByteString.Char8 as S8 | ||
17 | import Control.Monad | ||
18 | import qualified Text.Show.Pretty as PP | ||
19 | import Text.PrettyPrint as PP hiding ((<>)) | ||
20 | import Data.List | ||
21 | import Data.OpenPGP.CryptoAPI | ||
22 | import Data.Ord | ||
23 | import Data.Maybe | ||
24 | import Data.Bits | ||
25 | import qualified Data.Text as T | ||
26 | import Data.Text.Encoding | ||
27 | import qualified Codec.Binary.Base32 as Base32 | ||
28 | import qualified Codec.Binary.Base64 as Base64 | ||
29 | import qualified Crypto.Hash.SHA1 as SHA1 | ||
30 | import Data.Char (toLower) | ||
31 | import qualified Crypto.PubKey.RSA as RSA | ||
32 | import Crypto.Random (newGenIO,SystemRandom) | ||
33 | import Data.ASN1.Types | ||
34 | import Data.ASN1.Encoding | ||
35 | import Data.ASN1.BinaryEncoding | ||
36 | import Control.Applicative | ||
37 | import System.Environment | ||
38 | import System.Directory | ||
39 | import System.Exit | ||
40 | import ControlMaybe | ||
41 | import Data.Char | ||
42 | import Control.Arrow (second) | ||
43 | import Data.Traversable | ||
44 | import System.Console.CmdArgs | ||
45 | -- import System.Posix.Time | ||
46 | import Data.Time.Clock.POSIX | ||
47 | import System.Posix.IO (fdToHandle,fdRead) | ||
48 | import System.Posix.Files | ||
49 | import Data.Monoid ((<>)) | ||
50 | -- import Data.X509 | ||
51 | |||
52 | data RSAPublicKey = RSAKey MPI MPI deriving Show | ||
53 | |||
54 | instance ASN1Object RSAPublicKey where | ||
55 | toASN1 (RSAKey (MPI n) (MPI e)) | ||
56 | = \xs -> Start Sequence | ||
57 | : IntVal n | ||
58 | : IntVal e | ||
59 | : End Sequence | ||
60 | : xs | ||
61 | fromASN1 (Start Sequence:IntVal modulus:IntVal pubexp:End Sequence:xs) = | ||
62 | Right (RSAKey (MPI modulus) (MPI pubexp) , xs) | ||
63 | fromASN1 _ = | ||
64 | Left "fromASN1: RSAPublicKey: unexpected format" | ||
65 | |||
66 | data RSAPrivateKey = RSAPrivateKey | ||
67 | { rsaN :: MPI | ||
68 | , rsaE :: MPI | ||
69 | , rsaD :: MPI | ||
70 | , rsaP :: MPI | ||
71 | , rsaQ :: MPI | ||
72 | , rsaDmodP1 :: MPI | ||
73 | , rsaDmodQminus1 :: MPI | ||
74 | , rsaCoefficient :: MPI | ||
75 | } | ||
76 | deriving Show | ||
77 | |||
78 | {- | ||
79 | RSAPrivateKey ::= SEQUENCE { | ||
80 | version Version, | ||
81 | modulus INTEGER, -- n | ||
82 | publicExponent INTEGER, -- e | ||
83 | privateExponent INTEGER, -- d | ||
84 | prime1 INTEGER, -- p | ||
85 | prime2 INTEGER, -- q | ||
86 | exponent1 INTEGER, -- d mod (p1) | ||
87 | exponent2 INTEGER, -- d mod (q-1) | ||
88 | coefficient INTEGER, -- (inverse of q) mod p | ||
89 | otherPrimeInfos OtherPrimeInfos OPTIONAL | ||
90 | } | ||
91 | -} | ||
92 | |||
93 | instance ASN1Object RSAPrivateKey where | ||
94 | toASN1 rsa@(RSAPrivateKey {}) | ||
95 | = \xs -> Start Sequence | ||
96 | : IntVal 0 | ||
97 | : mpiVal rsaN | ||
98 | : mpiVal rsaE | ||
99 | : mpiVal rsaD | ||
100 | : mpiVal rsaP | ||
101 | : mpiVal rsaQ | ||
102 | : mpiVal rsaDmodP1 | ||
103 | : mpiVal rsaDmodQminus1 | ||
104 | : mpiVal rsaCoefficient | ||
105 | : End Sequence | ||
106 | : xs | ||
107 | where mpiVal f = IntVal x where MPI x = f rsa | ||
108 | |||
109 | fromASN1 ( Start Sequence | ||
110 | : IntVal _ -- version | ||
111 | : IntVal n | ||
112 | : IntVal e | ||
113 | : IntVal d | ||
114 | : IntVal p | ||
115 | : IntVal q | ||
116 | : IntVal dmodp1 | ||
117 | : IntVal dmodqminus1 | ||
118 | : IntVal coefficient | ||
119 | : ys) = | ||
120 | Right ( privkey, tail $ dropWhile notend ys) | ||
121 | where | ||
122 | notend (End Sequence) = False | ||
123 | notend _ = True | ||
124 | privkey = RSAPrivateKey | ||
125 | { rsaN = MPI n | ||
126 | , rsaE = MPI e | ||
127 | , rsaD = MPI d | ||
128 | , rsaP = MPI p | ||
129 | , rsaQ = MPI q | ||
130 | , rsaDmodP1 = MPI dmodp1 | ||
131 | , rsaDmodQminus1 = MPI dmodqminus1 | ||
132 | , rsaCoefficient = MPI coefficient | ||
133 | } | ||
134 | fromASN1 _ = | ||
135 | Left "fromASN1: RSAPrivateKey: unexpected format" | ||
136 | |||
137 | rsaKeyFromPacket p@(PublicKeyPacket {}) = do | ||
138 | n <- lookup 'n' $ key p | ||
139 | e <- lookup 'e' $ key p | ||
140 | return $ RSAKey n e | ||
141 | rsaKeyFromPacket p@(SecretKeyPacket {}) = do | ||
142 | n <- lookup 'n' $ key p | ||
143 | e <- lookup 'e' $ key p | ||
144 | return $ RSAKey n e | ||
145 | rsaKeyFromPacket _ = Nothing | ||
146 | derRSA rsa = do | ||
147 | k <- rsaKeyFromPacket rsa | ||
148 | return $ encodeASN1 DER (toASN1 k []) | ||
149 | |||
150 | getPackets :: IO [Packet] | ||
151 | getPackets = do | ||
152 | input <- L.getContents | ||
153 | case decodeOrFail input of | ||
154 | Right (_,_,Message pkts) -> return pkts | ||
155 | Left (_,_,_) -> return [] | ||
156 | |||
157 | |||
158 | secretToPublic pkt@(SecretKeyPacket {}) = | ||
159 | PublicKeyPacket { version = version pkt | ||
160 | , timestamp = timestamp pkt | ||
161 | , key_algorithm = key_algorithm pkt | ||
162 | , key = let seckey = key pkt | ||
163 | pubs = public_key_fields (key_algorithm pkt) | ||
164 | in filter (\(k,v) -> k `elem` pubs) seckey | ||
165 | , is_subkey = is_subkey pkt | ||
166 | , v3_days_of_validity = Nothing | ||
167 | } | ||
168 | secretToPublic pkt = pkt | ||
169 | |||
170 | |||
171 | extractPEM typ pem = dta | ||
172 | where | ||
173 | dta = case ys of | ||
174 | _:dta_lines -> Char8.concat dta_lines | ||
175 | [] -> "" | ||
176 | xs = dropWhile (/="-----BEGIN " <> typ <> "-----") (Char8.lines pem) | ||
177 | ys = takeWhile (/="-----END " <> typ <> "-----") xs | ||
178 | |||
179 | isKey (PublicKeyPacket {}) = True | ||
180 | isKey (SecretKeyPacket {}) = True | ||
181 | isKey _ = False | ||
182 | |||
183 | isUserID (UserIDPacket {}) = True | ||
184 | isUserID _ = False | ||
185 | |||
186 | isEmbeddedSignature (EmbeddedSignaturePacket {}) = True | ||
187 | isEmbeddedSignature _ = False | ||
188 | |||
189 | isCertificationSig (CertificationSignature {}) = True | ||
190 | isCertificationSig _ = True | ||
191 | |||
192 | issuer (IssuerPacket issuer) = Just issuer | ||
193 | issuer _ = Nothing | ||
194 | backsig (EmbeddedSignaturePacket s) = Just s | ||
195 | backsig _ = Nothing | ||
196 | |||
197 | isSubkeySignature (SubkeySignature {}) = True | ||
198 | isSubkeySignature _ = False | ||
199 | |||
200 | isMasterKey k@(PublicKeyPacket {}) = not $ is_subkey k | ||
201 | isMasterKey _ = False | ||
202 | |||
203 | now = floor <$> Data.Time.Clock.POSIX.getPOSIXTime | ||
204 | |||
205 | usage (NotationDataPacket | ||
206 | { human_readable = True | ||
207 | , notation_name = "usage@" | ||
208 | , notation_value = u | ||
209 | }) = Just u | ||
210 | usage _ = Nothing | ||
211 | |||
212 | verifyBindings keys nonkeys = (top ++ filter isSubkeySignature embedded,othersigs) | ||
213 | where | ||
214 | verified = do | ||
215 | sig <- signatures (Message nonkeys) | ||
216 | let v = verify (Message keys) sig | ||
217 | guard (not . null $ signatures_over v) | ||
218 | return v | ||
219 | (top,othersigs) = partition isSubkeySignature verified | ||
220 | embedded = do | ||
221 | sub <- top | ||
222 | let sigover = signatures_over sub | ||
223 | unhashed = sigover >>= unhashed_subpackets | ||
224 | subsigs = mapMaybe backsig unhashed | ||
225 | sig <- signatures (Message ([topkey sub,subkey sub]++subsigs)) | ||
226 | let v = verify (Message [subkey sub]) sig | ||
227 | guard (not . null $ signatures_over v) | ||
228 | return v | ||
229 | |||
230 | grip k = drop 32 $ fingerprint k | ||
231 | |||
232 | smallpr k = drop 24 $ fingerprint k | ||
233 | |||
234 | -- matchpr computes the fingerprint of the given key truncated to | ||
235 | -- be the same lenght as the given fingerprint for comparison. | ||
236 | matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp | ||
237 | |||
238 | |||
239 | disjoint_fp ks = {- concatMap group2 $ -} transpose grouped | ||
240 | where | ||
241 | grouped = groupBy samepr . sortBy (comparing smallpr) $ ks | ||
242 | samepr a b = smallpr a == smallpr b | ||
243 | |||
244 | {- | ||
245 | -- useful for testing | ||
246 | group2 :: [a] -> [[a]] | ||
247 | group2 (x:y:ys) = [x,y]:group2 ys | ||
248 | group2 [x] = [[x]] | ||
249 | group2 [] = [] | ||
250 | -} | ||
251 | |||
252 | verifyBindingsEx :: [Packet] -> ([SignatureOver], [SignatureOver]) -- ^ (binding signatures, other signatures) | ||
253 | verifyBindingsEx pkts = bicat . unzip $ do | ||
254 | let (keys,_) = partition isKey pkts | ||
255 | keys <- disjoint_fp keys | ||
256 | return $ verifyBindings keys pkts | ||
257 | where | ||
258 | bicat (xs,ys) = (concat xs,concat ys) | ||
259 | |||
260 | getBindings :: | ||
261 | [Packet] | ||
262 | -> | ||
263 | ( [([Packet],[SignatureOver])] -- ^ other signatures with key sets | ||
264 | -- that were used for the verifications | ||
265 | , [(Word8, | ||
266 | (Packet, Packet), -- (topkey,subkey) | ||
267 | [String], -- usage flags | ||
268 | [SignatureSubpacket], -- hashed data | ||
269 | [Packet])] -- ^ binding signatures | ||
270 | ) | ||
271 | getBindings pkts = (sigs,bindings) | ||
272 | where | ||
273 | (sigs,concat->bindings) = unzip $ do | ||
274 | let (keys,nonkeys) = partition isKey pkts | ||
275 | keys <- disjoint_fp keys | ||
276 | let (bs,sigs) = verifyBindings keys pkts | ||
277 | return . ((keys,sigs),) $ do | ||
278 | b <- bs -- trace ("sigs = "++show (map (map signature_issuer . signatures_over) sigs)) bs | ||
279 | i <- map signature_issuer (signatures_over b) | ||
280 | i <- maybeToList i | ||
281 | who <- maybeToList $ find_key fingerprint (Message keys) i | ||
282 | let (code,claimants) = | ||
283 | case () of | ||
284 | _ | who == topkey b -> (1,[]) | ||
285 | _ | who == subkey b -> (2,[]) | ||
286 | _ -> (0,[who]) | ||
287 | let hashed = signatures_over b >>= hashed_subpackets | ||
288 | kind = guard (code==1) >> hashed >>= maybeToList . usage | ||
289 | return (code,(topkey b,subkey b), kind, hashed,claimants) | ||
290 | |||
291 | -- Returned data is simmilar to getBindings but the Word8 codes | ||
292 | -- are ORed together. | ||
293 | accBindings :: | ||
294 | Bits t => | ||
295 | [(t, (Packet, Packet), [a], [a1], [a2])] | ||
296 | -> [(t, (Packet, Packet), [a], [a1], [a2])] | ||
297 | accBindings bs = as | ||
298 | where | ||
299 | gs = groupBy samePair . sortBy (comparing bindingPair) $ bs | ||
300 | as = map (foldl1 combine) gs | ||
301 | bindingPair (_,p,_,_,_) = pub2 p | ||
302 | where | ||
303 | pub2 (a,b) = (pub a, pub b) | ||
304 | pub a = fingerprint_material a | ||
305 | samePair a b = bindingPair a == bindingPair b | ||
306 | combine (ac,p,akind,ahashed,aclaimaints) | ||
307 | (bc,_,bkind,bhashed,bclaimaints) | ||
308 | = (ac .|. bc,p,akind++bkind,ahashed++bhashed,aclaimaints++bclaimaints) | ||
309 | |||
310 | |||
311 | data UserIDRecord = UserIDRecord { | ||
312 | uid_full :: String, | ||
313 | uid_realname :: T.Text, | ||
314 | uid_user :: T.Text, | ||
315 | uid_subdomain :: T.Text, | ||
316 | uid_topdomain :: T.Text | ||
317 | } | ||
318 | deriving Show | ||
319 | |||
320 | isBracket '<' = True | ||
321 | isBracket '>' = True | ||
322 | isBracket _ = False | ||
323 | |||
324 | parseUID str = UserIDRecord { | ||
325 | uid_full = str, | ||
326 | uid_realname = realname, | ||
327 | uid_user = user, | ||
328 | uid_subdomain = subdomain, | ||
329 | uid_topdomain = topdomain | ||
330 | } | ||
331 | where | ||
332 | text = T.pack str | ||
333 | (T.strip-> realname, T.dropAround isBracket-> email) | ||
334 | = T.break (=='<') text | ||
335 | (user, T.tail-> hostname) = T.break (=='@') email | ||
336 | ( T.reverse -> topdomain, | ||
337 | T.reverse . T.drop 1 -> subdomain) | ||
338 | = T.break (=='.') . T.reverse $ hostname | ||
339 | |||
340 | |||
341 | derToBase32 = map toLower . Base32.encode . S.unpack . SHA1.hashlazy | ||
342 | |||
343 | fpmatch grip key = | ||
344 | (==) Nothing | ||
345 | (fmap (backend (fingerprint key)) grip >>= guard . not) | ||
346 | where | ||
347 | backend xs ys = and $ zipWith (==) (reverse xs) (reverse ys) | ||
348 | |||
349 | listKeys pkts = listKeysFiltered [] pkts | ||
350 | |||
351 | listKeysFiltered grips pkts = do | ||
352 | let (certs,bs) = getBindings pkts | ||
353 | as = accBindings bs | ||
354 | defaultkind (k:_) hs = k | ||
355 | defaultkind [] hs = maybe "subkey" | ||
356 | id | ||
357 | ( listToMaybe | ||
358 | . mapMaybe (fmap usageString . keyflags) | ||
359 | $ hs) | ||
360 | kinds = map (\(_,_,k,h,_)->defaultkind k h) as | ||
361 | kindwidth = maximum $ map length kinds | ||
362 | kindcol = min 20 kindwidth | ||
363 | code (c,(m,s),_,_,_) = (fingerprint_material m,-c) | ||
364 | ownerkey (_,(a,_),_,_,_) = a | ||
365 | sameMaster (ownerkey->a) (ownerkey->b) = fingerprint_material a==fingerprint_material b | ||
366 | matchgrip _ | null grips = True | ||
367 | matchgrip ((code,(top,sub), kind, hashed,claimants):_) | any (flip fpmatch top . Just) grips = True | ||
368 | matchgrip _ = False | ||
369 | gs = filter matchgrip $ groupBy sameMaster (sortBy (comparing code) as) | ||
370 | subs <- gs | ||
371 | let (code,(top,sub), kind, hashed,claimants):_ = subs | ||
372 | subkeys = do | ||
373 | (code,(top,sub), kind, hashed,claimants) <- subs | ||
374 | let ar = case code of | ||
375 | 0 -> " ??? " | ||
376 | 1 -> " --> " | ||
377 | 2 -> " <-- " | ||
378 | 3 -> " <-> " | ||
379 | formkind = take kindcol $ defaultkind kind hashed ++ repeat ' ' | ||
380 | -- torhash = maybe "" id $ derToBase32 <$> derRSA sub | ||
381 | concat [ " " | ||
382 | -- , grip top | ||
383 | , (if not (null claimants) | ||
384 | then trace ("claimants: "++show (map fingerprint claimants)) | ||
385 | else id) ar | ||
386 | , formkind | ||
387 | , " " | ||
388 | , fingerprint sub | ||
389 | -- , " " ++ torhash | ||
390 | , "\n" ] | ||
391 | -- ++ ppShow hashed | ||
392 | torkeys = do | ||
393 | (code,(top,sub), kind, hashed,claimants) <- subs | ||
394 | guard ("tor" `elem` kind) | ||
395 | guard (code .&. 0x2 /= 0) | ||
396 | maybeToList $ derToBase32 <$> derRSA sub | ||
397 | uid = {- maybe "" id . listToMaybe $ -} do | ||
398 | (keys,sigs) <- certs | ||
399 | sig <- sigs | ||
400 | guard (isCertificationSig sig) | ||
401 | guard (topkey sig == top) | ||
402 | let issuers = do | ||
403 | sig_over <- signatures_over sig | ||
404 | i <- maybeToList $ signature_issuer sig_over | ||
405 | maybeToList $ find_key (matchpr i) (Message keys) (reverse (take 16 (reverse i))) | ||
406 | (primary,secondary) = partition (==top) issuers | ||
407 | |||
408 | -- trace ("PRIMARY: "++show (map fingerprint primary)) $ return () | ||
409 | -- trace ("SECONDARY: "++show (map fingerprint secondary)) $ return () | ||
410 | guard (not (null primary)) | ||
411 | |||
412 | let UserIDPacket uid = user_id sig | ||
413 | parsed = parseUID uid | ||
414 | ar = maybe " --> " (const " <-> ") $ do | ||
415 | guard (uid_topdomain parsed == "onion" ) | ||
416 | guard ( uid_realname parsed `elem` ["","Anonymous"]) | ||
417 | guard ( uid_user parsed == "root" ) | ||
418 | let subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)] | ||
419 | len = L.length subdom0 | ||
420 | subdom = Char8.unpack subdom0 | ||
421 | match = ( (==subdom) . take (fromIntegral len)) | ||
422 | guard (len >= 16) | ||
423 | listToMaybe $ filter match torkeys | ||
424 | unlines $ [ " " ++ ar ++ "@" ++ " " ++ uid_full parsed ] | ||
425 | ++ map (\k -> " " ++ "^ signed: " ++ fingerprint k) secondary | ||
426 | (_,sigs) = unzip certs | ||
427 | "master-key " ++ fingerprint top ++ "\n" ++ uid ++" ...\n" ++ subkeys ++ "\n" | ||
428 | |||
429 | |||
430 | data PGPKeyFlags = | ||
431 | Special | ||
432 | | Vouch -- Signkey | ||
433 | | Sign | ||
434 | | VouchSign | ||
435 | | Communication | ||
436 | | VouchCommunication | ||
437 | | SignCommunication | ||
438 | | VouchSignCommunication | ||
439 | | Storage | ||
440 | | VouchStorage | ||
441 | | SignStorage | ||
442 | | VouchSignStorage | ||
443 | | Encrypt | ||
444 | | VouchEncrypt | ||
445 | | SignEncrypt | ||
446 | | VouchSignEncrypt | ||
447 | deriving (Eq,Show,Read,Enum) | ||
448 | |||
449 | usageString flgs = | ||
450 | case flgs of | ||
451 | Special -> "special" | ||
452 | Vouch -> "vouch" -- signkey | ||
453 | Sign -> "sign" | ||
454 | VouchSign -> "vouch-sign" | ||
455 | Communication -> "communication" | ||
456 | VouchCommunication -> "vouch-communication" | ||
457 | SignCommunication -> "sign-communication" | ||
458 | VouchSignCommunication -> "vouch-sign-communication" | ||
459 | Storage -> "storage" | ||
460 | VouchStorage -> "vouch-storage" | ||
461 | SignStorage -> "sign-storage" | ||
462 | VouchSignStorage -> "vouch-sign-storage" | ||
463 | Encrypt -> "encrypt" | ||
464 | VouchEncrypt -> "vouch-encrypt" | ||
465 | SignEncrypt -> "sign-encrypt" | ||
466 | VouchSignEncrypt -> "vouch-sign-encrypt" | ||
467 | |||
468 | |||
469 | keyflags flgs@(KeyFlagsPacket {}) = | ||
470 | Just . toEnum $ | ||
471 | ( bit 0x1 certify_keys | ||
472 | .|. bit 0x2 sign_data | ||
473 | .|. bit 0x4 encrypt_communication | ||
474 | .|. bit 0x8 encrypt_storage ) :: Maybe PGPKeyFlags | ||
475 | -- other flags: | ||
476 | -- split_key | ||
477 | -- authentication (ssh-client) | ||
478 | -- group_key | ||
479 | where | ||
480 | bit v f = if f flgs then v else 0 | ||
481 | keyflags _ = Nothing | ||
482 | |||
483 | |||
484 | modifyUID (UserIDPacket str) = UserIDPacket str' | ||
485 | where | ||
486 | (fstname,rst) = break (==' ') str | ||
487 | str' = mod fstname ++ rst | ||
488 | mod "Bob" = "Bob Fucking" | ||
489 | mod x = x | ||
490 | modifyUID other = other | ||
491 | |||
492 | todo = error "unimplemented" | ||
493 | |||
494 | -- TODO: switch to System.Environment.lookupEnv | ||
495 | -- when linking against newer base libraries. | ||
496 | lookupEnv var = | ||
497 | handleIO_ (return Nothing) $ fmap Just (getEnv var) | ||
498 | |||
499 | unmaybe def = fmap (maybe def id) | ||
500 | |||
501 | expandPath path (c:cs) | c/='/' = path ++ "/" ++ (c:cs) | ||
502 | | otherwise = c:cs | ||
503 | expandPath path [] = [] | ||
504 | |||
505 | |||
506 | readPacketsFromFile :: FilePath -> IO Message | ||
507 | readPacketsFromFile fname = do | ||
508 | input <- L.readFile fname | ||
509 | return $ | ||
510 | case decodeOrFail input of | ||
511 | Right (_,_,msg ) -> msg | ||
512 | Left (_,_,_) -> Message [] | ||
513 | |||
514 | |||
515 | parseOptionFile fname = do | ||
516 | xs <- fmap lines (readFile fname) | ||
517 | let ys = filter notComment xs | ||
518 | notComment ('#':_) = False | ||
519 | notComment cs = not (all isSpace cs) | ||
520 | return ys | ||
521 | |||
522 | {- | ||
523 | options_from_file :: | ||
524 | (forall a. [String] -> Term a -> IO (Either EvalExit a)) | ||
525 | -> Term b | ||
526 | -> (String,String,Term (Maybe String)) | ||
527 | -> ([String],Term (Maybe String)) | ||
528 | -> IO [String] | ||
529 | options_from_file unwrapCmd term (homevar,appdir,home) (optfile_alts,options_file) = doit | ||
530 | where | ||
531 | homedir = envhomedir <$> home | ||
532 | envhomedir opt = do | ||
533 | gnupghome <- lookupEnv homevar >>= | ||
534 | \d -> return $ d >>= guard . (/="") >> d | ||
535 | home <- flip fmap getHomeDirectory $ | ||
536 | \d -> fmap (const d) $ guard (d/="") | ||
537 | let homegnupg = (++('/':appdir)) <$> home | ||
538 | let val = (opt `mplus` gnupghome `mplus` homegnupg) | ||
539 | return $ val | ||
540 | |||
541 | doit = do | ||
542 | args <- getArgs | ||
543 | {- | ||
544 | let wants_help = | ||
545 | not . null $ filter cryForHelp args | ||
546 | where cryForHelp "--help" = True | ||
547 | cryForHelp "--version" = True | ||
548 | cryForHelp x = | ||
549 | and (zipWith (==) x "--help=") | ||
550 | -} | ||
551 | (o,h) <- do | ||
552 | val <- unwrapCmd args (liftA2 (,) options_file homedir) | ||
553 | case val of | ||
554 | Left e -> return (Nothing,Nothing) | ||
555 | Right (o,h) -> (o,) <$> h | ||
556 | ofile <- fmap listToMaybe . flip (maybe (return [])) h $ \h -> | ||
557 | let optfiles = map (second ((h++"/")++)) | ||
558 | (maybe optfile_alts' (:[]) o') | ||
559 | optfile_alts' = zip (False:repeat True) optfile_alts | ||
560 | o' = fmap (False,) o | ||
561 | in filterM (doesFileExist . snd) optfiles | ||
562 | args <- flip (maybe $ return args) ofile $ \(forgive,fname) -> do | ||
563 | let h' = fromJust h | ||
564 | newargs <- (:) <$> pure ("homedir "++h') <*> parseOptionFile fname | ||
565 | let toArgs = toHead ("--"++) . words | ||
566 | toHead f (x:xs) = f x : xs | ||
567 | toHead f [] = [] | ||
568 | voidTerm = fmap (const ()) | ||
569 | appendArgs as [] = return as | ||
570 | appendArgs as (configline:cs) = do | ||
571 | let xs = toArgs configline | ||
572 | w <-unwrap (xs++as) (voidTerm term,defTI) | ||
573 | case w of | ||
574 | Left _ -> appendArgs as cs | ||
575 | Right _ -> appendArgs (xs++as) cs | ||
576 | -- TODO: check errors if forgive = False | ||
577 | appendArgs args newargs | ||
578 | return args | ||
579 | |||
580 | runWithOptionsFile :: (Term (IO b), TermInfo) -> IO b | ||
581 | runWithOptionsFile (term,ti) = do | ||
582 | as <- options_from_file unwrapCmd | ||
583 | term | ||
584 | ("GNUPGHOME",".gnupg",opt_homedir) | ||
585 | (["keys.conf","gpg.conf-2","gpg.conf"] | ||
586 | ,opt_options) | ||
587 | q <- eval as (term , ti) | ||
588 | q | ||
589 | where | ||
590 | unwrapCmd args term = unwrap args (term,defTI) | ||
591 | |||
592 | runChoiceWithOptionsFile :: | ||
593 | (Term (IO b), TermInfo) -> [(Term (IO b), TermInfo)] -> IO b | ||
594 | runChoiceWithOptionsFile (realterm,ti) choices = do | ||
595 | as <- options_from_file unwrapCmd | ||
596 | realterm | ||
597 | ("GNUPGHOME",".gnupg",opt_homedir) | ||
598 | (["keys.conf","gpg.conf-2","gpg.conf"] | ||
599 | ,opt_options) | ||
600 | -- putStrLn $ "as = " ++ show as | ||
601 | q <- evalChoice as (realterm , ti) choices | ||
602 | q | ||
603 | where | ||
604 | unwrapCmd args t = | ||
605 | unwrapChoice args (realterm <:> t,ti) (map (neuter t) choices) | ||
606 | neuter term (t,ti) = (t <:> term, ti) | ||
607 | |||
608 | data Command = | ||
609 | List | ||
610 | | Autosign | ||
611 | deriving (Eq,Show,Read,Enum) | ||
612 | |||
613 | capitolizeFirstLetter (x:xs) = toUpper x : xs | ||
614 | capitolizeFirstLetter xs = xs | ||
615 | |||
616 | instance ArgVal Command where | ||
617 | converter = | ||
618 | ( maybe (Left $ text "unknown command") Right | ||
619 | . fmap fst . listToMaybe . reads | ||
620 | . capitolizeFirstLetter . map toLower | ||
621 | , text . map toLower . show | ||
622 | ) | ||
623 | class AutoMaybe a | ||
624 | instance AutoMaybe Command | ||
625 | instance (ArgVal a, AutoMaybe a) => ArgVal (Maybe a) where | ||
626 | converter = | ||
627 | ( toRight Just . fst converter | ||
628 | , maybe (text "(unspecified)") id . fmap (snd converter) | ||
629 | ) | ||
630 | |||
631 | toRight f (Right x) = Right (f x) | ||
632 | toRight f (Left y) = Left y | ||
633 | |||
634 | cmd :: Term Command | ||
635 | cmd = required . pos 0 Nothing $ posInfo | ||
636 | { posName = "command" | ||
637 | , posDoc = "What action to perform." | ||
638 | } | ||
639 | |||
640 | a <:> b = flip const <$> a <*> b | ||
641 | infixr 2 <:> | ||
642 | |||
643 | selectAction cmd actions = actions !! fromEnum cmd | ||
644 | |||
645 | cmdInfo :: ArgVal cmd => | ||
646 | cmd -> String -> Term a -> (cmd, (Term a, TermInfo)) | ||
647 | cmdInfo cmd doc action = | ||
648 | ( cmd | ||
649 | , ( action | ||
650 | , defTI { termName = print cmd | ||
651 | , termDoc = doc } ) ) | ||
652 | where | ||
653 | print = show . snd converter | ||
654 | |||
655 | cmdlist :: (Command, (Term (IO ()), TermInfo)) | ||
656 | cmdlist = cmdInfo List "list key pairs for which secrets are known" $ | ||
657 | (>>= putStrLn . listKeys . unMessage) <$> secret_packets | ||
658 | where unMessage (Message pkts) = pkts | ||
659 | |||
660 | cmdautosign = cmdInfo Autosign "auto-sign tor-style uids" $ | ||
661 | pure (putStrLn "autosign") | ||
662 | |||
663 | |||
664 | multiCommand :: | ||
665 | TermInfo | ||
666 | -> [(Command, (Term a, TermInfo))] | ||
667 | -> ( (Term a, TermInfo) | ||
668 | , [(Term a, TermInfo)] ) | ||
669 | multiCommand ti choices = | ||
670 | ( ( selectAction <$> cmd <*> sequenceA (map strip choices) | ||
671 | , ti ) | ||
672 | , map snd choices ) | ||
673 | where | ||
674 | selectAction cmd choices = | ||
675 | fromJust $ lookup (cmd::Command) choices | ||
676 | strip (cmd,(action,_)) = fmap (cmd,) action | ||
677 | -} | ||
678 | |||
679 | |||
680 | trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs | ||
681 | |||
682 | guessKeyFormat 'P' "ssh-client" = "SSH" | ||
683 | guessKeyFormat 'S' "ssh-client" = "PEM" | ||
684 | guessKeyFormat 'S' "ssh-host" = "PEM" | ||
685 | guessKeyFormat _ _ = "PEM" -- "PGP" | ||
686 | |||
687 | readKeyFromFile False "PEM" fname = do | ||
688 | timestamp <- modificationTime <$> getFileStatus fname | ||
689 | input <- L.readFile fname | ||
690 | let dta = extractPEM "RSA PRIVATE KEY" input | ||
691 | -- Char8.putStrLn $ "dta = " <> dta | ||
692 | let rsa = do | ||
693 | e <- decodeASN1 DER . L.pack <$> Base64.decode (Char8.unpack dta) | ||
694 | asn1 <- either (const Nothing) Just e | ||
695 | k <- either (const Nothing) (Just . fst) (fromASN1 asn1) | ||
696 | let _ = k :: RSAPrivateKey | ||
697 | return k | ||
698 | -- putStrLn $ "rsa = "++ show rsa | ||
699 | return . Message $ do | ||
700 | rsa <- maybeToList rsa | ||
701 | return $ SecretKeyPacket | ||
702 | { version = 4 | ||
703 | , timestamp = toEnum (fromEnum timestamp) | ||
704 | , key_algorithm = RSA | ||
705 | , key = [ -- public fields... | ||
706 | ('n',rsaN rsa) | ||
707 | ,('e',rsaE rsa) | ||
708 | -- secret fields | ||
709 | ,('d',rsaD rsa) | ||
710 | ,('p',rsaQ rsa) -- Note: p & q swapped | ||
711 | ,('q',rsaP rsa) -- Note: p & q swapped | ||
712 | ,('u',rsaCoefficient rsa) | ||
713 | ] | ||
714 | , s2k_useage = 0 | ||
715 | , s2k = S2K 100 "" | ||
716 | , symmetric_algorithm = Unencrypted | ||
717 | , encrypted_data = "" | ||
718 | , is_subkey = True | ||
719 | } | ||
720 | readKeyFromFile is_public fmt filename = error ("unimplemented key type: "++fmt) | ||
721 | |||
722 | data Arguments = | ||
723 | List { homedir :: Maybe FilePath } | ||
724 | | WorkingKey { homedir :: Maybe FilePath } | ||
725 | | AutoSign { homedir :: Maybe FilePath | ||
726 | , passphrase_fd :: Maybe Int | ||
727 | , input :: FilePath | ||
728 | , output :: FilePath} | ||
729 | | Public { homedir :: Maybe FilePath | ||
730 | , output :: FilePath} | ||
731 | | Add { homedir :: Maybe FilePath | ||
732 | , passphrase_fd :: Maybe Int | ||
733 | , key_usage :: String | ||
734 | , seckey :: String | ||
735 | , output :: FilePath } | ||
736 | | PemFP { homedir :: Maybe FilePath | ||
737 | , seckey :: String } | ||
738 | | CatPub { homedir :: Maybe FilePath | ||
739 | , catpub_args :: [String] } | ||
740 | {- | ||
741 | | Decrypt { homedir :: Maybe FilePath | ||
742 | , passphrase_fd :: Maybe Int | ||
743 | , output :: FilePath } | ||
744 | -} | ||
745 | deriving (Show, Data, Typeable) | ||
746 | |||
747 | getPassphrase cmd = | ||
748 | case passphrase_fd cmd of | ||
749 | Just fd -> do pwh <- fdToHandle (toEnum fd) | ||
750 | fmap trimCR $ S.hGetContents pwh | ||
751 | Nothing -> return "" | ||
752 | |||
753 | |||
754 | #define HOMEOPTION (def &= explicit &= name "homedir" &= typDir ) | ||
755 | |||
756 | main = do | ||
757 | args <- cmdArgs $ modes | ||
758 | [ List HOMEOPTION | ||
759 | &= help "List key pairs in the secret keyring." | ||
760 | &= auto | ||
761 | , WorkingKey HOMEOPTION | ||
762 | &= help "Shows the current working key set that will be used to make signatures." | ||
763 | , Public HOMEOPTION | ||
764 | (def &= argPos 1 &= typFile ) | ||
765 | &= help "Extract public keys into the given file." | ||
766 | , AutoSign HOMEOPTION | ||
767 | (def &= opt ("passphrase"::String) | ||
768 | &= typ "FD" | ||
769 | &= (help . concat) ["file descriptor from " | ||
770 | ,"which to read passphrase"]) | ||
771 | (def &= argPos 1 &= typFile ) | ||
772 | (def &=argPos 2 &= typFile) | ||
773 | &= (help . concat) | ||
774 | [ "Copies the first file to the second while adding" | ||
775 | , " signatures for tor-style uids that match" | ||
776 | , " cross-certified keys." ] | ||
777 | {- | ||
778 | , Decrypt HOMEOPTION | ||
779 | (def &= opt ("passphrase"::String) | ||
780 | &= typ "FD" | ||
781 | &= (help . concat) ["file descriptor from " | ||
782 | ,"which to read passphrase"]) | ||
783 | (def &= argPos 1 &= typFile ) | ||
784 | -- (def &= argPos 3 &= typ "PUBLIC-KEY") | ||
785 | &= (help . concat) | ||
786 | [ "Remove password protection from the working keyring" | ||
787 | , " and save the result into the given file."] | ||
788 | -} | ||
789 | , CatPub HOMEOPTION | ||
790 | (def &= args &= typ "KEYSPEC FILES") | ||
791 | &= help "Extract a public subkey to stdout." | ||
792 | , Add HOMEOPTION | ||
793 | (def &= opt ("passphrase"::String) | ||
794 | &= typ "FD" | ||
795 | &= (help . concat) ["file descriptor from " | ||
796 | ,"which to read passphrase"]) | ||
797 | (def &= argPos 1 &= typ "USAGE") | ||
798 | (def &= argPos 2 &= typ "PRIVATE-KEY") | ||
799 | (def &= argPos 3 &= typFile) | ||
800 | -- (def &= argPos 3 &= typ "PUBLIC-KEY") | ||
801 | &= (help . concat) | ||
802 | [ "Add a subkey." | ||
803 | , " USAGE is the usage@ annotation of the subkey." | ||
804 | , " Keys are specified as FMT:FILE where" | ||
805 | , " FMT may be one of following: PEM." | ||
806 | , " Results are written to the given file." ] | ||
807 | |||
808 | , PemFP HOMEOPTION | ||
809 | (def &= argPos 1 &= typFile ) | ||
810 | &= (help . concat) | ||
811 | [ "Display the fingerprint of a PEM key pair."] | ||
812 | ] | ||
813 | &= program "kiki" | ||
814 | &= summary "kiki - a pgp key editing utility" | ||
815 | doCmd args | ||
816 | where | ||
817 | envhomedir opt = do | ||
818 | gnupghome <- lookupEnv homevar >>= | ||
819 | \d -> return $ d >>= guard . (/="") >> d | ||
820 | home <- flip fmap getHomeDirectory $ | ||
821 | \d -> fmap (const d) $ guard (d/="") | ||
822 | let homegnupg = (++('/':appdir)) <$> home | ||
823 | let val = (opt `mplus` gnupghome `mplus` homegnupg) | ||
824 | return $ val | ||
825 | |||
826 | homevar = "GNUPGHOME" | ||
827 | appdir = ".gnupg" | ||
828 | optfile_alts = ["keys.conf","gpg.conf-2","gpg.conf"] | ||
829 | |||
830 | getHomeDir cmd = do | ||
831 | homedir <- envhomedir (homedir cmd) | ||
832 | flip (maybe (error "Could not determine home directory.")) | ||
833 | homedir $ \homedir -> do | ||
834 | -- putStrLn $ "homedir = " ++show homedir | ||
835 | let secring = homedir ++ "/" ++ "secring.gpg" | ||
836 | -- putStrLn $ "secring = " ++ show secring | ||
837 | workingkey <- getWorkingKey homedir | ||
838 | return (homedir,secring,workingkey) | ||
839 | |||
840 | getWorkingKey homedir = do | ||
841 | let o = Nothing | ||
842 | h = Just homedir | ||
843 | args = ["hi"] | ||
844 | ofile <- fmap listToMaybe . flip (maybe (return [])) h $ \h -> | ||
845 | let optfiles = map (second ((h++"/")++)) | ||
846 | (maybe optfile_alts' (:[]) o') | ||
847 | optfile_alts' = zip (False:repeat True) optfile_alts | ||
848 | o' = fmap (False,) o | ||
849 | in filterM (doesFileExist . snd) optfiles | ||
850 | args <- flip (maybe $ return []) ofile $ | ||
851 | \(forgive,fname) -> parseOptionFile fname | ||
852 | let config = map (topair . words) args | ||
853 | where topair (x:xs) = (x,xs) | ||
854 | return $ lookup "default-key" config >>= listToMaybe | ||
855 | |||
856 | getPGPEnviron cmd = do | ||
857 | (homedir,secring,grip) <- getHomeDir cmd | ||
858 | (Message sec) <- readPacketsFromFile secring | ||
859 | let (keys,_) = partition (\k -> case k of | ||
860 | { SecretKeyPacket {} -> True | ||
861 | ; _ -> False }) | ||
862 | sec | ||
863 | return (homedir,sec, grip `mplus` fmap fingerprint (listToMaybe keys)) | ||
864 | |||
865 | getTorKeys pub = do | ||
866 | xs <- groupBindings pub | ||
867 | (_,(top,sub),us,_,_) <- xs | ||
868 | guard ("tor" `elem` us) | ||
869 | let torhash = maybe "" id $ derToBase32 <$> derRSA sub | ||
870 | return (top,(torhash,sub)) | ||
871 | |||
872 | uidScan pub = scanl (\(mkey,u) w -> | ||
873 | case () of | ||
874 | _ | isMasterKey w -> (w,u) | ||
875 | _ | isUserID w -> (mkey,w) | ||
876 | _ | otherwise -> (mkey,u) | ||
877 | ) | ||
878 | (w0,w0) | ||
879 | ws | ||
880 | where | ||
881 | w0:ws = pub | ||
882 | |||
883 | signSelfAuthTorKeys pw g sec grip timestamp xs = ys | ||
884 | where | ||
885 | keys = filter isKey sec | ||
886 | selfkey = find_key fingerprint (Message keys) (fromJust grip) >>= decryptKey | ||
887 | where | ||
888 | decryptKey k = decryptSecretKey pw k | ||
889 | mainpubkey = fst (head xs) | ||
890 | uid:xs' = map snd xs | ||
891 | (sigs, xs'') = span isSignaturePacket xs' | ||
892 | overs sig = signatures $ Message (keys++[uid,sig]) | ||
893 | vs :: [ ( Packet -- signature | ||
894 | , Maybe SignatureOver) -- Nothing means non-verified | ||
895 | ] | ||
896 | vs = do | ||
897 | sig <- sigs | ||
898 | let vs = overs sig >>= return . verify (Message keys) | ||
899 | ws = filter (not . null . signatures_over) vs | ||
900 | ws' = if null ws then [Nothing] else map Just ws | ||
901 | v <- ws' | ||
902 | return (sig,v) | ||
903 | has_self = not . null $ filter (\(sig,v) -> fmap topkey v == selfkey) vs | ||
904 | sigs' = if has_self | ||
905 | then sigs | ||
906 | {- | ||
907 | else trace ( "key params: "++params (fromJust selfkey)++"\n" | ||
908 | ++traceSig (topkey new_sig) (user_id new_sig) (signatures_over new_sig)) sigs ++ map modsig (signatures_over new_sig) | ||
909 | -} | ||
910 | else sigs ++ signatures_over new_sig | ||
911 | modsig sig = sig { signature = map id (signature sig) } | ||
912 | where plus1 (MPI x) = MPI (x+1) | ||
913 | params newtop = public ++ map fst (key newtop) ++ "}" | ||
914 | where | ||
915 | public = case newtop of | ||
916 | PublicKeyPacket {} -> "public{" | ||
917 | SecretKeyPacket {} -> if L.null (encrypted_data newtop ) | ||
918 | then "secret{" | ||
919 | else "encrypted{" | ||
920 | _ -> "??????{" | ||
921 | traceSig newtop newuid new_sig = (unlines ["mainpubkey:"++ show (fingerprint mainpubkey) | ||
922 | ,"new_sig topkey:"++ (show . fingerprint $ newtop) | ||
923 | ,"new_sig topkey params: "++ params newtop | ||
924 | ,"new_sig user_id:"++ (show newuid) | ||
925 | ,"new_sig |over| = " ++ (show . length $ new_sig) | ||
926 | ,"new_sig hashed = " ++ (PP.ppShow . concatMap hashed_subpackets $ new_sig) | ||
927 | ,"new_sig unhashed = " ++ (show . concatMap unhashed_subpackets $ new_sig) | ||
928 | ,"new_sig type: " ++ (show . map signature_type $ new_sig) | ||
929 | ,"new_sig signature:" ++ (show . concatMap signature $ new_sig) | ||
930 | ,"new_sig isSignaturePacket(over) = " ++ (show . map isSignaturePacket $ new_sig) | ||
931 | ,"issuer = " ++ show (map signature_issuer new_sig) | ||
932 | ]) | ||
933 | new_sig = fst $ sign (Message (maybeToList selfkey)) | ||
934 | (CertificationSignature mainpubkey | ||
935 | uid | ||
936 | []) --fromJust selfkey, uid]) | ||
937 | SHA1 | ||
938 | (fromJust grip) | ||
939 | timestamp | ||
940 | g | ||
941 | ys = uid:sigs'++xs'' | ||
942 | |||
943 | doCmd cmd@(List {}) = do | ||
944 | (homedir,secring,grip) <- getHomeDir cmd | ||
945 | (Message sec) <- readPacketsFromFile secring | ||
946 | putStrLn $ listKeys sec | ||
947 | |||
948 | doCmd cmd@(WorkingKey {}) = do | ||
949 | (homedir,secring,grip) <- getHomeDir cmd | ||
950 | (Message sec) <- readPacketsFromFile secring | ||
951 | -- let s2k' = map s2k (filter isKey sec) | ||
952 | -- putStrLn $ "s2k = " ++ show s2k' | ||
953 | putStrLn $ listKeysFiltered (maybeToList grip) sec | ||
954 | return () | ||
955 | |||
956 | doCmd cmd@(AutoSign {}) = do | ||
957 | ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome | ||
958 | , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg | ||
959 | , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321" | ||
960 | ) <- getPGPEnviron cmd | ||
961 | S8.putStrLn $ "pasphrase_fd = " <> S8.pack (show (passphrase_fd cmd)) | ||
962 | pw <- getPassphrase cmd | ||
963 | -- putStrLn $ "getPGPEnviron -> " ++ show (homedir,length sec,grip) | ||
964 | (Message pub) <- readPacketsFromFile (input cmd) | ||
965 | putStrLn $ listKeys pub | ||
966 | -- forM_ (zip [1..] pub) $ \(i,k) -> do | ||
967 | -- putStrLn $ show i ++ ": " ++ show k | ||
968 | let torbindings = getTorKeys pub | ||
969 | keyed = uidScan pub | ||
970 | marked = zipWith doit keyed pub | ||
971 | doit (mkey,u) packet = (isTorID packet, (mkey,u,packet)) | ||
972 | where | ||
973 | isTorID (UserIDPacket str) = | ||
974 | and [ uid_topdomain parsed == "onion" | ||
975 | , uid_realname parsed `elem` ["","Anonymous"] | ||
976 | , uid_user parsed == "root" | ||
977 | , fmap (match . fst) (lookup mkey torbindings) | ||
978 | == Just True ] | ||
979 | where parsed = parseUID str | ||
980 | match = ( (==subdom) . take (fromIntegral len)) | ||
981 | subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)] | ||
982 | subdom = Char8.unpack subdom0 | ||
983 | len = T.length (uid_subdomain parsed) | ||
984 | |||
985 | isTorID _ = False | ||
986 | |||
987 | g <- newGenIO | ||
988 | timestamp <- now | ||
989 | -- timestamp <- epochTime | ||
990 | let xs:xss = groupBy (\_ (b,_)->not b) marked | ||
991 | pub' = map (snd . cleanup) xs | ||
992 | ++ concatMap (signSelfAuthTorKeys pw (g::SystemRandom) sec grip timestamp) | ||
993 | (map (map cleanup) xss) | ||
994 | cleanup (_,(topkey,_,pkt)) = (topkey,pkt) | ||
995 | putStrLn $ "-------- signed ------> " -- ++ show (length pub, length pub') | ||
996 | putStrLn "" | ||
997 | putStrLn $ listKeysFiltered (map fingerprint (filter isMasterKey pub')) (sec++pub') | ||
998 | |||
999 | let signed_bs = encode (Message pub') | ||
1000 | L.writeFile (output cmd) signed_bs | ||
1001 | |||
1002 | doCmd cmd@(Public {}) = do | ||
1003 | ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome | ||
1004 | , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg | ||
1005 | , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321" | ||
1006 | ) <- getPGPEnviron cmd | ||
1007 | let pub = map secretToPublic sec | ||
1008 | bs = encode (Message pub) | ||
1009 | L.writeFile (output cmd) bs | ||
1010 | |||
1011 | {- | ||
1012 | doCmd cmd@(Decrypt {}) = do | ||
1013 | ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome | ||
1014 | , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg | ||
1015 | , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321" | ||
1016 | ) <- getPGPEnviron cmd | ||
1017 | pw <- getPassphrase cmd | ||
1018 | |||
1019 | let sec' = map decrypt sec | ||
1020 | decrypt k@(SecretKeyPacket {}) = k -- TODO | ||
1021 | |||
1022 | L.writeFile (output cmd) (encode $ Message sec') | ||
1023 | |||
1024 | {- | ||
1025 | let wk = grip >>= find_key fingerprint (Message sec) | ||
1026 | case wk of | ||
1027 | Nothing -> error "No working key?" | ||
1028 | Just wk -> do | ||
1029 | putStrLn $ "wk = " ++ fingerprint wk | ||
1030 | -} | ||
1031 | -} | ||
1032 | |||
1033 | doCmd cmd@(CatPub {}) = do | ||
1034 | let spec:files = catpub_args cmd | ||
1035 | putStrLn $ "spec = " ++show spec | ||
1036 | putStrLn $ "files = " ++ show files | ||
1037 | return () | ||
1038 | |||
1039 | doCmd cmd@(Add {}) = do | ||
1040 | ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome | ||
1041 | , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg | ||
1042 | , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321" | ||
1043 | ) <- getPGPEnviron cmd | ||
1044 | pw <- getPassphrase cmd | ||
1045 | |||
1046 | flip (maybe (error "No working key?")) grip $ \grip -> do | ||
1047 | |||
1048 | let (pre, wk:subs) = seek_key grip sec | ||
1049 | wkun = if symmetric_algorithm wk == Unencrypted | ||
1050 | then Just wk | ||
1051 | else do | ||
1052 | k <- decryptSecretKey pw wk | ||
1053 | guard (symmetric_algorithm k == Unencrypted) | ||
1054 | return k | ||
1055 | |||
1056 | flip (maybe (error "Bad passphrase?")) wkun$ \wkun -> do | ||
1057 | |||
1058 | let (uids,subkeys) = break isSubkey subs | ||
1059 | isSubkey p = isKey p && is_subkey p | ||
1060 | |||
1061 | let parseKeySpec hint spec = case break (==':') spec of | ||
1062 | (fmt,_:file) -> (fmt,file) | ||
1063 | (file,"") -> (guessKeyFormat hint (key_usage cmd), file) | ||
1064 | (secfmt,secfile) = parseKeySpec 'S' $ seckey cmd | ||
1065 | -- (pubfmt,pubfile) = parseKeySpec 'P' $ pubkey cmd | ||
1066 | Message parsedkey <- readKeyFromFile False secfmt secfile | ||
1067 | -- -- Message pubkey <- readKeyFromFile True pubfmt pubfile | ||
1068 | |||
1069 | -- putStrLn $ "parsedkey = " ++ show (head parsedkey) | ||
1070 | -- putStrLn $ "----------" | ||
1071 | |||
1072 | {- | ||
1073 | let seckeys = filter isSecretKey sec | ||
1074 | isSecretKey (SecretKeyPacket {}) = True | ||
1075 | isSecretKey _ = False | ||
1076 | algos = map symmetric_algorithm seckeys | ||
1077 | putStrLn $ show $ symmetric_algorithm wk | ||
1078 | putStrLn $ show $ s2k wk | ||
1079 | putStrLn $ show $ s2k_useage wk | ||
1080 | putStrLn $ PP.ppShow sec | ||
1081 | let -- e = encryptSecretKey wk pw (head seckey) | ||
1082 | e = head seckey | ||
1083 | d = if symmetric_algorithm e /= Unencrypted | ||
1084 | then maybeToList $ decryptSecretKey pw e | ||
1085 | else [e] | ||
1086 | putStrLn $ "e = " ++ show (e) | ||
1087 | -} | ||
1088 | -- putStrLn $ "wkun = " ++ show wkun | ||
1089 | -- putStrLn $ "head subkeys = " ++ show (head subkeys) | ||
1090 | |||
1091 | g <- newGenIO | ||
1092 | timestamp <- now | ||
1093 | |||
1094 | let | ||
1095 | new_sig = fst $ sign (Message [wkun]) | ||
1096 | (SubkeySignature wk | ||
1097 | (head parsedkey) | ||
1098 | (sigpackets 0x18 | ||
1099 | hashed0 | ||
1100 | ( IssuerPacket (fingerprint wk) | ||
1101 | : map EmbeddedSignaturePacket (signatures_over back_sig)))) | ||
1102 | SHA1 | ||
1103 | grip | ||
1104 | timestamp | ||
1105 | (g::SystemRandom) | ||
1106 | sigpackets typ hashed unhashed = return $ | ||
1107 | signaturePacket | ||
1108 | 4 -- version | ||
1109 | typ -- 0x18 subkey binding sig, or 0x19 back-signature | ||
1110 | RSA | ||
1111 | SHA1 | ||
1112 | hashed | ||
1113 | unhashed | ||
1114 | 0 -- Word16 -- Left 16 bits of the signed hash value | ||
1115 | [] -- [MPI] | ||
1116 | |||
1117 | hashed0 = | ||
1118 | [ KeyFlagsPacket | ||
1119 | { certify_keys = False | ||
1120 | , sign_data = False | ||
1121 | , encrypt_communication = False | ||
1122 | , encrypt_storage = False | ||
1123 | , split_key = False | ||
1124 | , authentication = True | ||
1125 | , group_key = False } | ||
1126 | , NotationDataPacket | ||
1127 | { human_readable = True | ||
1128 | , notation_name = "usage@" | ||
1129 | , notation_value = key_usage cmd | ||
1130 | } | ||
1131 | ] | ||
1132 | |||
1133 | subgrip = fingerprint (head parsedkey) | ||
1134 | |||
1135 | back_sig = fst $ sign (Message parsedkey) | ||
1136 | (SubkeySignature wk | ||
1137 | (head parsedkey) | ||
1138 | (sigpackets 0x19 | ||
1139 | hashed0 | ||
1140 | [IssuerPacket subgrip])) | ||
1141 | SHA1 | ||
1142 | subgrip | ||
1143 | timestamp | ||
1144 | (g::SystemRandom) | ||
1145 | |||
1146 | let sec' = pre ++ [wk] ++ uids ++ parsedkey ++ signatures_over new_sig ++ subkeys | ||
1147 | putStrLn $ listKeys sec' | ||
1148 | |||
1149 | L.writeFile (output cmd) (encode (Message sec')) | ||
1150 | |||
1151 | {- | ||
1152 | let backsigs = do | ||
1153 | sig <- signatures (Message sec') | ||
1154 | sigover <- signatures_over sig | ||
1155 | subp <- unhashed_subpackets sigover | ||
1156 | -- guard (isEmbeddedSignature subp) | ||
1157 | subp <- maybeToList (backsig subp) | ||
1158 | over <- signatures (Message (filter isKey sec ++ [subp])) | ||
1159 | return over | ||
1160 | |||
1161 | -- putStrLn $ PP.ppShow backsigs | ||
1162 | -} | ||
1163 | |||
1164 | return () | ||
1165 | |||
1166 | doCmd cmd@(PemFP {}) = do | ||
1167 | let parseKeySpec hint spec = case break (==':') spec of | ||
1168 | (fmt,_:file) -> (fmt,file) | ||
1169 | (file,"") -> (guessKeyFormat hint ("ssh-host"), file) | ||
1170 | (secfmt,secfile) = parseKeySpec 'S' $ seckey cmd | ||
1171 | Message seckey <- readKeyFromFile False secfmt secfile | ||
1172 | -- Message pubkey <- readKeyFromFile True pubfmt pubfile | ||
1173 | putStrLn $ fingerprint (head seckey) | ||
1174 | |||
1175 | |||
1176 | |||
1177 | |||
1178 | groupBindings pub = | ||
1179 | let (sigs,bindings) = getBindings pub | ||
1180 | bindings' = accBindings bindings | ||
1181 | code (c,(m,s),_,_,_) = (fingerprint_material m,-c) | ||
1182 | ownerkey (_,(a,_),_,_,_) = a | ||
1183 | sameMaster (ownerkey->a) (ownerkey->b) = fingerprint_material a==fingerprint_material b | ||
1184 | -- matchgrip ((code,(top,sub), kind, hashed,claimants):_) | fpmatch grip top = True | ||
1185 | -- matchgrip _ = False | ||
1186 | gs = {- filter matchgrip $ -} groupBy sameMaster (sortBy (comparing code) bindings') | ||
1187 | in gs | ||
1188 | |||
1189 | |||
1190 | seek_key :: String -> [Packet] -> ([Packet],[Packet]) | ||
1191 | seek_key grip sec = (pre, subs) | ||
1192 | where | ||
1193 | (pre,subs) = break pred sec | ||
1194 | pred p@(SecretKeyPacket {}) = matchpr grip p == grip | ||
1195 | pred _ = False | ||