summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoe <joe@jerkface.net>2013-10-30 18:59:19 -0400
committerjoe <joe@jerkface.net>2013-10-30 18:59:19 -0400
commit0b65ae400ee5f2d04b188c618a5927aa7113d9be (patch)
tree9e174e7b996d9d8a5c727509f40ea75a074b217e
parent749306b18c26c868b1653e854a51ad7f6bf83cb9 (diff)
Functional add command for adding subkeys to a gpg keyring.
-rw-r--r--keys.hs356
1 files changed, 346 insertions, 10 deletions
diff --git a/keys.hs b/keys.hs
index 10bec0c..c36a01e 100644
--- a/keys.hs
+++ b/keys.hs
@@ -25,6 +25,7 @@ import Data.Bits
25import qualified Data.Text as T 25import qualified Data.Text as T
26import Data.Text.Encoding 26import Data.Text.Encoding
27import qualified Codec.Binary.Base32 as Base32 27import qualified Codec.Binary.Base32 as Base32
28import qualified Codec.Binary.Base64 as Base64
28import qualified Crypto.Hash.SHA1 as SHA1 29import qualified Crypto.Hash.SHA1 as SHA1
29import Data.Char (toLower) 30import Data.Char (toLower)
30import qualified Crypto.PubKey.RSA as RSA 31import qualified Crypto.PubKey.RSA as RSA
@@ -44,9 +45,11 @@ import System.Console.CmdArgs
44-- import System.Posix.Time 45-- import System.Posix.Time
45import Data.Time.Clock.POSIX 46import Data.Time.Clock.POSIX
46import System.Posix.IO (fdToHandle,fdRead) 47import System.Posix.IO (fdToHandle,fdRead)
48import System.Posix.Files
47import Data.Monoid ((<>)) 49import Data.Monoid ((<>))
50-- import Data.X509
48 51
49data RSAPublicKey = RSAKey MPI MPI 52data RSAPublicKey = RSAKey MPI MPI deriving Show
50 53
51instance ASN1Object RSAPublicKey where 54instance ASN1Object RSAPublicKey where
52 toASN1 (RSAKey (MPI n) (MPI e)) 55 toASN1 (RSAKey (MPI n) (MPI e))
@@ -60,6 +63,77 @@ instance ASN1Object RSAPublicKey where
60 fromASN1 _ = 63 fromASN1 _ =
61 Left "fromASN1: RSAPublicKey: unexpected format" 64 Left "fromASN1: RSAPublicKey: unexpected format"
62 65
66data 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{-
79RSAPrivateKey ::= 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
93instance 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
63rsaKeyFromPacket p@(PublicKeyPacket {}) = do 137rsaKeyFromPacket p@(PublicKeyPacket {}) = do
64 n <- lookup 'n' $ key p 138 n <- lookup 'n' $ key p
65 e <- lookup 'e' $ key p 139 e <- lookup 'e' $ key p
@@ -93,6 +167,15 @@ secretToPublic pkt@(SecretKeyPacket {}) =
93 } 167 }
94secretToPublic pkt = pkt 168secretToPublic pkt = pkt
95 169
170
171extractPEM 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
96isKey (PublicKeyPacket {}) = True 179isKey (PublicKeyPacket {}) = True
97isKey (SecretKeyPacket {}) = True 180isKey (SecretKeyPacket {}) = True
98isKey _ = False 181isKey _ = False
@@ -117,6 +200,8 @@ isSubkeySignature _ = False
117isMasterKey k@(PublicKeyPacket {}) = not $ is_subkey k 200isMasterKey k@(PublicKeyPacket {}) = not $ is_subkey k
118isMasterKey _ = False 201isMasterKey _ = False
119 202
203now = floor <$> Data.Time.Clock.POSIX.getPOSIXTime
204
120usage (NotationDataPacket 205usage (NotationDataPacket
121 { human_readable = True 206 { human_readable = True
122 , notation_name = "usage@" 207 , notation_name = "usage@"
@@ -146,6 +231,8 @@ grip k = drop 32 $ fingerprint k
146 231
147smallpr k = drop 24 $ fingerprint k 232smallpr k = drop 24 $ fingerprint k
148 233
234-- matchpr computes the fingerprint of the given key truncated to
235-- be the same lenght as the given fingerprint for comparison.
149matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp 236matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp
150 237
151 238
@@ -590,6 +677,46 @@ multiCommand ti choices =
590 677
591trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs 678trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs
592 679
680guessKeyFormat 'P' "ssh-client" = "SSH"
681guessKeyFormat 'S' "ssh-client" = "PEM"
682guessKeyFormat 'S' "ssh-host" = "PEM"
683guessKeyFormat _ _ = "PEM" -- "PGP"
684
685readKeyFromFile False "PEM" fname = do
686 timestamp <- modificationTime <$> getFileStatus fname
687 input <- L.readFile fname
688 let dta = extractPEM "RSA PRIVATE KEY" input
689 -- Char8.putStrLn $ "dta = " <> dta
690 let rsa = do
691 e <- decodeASN1 DER . L.pack <$> Base64.decode (Char8.unpack dta)
692 asn1 <- either (const Nothing) Just e
693 k <- either (const Nothing) (Just . fst) (fromASN1 asn1)
694 let _ = k :: RSAPrivateKey
695 return k
696 -- putStrLn $ "rsa = "++ show rsa
697 return . Message $ do
698 rsa <- maybeToList rsa
699 return $ SecretKeyPacket
700 { version = 4
701 , timestamp = toEnum (fromEnum timestamp)
702 , key_algorithm = RSA
703 , key = [ -- public fields...
704 ('n',rsaN rsa)
705 ,('e',rsaE rsa)
706 -- secret fields
707 ,('d',rsaD rsa)
708 ,('p',rsaQ rsa) -- Note: p & q swapped
709 ,('q',rsaP rsa) -- Note: p & q swapped
710 ,('u',rsaCoefficient rsa)
711 ]
712 , s2k_useage = 0
713 , s2k = S2K 100 ""
714 , symmetric_algorithm = Unencrypted
715 , encrypted_data = ""
716 , is_subkey = True
717 }
718readKeyFromFile is_public fmt filename = error ("unimplemented key type: "++fmt)
719
593data Arguments = 720data Arguments =
594 List { homedir :: Maybe FilePath } 721 List { homedir :: Maybe FilePath }
595 | WorkingKey { homedir :: Maybe FilePath } 722 | WorkingKey { homedir :: Maybe FilePath }
@@ -599,8 +726,25 @@ data Arguments =
599 , output :: FilePath} 726 , output :: FilePath}
600 | Public { homedir :: Maybe FilePath 727 | Public { homedir :: Maybe FilePath
601 , output :: FilePath} 728 , output :: FilePath}
729 | Add { homedir :: Maybe FilePath
730 , passphrase_fd :: Maybe Int
731 , key_usage :: String
732 , seckey :: String
733 , output :: FilePath }
734 | PemFP { homedir :: Maybe FilePath
735 , seckey :: String }
736 | Decrypt { homedir :: Maybe FilePath
737 , passphrase_fd :: Maybe Int
738 , output :: FilePath }
602 deriving (Show, Data, Typeable) 739 deriving (Show, Data, Typeable)
603 740
741getPassphrase cmd =
742 case passphrase_fd cmd of
743 Just fd -> do pwh <- fdToHandle (toEnum fd)
744 fmap trimCR $ S.hGetContents pwh
745 Nothing -> return ""
746
747
604#define HOMEOPTION (def &= explicit &= name "homedir" &= typDir ) 748#define HOMEOPTION (def &= explicit &= name "homedir" &= typDir )
605 749
606main = do 750main = do
@@ -610,9 +754,13 @@ main = do
610 &= auto 754 &= auto
611 , WorkingKey HOMEOPTION 755 , WorkingKey HOMEOPTION
612 &= help "Shows the current working key set that will be used to make signatures." 756 &= help "Shows the current working key set that will be used to make signatures."
757 , Public HOMEOPTION
758 (def &= argPos 1 &= typFile )
759 &= help "Extract public keys into the given file."
613 , AutoSign HOMEOPTION 760 , AutoSign HOMEOPTION
614 (def &= opt ("passphrase"::String) 761 (def &= opt ("passphrase"::String)
615 &= (help . concat) ["file descriptor from" 762 &= typ "FD"
763 &= (help . concat) ["file descriptor from "
616 ,"which to read passphrase"]) 764 ,"which to read passphrase"])
617 (def &= argPos 1 &= typFile ) 765 (def &= argPos 1 &= typFile )
618 (def &=argPos 2 &= typFile) 766 (def &=argPos 2 &= typFile)
@@ -620,9 +768,36 @@ main = do
620 [ "Copies the first file to the second while adding" 768 [ "Copies the first file to the second while adding"
621 , " signatures for tor-style uids that match" 769 , " signatures for tor-style uids that match"
622 , " cross-certified keys." ] 770 , " cross-certified keys." ]
623 , Public HOMEOPTION 771 , Decrypt HOMEOPTION
624 (def &= argPos 1 &= typFile ) 772 (def &= opt ("passphrase"::String)
625 &= help "Extract public keys into the given file." 773 &= typ "FD"
774 &= (help . concat) ["file descriptor from "
775 ,"which to read passphrase"])
776 (def &= argPos 1 &= typFile )
777 -- (def &= argPos 3 &= typ "PUBLIC-KEY")
778 &= (help . concat)
779 [ "Remove password protection from the working keyring"
780 , " and save the result into the given file."]
781 , Add HOMEOPTION
782 (def &= opt ("passphrase"::String)
783 &= typ "FD"
784 &= (help . concat) ["file descriptor from "
785 ,"which to read passphrase"])
786 (def &= argPos 1 &= typ "USAGE")
787 (def &= argPos 2 &= typ "PRIVATE-KEY")
788 (def &= argPos 3 &= typFile)
789 -- (def &= argPos 3 &= typ "PUBLIC-KEY")
790 &= (help . concat)
791 [ "Add a subkey."
792 , " USAGE is the usage@ annotation of the subkey."
793 , " Keys are specified as FMT:FILE where"
794 , " FMT may be one of following: PEM."
795 , " Results are written to the given file." ]
796
797 , PemFP HOMEOPTION
798 (def &= argPos 1 &= typFile )
799 &= (help . concat)
800 [ "Display the fingerprint of a PEM key pair."]
626 ] 801 ]
627 &= program "keys" 802 &= program "keys"
628 &= summary "keys - a pgp key editing utility" 803 &= summary "keys - a pgp key editing utility"
@@ -762,6 +937,8 @@ main = do
762 doCmd cmd@(WorkingKey {}) = do 937 doCmd cmd@(WorkingKey {}) = do
763 (homedir,secring,grip) <- getHomeDir cmd 938 (homedir,secring,grip) <- getHomeDir cmd
764 (Message sec) <- readPacketsFromFile secring 939 (Message sec) <- readPacketsFromFile secring
940 -- let s2k' = map s2k (filter isKey sec)
941 -- putStrLn $ "s2k = " ++ show s2k'
765 putStrLn $ listKeysFiltered (maybeToList grip) sec 942 putStrLn $ listKeysFiltered (maybeToList grip) sec
766 return () 943 return ()
767 944
@@ -771,10 +948,7 @@ main = do
771 , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321" 948 , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321"
772 ) <- getPGPEnviron cmd 949 ) <- getPGPEnviron cmd
773 S8.putStrLn $ "pasphrase_fd = " <> S8.pack (show (passphrase_fd cmd)) 950 S8.putStrLn $ "pasphrase_fd = " <> S8.pack (show (passphrase_fd cmd))
774 pw <- case passphrase_fd cmd of 951 pw <- getPassphrase cmd
775 Just fd -> do pwh <- fdToHandle (toEnum fd)
776 fmap trimCR $ S.hGetContents pwh
777 Nothing -> return ""
778 -- putStrLn $ "getPGPEnviron -> " ++ show (homedir,length sec,grip) 952 -- putStrLn $ "getPGPEnviron -> " ++ show (homedir,length sec,grip)
779 (Message pub) <- readPacketsFromFile (input cmd) 953 (Message pub) <- readPacketsFromFile (input cmd)
780 putStrLn $ listKeys pub 954 putStrLn $ listKeys pub
@@ -800,8 +974,8 @@ main = do
800 isTorID _ = False 974 isTorID _ = False
801 975
802 g <- newGenIO 976 g <- newGenIO
977 timestamp <- now
803 -- timestamp <- epochTime 978 -- timestamp <- epochTime
804 timestamp <- floor <$> Data.Time.Clock.POSIX.getPOSIXTime
805 let xs:xss = groupBy (\_ (b,_)->not b) marked 979 let xs:xss = groupBy (\_ (b,_)->not b) marked
806 pub' = map (snd . cleanup) xs 980 pub' = map (snd . cleanup) xs
807 ++ concatMap (signSelfAuthTorKeys pw (g::SystemRandom) sec grip timestamp) 981 ++ concatMap (signSelfAuthTorKeys pw (g::SystemRandom) sec grip timestamp)
@@ -823,6 +997,161 @@ main = do
823 bs = encode (Message pub) 997 bs = encode (Message pub)
824 L.writeFile (output cmd) bs 998 L.writeFile (output cmd) bs
825 999
1000 doCmd cmd@(Decrypt {}) = do
1001 ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome
1002 , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg
1003 , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321"
1004 ) <- getPGPEnviron cmd
1005 pw <- getPassphrase cmd
1006
1007 let sec' = map decrypt sec
1008 decrypt k@(SecretKeyPacket {}) = k -- TODO
1009
1010 L.writeFile (output cmd) (encode $ Message sec')
1011
1012 {-
1013 let wk = grip >>= find_key fingerprint (Message sec)
1014 case wk of
1015 Nothing -> error "No working key?"
1016 Just wk -> do
1017 putStrLn $ "wk = " ++ fingerprint wk
1018 -}
1019
1020 doCmd cmd@(Add {}) = do
1021 ( homedir -- e3ozbhvej4jvlu43.onion/gpg/gnupghome
1022 , sec -- e3ozbhvej4jvlu43.onion/gpg/gnupghome/secring.gpg
1023 , grip -- Just "AD1CA892FCF4ED9829C762269BDEA5B4D5643321"
1024 ) <- getPGPEnviron cmd
1025 pw <- getPassphrase cmd
1026
1027 flip (maybe (error "No working key?")) grip $ \grip -> do
1028
1029 let (pre, wk:subs) = seek_key grip sec
1030 wkun = do
1031 k <- decryptSecretKey pw wk
1032 guard (symmetric_algorithm k == Unencrypted)
1033 return k
1034
1035 flip (maybe (error "Bad passphrase?")) wkun$ \wkun -> do
1036
1037 let (uids,subkeys) = break isSubkey subs
1038 isSubkey p = isKey p && is_subkey p
1039
1040 let parseKeySpec hint spec = case break (==':') spec of
1041 (fmt,_:file) -> (fmt,file)
1042 (file,"") -> (guessKeyFormat hint (key_usage cmd), file)
1043 (secfmt,secfile) = parseKeySpec 'S' $ seckey cmd
1044 -- (pubfmt,pubfile) = parseKeySpec 'P' $ pubkey cmd
1045 Message parsedkey <- readKeyFromFile False secfmt secfile
1046 -- -- Message pubkey <- readKeyFromFile True pubfmt pubfile
1047
1048 -- putStrLn $ "parsedkey = " ++ show (head parsedkey)
1049 -- putStrLn $ "----------"
1050
1051 {-
1052 let seckeys = filter isSecretKey sec
1053 isSecretKey (SecretKeyPacket {}) = True
1054 isSecretKey _ = False
1055 algos = map symmetric_algorithm seckeys
1056 putStrLn $ show $ symmetric_algorithm wk
1057 putStrLn $ show $ s2k wk
1058 putStrLn $ show $ s2k_useage wk
1059 putStrLn $ PP.ppShow sec
1060 let -- e = encryptSecretKey wk pw (head seckey)
1061 e = head seckey
1062 d = if symmetric_algorithm e /= Unencrypted
1063 then maybeToList $ decryptSecretKey pw e
1064 else [e]
1065 putStrLn $ "e = " ++ show (e)
1066 -}
1067 -- putStrLn $ "wkun = " ++ show wkun
1068 -- putStrLn $ "head subkeys = " ++ show (head subkeys)
1069
1070 g <- newGenIO
1071 timestamp <- now
1072
1073 let
1074 new_sig = fst $ sign (Message [wkun])
1075 (SubkeySignature wk
1076 (head parsedkey)
1077 (sigpackets 0x18
1078 hashed0
1079 ( IssuerPacket (fingerprint wk)
1080 : map EmbeddedSignaturePacket (signatures_over back_sig))))
1081 SHA1
1082 grip
1083 timestamp
1084 (g::SystemRandom)
1085 sigpackets typ hashed unhashed = return $
1086 signaturePacket
1087 4 -- version
1088 typ -- 0x18 subkey binding sig, or 0x19 back-signature
1089 RSA
1090 SHA1
1091 hashed
1092 unhashed
1093 0 -- Word16 -- Left 16 bits of the signed hash value
1094 [] -- [MPI]
1095
1096 hashed0 =
1097 [ KeyFlagsPacket
1098 { certify_keys = True
1099 , sign_data = True
1100 , encrypt_communication = True
1101 , encrypt_storage = True
1102 , split_key = False
1103 , authentication = True
1104 , group_key = False }
1105 , NotationDataPacket
1106 { human_readable = True
1107 , notation_name = "usage@"
1108 , notation_value = key_usage cmd
1109 }
1110 ]
1111
1112 subgrip = fingerprint (head parsedkey)
1113
1114 back_sig = fst $ sign (Message parsedkey)
1115 (SubkeySignature wk
1116 (head parsedkey)
1117 (sigpackets 0x19
1118 hashed0
1119 [IssuerPacket subgrip]))
1120 SHA1
1121 subgrip
1122 timestamp
1123 (g::SystemRandom)
1124
1125 let sec' = pre ++ [wk] ++ uids ++ parsedkey ++ signatures_over new_sig ++ subkeys
1126 putStrLn $ listKeys sec'
1127
1128 L.writeFile (output cmd) (encode (Message sec'))
1129
1130 {-
1131 let backsigs = do
1132 sig <- signatures (Message sec')
1133 sigover <- signatures_over sig
1134 subp <- unhashed_subpackets sigover
1135 -- guard (isEmbeddedSignature subp)
1136 subp <- maybeToList (backsig subp)
1137 over <- signatures (Message (filter isKey sec ++ [subp]))
1138 return over
1139
1140 -- putStrLn $ PP.ppShow backsigs
1141 -}
1142
1143 return ()
1144
1145 doCmd cmd@(PemFP {}) = do
1146 let parseKeySpec hint spec = case break (==':') spec of
1147 (fmt,_:file) -> (fmt,file)
1148 (file,"") -> (guessKeyFormat hint ("ssh-host"), file)
1149 (secfmt,secfile) = parseKeySpec 'S' $ seckey cmd
1150 Message seckey <- readKeyFromFile False secfmt secfile
1151 -- Message pubkey <- readKeyFromFile True pubfmt pubfile
1152 putStrLn $ fingerprint (head seckey)
1153
1154
826 1155
827 1156
828groupBindings pub = 1157groupBindings pub =
@@ -836,3 +1165,10 @@ groupBindings pub =
836 gs = {- filter matchgrip $ -} groupBy sameMaster (sortBy (comparing code) bindings') 1165 gs = {- filter matchgrip $ -} groupBy sameMaster (sortBy (comparing code) bindings')
837 in gs 1166 in gs
838 1167
1168
1169seek_key :: String -> [Packet] -> ([Packet],[Packet])
1170seek_key grip sec = (pre, subs)
1171 where
1172 (pre,subs) = break pred sec
1173 pred p@(SecretKeyPacket {}) = matchpr grip p == grip
1174 pred _ = False