summaryrefslogtreecommitdiff
path: root/lib/Transforms.hs
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2016-08-31 22:16:21 -0400
committerjoe <joe@jerkface.net>2016-08-31 22:16:21 -0400
commitd8950d3ccdf51f308aa93f06c16f26b15a6c55c4 (patch)
treea44064fbfc52d5ca1d51cfd0229625c6049bbfa0 /lib/Transforms.hs
parentfae3728a6b7e8ee13ed009e7c9cf3918eb4b89d7 (diff)
New command to rename subkeys.
Diffstat (limited to 'lib/Transforms.hs')
-rw-r--r--lib/Transforms.hs255
1 files changed, 241 insertions, 14 deletions
diff --git a/lib/Transforms.hs b/lib/Transforms.hs
index 093d594..ba85b18 100644
--- a/lib/Transforms.hs
+++ b/lib/Transforms.hs
@@ -1,9 +1,13 @@
1{-# LANGUAGE CPP #-} 1{-# LANGUAGE CPP #-}
2{-# LANGUAGE TupleSections #-} 2{-# LANGUAGE DoAndIfThenElse #-}
3{-# LANGUAGE ViewPatterns #-}
4{-# LANGUAGE OverloadedStrings #-} 3{-# LANGUAGE OverloadedStrings #-}
4{-# LANGUAGE PatternGuards #-}
5{-# LANGUAGE TupleSections #-}
6{-# LANGUAGE ViewPatterns #-}
5module Transforms where 7module Transforms where
6 8
9import Control.Applicative
10import Control.Arrow
7import Control.Monad 11import Control.Monad
8import Data.Char 12import Data.Char
9import Data.List 13import Data.List
@@ -11,11 +15,12 @@ import Data.Maybe
11import Data.Ord 15import Data.Ord
12import Data.OpenPGP 16import Data.OpenPGP
13import Data.OpenPGP.Util 17import Data.OpenPGP.Util
14import Data.Word (Word8) 18import Data.Word
15import Types 19import Types
16import FunctorToMaybe 20import FunctorToMaybe
17import GnuPGAgent ( key_nbits ) 21import GnuPGAgent ( key_nbits )
18import PacketTranscoder 22import PacketTranscoder
23import TimeUtil
19import qualified Data.Traversable as Traversable 24import qualified Data.Traversable as Traversable
20import qualified Data.ByteString as S 25import qualified Data.ByteString as S
21import qualified Data.ByteString.Lazy as L 26import qualified Data.ByteString.Lazy as L
@@ -88,6 +93,7 @@ data KeyRingRuntime = KeyRingRuntime
88-- | Roster-entry level actions 93-- | Roster-entry level actions
89data PacketUpdate = InducerSignature String [SignatureSubpacket] 94data PacketUpdate = InducerSignature String [SignatureSubpacket]
90 | SubKeyDeletion KeyKey KeyKey 95 | SubKeyDeletion KeyKey KeyKey
96 | SubKeyRenaming String String
91 97
92data RSAPublicKey = RSAKey MPI MPI deriving (Eq,Show) 98data RSAPublicKey = RSAKey MPI MPI deriving (Eq,Show)
93 99
@@ -166,6 +172,99 @@ data PGPKeyFlags =
166 172
167-- Functions 173-- Functions
168 174
175splitAtMinBy :: (t -> t -> Ordering) -> [t] -> ([t], [t])
176splitAtMinBy comp xs = minimumBy comp' xxs
177 where
178 xxs = zip (inits xs) (tails xs)
179 comp' (_,as) (_,bs) = compM (listToMaybe as) (listToMaybe bs)
180 compM (Just a) (Just b) = comp a b
181 compM Nothing mb = GT
182 compM _ _ = LT
183
184
185-- | Get the time stamp of a signature.
186--
187-- Warning: This function checks unhashed_subpackets if no timestamp occurs in
188-- the hashed section. TODO: change this?
189--
190signature_time :: SignatureOver -> Word32
191signature_time ov = case (if null cs then ds else cs) of
192 [] -> minBound
193 xs -> maximum xs
194 where
195 ps = signatures_over ov
196 ss = filter isSignaturePacket ps
197 cs = concatMap (concatMap creationTime . hashed_subpackets) ss
198 ds = concatMap (concatMap creationTime . unhashed_subpackets) ss
199 creationTime (SignatureCreationTimePacket t) = [t]
200 creationTime _ = []
201
202
203-- | Given list of subpackets, a master key, one of its subkeys and a
204-- list of signatures on that subkey, yields:
205--
206-- * preceding list of signatures
207--
208-- * The most recent valid signature made by the master key along with a
209-- flag that indicates whether or not all of the supplied subpackets occur in
210-- it or, if no valid signature from the working key is present, Nothing.
211--
212-- * following list of signatures
213--
214findTag ::
215 [SignatureSubpacket]
216 -> Packet
217 -> Packet
218 -> [(MappedPacket, b)]
219 -> ([(MappedPacket, b)],
220 Maybe (Bool, (MappedPacket, b)),
221 [(MappedPacket, b)])
222findTag tag topk subkey subsigs = (xs',minsig,ys')
223 where
224 vs = map (\sig ->
225 (sig, do
226 sig <- Just (packet . fst $ sig)
227 guard (isSignaturePacket sig)
228 guard $ flip isSuffixOf
229 (fingerprint topk)
230 . fromMaybe "%bad%"
231 . signature_issuer
232 $ sig
233 listToMaybe $
234 map (signature_time . verify (Message [topk]))
235 (signatures $ Message [topk,subkey,sig])))
236 subsigs
237 (xs,ys) = splitAtMinBy (comparing (Down . snd)) vs
238 xs' = map fst xs
239 ys' = map fst $ if isNothing minsig then ys else drop 1 ys
240 minsig = do
241 (sig,ov) <- listToMaybe ys
242 ov
243 let hshed = hashed_subpackets $ packet $ fst sig
244 return ( null $ tag \\ hshed, sig)
245
246mkUsage :: String -> SignatureSubpacket
247mkUsage tag | Just flags <- lookup tag specials
248 = KeyFlagsPacket
249 { certify_keys = fromEnum flags .&. 0x1 /= 0
250 , sign_data = fromEnum flags .&. 0x2 /= 0
251 , encrypt_communication = fromEnum flags .&. 0x4 /= 0
252 , encrypt_storage = fromEnum flags .&. 0x8 /= 0
253 , split_key = False
254 , authentication = False
255 , group_key = False
256 }
257 where
258 flagsets = [Special .. VouchSignEncrypt]
259 specials = map (\f -> (usageString f, f)) flagsets
260
261mkUsage tag = NotationDataPacket
262 { human_readable = True
263 , notation_name = "usage@"
264 , notation_value = tag
265 }
266
267
169unk :: Bool -> MappedPacket -> MappedPacket 268unk :: Bool -> MappedPacket -> MappedPacket
170unk isPublic = if isPublic then toPacket secretToPublic else id 269unk isPublic = if isPublic then toPacket secretToPublic else id
171 where toPacket f mp@(MappedPacket {packet=p}) = mp {packet=(f p)} 270 where toPacket f mp@(MappedPacket {packet=p}) = mp {packet=(f p)}
@@ -421,15 +520,14 @@ showPacket p | isKey p = (if is_subkey p
421 | otherwise = showPacket0 p 520 | otherwise = showPacket0 p
422 where 521 where
423 sigusage p = 522 sigusage p =
424 case take 1 (tagStrings p) of 523 case (usage_tags,flags) of
425 [] -> "" 524 ([],[]) -> ""
426 tag:_ -> " "++show tag -- "("++tag++")" 525 (_:_,_) -> " "++show usage_tags
526 (_,ts) -> " "++show ts
427 where 527 where
428 tagStrings p = usage_tags ++ flags 528 usage_tags = mapMaybe usage xs
429 where 529 flags = mapMaybe (fmap usageString . keyflags) xs
430 usage_tags = mapMaybe usage xs 530 xs = hashed_subpackets p
431 flags = mapMaybe (fmap usageString . keyflags) xs
432 xs = hashed_subpackets p
433 531
434 532
435showPacket0 p = dropSuffix "Packet" . concat . take 1 $ words (show p) 533showPacket0 p = dropSuffix "Packet" . concat . take 1 $ words (show p)
@@ -588,6 +686,99 @@ try x body =
588 Left e -> return e 686 Left e -> return e
589 Right x -> body x 687 Right x -> body x
590 688
689makeSig ::
690 PacketDecrypter
691 -> MappedPacket
692 -> FilePath
693 -> MappedPacket
694 -> [SignatureSubpacket]
695 -> Maybe (MappedPacket, Map.Map k a)
696 -> IO (KikiCondition ((MappedPacket, Map.Map k a), [KikiReportAction]))
697makeSig doDecrypt top fname subkey_p tags mbsig = do
698 let wk = packet top
699 wkun <- doDecrypt top
700 try wkun $ \wkun -> do
701 let grip = fingerprint wk
702 addOrigin new_sig =
703 flip (maybe $ return FailedToMakeSignature)
704 (new_sig >>= listToMaybe . signatures_over)
705 $ \new_sig -> do
706 let mp' = mappedPacket fname new_sig
707 return $ KikiSuccess (mp', Map.empty)
708 parsedkey = [packet subkey_p]
709 hashed0 | any isFlagsPacket tags = tags
710 | otherwise
711 = KeyFlagsPacket
712 { certify_keys = False
713 , sign_data = False
714 , encrypt_communication = False
715 , encrypt_storage = False
716 , split_key = False
717 , authentication = True
718 , group_key = False }
719 : tags
720 -- implicitly added:
721 -- , SignatureCreationTimePacket (fromIntegral timestamp)
722 isFlagsPacket (KeyFlagsPacket {}) = True
723 isFlagsPacket _ = False
724 subgrip = fingerprint (head parsedkey)
725
726 back_sig <- pgpSign (Message parsedkey)
727 (SubkeySignature wk
728 (head parsedkey)
729 (sigpackets 0x19
730 hashed0
731 [IssuerPacket subgrip]))
732 (if key_algorithm (head parsedkey)==ECDSA
733 then SHA256
734 else SHA1)
735 subgrip
736 let iss = IssuerPacket (fingerprint wk)
737 cons_iss back_sig = iss : map EmbeddedSignaturePacket (signatures_over back_sig)
738 unhashed0 = maybe [iss] cons_iss back_sig
739
740 new_sig <- pgpSign (Message [wkun])
741 (SubkeySignature wk
742 (head parsedkey)
743 (sigpackets 0x18
744 hashed0
745 unhashed0))
746 SHA1
747 grip
748 let newSig = do
749 r <- addOrigin new_sig
750 return $ fmap (,[]) r
751 flip (maybe newSig) mbsig $ \(mp,trustmap) -> do
752 let sig = packet mp
753 isCreation (SignatureCreationTimePacket {}) = True
754 isCreation _ = False
755 isExpiration (SignatureExpirationTimePacket {}) = True
756 isExpiration _ = False
757 (cs,ps) = partition isCreation (hashed_subpackets sig)
758 (es,qs) = partition isExpiration ps
759 stamp = listToMaybe . sortBy (comparing Down) $
760 map unwrap cs where unwrap (SignatureCreationTimePacket x) = x
761 exp = listToMaybe $ sort $
762 map unwrap es where unwrap (SignatureExpirationTimePacket x) = x
763 expires = liftA2 (+) stamp exp
764 timestamp <- now
765 if fmap ( (< timestamp) . fromIntegral) expires == Just True then
766 return $ KikiSuccess ((mp,trustmap), [ UnableToUpdateExpiredSignature ] )
767 else do
768 let times = (:) (SignatureExpirationTimePacket (fromIntegral timestamp))
769 $ maybeToList $ do
770 e <- expires
771 return $ SignatureExpirationTimePacket (e - fromIntegral timestamp)
772 sig' = sig { hashed_subpackets = times ++ (qs `union` tags) }
773 new_sig <- pgpSign (Message [wkun])
774 (SubkeySignature wk
775 (packet subkey_p)
776 [sig'] )
777 SHA1
778 (fingerprint wk)
779 newsig <- addOrigin new_sig
780 return $ fmap (,[]) newsig
781
591 782
592 783
593 784
@@ -660,6 +851,40 @@ performManipulations doDecrypt rt wk manip = do
660 rings = [HomeSec, HomePub] >>= resolveInputFile ctx 851 rings = [HomeSec, HomePub] >>= resolveInputFile ctx
661 return $ KikiSuccess (kd' , report ++ [ (f,DeletedPacket $ showPacket p) | f <- rings, p <- ps ]) 852 return $ KikiSuccess (kd' , report ++ [ (f,DeletedPacket $ showPacket p) | f <- rings, p <- ps ])
662 853
854 -- perform :: KikiCondition (KeyData,KikiReport) -> PacketUpdate -> IO (KikiCondition (KeyData,KikiReport))
855 perform kd (SubKeyRenaming srctag dsttag) = do
856 try kd $ \(kd,report) -> do
857 flip (maybe $ return NoWorkingKey) wk $ \wk' -> do
858 subkeys' <- traverse (freshenOne wk') (keySubKeys kd)
859 let _ = subkeys' :: Map.Map KeyKey (KikiCondition (SubKey, KikiReport))
860 succeded (KikiSuccess a) = True
861 succeded _ = False
862 (good,bad) = Map.partition succeded subkeys'
863 uncondition (KikiSuccess a) = a
864 uncondition _ = error "unexpected error"
865 good' = fmap uncondition good
866 if not (Map.null bad)
867 then return $ fmap (error "bad cast") $ head (Map.elems bad)
868 else return $ KikiSuccess ( kd { keySubKeys = fmap fst good' }
869 , report ++ concatMap snd (Map.elems good'))
870 where
871 freshenOne :: MappedPacket -> SubKey -> IO (KikiCondition (SubKey,[(FilePath, KikiReportAction)]))
872 freshenOne wk subkey@(SubKey subkey_p subsigs) = do
873 let (xs',minsig,ys') = findTag [mkUsage srctag] (packet wk) (packet subkey_p) subsigs
874 case minsig of
875 Just (True,sig) -> do
876 let fname = "--rename-subkey"
877 not_deleted p = mkUsage srctag /= p
878 mod sig = sig { hashed_subpackets = filter not_deleted $ hashed_subpackets sig }
879 sig' = first (fmap mod) sig
880 sigr <- makeSig doDecrypt wk fname subkey_p [mkUsage dsttag] (Just sig')
881 try sigr $ \(sig',sigreport) -> do
882 let old = packet (fst sig)
883 report <- return $ fmap (fname,) sigreport ++ [(fname, DeletedPacket (showPacket old)),(fname, YieldSignature)]
884 return $ KikiSuccess $ (SubKey subkey_p $ xs'++[sig']++ys', report)
885 _ -> return $ KikiSuccess (subkey, [])
886
887
663isBracket :: Char -> Bool 888isBracket :: Char -> Bool
664isBracket '<' = True 889isBracket '<' = True
665isBracket '>' = True 890isBracket '>' = True
@@ -717,7 +942,7 @@ resolveTransform Autosign rt kd@(KeyData k ksigs umap submap) = ops
717 gs = groupBy sameMaster (sortBy (comparing code) bindings') 942 gs = groupBy sameMaster (sortBy (comparing code) bindings')
718 943
719 944
720-- (2 of 3) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate] 945-- (2 of 4) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate]
721resolveTransform (DeleteSubkeyByFingerprint fp) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk 946resolveTransform (DeleteSubkeyByFingerprint fp) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk
722 where 947 where
723 topk = keykey $ packet k -- key to master of key to be deleted 948 topk = keykey $ packet k -- key to master of key to be deleted
@@ -726,7 +951,7 @@ resolveTransform (DeleteSubkeyByFingerprint fp) rt kd@(KeyData k ksigs umap subm
726 guard (map toUpper fp == fingerprint (packet (subkeyMappedPacket sub))) 951 guard (map toUpper fp == fingerprint (packet (subkeyMappedPacket sub)))
727 return k 952 return k
728 953
729-- (3 of 3) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate] 954-- (3 of 4) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate]
730resolveTransform (DeleteSubkeyByUsage tag) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk 955resolveTransform (DeleteSubkeyByUsage tag) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk
731 where 956 where
732 topk = keykey $ packet k -- key to master of key to be deleted 957 topk = keykey $ packet k -- key to master of key to be deleted
@@ -736,3 +961,5 @@ resolveTransform (DeleteSubkeyByUsage tag) rt kd@(KeyData k ksigs umap submap) =
736 take 1 $ filter (has_tag tag) $ map (packet . fst) sigs 961 take 1 $ filter (has_tag tag) $ map (packet . fst) sigs
737 return k 962 return k
738 963
964-- (4 of 4) resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate]
965resolveTransform (RenameSubkeys srctag dsttag) rt kd = [SubKeyRenaming srctag dsttag]