summaryrefslogtreecommitdiff
path: root/lib/KeyRing.hs
diff options
context:
space:
mode:
Diffstat (limited to 'lib/KeyRing.hs')
-rw-r--r--lib/KeyRing.hs3583
1 files changed, 3583 insertions, 0 deletions
diff --git a/lib/KeyRing.hs b/lib/KeyRing.hs
new file mode 100644
index 0000000..1c6dea8
--- /dev/null
+++ b/lib/KeyRing.hs
@@ -0,0 +1,3583 @@
1---------------------------------------------------------------------------
2-- |
3-- Module : KeyRing
4--
5-- Maintainer : joe@jerkface.net
6-- Stability : experimental
7--
8-- kiki is a command-line utility for manipulating GnuPG's keyring files. This
9-- module is the programmer-facing API it uses to do that.
10--
11-- Note: This is *not* a public facing API. I (the author) consider this
12-- library to be internal to kiki and subject to change at my whim.
13--
14-- Typically, a client to this module would prepare a 'KeyRingOperation'
15-- describing what he wants done, and then invoke 'runKeyRing' to make it
16-- happen.
17{-# LANGUAGE CPP #-}
18{-# LANGUAGE TupleSections #-}
19{-# LANGUAGE ViewPatterns #-}
20{-# LANGUAGE OverloadedStrings #-}
21{-# LANGUAGE DeriveFunctor #-}
22{-# LANGUAGE DoAndIfThenElse #-}
23{-# LANGUAGE NoPatternGuards #-}
24{-# LANGUAGE ForeignFunctionInterface #-}
25module KeyRing
26 (
27 -- * Error Handling
28 KikiResult(..)
29 , KikiCondition(..)
30 , KikiReportAction(..)
31 , errorString
32 , reportString
33 -- * Manipulating Keyrings
34 , runKeyRing
35 , KeyRingOperation(..)
36 , PassphraseSpec(..)
37 , Transform(..)
38 -- , PacketUpdate(..)
39 -- , guardAuthentic
40 -- * Describing File Operations
41 , StreamInfo(..)
42 , Access(..)
43 , FileType(..)
44 , InputFile(..)
45 , Initializer(..)
46 , KeyFilter(..)
47 -- * Results of a KeyRing Operation
48 , KeyRingRuntime(..)
49 , MappedPacket(..)
50 , KeyDB
51 , KeyData(..)
52 , SubKey(..)
53 , keyflags
54 -- * Miscelaneous Utilities
55 , isKey
56 , derRSA
57 , derToBase32
58 , backsig
59 , filterMatches
60 , flattenKeys
61 , flattenTop
62 , Hosts.Hosts
63 , isCryptoCoinKey
64 , matchpr
65 , parseSpec
66 , parseUID
67 , UserIDRecord(..)
68 , pkcs8
69 , RSAPublicKey(..)
70 , PKCS8_RSAPublicKey(..)
71 , rsaKeyFromPacket
72 , secretToPublic
73 , selectPublicKey
74 , selectSecretKey
75 , usage
76 , usageString
77 , walletImportFormat
78 , writePEM
79 , getBindings
80 , accBindings
81 , isSubkeySignature
82 , torhash
83 , ParsedCert(..)
84 , parseCertBlob
85 , packetFromPublicRSAKey
86 , decodeBlob
87 , selectPublicKeyAndSigs
88 , x509cert
89 , getHomeDir
90 , unconditionally
91 , SecretPEMData(..)
92 , readSecretPEMFile
93 , writeInputFileL
94 , InputFileContext(..)
95 , onionNameForContact
96 , keykey
97 , keyPacket
98 , KeySpec(..)
99 , getHostnames
100 , secretPemFromPacket
101 , getCrossSignedSubkeys
102 ) where
103
104import System.Environment
105import Control.Monad
106import Data.Maybe
107import Data.Either
108import Data.Char
109import Data.Ord
110import Data.List
111import Data.OpenPGP
112import Data.Functor
113import Data.Monoid
114import Data.Tuple ( swap )
115import Data.Bits ( (.|.), (.&.) )
116import Control.Applicative ( Applicative, pure, liftA2, (<*>) )
117import System.Directory ( getHomeDirectory, doesFileExist, createDirectoryIfMissing )
118import Control.Arrow ( first, second )
119import Data.OpenPGP.Util (verify,fingerprint,decryptSecretKey,pgpSign, generateKey, GenerateKeyParams(..))
120import Data.ByteString.Lazy ( ByteString )
121import Text.Show.Pretty as PP ( ppShow )
122import Data.Binary {- decode, decodeOrFail -}
123import ControlMaybe ( handleIO_ )
124import Data.ASN1.Types ( toASN1, ASN1Object, fromASN1
125 , ASN1(Start,End,IntVal,OID,BitString,Null), ASN1ConstructionType(Sequence) )
126import Data.ASN1.BitArray ( BitArray(..), toBitArray )
127import Data.ASN1.Encoding ( encodeASN1, encodeASN1', decodeASN1, decodeASN1' )
128import Data.ASN1.BinaryEncoding ( DER(..) )
129import Data.Time.Clock.POSIX ( POSIXTime, utcTimeToPOSIXSeconds )
130import Data.Time.Clock ( UTCTime )
131import Data.Bits ( Bits, shiftR )
132import Data.Text.Encoding ( encodeUtf8 )
133import qualified Data.Map as Map
134import qualified Data.ByteString.Lazy as L ( unpack, null, readFile, writeFile
135 , ByteString, toChunks, hGetContents, hPut, concat, fromChunks, splitAt
136 , index, break, pack )
137import qualified Data.ByteString as S ( ByteString, unpack, splitAt, concat, cons, spanEnd, hGetContents, readFile, breakSubstring, drop, length, null, hPutStr, singleton, unfoldr, reverse )
138import qualified Codec.Binary.Base32 as Base32
139import qualified Codec.Binary.Base64 as Base64
140#if !defined(VERSION_cryptonite)
141import qualified Crypto.Hash.SHA1 as SHA1
142import qualified Crypto.Types.PubKey.ECC as ECC
143#else
144import qualified Crypto.Hash as Vincent
145import Data.ByteArray (convert)
146import qualified Crypto.PubKey.ECC.Types as ECC
147#endif
148import qualified Data.X509 as X509
149import qualified Crypto.PubKey.RSA as RSA
150import qualified Codec.Compression.GZip as GZip
151import qualified Data.Text as T ( Text, unpack, pack,
152 strip, reverse, drop, break, dropAround, length )
153import qualified System.Posix.Types as Posix
154import System.Posix.Files ( modificationTime, getFileStatus, getFdStatus
155 , setFileCreationMask, setFileTimes )
156#if MIN_VERSION_x509(1,5,0)
157import Data.Hourglass.Types
158import Data.Hourglass
159#endif
160#if MIN_VERSION_unix(2,7,0)
161import System.Posix.Files ( setFdTimesHiRes )
162import Foreign.C.Types ( CTime(..), CLong, CInt(..) )
163#else
164import Foreign.C.Types ( CTime(..), CLong, CInt(..) )
165import Foreign.Marshal.Array ( withArray )
166import Foreign.Ptr
167import Foreign.C.Error ( throwErrnoIfMinus1_ )
168import Foreign.Storable
169#endif
170import System.FilePath ( takeDirectory )
171import System.IO (hPutStrLn,withFile,IOMode(..), Handle, hPutStr, stderr)
172import Data.IORef
173import System.Posix.IO ( fdToHandle )
174import qualified Data.Traversable as Traversable
175import Data.Traversable ( sequenceA )
176#if ! MIN_VERSION_base(4,6,0)
177import GHC.Exts ( Down(..) )
178#endif
179#if MIN_VERSION_binary(0,7,0)
180import Debug.Trace
181#endif
182import Network.Socket -- (SockAddr)
183import qualified Data.ByteString.Lazy.Char8 as Char8
184import Compat
185
186import TimeUtil
187import PEM
188import ScanningParser
189import qualified Hosts
190import qualified CryptoCoins
191import Base58
192import FunctorToMaybe
193import DotLock
194import ProcessUtils (systemEnv, ExitCode(ExitFailure, ExitSuccess) )
195
196-- DER-encoded elliptic curve ids
197-- nistp256_id = 0x2a8648ce3d030107
198secp256k1_id :: Integer
199secp256k1_id = 0x2b8104000a
200-- "\x2a\x86\x48\xce\x3d\x03\x01\x07"
201{- OID Curve description Curve name
202 ----------------------------------------------------------------
203 1.2.840.10045.3.1.7 NIST Curve P-256 [FIPS 186-2] "NIST P-256"
204 1.3.132.0.34 NIST Curve P-384 [FIPS 186-2] "NIST P-384"
205 1.3.132.0.35 NIST Curve P-521 [FIPS 186-2] "NIST P-521"
206
207 Implementations MUST implement "NIST P-256", "NIST P-384" and "NIST
208 P-521". The hexadecimal representation used in the public and
209 private key encodings are:
210
211 Curve Name Len Hexadecimal representation of the OID
212 ----------------------------------------------------------------
213 "NIST P-256" 8 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07
214 "NIST P-384" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22
215 "NIST P-521" 6 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23
216-}
217
218data HomeDir =
219 HomeDir { homevar :: String
220 , appdir :: String
221 , optfile_alts :: [String]
222 }
223
224home :: HomeDir
225home = HomeDir
226 { homevar = "GNUPGHOME"
227 , appdir = ".gnupg"
228 , optfile_alts = ["keys.conf","gpg.conf-2","gpg.conf"]
229 }
230
231data InputFile = HomeSec
232 -- ^ A file named secring.gpg located in the home directory.
233 -- See 'opHome'.
234 | HomePub
235 -- ^ A file named pubring.gpg located in the home directory.
236 -- See 'opHome'.
237 | ArgFile FilePath
238 -- ^ Contents will be read or written from the specified path.
239 | FileDesc Posix.Fd
240 -- ^ Contents will be read or written from the specified file
241 -- descriptor.
242 | Pipe Posix.Fd Posix.Fd
243 -- ^ Contents will be read from the first descriptor and updated
244 -- content will be writen to the second. Note: Don't use Pipe
245 -- for 'Wallet' files. (TODO: Wallet support)
246 | Generate Int GenerateKeyParams
247 -- ^ New key packets will be generated if there is no
248 -- matching content already in the key pool. The integer is
249 -- a unique id number so that multiple generations can be
250 -- inserted into 'opFiles'
251 deriving (Eq,Ord,Show)
252
253-- type UsageTag = String
254data Initializer = NoCreate | Internal GenerateKeyParams | External String
255 deriving (Eq,Ord,Show)
256
257data FileType = KeyRingFile
258 | PEMFile
259 | WalletFile
260 | DNSPresentation
261 | Hosts
262 deriving (Eq,Ord,Enum,Show)
263
264-- | Use this type to indicate whether a file of type 'KeyRingFile' is expected
265-- to contain secret or public PGP key packets. Note that it is not supported
266-- to mix both in the same file and that the secret key packets include all of
267-- the information contained in their corresponding public key packets.
268data Access = AutoAccess -- ^ secret or public as appropriate based on existing content.
269 -- (see 'rtRingAccess')
270 | Sec -- ^ secret information
271 | Pub -- ^ public information
272 deriving (Eq,Ord,Show)
273
274-- | Note that the documentation here is intended for when this value is
275-- assigned to 'fill'. For other usage, see 'spill'.
276data KeyFilter = KF_None -- ^ No keys will be imported.
277 | KF_Match String -- ^ Only the key that matches the spec will be imported.
278 | KF_Subkeys -- ^ Subkeys will be imported if their owner key is
279 -- already in the ring. TODO: Even if their signatures
280 -- are bad?
281 | KF_Authentic -- ^ Keys are imported if they belong to an authenticated
282 -- identity (signed or self-authenticating).
283 | KF_All -- ^ All keys will be imported.
284 deriving (Eq,Ord,Show)
285
286-- | This type describes how 'runKeyRing' will treat a file.
287data StreamInfo = StreamInfo
288 { access :: Access
289 -- ^ Indicates whether the file is allowed to contain secret information.
290 , typ :: FileType
291 -- ^ Indicates the format and content type of the file.
292 , fill :: KeyFilter
293 -- ^ This filter controls what packets will be inserted into a file.
294 , spill :: KeyFilter
295 --
296 -- ^ Use this to indicate whether or not a file's contents should be
297 -- available for updating other files. Note that although its type is
298 -- 'KeyFilter', it is usually interpretted as a boolean flag. Details
299 -- depend on 'typ' and are as follows:
300 --
301 -- 'KeyRingFile':
302 --
303 -- * 'KF_None' - The file's contents will not be shared.
304 --
305 -- * otherwise - The file's contents will be shared.
306 --
307 -- 'PEMFile':
308 --
309 -- * 'KF_None' - The file's contents will not be shared.
310 --
311 -- * 'KF_Match' - The file's key will be shared with the specified owner
312 -- key and usage tag. If 'fill' is also a 'KF_Match', then it must be
313 -- equal to this value; changing the usage or owner of a key is not
314 -- supported via the fill/spill mechanism.
315 --
316 -- * otherwise - Unspecified. Do not use.
317 --
318 -- 'WalletFile':
319 --
320 -- * The 'spill' setting is ignored and the file's contents are shared.
321 -- (TODO)
322 --
323 -- 'Hosts':
324 --
325 -- * The 'spill' setting is ignored and the file's contents are shared.
326 -- (TODO)
327 --
328 , initializer :: Initializer
329 -- ^ If 'typ' is 'PEMFile' and an 'External' 'initializer' string is set,
330 -- then it is interpretted as a shell command that may be used to create
331 -- the key if it does not exist.
332 , transforms :: [Transform]
333 -- ^ Per-file transformations that occur before the contents of a file are
334 -- spilled into the common pool.
335 }
336 deriving (Eq,Show)
337
338
339spillable :: StreamInfo -> Bool
340spillable (spill -> KF_None) = False
341spillable _ = True
342
343isMutable :: StreamInfo -> Bool
344isMutable (fill -> KF_None) = False
345isMutable _ = True
346
347isring :: FileType -> Bool
348isring (KeyRingFile {}) = True
349isring _ = False
350
351isSecretKeyFile :: FileType -> Bool
352isSecretKeyFile PEMFile = True
353isSecretKeyFile DNSPresentation = True
354isSecretKeyFile _ = False
355
356{-
357pwfile :: FileType -> Maybe InputFile
358pwfile (KeyRingFile f) = f
359pwfile _ = Nothing
360-}
361
362iswallet :: FileType -> Bool
363iswallet (WalletFile {}) = True
364iswallet _ = False
365
366usageFromFilter :: MonadPlus m => KeyFilter -> m String
367usageFromFilter (KF_Match usage) = return usage
368usageFromFilter _ = mzero
369
370data KeyRingRuntime = KeyRingRuntime
371 { rtPubring :: FilePath
372 -- ^ Path to the file represented by 'HomePub'
373 , rtSecring :: FilePath
374 -- ^ Path to the file represented by 'HomeSec'
375 , rtGrip :: Maybe String
376 -- ^ Fingerprint or portion of a fingerprint used
377 -- to identify the working GnuPG identity used to
378 -- make signatures.
379 , rtWorkingKey :: Maybe Packet
380 -- ^ The master key of the working GnuPG identity.
381 , rtKeyDB :: KeyDB
382 -- ^ The common information pool where files spilled
383 -- their content and from which they received new
384 -- content.
385 , rtRingAccess :: Map.Map InputFile Access
386 -- ^ The 'Access' values used for files of type
387 -- 'KeyRingFile'. If 'AutoAccess' was specified
388 -- for a file, this 'Map.Map' will indicate the
389 -- detected value that was used by the algorithm.
390 , rtPassphrases :: MappedPacket -> IO (KikiCondition Packet)
391 }
392
393-- | Roster-entry level actions
394data PacketUpdate = InducerSignature String [SignatureSubpacket]
395 | SubKeyDeletion KeyKey KeyKey
396
397-- | This type is used to indicate where to obtain passphrases.
398data PassphraseSpec = PassphraseSpec
399 { passSpecRingFile :: Maybe FilePath
400 -- ^ If not Nothing, the passphrase is to be used for packets
401 -- from this file.
402 , passSpecKeySpec :: Maybe String
403 -- ^ Non-Nothing value reserved for future use.
404 -- (TODO: Use this to implement per-key passphrase associations).
405 , passSpecPassFile :: InputFile
406 -- ^ The passphrase will be read from this file or file descriptor.
407 }
408 -- | Use this to carry pasphrases from a previous run.
409 | PassphraseMemoizer (MappedPacket -> IO (KikiCondition Packet))
410
411instance Show PassphraseSpec where
412 show (PassphraseSpec a b c) = "PassphraseSpec "++show (a,b,c)
413 show (PassphraseMemoizer _) = "PassphraseMemoizer"
414instance Eq PassphraseSpec where
415 PassphraseSpec a b c == PassphraseSpec d e f
416 = and [a==d,b==e,c==f]
417 _ == _
418 = False
419
420
421
422data Transform =
423 Autosign
424 -- ^ This operation will make signatures for any tor-style UID
425 -- that matches a tor subkey and thus can be authenticated without
426 -- requring the judgement of a human user.
427 --
428 -- A tor-style UID is one of the following form:
429 --
430 -- > Anonymous <root@HOSTNAME.onion>
431 | DeleteSubKey String
432 -- ^ Delete the subkey specified by the given fingerprint and any
433 -- associated signatures on that key.
434 deriving (Eq,Ord,Show)
435
436-- | This type describes an idempotent transformation (merge or import) on a
437-- set of GnuPG keyrings and other key files.
438data KeyRingOperation = KeyRingOperation
439 { opFiles :: Map.Map InputFile StreamInfo
440 -- ^ Indicates files to be read or updated.
441 , opPassphrases :: [PassphraseSpec]
442 -- ^ Indicates files or file descriptors where passphrases can be found.
443 , opTransforms :: [Transform]
444 -- ^ Transformations to be performed on the key pool after all files have
445 -- been read and before any have been written.
446 , opHome :: Maybe FilePath
447 -- ^ If provided, this is the directory where the 'HomeSec' and 'HomePub'
448 -- files reside. Otherwise, the evironment variable $GNUPGHOME is consulted
449 -- and if that is not set, it falls back to $HOME/.gnupg.
450 }
451 deriving (Eq,Show)
452
453resolveInputFile :: InputFileContext -> InputFile -> [FilePath]
454resolveInputFile ctx = resolve
455 where
456 resolve HomeSec = return (homesecPath ctx)
457 resolve HomePub = return (homepubPath ctx)
458 resolve (ArgFile f) = return f
459 resolve _ = []
460
461resolveForReport :: Maybe InputFileContext -> InputFile -> FilePath
462resolveForReport mctx (Pipe fdr fdw) = resolveForReport mctx (ArgFile str)
463 where str = case (fdr,fdw) of
464 (0,1) -> "-"
465 _ -> "&pipe" ++ show (fdr,fdw)
466resolveForReport mctx (FileDesc fd) = resolveForReport mctx (ArgFile str)
467 where str = "&" ++ show fd
468resolveForReport mctx f = concat $ resolveInputFile ctx f
469 where ctx = fromMaybe (InputFileContext "&secret" "&public") mctx
470
471filesToLock ::
472 KeyRingOperation -> InputFileContext -> [FilePath]
473filesToLock k ctx = do
474 (f,stream) <- Map.toList (opFiles k)
475 case fill stream of
476 KF_None -> []
477 _ -> resolveInputFile ctx f
478
479
480-- kret :: a -> KeyRingOperation a
481-- kret x = KeyRingOperation Map.empty Nothing (KeyRingAction x)
482
483data RSAPublicKey = RSAKey MPI MPI deriving (Eq,Show)
484data PKCS8_RSAPublicKey = RSAKey8 MPI MPI deriving Show
485
486pkcs8 :: RSAPublicKey -> PKCS8_RSAPublicKey
487pkcs8 (RSAKey n e) = RSAKey8 n e
488
489instance ASN1Object RSAPublicKey where
490 -- PKCS #1 RSA Public Key
491 toASN1 (RSAKey (MPI n) (MPI e))
492 = \xs -> Start Sequence
493 : IntVal n
494 : IntVal e
495 : End Sequence
496 : xs
497 fromASN1 (Start Sequence:IntVal n:IntVal e:End Sequence:xs) =
498 Right (RSAKey (MPI n) (MPI e), xs)
499
500 fromASN1 _ =
501 Left "fromASN1: RSAPublicKey: unexpected format"
502
503instance ASN1Object PKCS8_RSAPublicKey where
504
505 -- PKCS #8 Public key data
506 toASN1 (RSAKey8 (MPI n) (MPI e))
507 = \xs -> Start Sequence
508 : Start Sequence
509 : OID [1,2,840,113549,1,1,1]
510 : Null -- Doesn't seem to be neccessary, but i'm adding it
511 -- to match PEM files I see in the wild.
512 : End Sequence
513 : BitString (toBitArray bs 0)
514 : End Sequence
515 : xs
516 where
517 pubkey = [ Start Sequence, IntVal n, IntVal e, End Sequence ]
518 bs = encodeASN1' DER pubkey
519
520 fromASN1 (Start Sequence:IntVal modulus:IntVal pubexp:End Sequence:xs) =
521 Right (RSAKey8 (MPI modulus) (MPI pubexp) , xs)
522 fromASN1 (Start Sequence:Start Sequence:OID [1,2,840,113549,1,1,1]:Null:End Sequence:BitString b:End Sequence:xs) =
523 case decodeASN1' DER bs of
524 Right as -> fromASN1 as
525 Left e -> Left ("fromASN1: RSAPublicKey: "++show e)
526 where
527 BitArray _ bs = b
528 fromASN1 (Start Sequence:Start Sequence:OID [1,2,840,113549,1,1,1]:End Sequence:BitString b:End Sequence:xs) =
529 case decodeASN1' DER bs of
530 Right as -> fromASN1 as
531 Left e -> Left ("fromASN1: RSAPublicKey: "++show e)
532 where
533 BitArray _ bs = b
534
535 fromASN1 _ =
536 Left "fromASN1: RSAPublicKey: unexpected format"
537
538{-
539RSAPrivateKey ::= SEQUENCE {
540 version Version,
541 modulus INTEGER, -- n
542 publicExponent INTEGER, -- e
543 privateExponent INTEGER, -- d
544 prime1 INTEGER, -- p
545 prime2 INTEGER, -- q
546 exponent1 INTEGER, -- d mod (p1) -- ?? d mod (p-1)
547 exponent2 INTEGER, -- d mod (q-1)
548 coefficient INTEGER, -- (inverse of q) mod p
549 otherPrimeInfos OtherPrimeInfos OPTIONAL
550 }
551-}
552data RSAPrivateKey = RSAPrivateKey
553 { rsaN :: MPI
554 , rsaE :: MPI
555 , rsaD :: MPI
556 , rsaP :: MPI
557 , rsaQ :: MPI
558 , rsaDmodP1 :: MPI
559 , rsaDmodQminus1 :: MPI
560 , rsaCoefficient :: MPI
561 }
562 deriving Show
563
564instance ASN1Object RSAPrivateKey where
565 toASN1 rsa@(RSAPrivateKey {})
566 = \xs -> Start Sequence
567 : IntVal 0
568 : mpiVal rsaN
569 : mpiVal rsaE
570 : mpiVal rsaD
571 : mpiVal rsaP
572 : mpiVal rsaQ
573 : mpiVal rsaDmodP1
574 : mpiVal rsaDmodQminus1
575 : mpiVal rsaCoefficient
576 : End Sequence
577 : xs
578 where mpiVal f = IntVal x where MPI x = f rsa
579
580 fromASN1 ( Start Sequence
581 : IntVal _ -- version
582 : IntVal n
583 : IntVal e
584 : IntVal d
585 : IntVal p
586 : IntVal q
587 : IntVal dmodp1
588 : IntVal dmodqminus1
589 : IntVal coefficient
590 : ys) =
591 Right ( privkey, tail $ dropWhile notend ys)
592 where
593 notend (End Sequence) = False
594 notend _ = True
595 privkey = RSAPrivateKey
596 { rsaN = MPI n
597 , rsaE = MPI e
598 , rsaD = MPI d
599 , rsaP = MPI p
600 , rsaQ = MPI q
601 , rsaDmodP1 = MPI dmodp1
602 , rsaDmodQminus1 = MPI dmodqminus1
603 , rsaCoefficient = MPI coefficient
604 }
605 fromASN1 _ =
606 Left "fromASN1: RSAPrivateKey: unexpected format"
607
608
609
610-- | This type is used to indicate success or failure
611-- and in the case of success, return the computed object.
612-- The 'FunctorToMaybe' class is implemented to facilitate
613-- branching on failture.
614data KikiCondition a = KikiSuccess a
615 | FailedToLock [FilePath]
616 | BadPassphrase
617 | FailedToMakeSignature
618 | CantFindHome
619 | AmbiguousKeySpec FilePath
620 | CannotImportMasterKey
621 | NoWorkingKey
622 deriving ( Functor, Show )
623
624instance FunctorToMaybe KikiCondition where
625 functorToMaybe (KikiSuccess a) = Just a
626 functorToMaybe _ = Nothing
627
628instance Applicative KikiCondition where
629 pure a = KikiSuccess a
630 f <*> a =
631 case functorToEither f of
632 Right f -> case functorToEither a of
633 Right a -> pure (f a)
634 Left err -> err
635 Left err -> err
636
637-- | This type is used to describe events triggered by 'runKeyRing'. In
638-- addition to normal feedback (e.g. 'NewPacket'), it also may indicate
639-- non-fatal IO exceptions (e.g. 'FailedExternal'). Because a
640-- 'KeyRingOperation' may describe a very intricate multifaceted algorithm with
641-- many inputs and outputs, an operation may be partially (or even mostly)
642-- successful even when I/O failures occured. In this situation, the files may
643-- not have all the information they were intended to store, but they will be
644-- in a valid format for GnuPG or kiki to operate on in the future.
645data KikiReportAction =
646 NewPacket String
647 | MissingPacket String
648 | ExportedSubkey
649 | GeneratedSubkeyFile
650 | NewWalletKey String
651 | YieldSignature
652 | YieldSecretKeyPacket String
653 | UnableToUpdateExpiredSignature
654 | WarnFailedToMakeSignature
655 | FailedExternal Int
656 | ExternallyGeneratedFile
657 | UnableToExport KeyAlgorithm String
658 | FailedFileWrite
659 | HostsDiff ByteString
660 | DeletedPacket String
661 deriving Show
662
663uncamel :: String -> String
664uncamel str = unwords $ firstWord ++ (toLower .: otherWords) ++ args
665 where
666 (.:) = fmap . fmap
667 ( firstWord ,
668 otherWords ) = splitAt 1 ws
669 ws = camel >>= groupBy (\_ c -> isLower c)
670 ( camel, args) = splitAt 1 $ words str
671
672reportString :: KikiReportAction -> String
673reportString x = uncamel $ show x
674
675errorString :: KikiCondition a -> String
676errorString (KikiSuccess {}) = "success"
677errorString e = uncamel . show $ fmap (const ()) e
678
679-- | Errors in kiki are indicated by the returning of this record.
680data KikiResult a = KikiResult
681 { kikiCondition :: KikiCondition a
682 -- ^ The result or a fatal error condition.
683 , kikiReport :: KikiReport
684 -- ^ A list of non-fatal warnings and informational messages
685 -- along with the files that triggered them.
686 }
687
688type KikiReport = [ (FilePath, KikiReportAction) ]
689
690keyPacket :: KeyData -> Packet
691keyPacket (KeyData k _ _ _) = packet k
692
693subkeyMappedPacket :: SubKey -> MappedPacket
694subkeyMappedPacket (SubKey k _ ) = k
695
696
697usage :: SignatureSubpacket -> Maybe String
698usage (NotationDataPacket
699 { human_readable = True
700 , notation_name = "usage@"
701 , notation_value = u
702 }) = Just u
703usage _ = Nothing
704
705x509cert :: SignatureSubpacket -> Maybe Char8.ByteString
706x509cert (NotationDataPacket
707 { human_readable = False
708 , notation_name = "x509cert@"
709 , notation_value = u
710 }) = Just (Char8.pack u)
711x509cert _ = Nothing
712
713makeInducerSig
714 :: Packet
715 -> Packet -> Packet -> [SignatureSubpacket] -> SignatureOver
716-- torsig g topk wkun uid timestamp extras = todo
717makeInducerSig topk wkun uid extras
718 = CertificationSignature (secretToPublic topk)
719 uid
720 (sigpackets 0x13
721 subpackets
722 subpackets_unh)
723 where
724 subpackets = -- implicit: [ SignatureCreationTimePacket (fromIntegral timestamp) ]
725 tsign
726 ++ extras
727 subpackets_unh = [IssuerPacket (fingerprint wkun)]
728 tsign = if keykey wkun == keykey topk
729 then [] -- tsign doesnt make sense for self-signatures
730 else [ TrustSignaturePacket 1 120
731 , RegularExpressionPacket regex]
732 -- <[^>]+[@.]asdf\.nowhere>$
733 regex = "<[^>]+[@.]"++hostname++">$"
734 -- regex = username ++ "@" ++ hostname
735 -- username = "[a-zA-Z0-9.][-a-zA-Z0-9.]*\\$?" :: String
736 hostname = subdomain' pu ++ "\\." ++ topdomain' pu
737 pu = parseUID uidstr where UserIDPacket uidstr = uid
738 subdomain' = escape . T.unpack . uid_subdomain
739 topdomain' = escape . T.unpack . uid_topdomain
740 escape s = concatMap echar s
741 where
742 echar '|' = "\\|"
743 echar '*' = "\\*"
744 echar '+' = "\\+"
745 echar '?' = "\\?"
746 echar '.' = "\\."
747 echar '^' = "\\^"
748 echar '$' = "\\$"
749 echar '\\' = "\\\\"
750 echar '[' = "\\["
751 echar ']' = "\\]"
752 echar c = [c]
753
754
755keyflags :: SignatureSubpacket -> Maybe PGPKeyFlags
756keyflags flgs@(KeyFlagsPacket {}) =
757 Just . toEnum $
758 ( bit 0x1 certify_keys
759 .|. bit 0x2 sign_data
760 .|. bit 0x4 encrypt_communication
761 .|. bit 0x8 encrypt_storage ) :: Maybe PGPKeyFlags
762 -- other flags:
763 -- split_key
764 -- authentication (ssh-client)
765 -- group_key
766 where
767 bit v f = if f flgs then v else 0
768keyflags _ = Nothing
769
770
771data PGPKeyFlags =
772 Special
773 | Vouch -- Signkey
774 | Sign
775 | VouchSign
776 | Communication
777 | VouchCommunication
778 | SignCommunication
779 | VouchSignCommunication
780 | Storage
781 | VouchStorage
782 | SignStorage
783 | VouchSignStorage
784 | Encrypt
785 | VouchEncrypt
786 | SignEncrypt
787 | VouchSignEncrypt
788 deriving (Eq,Show,Read,Enum)
789
790
791usageString :: PGPKeyFlags -> String
792usageString flgs =
793 case flgs of
794 Special -> "special"
795 Vouch -> "vouch" -- signkey
796 Sign -> "sign"
797 VouchSign -> "vouch-sign"
798 Communication -> "communication"
799 VouchCommunication -> "vouch-communication"
800 SignCommunication -> "sign-communication"
801 VouchSignCommunication -> "vouch-sign-communication"
802 Storage -> "storage"
803 VouchStorage -> "vouch-storage"
804 SignStorage -> "sign-storage"
805 VouchSignStorage -> "vouch-sign-storage"
806 Encrypt -> "encrypt"
807 VouchEncrypt -> "vouch-encrypt"
808 SignEncrypt -> "sign-encrypt"
809 VouchSignEncrypt -> "vouch-sign-encrypt"
810
811
812
813
814-- matchpr computes the fingerprint of the given key truncated to
815-- be the same lenght as the given fingerprint for comparison.
816matchpr :: String -> Packet -> String
817matchpr fp k = reverse $ zipWith const (reverse (fingerprint k)) fp
818
819keyFlags :: t -> [Packet] -> [SignatureSubpacket]
820keyFlags wkun uids = keyFlags0 wkun (filter isSignaturePacket uids)
821
822keyFlags0 :: t -> [Packet] -> [SignatureSubpacket]
823keyFlags0 wkun uidsigs = concat
824 [ keyflags
825 , preferredsym
826 , preferredhash
827 , preferredcomp
828 , features ]
829
830 where
831 subs = concatMap hashed_subpackets uidsigs
832 keyflags = filterOr isflags subs $
833 KeyFlagsPacket { certify_keys = True
834 , sign_data = True
835 , encrypt_communication = False
836 , encrypt_storage = False
837 , split_key = False
838 , authentication = False
839 , group_key = False
840 }
841 preferredsym = filterOr ispreferedsym subs $
842 PreferredSymmetricAlgorithmsPacket
843 [ AES256
844 , AES192
845 , AES128
846 , CAST5
847 , TripleDES
848 ]
849 preferredhash = filterOr ispreferedhash subs $
850 PreferredHashAlgorithmsPacket
851 [ SHA256
852 , SHA1
853 , SHA384
854 , SHA512
855 , SHA224
856 ]
857 preferredcomp = filterOr ispreferedcomp subs $
858 PreferredCompressionAlgorithmsPacket
859 [ ZLIB
860 , BZip2
861 , ZIP
862 ]
863 features = filterOr isfeatures subs $
864 FeaturesPacket { supports_mdc = True
865 }
866
867 filterOr pred xs def = if null rs then [def] else rs where rs=filter pred xs
868
869 isflags (KeyFlagsPacket {}) = True
870 isflags _ = False
871 ispreferedsym (PreferredSymmetricAlgorithmsPacket {}) = True
872 ispreferedsym _ = False
873 ispreferedhash (PreferredHashAlgorithmsPacket {}) = True
874 ispreferedhash _ = False
875 ispreferedcomp (PreferredCompressionAlgorithmsPacket {}) = True
876 ispreferedcomp _ = False
877 isfeatures (FeaturesPacket {}) = True
878 isfeatures _ = False
879
880
881matchSpec :: KeySpec -> KeyData -> Bool
882matchSpec (KeyGrip grip) (KeyData p _ _ _)
883 | matchpr grip (packet p)==grip = True
884 | otherwise = False
885
886matchSpec (KeyTag key tag) (KeyData _ sigs _ _) = not . null $ filter match ps
887 where
888 ps = map (packet .fst) sigs
889 match p = isSignaturePacket p
890 && has_tag tag p
891 && has_issuer key p
892 has_issuer key p = isJust $ do
893 issuer <- signature_issuer p
894 guard $ matchpr issuer key == issuer
895 has_tag tag p = tag `elem` mapMaybe usage (hashed_subpackets p)
896 || tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p))
897
898matchSpec (KeyUidMatch pat) (KeyData _ _ uids _) = not $ null us
899 where
900 us = filter (isInfixOf pat) $ Map.keys uids
901
902data UserIDRecord = UserIDRecord {
903 uid_full :: String,
904 uid_realname :: T.Text,
905 uid_user :: T.Text,
906 uid_subdomain :: T.Text,
907 uid_topdomain :: T.Text
908}
909 deriving Show
910
911parseUID :: String -> UserIDRecord
912parseUID str = UserIDRecord {
913 uid_full = str,
914 uid_realname = realname,
915 uid_user = user,
916 uid_subdomain = subdomain,
917 uid_topdomain = topdomain
918 }
919 where
920 text = T.pack str
921 (T.strip-> realname, T.dropAround isBracket-> email)
922 = T.break (=='<') text
923 (user, T.drop 1-> hostname) = T.break (=='@') email
924 ( T.reverse -> topdomain,
925 T.reverse . T.drop 1 -> subdomain)
926 = T.break (=='.') . T.reverse $ hostname
927isBracket :: Char -> Bool
928isBracket '<' = True
929isBracket '>' = True
930isBracket _ = False
931
932
933
934
935data KeySpec =
936 KeyGrip String -- fp:
937 | KeyTag Packet String -- fp:????/t:
938 | KeyUidMatch String -- u:
939 deriving Show
940
941data MatchingField = UserIDField | KeyTypeField deriving (Show,Eq,Ord,Enum)
942data SingleKeySpec = FingerprintMatch String
943 | SubstringMatch (Maybe MatchingField) String
944 | EmptyMatch
945 | AnyMatch
946 | WorkingKeyMatch
947 deriving (Show,Eq,Ord)
948
949-- A pair of specs. The first specifies an identity and the second
950-- specifies a specific key (possibly master) associated with that
951-- identity.
952--
953-- When no slash is specified, context will decide whether the SingleKeySpec
954-- is specifying an identity or a key belonging to the working identity.
955type Spec = (SingleKeySpec,SingleKeySpec)
956
957parseSingleSpec :: String -> SingleKeySpec
958parseSingleSpec "*" = AnyMatch
959parseSingleSpec "-" = WorkingKeyMatch
960parseSingleSpec "" = EmptyMatch
961parseSingleSpec ('t':':':tag) = SubstringMatch (Just KeyTypeField) tag
962parseSingleSpec ('u':':':tag) = SubstringMatch (Just UserIDField) tag
963parseSingleSpec ('f':'p':':':fp) = FingerprintMatch fp
964parseSingleSpec str
965 | is40digitHex str = FingerprintMatch str
966 | otherwise = SubstringMatch Nothing str
967
968is40digitHex xs = ys == xs && length ys==40
969 where
970 ys = filter ishex xs
971 ishex c | '0' <= c && c <= '9' = True
972 | 'A' <= c && c <= 'F' = True
973 | 'a' <= c && c <= 'f' = True
974 ishex c = False
975
976
977 -- t:tor -- (FingerprintMatch "", SubstringMatch "tor")
978 -- u:joe -- (SubstringMatch "joe", FingerprintMatch "")
979 -- u:joe/ -- (SubstringMatch "joe", FingerprintMatch "!")
980 -- fp:4A39F/tor -- (FingerprintMatch "4A39F", SubstringMatch "tor")
981 -- u:joe/tor -- (SubstringMatch "joe", SubstringMatch "tor")
982 -- u:joe/t:tor -- (SubstringMatch "joe", SubstringMatch "tor")
983 -- u:joe/fp:4abf30 -- (SubstringMatch "joe", FingerprintMatch "4abf30")
984 -- joe/tor -- (SubstringMatch "joe", SubstringMatch "tor")
985
986-- | Parse a key specification.
987-- The first argument is a grip for the default working key.
988parseSpec :: String -> String -> (KeySpec,Maybe String)
989parseSpec wkgrip spec =
990 if not slashed
991 then
992 case prespec of
993 AnyMatch -> (KeyGrip "", Nothing)
994 EmptyMatch -> error "Bad key spec."
995 WorkingKeyMatch -> (KeyGrip wkgrip, Nothing)
996 SubstringMatch (Just KeyTypeField) tag -> (KeyGrip wkgrip, Just tag)
997 SubstringMatch Nothing str -> (KeyGrip wkgrip, Just str)
998 SubstringMatch (Just UserIDField) ustr -> (KeyUidMatch ustr, Nothing)
999 FingerprintMatch fp -> (KeyGrip fp, Nothing)
1000 else
1001 case (prespec,postspec) of
1002 (FingerprintMatch fp, SubstringMatch st t)
1003 | st /= Just UserIDField -> (KeyGrip fp, Just t)
1004 (SubstringMatch mt u, _)
1005 | postspec `elem` [AnyMatch,EmptyMatch]
1006 && mt /= Just KeyTypeField -> (KeyUidMatch u, Nothing)
1007 (SubstringMatch mt u, SubstringMatch st t)
1008 | mt /= Just KeyTypeField
1009 && st /= Just UserIDField -> (KeyUidMatch u, Just t)
1010 (FingerprintMatch _,FingerprintMatch _) -> error "todo: support fp:/fp: spec"
1011 (_,FingerprintMatch fp) -> error "todo: support /fp: spec"
1012 (FingerprintMatch fp,_) -> error "todo: support fp:/ spec"
1013 _ -> error "Bad key spec."
1014 where
1015 (preslash,slashon) = break (=='/') spec
1016 slashed = not $ null $ take 1 slashon
1017 postslash = drop 1 slashon
1018
1019 prespec = parseSingleSpec preslash
1020 postspec = parseSingleSpec postslash
1021
1022{-
1023 - BUGGY
1024parseSpec grip spec = (topspec,subspec)
1025 where
1026 (topspec0,subspec0) = unprefix '/' spec
1027 (toptyp,top) = unprefix ':' topspec0
1028 (subtyp,sub) = unprefix ':' subspec0
1029 topspec = case () of
1030 _ | null top && or [ subtyp=="fp"
1031 , null subtyp && is40digitHex sub
1032 ]
1033 -> KeyGrip sub
1034 _ | null top && null grip -> KeyUidMatch sub
1035 _ | null top -> KeyGrip grip
1036 _ | toptyp=="fp" || (null toptyp && is40digitHex top)
1037 -> KeyGrip top
1038 _ | toptyp=="u" -> KeyUidMatch top
1039 _ -> KeyUidMatch top
1040 subspec = case subtyp of
1041 "t" -> Just sub
1042 "fp" | top=="" -> Nothing
1043 "" | top=="" && is40digitHex sub -> Nothing
1044 "" -> listToMaybe sub >> Just sub
1045 _ -> Nothing
1046
1047 is40digitHex xs = ys == xs && length ys==40
1048 where
1049 ys = filter ishex xs
1050 ishex c | '0' <= c && c <= '9' = True
1051 | 'A' <= c && c <= 'F' = True
1052 | 'a' <= c && c <= 'f' = True
1053 ishex c = False
1054
1055 -- | Split a string into two at the first occurance of the given
1056 -- delimiter. If the delimeter does not occur, then the first
1057 -- item of the returned pair is empty and the second item is the
1058 -- input string.
1059 unprefix c spec = if null (snd p) then swap p else (fst p, tail (snd p))
1060 where p = break (==c) spec
1061-}
1062
1063
1064filterMatches :: KeySpec -> [(KeyKey,KeyData)] -> [(KeyKey,KeyData)]
1065filterMatches spec ks = filter (matchSpec spec . snd) ks
1066
1067filterNewSubs :: FilePath -> (KeySpec,Maybe String) -> KeyData -> KeyData
1068filterNewSubs fname spec (KeyData p sigs uids subs) = KeyData p sigs uids subs'
1069 where
1070 matchAll = KeyGrip ""
1071
1072 subkeySpec (KeyGrip grip,Nothing) = (matchAll, KeyGrip grip)
1073 subkeySpec (topspec,Just mtag) = (topspec , KeyTag (packet p) mtag)
1074
1075 match spec mps
1076 = not . null
1077 . snd
1078 . seek_key spec
1079 . map packet
1080 $ mps
1081
1082 old sub = isJust (Map.lookup fname $ locations $ subkeyMappedPacket sub)
1083
1084 oldOrMatch spec sub = old sub
1085 || match spec (flattenSub "" True sub)
1086
1087 subs' = Map.filter (if match topspec $ flattenTop "" True (KeyData p sigs uids Map.empty)
1088 then oldOrMatch subspec
1089 else old)
1090 subs
1091 where
1092 (topspec,subspec) = subkeySpec spec
1093
1094selectSecretKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet
1095selectSecretKey (spec,mtag) db = selectKey0 False (spec,mtag) db
1096
1097selectPublicKey :: (KeySpec,Maybe String) -> KeyDB -> Maybe Packet
1098selectPublicKey (spec,mtag) db = selectKey0 True (spec,mtag) db
1099
1100selectPublicKeyAndSigs :: (KeySpec,Maybe String) -> KeyDB -> [(KeyKey,Packet,[Packet])]
1101selectPublicKeyAndSigs (spec,mtag) db =
1102 case mtag of
1103 Nothing -> do
1104 (kk,r) <- Map.toList $ fmap (findbyspec spec) db
1105 (sub,sigs) <- r
1106 return (kk,sub,sigs)
1107 Just tag -> Map.toList (Map.filter (matchSpec spec) db) >>= findsubs tag
1108 where
1109 topresult kd = (keyPacket kd, map (packet .fst) $ keySigAndTrusts kd)
1110
1111 findbyspec (KeyGrip g) kd = do
1112 filter ismatch $
1113 topresult kd
1114 : map (\(SubKey sub sigs)-> (packet sub, map (packet . fst) sigs))
1115 (Map.elems $ keySubKeys kd)
1116 where
1117 ismatch (p,sigs) = matchpr g p ==g
1118 findbyspec spec kd = if matchSpec spec kd then [topresult kd] else []
1119
1120 findsubs tag (kk, KeyData topk _ _ subs) = Map.elems subs >>= gettag
1121 where
1122 gettag (SubKey sub sigs) = do
1123 let (_,mb,_) = findTag [mkUsage tag] (packet topk) (packet sub) sigs
1124 (hastag,_) <- maybeToList mb
1125 guard hastag
1126 return $ (kk, packet sub, map (packet . fst) sigs)
1127
1128selectKey0 :: Bool -> (KeySpec,Maybe String) -> KeyDB -> Maybe Packet
1129selectKey0 wantPublic (spec,mtag) db = do
1130 let Message ps = flattenKeys wantPublic db
1131 ys = snd $ seek_key spec ps
1132 flip (maybe (listToMaybe ys)) mtag $ \tag -> do
1133 case ys of
1134 y:ys1 -> listToMaybe $ snd $ seek_key (KeyTag y tag) ys1
1135 [] -> Nothing
1136
1137{-
1138selectAll :: Bool -> (KeySpec,Maybe String) -> KeyDB -> [(Packet,Maybe Packet)]
1139selectAll wantPublic (spec,mtag) db = do
1140 let Message ps = flattenKeys wantPublic db
1141 ys = snd $ seek_key spec ps
1142 y <- take 1 ys
1143 case mtag of
1144 Nothing -> return (y,Nothing)
1145 Just tag ->
1146 let search ys1 = do
1147 let zs = snd $ seek_key (KeyTag y tag) ys1
1148 z <- take 1 zs
1149 (y,Just z):search (drop 1 zs)
1150 in search (drop 1 ys)
1151-}
1152
1153seek_key :: KeySpec -> [Packet] -> ([Packet],[Packet])
1154seek_key (KeyGrip grip) sec = (pre, subs)
1155 where
1156 (pre,subs) = break pred sec
1157 pred p@(SecretKeyPacket {}) = matchpr grip p == grip
1158 pred p@(PublicKeyPacket {}) = matchpr grip p == grip
1159 pred _ = False
1160
1161seek_key (KeyTag key tag) ps
1162 | null bs = (ps, [])
1163 | null qs =
1164 let (as', bs') = seek_key (KeyTag key tag) (tail bs) in
1165 (as ++ (head bs : as'), bs')
1166 | otherwise = (reverse (tail qs), head qs : reverse rs ++ bs)
1167 where
1168 (as,bs) = break (\p -> isSignaturePacket p
1169 && has_tag tag p
1170 && isJust (signature_issuer p)
1171 && matchpr (fromJust $ signature_issuer p) key == fromJust (signature_issuer p) )
1172 ps
1173 (rs,qs) = break isKey (reverse as)
1174
1175 has_tag tag p = tag `elem` mapMaybe usage (hashed_subpackets p)
1176 || tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p))
1177
1178seek_key (KeyUidMatch pat) ps
1179 | null bs = (ps, [])
1180 | null qs = let (as', bs') = seek_key (KeyUidMatch pat) (tail bs) in
1181 (as ++ (head bs : as'), bs')
1182 | otherwise = (reverse (tail qs), head qs : reverse rs ++ bs)
1183 where
1184 (as,bs) = break (isInfixOf pat . uidStr) ps
1185 (rs,qs) = break isKey (reverse as)
1186
1187 uidStr (UserIDPacket s) = s
1188 uidStr _ = ""
1189
1190
1191data InputFileContext = InputFileContext
1192 { homesecPath :: FilePath
1193 , homepubPath :: FilePath
1194 }
1195
1196readInputFileS :: InputFileContext -> InputFile -> IO S.ByteString
1197readInputFileS ctx (Pipe fd _) = fdToHandle fd >>= S.hGetContents
1198readInputFileS ctx (FileDesc fd) = fdToHandle fd >>= S.hGetContents
1199readInputFileS ctx inp = do
1200 let fname = resolveInputFile ctx inp
1201 fmap S.concat $ mapM S.readFile fname
1202
1203readInputFileL :: InputFileContext -> InputFile -> IO L.ByteString
1204readInputFileL ctx (Pipe fd _) = fdToHandle fd >>= L.hGetContents
1205readInputFileL ctx (FileDesc fd) = fdToHandle fd >>= L.hGetContents
1206readInputFileL ctx inp = do
1207 let fname = resolveInputFile ctx inp
1208 fmap L.concat $ mapM L.readFile fname
1209
1210
1211writeInputFileL ctx (Pipe _ fd) bs = fdToHandle fd >>= (`L.hPut` bs)
1212writeInputFileL ctx (FileDesc fd) bs = fdToHandle fd >>= (`L.hPut` bs)
1213writeInputFileL ctx inp bs = do
1214 let fname = resolveInputFile ctx inp
1215 mapM_ (`L.writeFile` bs) fname
1216
1217-- writeStamped0 :: InputFileContext -> InputFile -> Posix.EpochTime -> L.ByteString -> IO ()
1218-- writeStamped0 :: InputFileContext -> InputFile
1219
1220getWriteFD :: InputFile -> Maybe Posix.Fd
1221getWriteFD (Pipe _ fd) = Just fd
1222getWriteFD (FileDesc fd) = Just fd
1223getWriteFD _ = Nothing
1224
1225writeStamped0 :: InputFileContext
1226 -> InputFile
1227 -> Posix.EpochTime
1228 -> (Either Handle FilePath -> t -> IO ())
1229 -> t
1230 -> IO ()
1231writeStamped0 ctx (getWriteFD -> Just fd) stamp dowrite bs = do
1232 h <- fdToHandle fd
1233 dowrite (Left h) bs
1234 handleIO_ (return ())
1235 $ setFdTimesHiRes fd (realToFrac stamp) (realToFrac stamp)
1236writeStamped0 ctx inp stamp dowrite bs = do
1237 let fname = resolveInputFile ctx inp
1238 forM_ fname $ \fname -> do
1239 createDirectoryIfMissing True $ takeDirectory fname
1240 dowrite (Right fname) bs
1241 setFileTimes fname stamp stamp
1242
1243{- This may be useful later. Commented for now, as it is not used.
1244 -
1245writeStampedL :: InputFileContext -> InputFile -> Posix.EpochTime -> L.ByteString -> IO ()
1246writeStampedL ctx f stamp bs = writeStamped0 ctx f stamp (either L.hPut L.writeFile) bs
1247-}
1248
1249writeStamped :: InputFileContext -> InputFile -> Posix.EpochTime -> String -> IO ()
1250writeStamped ctx f stamp str = writeStamped0 ctx f stamp (either hPutStr writeFile) str
1251
1252getInputFileTime :: InputFileContext -> InputFile -> IO CTime
1253getInputFileTime ctx (Pipe fdr fdw) = do
1254 mt <- handleIO_ (return Nothing) $ Just <$> modificationTime <$> getFdStatus fdr
1255 maybe tryw return mt
1256 where
1257 tryw = do
1258 handleIO_ (error $ (resolveForReport Nothing $ Pipe fdr fdw) ++": modificaiton time?")
1259 $ modificationTime <$> getFdStatus fdw
1260getInputFileTime ctx (FileDesc fd) = do
1261 handleIO_ (error $ "&"++show fd++": modificaiton time?") $
1262 modificationTime <$> getFdStatus fd
1263getInputFileTime ctx (resolveInputFile ctx -> [fname]) = do
1264 handleIO_ (error $ fname++": modificaiton time?") $
1265 modificationTime <$> getFileStatus fname
1266
1267{-
1268 - This may be useful later. Commented for now as it is not used.
1269 -
1270doesInputFileExist :: InputFileContext -> InputFile -> IO Bool
1271doesInputFileExist ctx f = do
1272 case resolveInputFile ctx f of
1273 [n] -> doesFileExist n
1274 _ -> return True
1275-}
1276
1277
1278cachedContents :: Maybe S.ByteString -> InputFileContext -> InputFile -> IO (IO S.ByteString)
1279cachedContents maybePrompt ctx fd = do
1280 ref <- newIORef Nothing
1281 return $ get maybePrompt ref fd
1282 where
1283 trimCR bs = fst $ S.spanEnd (\x -> x==10 || x==13) bs
1284
1285 get maybePrompt ref fd = do
1286 pw <- readIORef ref
1287 flip (flip maybe return) pw $ do
1288 if fd == FileDesc 0 then case maybePrompt of
1289 Just prompt -> S.hPutStr stderr prompt
1290 Nothing -> return ()
1291 else return ()
1292 pw <- fmap trimCR $ readInputFileS ctx fd
1293 writeIORef ref (Just pw)
1294 return pw
1295
1296generateSubkey ::
1297 (MappedPacket -> IO (KikiCondition Packet)) -- decrypt[
1298 -> KikiCondition (KeyData, [(FilePath, KikiReportAction)]) -- db
1299 -> (GenerateKeyParams, StreamInfo)
1300 -> IO (KikiCondition (KeyData, [(FilePath, KikiReportAction)]))
1301generateSubkey doDecrypt kd' (genparam,StreamInfo { spill = KF_Match tag }) = do
1302 try kd' $ \(kd,report0) -> do
1303 let subs = do
1304 SubKey p sigs <- Map.elems $ keySubKeys kd
1305 filter (has_tag tag) $ map (packet . fst) sigs
1306 if null subs
1307 then do
1308 newkey <- generateKey genparam
1309 kdr <- insertSubkey doDecrypt (keykey (keyPacket kd)) kd [mkUsage tag] "" newkey
1310 try kdr $ \(newkd,report) -> do
1311 return $ KikiSuccess (newkd, report ++ [("", NewPacket $ showPacket newkey)])
1312 else do
1313 return $ KikiSuccess (kd,report0)
1314generateSubkey _ kd _ = return kd
1315
1316importSecretKey ::
1317 (MappedPacket -> IO (KikiCondition Packet))
1318 -> KikiCondition
1319 (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)])
1320 -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t)
1321 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]))
1322importSecretKey doDecrypt db' tup = do
1323 try db' $ \(db',report0) -> do
1324 r <- doImport doDecrypt
1325 db'
1326 tup
1327 try r $ \(db'',report) -> do
1328 return $ KikiSuccess (db'', report0 ++ report)
1329
1330
1331mergeHostFiles :: KeyRingOperation -> KeyDB -> InputFileContext
1332 -> IO
1333 (KikiCondition
1334 ( ( Map.Map [Char8.ByteString] KeyData
1335 , ( [Hosts.Hosts]
1336 , [Hosts.Hosts]
1337 , Hosts.Hosts
1338 , [(SockAddr, ([Char8.ByteString], [Char8.ByteString]))]
1339 , [SockAddr]))
1340 , [(FilePath,KikiReportAction)]))
1341mergeHostFiles krd db ctx = do
1342 let hns = files ishosts
1343 ishosts Hosts = True
1344 ishosts _ = False
1345 files istyp = do
1346 (f,stream) <- Map.toList (opFiles krd)
1347 guard (istyp $ typ stream)
1348 return f
1349
1350 hostdbs0 <- mapM (fmap Hosts.decode . readInputFileL ctx) hns
1351
1352 let gpgnames = map getHostnames $ Map.elems db
1353 os = do
1354 (addr,(ns,_)) <- gpgnames
1355 n <- ns
1356 return (addr,n)
1357 setOnions hosts = foldl' (flip $ uncurry Hosts.assignName) hosts os
1358 -- we ensure .onion names are set properly
1359 hostdbs = map setOnions hostdbs0
1360 outgoing_names = do
1361 (addr,(_,gns)) <- gpgnames
1362 guard . not $ null gns
1363 guard $ all (null . Hosts.namesForAddress addr) hostdbs0
1364 return addr
1365 -- putStrLn $ "hostdbs = " ++ show hostdbs
1366
1367 -- 1. let U = union all the host dbs
1368 -- preserving whitespace and comments of the first
1369 let u0 = foldl' Hosts.plus Hosts.empty hostdbs
1370 -- we filter U to be only finger-dresses
1371 u1 = Hosts.filterAddrs (hasFingerDress db) u0
1372
1373 -- let nf h = map Char8.unpack $ Hosts.namesForAddress (fromJust $ Hosts.inet_pton "fdf4:ed98:29c7:6226:9bde:a5b4:d564:3321") h
1374 {-
1375 putStrLn $ "_ = {\n" ++ show (head hostdbs) ++ "}"
1376 putStrLn $ "--> " ++ show (nf (head hostdbs))
1377 putStrLn $ "u0 = {\n" ++ show u0 ++ "}"
1378 putStrLn $ "--> " ++ show (nf u0)
1379 putStrLn $ "u1 = {\n" ++ show u1 ++ "}"
1380 putStrLn $ "--> " ++ show (nf u1)
1381 -}
1382
1383 -- 2. replace gpg annotations with those in U
1384 -- forM use_db
1385 db' <- Traversable.mapM (setHostnames (`notElem` outgoing_names) u1) db
1386
1387 return $ KikiSuccess ((db',(hostdbs0,hostdbs,u1,gpgnames,outgoing_names)),[])
1388
1389writeHostsFiles
1390 :: KeyRingOperation -> InputFileContext
1391 -> ([Hosts.Hosts],
1392 [Hosts.Hosts],
1393 Hosts.Hosts,
1394 [(SockAddr, (t1, [Char8.ByteString]))],
1395 [SockAddr])
1396 -> IO [(FilePath, KikiReportAction)]
1397writeHostsFiles krd ctx (hostdbs0,hostdbs,u1,gpgnames,outgoing_names) = do
1398 let hns = files isMutableHosts
1399 isMutableHosts (fill -> KF_None) = False
1400 isMutableHosts (typ -> Hosts) = True
1401 isMutableHosts _ = False
1402 files istyp = do
1403 (f,stream) <- Map.toList (opFiles krd)
1404 guard (istyp stream)
1405 return f -- resolveInputFile ctx f
1406
1407 -- 3. add hostnames from gpg for addresses not in U
1408 let u = foldl' f u1 ans
1409 ans = reverse $ do
1410 (addr,(_,ns)) <- gpgnames
1411 guard $ addr `elem` outgoing_names -- . null $ Hosts.namesForAddress addr u0
1412 n <- ns
1413 return (addr,n)
1414 f h (addr,n) = Hosts.assignNewName addr n h
1415
1416 -- 4. for each host db H, union H with U and write it out as H'
1417 -- only if there is a non-empty diff
1418 rss <- forM (zip hns $ zip hostdbs0 hostdbs) $ \(fname,(h0,h1)) -> do
1419 let h = h1 `Hosts.plus` u
1420 d = Hosts.diff h0 h
1421 rs = map ((fname,) . HostsDiff) d
1422 unless (null d) $ writeInputFileL ctx fname $ Hosts.encode h
1423 return $ map (first $ resolveForReport $ Just ctx) rs
1424 return $ concat rss
1425
1426isSecretKey :: Packet -> Bool
1427isSecretKey (SecretKeyPacket {}) = True
1428isSecretKey _ = False
1429
1430buildKeyDB :: InputFileContext -> Maybe String -> KeyRingOperation
1431 -> IO (KikiCondition ((KeyDB
1432 ,Maybe String
1433 ,Maybe MappedPacket
1434 ,([Hosts.Hosts],
1435 [Hosts.Hosts],
1436 Hosts.Hosts,
1437 [(SockAddr, (KeyKey, KeyKey))],
1438 [SockAddr])
1439 ,Map.Map InputFile Access
1440 ,MappedPacket -> IO (KikiCondition Packet)
1441 ,Map.Map InputFile Message
1442 )
1443 ,[(FilePath,KikiReportAction)]))
1444buildKeyDB ctx grip0 keyring = do
1445 let
1446 files istyp = do
1447 (f,stream) <- Map.toList (opFiles keyring)
1448 guard (istyp $ typ stream)
1449 resolveInputFile ctx f
1450
1451 ringMap0 = Map.filter (isring . typ) $ opFiles keyring
1452 (genMap,ringMap) = Map.partitionWithKey isgen ringMap0
1453 where
1454 isgen (Generate _ _) _ = True
1455 isgen _ _ = False
1456
1457 readp f stream = fmap readp0 $ readPacketsFromFile ctx f
1458 where
1459 readp0 ps = (stream { access = acc' }, ps)
1460 where acc' = case access stream of
1461 AutoAccess ->
1462 case ps of
1463 Message ((PublicKeyPacket {}):_) -> Pub
1464 Message ((SecretKeyPacket {}):_) -> Sec
1465 _ -> AutoAccess
1466 acc -> acc
1467
1468 readw wk n = fmap (n,) (readPacketsFromWallet wk (ArgFile n))
1469
1470 -- KeyRings (todo: KikiCondition reporting?)
1471 (spilled,mwk,grip,accs,keys,unspilled) <- do
1472#if MIN_VERSION_containers(0,5,0)
1473 ringPackets <- Map.traverseWithKey readp ringMap
1474#else
1475 ringPackets <- Traversable.traverse (uncurry readp) $ Map.mapWithKey (,) ringMap
1476#endif
1477 let _ = ringPackets :: Map.Map InputFile (StreamInfo, Message)
1478
1479 let grip = grip0 `mplus` (fingerprint <$> fstkey)
1480 where
1481 fstkey = do
1482 (_,Message ps) <- Map.lookup HomeSec ringPackets
1483 listToMaybe ps
1484 (spilled,unspilled) = Map.partition (spillable . fst) ringPackets
1485 keys :: Map.Map KeyKey MappedPacket
1486 keys = Map.foldl slurpkeys Map.empty
1487 $ Map.mapWithKey filterSecrets ringPackets
1488 where
1489 filterSecrets f (_,Message ps) =
1490 filter (isSecretKey . packet)
1491 $ zipWith (mappedPacketWithHint fname) ps [1..]
1492 where fname = resolveForReport (Just ctx) f
1493 slurpkeys m ps = m `Map.union` Map.fromList ps'
1494 where ps' = zip (map (keykey . packet) ps) ps
1495 wk = listToMaybe $ do
1496 fp <- maybeToList grip
1497 let matchfp mp = not (is_subkey p) && matchpr fp p == fp
1498 where p = packet mp
1499 Map.elems $ Map.filter matchfp keys
1500 accs = fmap (access . fst) ringPackets
1501 return (spilled,wk,grip,accs,keys,fmap snd unspilled)
1502
1503 doDecrypt <- makeMemoizingDecrypter keyring ctx keys
1504
1505 let wk = fmap packet mwk
1506 rt0 = KeyRingRuntime { rtPubring = homepubPath ctx
1507 , rtSecring = homesecPath ctx
1508 , rtGrip = grip
1509 , rtWorkingKey = wk
1510 , rtRingAccess = accs
1511 , rtKeyDB = Map.empty
1512 , rtPassphrases = doDecrypt
1513 }
1514 transformed0 <-
1515 let trans f (info,ps) = do
1516 let manip = combineTransforms (transforms info)
1517 rt1 = rt0 { rtKeyDB = merge Map.empty f ps }
1518 acc = Just Sec /= Map.lookup f accs
1519 r <- performManipulations doDecrypt rt1 mwk manip
1520 try r $ \(rt2,report) -> do
1521 return $ KikiSuccess (report,(info,flattenKeys acc $ rtKeyDB rt2))
1522#if MIN_VERSION_containers(0,5,0)
1523 in fmap sequenceA $ Map.traverseWithKey trans spilled
1524#else
1525 in fmap sequenceA $ Traversable.traverse (uncurry trans) $ Map.mapWithKey (,) spilled
1526#endif
1527 try transformed0 $ \transformed -> do
1528 let db_rings = Map.foldlWithKey' mergeIt Map.empty transformed
1529 where
1530 mergeIt db f (_,(info,ps)) = merge db f ps
1531 reportTrans = concat $ Map.elems $ fmap fst transformed
1532
1533 -- Wallets
1534 let importWalletKey wk db' (top,fname,sub,tag) = do
1535 try db' $ \(db',report0) -> do
1536 r <- doImportG doDecrypt
1537 db'
1538 (fmap keykey $ maybeToList wk)
1539 [mkUsage tag]
1540 fname
1541 sub
1542 try r $ \(db'',report) -> do
1543 return $ KikiSuccess (db'', report0 ++ report)
1544
1545 wms <- mapM (readw wk) (files iswallet)
1546 let wallet_keys = do
1547 maybeToList wk
1548 (fname,xs) <- wms
1549 (_,sub,(_,m)) <- xs
1550 (tag,top) <- Map.toList m
1551 return (top,fname,sub,tag)
1552 db <- foldM (importWalletKey wk) (KikiSuccess (db_rings,[])) wallet_keys
1553 try db $ \(db,reportWallets) -> do
1554
1555 -- PEM files
1556 let pems = do
1557 (n,stream) <- Map.toList $ opFiles keyring
1558 grip <- maybeToList grip
1559 n <- resolveInputFile ctx n
1560 guard $ spillable stream && isSecretKeyFile (typ stream)
1561 let us = mapMaybe usageFromFilter [fill stream,spill stream]
1562 usage <- take 1 us
1563 guard $ all (==usage) $ drop 1 us
1564 -- TODO: KikiCondition reporting for spill/fill usage mismatch?
1565 let (topspec,subspec) = parseSpec grip usage
1566 ms = map fst $ filterMatches topspec (Map.toList db)
1567 cmd = initializer stream
1568 return (n,subspec,ms,stream, cmd)
1569 imports <- filterM (\(n,_,_,_,_) -> doesFileExist n) pems
1570 db <- foldM (importSecretKey doDecrypt) (KikiSuccess (db,[])) imports
1571 try db $ \(db,reportPEMs) -> do
1572
1573 -- generate keys
1574 let gens = mapMaybe g $ Map.toList genMap
1575 where g (Generate _ params,v) = Just (params,v)
1576 g _ = Nothing
1577
1578 db <- generateInternals doDecrypt mwk db gens
1579 try db $ \(db,reportGens) -> do
1580
1581 r <- mergeHostFiles keyring db ctx
1582 try r $ \((db,hs),reportHosts) -> do
1583
1584 return $ KikiSuccess ( (db, grip, mwk, hs, accs, doDecrypt, unspilled)
1585 , reportTrans ++ reportWallets ++ reportPEMs ++ reportGens ++ reportHosts )
1586
1587generateInternals ::
1588 (MappedPacket -> IO (KikiCondition Packet))
1589 -> Maybe MappedPacket
1590 -> Map.Map KeyKey KeyData
1591 -> [(GenerateKeyParams,StreamInfo)]
1592 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath, KikiReportAction)]))
1593generateInternals doDecrypt mwk db gens = do
1594 case fmap packet mwk >>= \wk -> Map.lookup (keykey wk) db of
1595 Just kd0 -> do
1596 kd <- foldM (generateSubkey doDecrypt) (KikiSuccess (kd0,[])) gens
1597 try kd $ \(kd,reportGens) -> do
1598 let kk = keykey $ packet $ fromJust mwk
1599 return $ KikiSuccess (Map.insert kk kd db,reportGens)
1600 Nothing -> return $ KikiSuccess (db,[])
1601
1602torhash :: Packet -> String
1603torhash key = fromMaybe "" $ derToBase32 <$> derRSA key
1604
1605derToBase32 :: ByteString -> String
1606#if !defined(VERSION_cryptonite)
1607derToBase32 = map toLower . Base32.encode . S.unpack . SHA1.hashlazy
1608#else
1609derToBase32 = map toLower . Base32.encode . S.unpack . sha1
1610 where
1611 sha1 :: L.ByteString -> S.ByteString
1612 sha1 x = convert (Vincent.hashlazy x :: Vincent.Digest Vincent.SHA1)
1613#endif
1614
1615derRSA :: Packet -> Maybe ByteString
1616derRSA rsa = do
1617 k <- rsaKeyFromPacket rsa
1618 return $ encodeASN1 DER (toASN1 k [])
1619
1620unconditionally :: IO (KikiCondition a) -> IO a
1621unconditionally action = do
1622 r <- action
1623 case r of
1624 KikiSuccess x -> return x
1625 e -> error $ errorString e
1626
1627try :: Monad m => KikiCondition a -> (a -> m (KikiCondition b)) -> m (KikiCondition b)
1628try x body =
1629 case functorToEither x of
1630 Left e -> return e
1631 Right x -> body x
1632
1633
1634data ParsedCert = ParsedCert
1635 { pcertKey :: Packet
1636 , pcertTimestamp :: UTCTime
1637 , pcertBlob :: L.ByteString
1638 }
1639 deriving (Show,Eq)
1640data SecretPEMData = PEMPacket Packet | PEMCertificate ParsedCert
1641 deriving (Show,Eq)
1642
1643spemPacket (PEMPacket p) = Just p
1644spemPacket _ = Nothing
1645
1646spemCert (PEMCertificate p) = Just p
1647spemCert _ = Nothing
1648
1649toStrict :: L.ByteString -> S.ByteString
1650toStrict = foldr1 (<>) . L.toChunks
1651
1652-- No instance for (ASN1Object RSA.PublicKey)
1653
1654parseCertBlob comp bs = do
1655 asn1 <- either (const Nothing) Just
1656 $ decodeASN1 DER bs
1657 let asn1' = drop 2 asn1
1658 cert <- either (const Nothing) (Just . fst) (fromASN1 asn1')
1659 let _ = cert :: X509.Certificate
1660 notBefore :: UTCTime
1661#if MIN_VERSION_x509(1,5,0)
1662 notBefore = toUTC ( timeFromElapsedP (timeGetElapsedP vincentTime) :: CTime) -- nanoToUTCTime nano
1663 where (vincentTime,_) = X509.certValidity cert
1664#else
1665 (notBefore,_) = X509.certValidity cert
1666#endif
1667 case X509.certPubKey cert of
1668 X509.PubKeyRSA key -> do
1669 let withoutkey =
1670 let ekey = toStrict $ encodeASN1 DER (toASN1 key [])
1671 (pre,post) = S.breakSubstring ekey $ toStrict bs
1672 post' = S.drop (S.length ekey) post
1673 len :: Word16
1674 len = if S.null post then maxBound
1675 else fromIntegral $ S.length pre
1676 in if len < 4096
1677 then encode len <> GZip.compress (Char8.fromChunks [pre,post'])
1678 else bs
1679 return
1680 ParsedCert { pcertKey = packetFromPublicRSAKey notBefore
1681 (MPI $ RSA.public_n key)
1682 (MPI $ RSA.public_e key)
1683 , pcertTimestamp = notBefore
1684 , pcertBlob = if comp then withoutkey
1685 else bs
1686 }
1687 _ -> Nothing
1688
1689packetFromPublicRSAKey notBefore n e =
1690 PublicKeyPacket { version = 4
1691 , timestamp = round $ utcTimeToPOSIXSeconds notBefore
1692 , key_algorithm = RSA
1693 , key = [('n',n),('e',e)]
1694 , is_subkey = True
1695 , v3_days_of_validity = Nothing
1696 }
1697
1698decodeBlob cert =
1699 if 0 /= (bs `L.index` 0) .&. 0x10
1700 then bs
1701 else let (keypos0,bs') = L.splitAt 2 bs
1702 keypos :: Word16
1703 keypos = decode keypos0
1704 ds = GZip.decompress bs'
1705 (prekey,postkey) = L.splitAt (fromIntegral keypos) ds
1706 in prekey <> key <> postkey
1707 where
1708 bs = pcertBlob cert
1709 key = maybe "" (encodeASN1 DER . flip toASN1 []) $ rsaKeyFromPacket $ pcertKey cert
1710
1711extractRSAKeyFields :: [(ByteString,ByteString)] -> Maybe RSAPrivateKey
1712extractRSAKeyFields kvs = do
1713 let kvs' = mapMaybe (\(k,v) -> (k,) <$> parseField v) kvs
1714 n <- lookup "Modulus" kvs'
1715 e <- lookup "PublicExponent" kvs'
1716 d <- lookup "PrivateExponent" kvs'
1717 p <- lookup "Prime1" kvs' -- p
1718 q <- lookup "Prime2" kvs' -- q
1719 dmodp1 <- lookup "Exponent1" kvs' -- dP = d `mod` (p - 1)
1720 dmodqminus1 <- lookup "Exponent2" kvs' -- dQ = d `mod` (q - 1)
1721 u <- lookup "Coefficient" kvs'
1722 {-
1723 case (d,p,dmodp1) of
1724 (MPI dd, MPI pp, MPI x) | x == dd `mod` (pp-1) -> return ()
1725 _ -> error "dmodp fail!"
1726 case (d,q,dmodqminus1) of
1727 (MPI dd, MPI qq, MPI x) | x == dd `mod` (qq-1) -> return ()
1728 _ -> error "dmodq fail!"
1729 -}
1730 return $ RSAPrivateKey
1731 { rsaN = n
1732 , rsaE = e
1733 , rsaD = d
1734 , rsaP = p
1735 , rsaQ = q
1736 , rsaDmodP1 = dmodp1
1737 , rsaDmodQminus1 = dmodqminus1
1738 , rsaCoefficient = u }
1739 where
1740 parseField blob = MPI <$> m
1741 where m = bigendian <$> Base64.decode (Char8.unpack blob)
1742
1743 bigendian bs = snd $ foldl' (\(c,a) w8 -> (c-1, a + 256^c * fromIntegral w8)) (nlen-1,0) bs
1744 where
1745 nlen = length bs
1746
1747rsaToPGP stamp rsa = SecretKeyPacket
1748 { version = 4
1749 , timestamp = fromTime stamp -- toEnum (fromEnum stamp)
1750 , key_algorithm = RSA
1751 , key = [ -- public fields...
1752 ('n',rsaN rsa)
1753 ,('e',rsaE rsa)
1754 -- secret fields
1755 ,('d',rsaD rsa)
1756 ,('p',rsaQ rsa) -- Note: p & q swapped
1757 ,('q',rsaP rsa) -- Note: p & q swapped
1758 ,('u',rsaCoefficient rsa)
1759 ]
1760 -- , ecc_curve = def
1761 , s2k_useage = 0
1762 , s2k = S2K 100 ""
1763 , symmetric_algorithm = Unencrypted
1764 , encrypted_data = ""
1765 , is_subkey = True
1766 }
1767
1768readSecretDNSFile :: InputFile -> IO Packet
1769readSecretDNSFile fname = do
1770 let ctx = InputFileContext "" ""
1771 stamp <- getInputFileTime ctx fname
1772 input <- readInputFileL ctx fname
1773 let kvs = map ( second (Char8.dropWhile isSpace . Char8.drop 1)
1774 . Char8.break (==':'))
1775 $ Char8.lines input
1776 alg = maybe RSA parseAlg $ lookup "Algorithm" kvs
1777 parseAlg spec = case Char8.words spec of
1778 nstr:_ -> case read (Char8.unpack nstr) :: Int of
1779 2 -> DH
1780 3 -> DSA -- SHA1
1781 5 -> RSA -- SHA1
1782 6 -> DSA -- NSEC3-SHA1 (RFC5155)
1783 7 -> RSA -- RSASHA1-NSEC3-SHA1 (RFC5155)
1784 8 -> RSA -- SHA256
1785 10 -> RSA -- SHA512 (RFC5702)
1786 -- 12 -> GOST
1787 13 -> ECDSA -- P-256 SHA256 (RFC6605)
1788 14 -> ECDSA -- P-384 SHA384 (RFC6605)
1789 _ -> RSA
1790 case alg of
1791 RSA -> return $ rsaToPGP stamp $ fromJust $ extractRSAKeyFields kvs
1792
1793
1794readSecretPEMFile :: InputFile -> IO [SecretPEMData]
1795readSecretPEMFile fname = do
1796 -- warn $ fname ++ ": reading ..."
1797 let ctx = InputFileContext "" ""
1798 -- Note: The key's timestamp is included in it's fingerprint.
1799 -- Therefore, we should attempt to preserve it.
1800 stamp <- getInputFileTime ctx fname
1801 input <- readInputFileL ctx fname
1802 let edta = scanAndParse (fmap Left dateParser <> fmap Right (pkcs1 <> cert)) $ Char8.lines input
1803 pkcs1 = fmap (parseRSAPrivateKey . pemBlob)
1804 $ pemParser $ Just "RSA PRIVATE KEY"
1805 cert = fmap (fmap PEMCertificate . parseCertBlob False . pemBlob)
1806 $ pemParser $ Just "CERTIFICATE"
1807 parseRSAPrivateKey dta = do
1808 let e = decodeASN1 DER dta
1809 asn1 <- either (const $ mzero) return e
1810 rsa <- either (const mzero) (return . fst) (fromASN1 asn1)
1811 let _ = rsa :: RSAPrivateKey
1812 return $ PEMPacket $ rsaToPGP stamp rsa
1813 dta = catMaybes $ map snd $ scanl mergeDate (stamp,Nothing) edta
1814 mergeDate (_,obj) (Left tm) = (fromTime tm,obj)
1815 mergeDate (tm,_) (Right (Just (PEMPacket key))) = (tm,Just $ PEMPacket key')
1816 where key' = if tm < fromTime (timestamp key)
1817 then key { timestamp = fromTime tm }
1818 else key
1819 mergeDate (tm,_) (Right mb) = (tm,mb)
1820 return $ dta
1821
1822doImport
1823 :: (MappedPacket -> IO (KikiCondition Packet))
1824 -> Map.Map KeyKey KeyData
1825 -> (FilePath, Maybe [Char], [KeyKey], StreamInfo, t)
1826 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)]))
1827doImport doDecrypt db (fname,subspec,ms,typ -> typ,_) = do
1828 flip (maybe $ return CannotImportMasterKey)
1829 subspec $ \tag -> do
1830 (certs,keys) <- case typ of
1831 PEMFile -> do
1832 ps <- readSecretPEMFile (ArgFile fname)
1833 let (mapMaybe spemCert -> certs,mapMaybe spemPacket-> keys)
1834 = partition (isJust . spemCert) ps
1835 return (certs,keys)
1836 DNSPresentation -> do
1837 p <- readSecretDNSFile (ArgFile fname)
1838 return ([],[p])
1839 -- TODO Probably we need to move to a new design where signature
1840 -- packets are merged into the database in one phase with null
1841 -- signatures, and then the signatures are made in the next phase.
1842 -- This would let us merge annotations (like certificates) from
1843 -- seperate files.
1844 foldM (importKey tag certs) (KikiSuccess (db,[])) keys
1845 where
1846 importKey tag certs prior key = do
1847 try prior $ \(db,report) -> do
1848 let (m0,tailms) = splitAt 1 ms
1849 if (not (null tailms) || null m0)
1850 then return $ AmbiguousKeySpec fname
1851 else do
1852 let kk = keykey key
1853 cs = filter (\c -> kk==keykey (pcertKey c)) certs
1854 blobs = map mkCertNotation $ nub $ map pcertBlob cs
1855 mkCertNotation bs = NotationDataPacket
1856 { human_readable = False
1857 , notation_name = "x509cert@"
1858 , notation_value = Char8.unpack bs }
1859 datedKey = key { timestamp = fromTime $ minimum dates }
1860 dates = fromTime (timestamp key) : map pcertTimestamp certs
1861 r <- doImportG doDecrypt db m0 (mkUsage tag:blobs) fname datedKey
1862 try r $ \(db',report') -> do
1863 return $ KikiSuccess (db',report++report')
1864
1865doImportG
1866 :: (MappedPacket -> IO (KikiCondition Packet))
1867 -> Map.Map KeyKey KeyData
1868 -> [KeyKey] -- m0, only head is used
1869 -> [SignatureSubpacket] -- tags
1870 -> FilePath
1871 -> Packet
1872 -> IO (KikiCondition (Map.Map KeyKey KeyData, [(FilePath,KikiReportAction)]))
1873doImportG doDecrypt db m0 tags fname key = do
1874 let kk = head m0
1875 Just kd@(KeyData top topsigs uids subs) = Map.lookup kk db
1876 kdr <- insertSubkey doDecrypt kk kd tags fname key
1877 try kdr $ \(kd',rrs) -> return $ KikiSuccess (Map.insert kk kd' db, rrs)
1878
1879insertSubkey doDecrypt kk (KeyData top topsigs uids subs) tags fname key = do
1880 let subkk = keykey key
1881 (is_new, subkey) = maybe (True, SubKey (mappedPacket fname key)
1882 [])
1883 ( (False,) . addOrigin )
1884 (Map.lookup subkk subs)
1885 where
1886 addOrigin (SubKey mp sigs) =
1887 let mp' = mp
1888 { locations = Map.insert fname
1889 (origin (packet mp) (-1))
1890 (locations mp) }
1891 in SubKey mp' sigs
1892 subs' = Map.insert subkk subkey subs
1893
1894 istor = do
1895 guard ("tor" `elem` mapMaybe usage tags)
1896 return $ "Anonymous <root@" ++ take 16 (torhash key) ++ ".onion>"
1897
1898 uids' <- flip (maybe $ return $ KikiSuccess (uids,[])) istor $ \idstr -> do
1899 let has_torid = do
1900 -- TODO: check for omitted real name field
1901 (sigtrusts,om) <- Map.lookup idstr uids
1902 listToMaybe $ do
1903 s <- (signatures $ Message (packet top:UserIDPacket idstr:map (packet . fst) sigtrusts))
1904 signatures_over $ verify (Message [packet top]) s
1905 flip (flip maybe $ const $ return $ KikiSuccess (uids,[])) has_torid $ do
1906 wkun <- doDecrypt top
1907
1908 try wkun $ \wkun -> do
1909
1910 let keyflags = keyFlags wkun (map packet $ flattenAllUids fname True uids)
1911 uid = UserIDPacket idstr
1912 -- sig_ov = fst $ torsig g (packet top) wkun uid timestamp keyflags
1913 tor_ov = makeInducerSig (packet top) wkun uid keyflags
1914 sig_ov <- pgpSign (Message [wkun])
1915 tor_ov
1916 SHA1
1917 (fingerprint wkun)
1918 flip (maybe $ return $ KikiSuccess (uids,[(fname, WarnFailedToMakeSignature)]))
1919 (sig_ov >>= listToMaybe . signatures_over)
1920 $ \sig -> do
1921 let om = Map.singleton fname (origin sig (-1))
1922 trust = Map.empty
1923 return $ KikiSuccess
1924 ( Map.insert idstr ([( (mappedPacket fname sig) {locations=om}
1925 , trust)],om) uids
1926 , [] )
1927
1928 try uids' $ \(uids',report) -> do
1929
1930 let SubKey subkey_p subsigs = subkey
1931 wk = packet top
1932 (xs',minsig,ys') = findTag tags wk key subsigs
1933 doInsert mbsig = do
1934 -- NEW SUBKEY BINDING SIGNATURE
1935 sig' <- makeSig doDecrypt top fname subkey_p tags mbsig
1936 try sig' $ \(sig',report) -> do
1937 report <- return $ fmap (fname,) report ++ [(fname, YieldSignature)]
1938 let subs' = Map.insert subkk
1939 (SubKey subkey_p $ xs'++[sig']++ys')
1940 subs
1941 return $ KikiSuccess ( KeyData top topsigs uids' subs'
1942 , report )
1943
1944 report <- let f = if is_new then (++[(fname,YieldSecretKeyPacket s)])
1945 else id
1946 s = show (fmap fst minsig,fingerprint key)
1947 in return (f report)
1948
1949 case minsig of
1950 Nothing -> doInsert Nothing -- we need to create a new sig
1951 Just (True,sig) -> -- we can deduce is_new == False
1952 -- we may need to add a tor id
1953 return $ KikiSuccess ( KeyData top topsigs uids' subs'
1954 , report )
1955 Just (False,sig) -> doInsert (Just sig) -- We have a sig, but is missing usage@ tag
1956
1957isCryptoCoinKey :: Packet -> Bool
1958isCryptoCoinKey p =
1959 and [ isKey p
1960 , key_algorithm p == ECDSA
1961 , lookup 'c' (key p) == Just (MPI secp256k1_id)
1962 ]
1963
1964getCryptoCoinTag :: Packet -> Maybe CryptoCoins.CoinNetwork
1965getCryptoCoinTag p | isSignaturePacket p = do
1966 -- CryptoCoins.secret
1967 let sps = hashed_subpackets p ++ unhashed_subpackets p
1968 u <- listToMaybe $ mapMaybe usage sps
1969 CryptoCoins.lookupNetwork CryptoCoins.network_name u
1970getCryptoCoinTag _ = Nothing
1971
1972
1973coinKeysOwnedBy :: KeyDB -> Maybe Packet -> [(CryptoCoins.CoinNetwork,MappedPacket)]
1974coinKeysOwnedBy db wk = do
1975 wk <- maybeToList wk
1976 let kk = keykey wk
1977 KeyData top topsigs uids subs <- maybeToList $ Map.lookup kk db
1978 (subkk,SubKey mp sigs) <- Map.toList subs
1979 let sub = packet mp
1980 guard $ isCryptoCoinKey sub
1981 tag <- take 1 $ mapMaybe (getCryptoCoinTag . packet . fst) sigs
1982 return (tag,mp)
1983
1984walletImportFormat :: Word8 -> Packet -> String
1985walletImportFormat idbyte k = secret_base58_foo
1986 where
1987 -- isSecret (SecretKeyPacket {}) = True
1988 -- isSecret _ = False
1989 secret_base58_foo = base58_encode seckey
1990 Just d = lookup 'd' (key k)
1991 (_,bigendian) = S.splitAt 2 (S.concat $ L.toChunks $ encode d)
1992 seckey = S.cons idbyte bigendian
1993
1994writeWalletKeys :: KeyRingOperation -> KeyDB -> Maybe Packet -> IO (KikiCondition [(FilePath,KikiReportAction)])
1995writeWalletKeys krd db wk = do
1996 let cs = db `coinKeysOwnedBy` wk
1997 -- export wallet keys
1998 isMutableWallet (fill -> KF_None) = False
1999 isMutableWallet (typ -> WalletFile {}) = True
2000 isMutableWallet _ = False
2001 files pred = do
2002 (f,stream) <- Map.toList (opFiles krd)
2003 guard (pred stream)
2004 resolveInputFile (InputFileContext "" "") f
2005 let writeWallet report n = do
2006 let cs' = do
2007 (nw,mp) <- cs
2008 -- let fns = Map.keys (locations mp)
2009 -- trace ("COIN KEY: "++show fns) $ return ()
2010 guard . not $ Map.member n (locations mp)
2011 let wip = walletImportFormat (CryptoCoins.private_byte_id nw) (packet mp)
2012 return (CryptoCoins.network_name nw,wip)
2013 handleIO_ (return report) $ do
2014 -- TODO: This AppendMode stratagy is not easy to adapt from FilePath-based
2015 -- to InputFile-based.
2016 withFile n AppendMode $ \fh -> do
2017 rs <- forM cs' $ \(net,wip) -> do
2018 hPutStrLn fh wip
2019 return (n, NewWalletKey net)
2020 return (report ++ rs)
2021 report <- foldM writeWallet [] (files isMutableWallet)
2022 return $ KikiSuccess report
2023
2024ifSecret :: Packet -> t -> t -> t
2025ifSecret (SecretKeyPacket {}) t f = t
2026ifSecret _ t f = f
2027
2028showPacket :: Packet -> String
2029showPacket p | isKey p = (if is_subkey p
2030 then showPacket0 p
2031 else ifSecret p "----Secret-----" "----Public-----")
2032 ++ " "++show (key_algorithm p)++" "++fingerprint p
2033 | isUserID p = showPacket0 p ++ " " ++ show (uidkey p)
2034 | otherwise = showPacket0 p
2035showPacket0 p = concat . take 1 $ words (show p)
2036
2037
2038-- | returns Just True so as to indicate that
2039-- the public portions of keys will be imported
2040importPublic :: Maybe Bool
2041importPublic = Just True
2042
2043-- | returns False True so as to indicate that
2044-- the public portions of keys will be imported
2045importSecret :: Maybe Bool
2046importSecret = Just False
2047
2048
2049-- TODO: Do we need to memoize this?
2050guardAuthentic :: KeyRingRuntime -> KeyData -> Maybe ()
2051guardAuthentic rt keydata = guard (isauth rt keydata)
2052
2053isauth :: KeyRingRuntime -> KeyData -> Bool
2054isauth rt keydata = dont_have keydata && maybe False (`has_good_sig` keydata) wk
2055 where wk = workingKey (rtGrip rt) (rtKeyDB rt)
2056 dont_have (KeyData p _ _ _) = not . Map.member (rtPubring rt)
2057 $ locations p
2058 has_good_sig wk (KeyData k sigs uids subs) = any goodsig $ Map.toList uids
2059 where
2060 goodsig (uidstr,(sigs,_)) = not . null $ do
2061 sig0 <- fmap (packet . fst) sigs
2062 pre_ov <- signatures (Message [packet k, UserIDPacket uidstr, sig0])
2063 signatures_over $ verify (Message [wk]) pre_ov
2064
2065 workingKey grip use_db = listToMaybe $ do
2066 fp <- maybeToList grip
2067 elm <- Map.elems use_db
2068 guard $ matchSpec (KeyGrip fp) elm
2069 return $ keyPacket elm
2070
2071writeRingKeys :: KeyRingOperation -> KeyRingRuntime -> Map.Map InputFile Message
2072 -> [(FilePath,KikiReportAction)]
2073 {-
2074 -> KeyDB -> Maybe Packet
2075 -> FilePath -> FilePath
2076 -}
2077 -> IO (KikiCondition [(FilePath,KikiReportAction)])
2078writeRingKeys krd rt {- db wk secring pubring -} unspilled report_manips = do
2079 let isring (KeyRingFile {}) = True
2080 isring _ = False
2081 db = rtKeyDB rt
2082 secring = rtSecring rt
2083 pubring = rtPubring rt
2084 ctx = InputFileContext secring pubring
2085 let s = do
2086 (f,f0,stream) <- do
2087 (f0,stream) <- Map.toList (opFiles krd)
2088 guard (isring $ typ stream)
2089 f <- resolveInputFile ctx f0
2090 return (f,f0,stream)
2091 let db' = fromMaybe db $ do
2092 msg <- Map.lookup f0 unspilled
2093 return $ merge db f0 msg
2094 x = do
2095 let wantedForFill :: Access -> KeyFilter -> KeyData -> Maybe Bool
2096 wantedForFill acc KF_None = importByExistingMaster
2097 -- Note the KF_None case is almost irrelevent as it will be
2098 -- filtered later when isMutable returns False.
2099 -- We use importByExistingMaster in order to generate
2100 -- MissingPacket warnings. To disable those warnings, use
2101 -- const Nothing instead.
2102 wantedForFill acc (KF_Match {}) = importByExistingMaster
2103 wantedForFill acc KF_Subkeys = importByExistingMaster
2104 wantedForFill acc KF_Authentic = \kd -> do guardAuthentic rt kd
2105 importByAccess acc kd
2106 wantedForFill acc KF_All = importByAccess acc
2107 importByAccess Pub kd = importPublic
2108 importByAccess Sec kd = importSecret
2109 importByAccess AutoAccess kd =
2110 mplus (importByExistingMaster kd)
2111 (error $ f ++ ": write public or secret key to file?")
2112 importByExistingMaster kd@(KeyData p _ _ _) =
2113 fmap originallyPublic $ Map.lookup f $ locations p
2114 d <- sortByHint f keyMappedPacket (Map.elems db')
2115 acc <- maybeToList $ Map.lookup f0 (rtRingAccess rt)
2116 only_public <- maybeToList $ wantedForFill acc (fill stream) d
2117 guard $ only_public || isSecretKey (keyPacket d)
2118 case fill stream of
2119 KF_Match usage -> do grip <- maybeToList $ rtGrip rt
2120 flattenTop f only_public
2121 $ filterNewSubs f (parseSpec grip usage) d
2122 _ -> flattenTop f only_public d
2123 new_packets = filter isnew x
2124 where isnew p = isNothing (Map.lookup (resolveForReport Nothing f0) $ locations p)
2125 -- TODO: We depend on an exact string match between the reported
2126 -- file origin of the deleted packet and the path of the file we are
2127 -- writing. Verify that this is a safe assumption.
2128 isdeleted (f',DeletedPacket _) = f'==f
2129 isdeleted _ = False
2130 guard (not (null new_packets) || any isdeleted report_manips)
2131 return ((f0,isMutable stream),(new_packets,x))
2132 let (towrites,report) = (\f -> foldl f ([],[]) s) $
2133 \(ws,report) ((f,mutable),(new_packets,x)) ->
2134 if mutable
2135 then
2136 let rs = flip map new_packets
2137 $ \c -> (concat $ resolveInputFile ctx f, NewPacket $ showPacket (packet c))
2138 in (ws++[(f,x)],report++rs)
2139 else
2140 let rs = flip map new_packets
2141 $ \c -> (concat $ resolveInputFile ctx f,MissingPacket (showPacket (packet c)))
2142 in (ws,report++rs)
2143 forM_ towrites $ \(f,x) -> do
2144 let m = Message $ map packet x
2145 -- warn $ "writing "++f
2146 writeInputFileL ctx f (encode m)
2147 return $ KikiSuccess report
2148
2149
2150{-
2151getSubkeysForExport kk subspec db = do
2152 kd <- maybeToList $ Map.lookup kk db
2153 subkeysForExport subspec kd
2154-}
2155
2156-- | If provided Nothing for the first argument, this function returns the
2157-- master key of the given identity. Otherwise, it returns all the subkeys of
2158-- the given identity which have a usage tag that matches the first argument.
2159subkeysForExport :: Maybe String -> KeyData -> [MappedPacket]
2160subkeysForExport subspec (KeyData key _ _ subkeys) = do
2161 let subs tag = do
2162 e <- Map.elems subkeys
2163 guard $ doSearch key tag e
2164 return $ subkeyMappedPacket e
2165 maybe [key] subs subspec
2166 where
2167 doSearch key tag (SubKey sub_mp sigtrusts) =
2168 let (_,v,_) = findTag [mkUsage tag]
2169 (packet key)
2170 (packet sub_mp)
2171 sigtrusts
2172 in fmap fst v==Just True
2173
2174writePEM :: String -> String -> String
2175writePEM typ dta = pem
2176 where
2177 pem = unlines . concat $
2178 [ ["-----BEGIN " <> typ <> "-----"]
2179 , split64s dta
2180 , ["-----END " <> typ <> "-----"] ]
2181 split64s :: String -> [String]
2182 split64s "" = []
2183 split64s dta = line : split64s rest where (line,rest) = splitAt 64 dta
2184
2185 -- 64 byte lines
2186
2187rsaPrivateKeyFromPacket :: Packet -> Maybe RSAPrivateKey
2188rsaPrivateKeyFromPacket pkt@(SecretKeyPacket {}) = do
2189 -- public fields...
2190 n <- lookup 'n' $ key pkt
2191 e <- lookup 'e' $ key pkt
2192 -- secret fields
2193 MPI d <- lookup 'd' $ key pkt
2194 MPI q <- lookup 'p' $ key pkt -- Note: p & q swapped
2195 MPI p <- lookup 'q' $ key pkt -- Note: p & q swapped
2196
2197 -- Note: Here we fail if 'u' key is missing.
2198 -- Ideally, it would be better to compute (inverse q) mod p
2199 -- see Algebra.Structures.EuclideanDomain.extendedEuclidAlg
2200 -- (package constructive-algebra)
2201 coefficient <- lookup 'u' $ key pkt
2202
2203 let dmodp1 = MPI $ d `mod` (p - 1)
2204 dmodqminus1 = MPI $ d `mod` (q - 1)
2205 return $ RSAPrivateKey
2206 { rsaN = n
2207 , rsaE = e
2208 , rsaD = MPI d
2209 , rsaP = MPI p
2210 , rsaQ = MPI q
2211 , rsaDmodP1 = dmodp1
2212 , rsaDmodQminus1 = dmodqminus1
2213 , rsaCoefficient = coefficient }
2214rsaPrivateKeyFromPacket _ = Nothing
2215
2216secretPemFromPacket packet = pemFromPacket Sec packet
2217
2218pemFromPacket Sec packet =
2219 case key_algorithm packet of
2220 RSA -> do
2221 rsa <- rsaPrivateKeyFromPacket packet -- RSAPrivateKey
2222 let asn1 = toASN1 rsa []
2223 bs = encodeASN1 DER asn1
2224 dta = Base64.encode (L.unpack bs)
2225 output = writePEM "RSA PRIVATE KEY" dta
2226 Just output
2227 algo -> Nothing
2228pemFromPacket Pub packet =
2229 case key_algorithm packet of
2230 RSA -> do
2231 rsa <- rsaKeyFromPacket packet
2232 let asn1 = toASN1 (pkcs8 rsa) []
2233 bs = encodeASN1 DER asn1
2234 dta = Base64.encode (L.unpack bs)
2235 output = writePEM "PUBLIC KEY" dta
2236 Just output
2237 algo -> Nothing
2238pemFromPacket AutoAccess p@(PublicKeyPacket {}) = pemFromPacket Pub p
2239pemFromPacket AutoAccess p@(SecretKeyPacket {}) = pemFromPacket Sec p
2240pemFromPacket AutoAccess _ = Nothing
2241
2242writeKeyToFile ::
2243 Bool -> StreamInfo -> InputFile -> Packet -> IO [(InputFile, KikiReportAction)]
2244writeKeyToFile False stream@(StreamInfo { typ = PEMFile }) fname packet = do
2245 case pemFromPacket (access stream) packet of
2246 Just output -> do
2247 let stamp = toEnum . fromEnum $ timestamp packet
2248 handleIO_ (return [(fname, FailedFileWrite)]) $ do
2249 saved_mask <- setFileCreationMask 0o077
2250 -- Note: The key's timestamp is included in it's fingerprint.
2251 -- Therefore, we should attempt to preserve it.
2252 writeStamped (InputFileContext "" "") fname stamp output
2253 setFileCreationMask saved_mask
2254 return [(fname, ExportedSubkey)]
2255 Nothing -> return [(fname, UnableToExport (key_algorithm packet) $ fingerprint packet)]
2256
2257writeKeyToFile False StreamInfo { typ = DNSPresentation } fname packet = do
2258 case key_algorithm packet of
2259 RSA -> do
2260 flip (maybe (return []))
2261 (rsaPrivateKeyFromPacket packet) -- RSAPrivateKey
2262 $ \rsa -> do
2263 let -- asn1 = toASN1 rsa []
2264 -- bs = encodeASN1 DER asn1
2265 -- dta = Base64.encode (L.unpack bs)
2266 b64 ac rsa = Base64.encode (S.unpack $ i2bs_unsized i)
2267 where
2268 MPI i = ac rsa
2269 i2bs_unsized :: Integer -> S.ByteString
2270 i2bs_unsized 0 = S.singleton 0
2271 i2bs_unsized i = S.reverse $ S.unfoldr go i
2272 where go i' = if i' <= 0 then Nothing
2273 else Just (fromIntegral i', (i' `shiftR` 8))
2274 output = unlines
2275 [ "Private-key-format: v1.2"
2276 , "Algorithm: 8 (RSASHA256)"
2277 , "Modulus: " ++ b64 rsaN rsa
2278 , "PublicExponent: " ++ b64 rsaE rsa
2279 , "PrivateExponent: " ++ b64 rsaD rsa
2280 , "Prime1: " ++ b64 rsaP rsa
2281 , "Prime2: " ++ b64 rsaQ rsa
2282 , "Exponent1: " ++ b64 rsaDmodP1 rsa
2283 , "Exponent2: " ++ b64 rsaDmodQminus1 rsa
2284 , "Coefficient: " ++ b64 rsaCoefficient rsa
2285 ]
2286 stamp = toEnum . fromEnum $ timestamp packet
2287 handleIO_ (return [(fname, FailedFileWrite)]) $ do
2288 saved_mask <- setFileCreationMask 0o077
2289 -- Note: The key's timestamp is included in it's fingerprint.
2290 -- Therefore, we should attempt to preserve it.
2291 writeStamped (InputFileContext "" "") fname stamp output
2292 setFileCreationMask saved_mask
2293 return [(fname, ExportedSubkey)]
2294 algo -> return [(fname, UnableToExport algo $ fingerprint packet)]
2295
2296writePEMKeys :: (MappedPacket -> IO (KikiCondition Packet))
2297 -> KeyDB
2298 -> [(FilePath,Maybe String,[MappedPacket],StreamInfo)]
2299 -> IO (KikiCondition [(FilePath,KikiReportAction)])
2300writePEMKeys doDecrypt db exports = do
2301 ds <- mapM decryptKeys exports
2302 let ds' = map functorToEither ds
2303 if null (lefts ds')
2304 then do
2305 rs <- mapM (\(f,stream,p) -> writeKeyToFile False stream (ArgFile f) p)
2306 (rights ds')
2307 return $ KikiSuccess (map (first $ resolveForReport Nothing) $ concat rs)
2308 else do
2309 return (head $ lefts ds')
2310 where
2311 decryptKeys (fname,subspec,[p],stream@(StreamInfo { access=Pub }))
2312 = return $ KikiSuccess (fname,stream,packet p) -- public keys are never encrypted.
2313 decryptKeys (fname,subspec,[p],stream) = do
2314 pun <- doDecrypt p
2315 try pun $ \pun -> do
2316 return $ KikiSuccess (fname,stream,pun)
2317
2318makeMemoizingDecrypter :: KeyRingOperation -> InputFileContext
2319 -> Map.Map KeyKey MappedPacket
2320 -> IO (MappedPacket -> IO (KikiCondition Packet))
2321makeMemoizingDecrypter operation ctx keys =
2322 if null chains then do
2323 -- (*) Notice we do not pass ctx to resolveForReport.
2324 -- This is because the merge function does not currently use a context
2325 -- and the pws map keys must match the MappedPacket locations.
2326 -- TODO: Perhaps these should both be of type InputFile rather than
2327 -- FilePath?
2328 -- pws :: Map.Map FilePath (IO S.ByteString)
2329 {-
2330 pws <-
2331 Traversable.mapM (cachedContents ctx . fromJust . pwfile . typ)
2332 (Map.mapKeys (resolveForReport Nothing) -- see note (*) note above
2333 $ Map.filter (isJust . pwfile . typ) $ opFiles operation)
2334 -}
2335 let prompt = Just "Enter possibly multi-line passphrase (Press CTRL-D when finished):\n"
2336 pws2 <-
2337 Traversable.mapM (cachedContents prompt ctx)
2338 $ Map.fromList $ mapMaybe
2339 (\spec -> (,passSpecPassFile spec) `fmap` do
2340 guard $ isNothing $ passSpecKeySpec spec
2341 passSpecRingFile spec)
2342 passspecs
2343 defpw <- do
2344 Traversable.mapM (cachedContents prompt ctx . passSpecPassFile)
2345 $ listToMaybe $ filter (\sp -> isNothing (passSpecRingFile sp)
2346 && isNothing (passSpecKeySpec sp))
2347 $ opPassphrases operation
2348 unkeysRef <- newIORef (Map.empty :: Map.Map KeyKey Packet)
2349 return $ doDecrypt unkeysRef ({- pws `Map.union` -} pws2) defpw
2350 else let PassphraseMemoizer f = head chains
2351 in return f
2352 where
2353 (chains,passspecs) = partition isChain $ opPassphrases operation
2354 where isChain (PassphraseMemoizer {}) = True
2355 isChain _ = False
2356 doDecrypt :: IORef (Map.Map KeyKey Packet)
2357 -> Map.Map FilePath (IO S.ByteString)
2358 -> Maybe (IO S.ByteString)
2359 -> MappedPacket
2360 -> IO (KikiCondition Packet)
2361 doDecrypt unkeysRef pws defpw mp0 = do
2362 unkeys <- readIORef unkeysRef
2363 let mp = fromMaybe mp0 $ do
2364 k <- Map.lookup kk keys
2365 return $ mergeKeyPacket "decrypt" mp0 k
2366 wk = packet mp0
2367 kk = keykey wk
2368 fs = Map.keys $ locations mp
2369
2370 decryptIt [] = return BadPassphrase
2371 decryptIt (getpw:getpws) = do
2372 -- TODO: This function should use mergeKeyPacket to
2373 -- combine the packet with it's unspilled version before
2374 -- attempting to decrypt it.
2375 pw <- getpw
2376 let wkun = fromMaybe wk $ decryptSecretKey pw (packet mp)
2377 case symmetric_algorithm wkun of
2378 Unencrypted -> do
2379 writeIORef unkeysRef (Map.insert kk wkun unkeys)
2380 return $ KikiSuccess wkun
2381 _ -> decryptIt getpws
2382
2383 getpws = mapMaybe (`Map.lookup` pws) fs ++ maybeToList defpw
2384
2385 case symmetric_algorithm wk of
2386 Unencrypted -> return (KikiSuccess wk)
2387 _ -> maybe (decryptIt getpws)
2388 (return . KikiSuccess)
2389 $ Map.lookup kk unkeys
2390
2391performManipulations ::
2392 (MappedPacket -> IO (KikiCondition Packet))
2393 -> KeyRingRuntime
2394 -> Maybe MappedPacket
2395 -> (KeyRingRuntime -> KeyData -> [PacketUpdate])
2396 -> IO (KikiCondition (KeyRingRuntime,KikiReport))
2397performManipulations doDecrypt rt wk manip = do
2398 let db = rtKeyDB rt
2399 performAll kd = foldM perform (KikiSuccess (kd,[])) $ manip rt kd
2400 r <- Traversable.mapM performAll db
2401 try (sequenceA r) $ \db -> do
2402 return $ KikiSuccess (rt { rtKeyDB = fmap fst db }, concatMap snd $ Map.elems db)
2403 where
2404 perform :: KikiCondition (KeyData,KikiReport) -> PacketUpdate -> IO (KikiCondition (KeyData,KikiReport))
2405 perform kd (InducerSignature uid subpaks) = do
2406 try kd $ \(kd,report) -> do
2407 flip (maybe $ return NoWorkingKey) wk $ \wk' -> do
2408 wkun' <- doDecrypt wk'
2409 try wkun' $ \wkun -> do
2410 let flgs = if keykey (keyPacket kd) == keykey wkun
2411 then keyFlags0 (keyPacket kd) (map (\(x,_,_)->x) selfsigs)
2412 else []
2413 sigOver = makeInducerSig (keyPacket kd)
2414 wkun
2415 (UserIDPacket uid)
2416 $ flgs ++ subpaks
2417 om = Map.singleton "--autosign" (origin p (-1)) where p = UserIDPacket uid
2418 toMappedPacket om p = (mappedPacket "" p) {locations=om}
2419 selfsigs = filter (\(sig,v,whosign) -> isJust (v >> Just wkun >>= guard
2420 . (== keykey whosign)
2421 . keykey)) vs
2422 keys = map keyPacket $ Map.elems (rtKeyDB rt)
2423 overs sig = signatures $ Message (keys++[keyPacket kd,UserIDPacket uid,sig])
2424 vs :: [ ( Packet -- signature
2425 , Maybe SignatureOver -- Nothing means non-verified
2426 , Packet ) -- key who signed
2427 ]
2428 vs = do
2429 x <- maybeToList $ Map.lookup uid (keyUids kd)
2430 sig <- map (packet . fst) (fst x)
2431 o <- overs sig
2432 k <- keys
2433 let ov = verify (Message [k]) $ o
2434 signatures_over ov
2435 return (sig,Just ov,k)
2436 additional new_sig = do
2437 new_sig <- maybeToList new_sig
2438 guard (null $ selfsigs)
2439 signatures_over new_sig
2440 sigr <- pgpSign (Message [wkun]) sigOver SHA1 (fingerprint wkun)
2441 let f ::([SigAndTrust],OriginMap) -> ([SigAndTrust],OriginMap)
2442 f x = ( map ( (,Map.empty) . toMappedPacket om) (additional sigr) ++ fst x
2443 , om `Map.union` snd x )
2444 -- XXX: Shouldn't this signature generation show up in the KikiReport ?
2445 return $ KikiSuccess $ ( kd { keyUids = Map.adjust f uid (keyUids kd) }, report )
2446
2447 perform kd (SubKeyDeletion topk subk) = do
2448 try kd $ \(kd,report) -> do
2449 let kk = keykey $ packet $ keyMappedPacket kd
2450 kd' | kk /= topk = kd
2451 | otherwise = kd { keySubKeys = Map.filterWithKey pred $ keySubKeys kd }
2452 pred k _ = k /= subk
2453 ps = concat $ maybeToList $ do
2454 SubKey mp sigs <- Map.lookup subk (keySubKeys kd)
2455 return $ packet mp : concatMap (\(p,ts) -> packet p : Map.elems ts) sigs
2456 ctx = InputFileContext (rtSecring rt) (rtPubring rt)
2457 rings = [HomeSec, HomePub] >>= resolveInputFile ctx
2458 return $ KikiSuccess (kd' , report ++ [ (f,DeletedPacket $ showPacket p) | f <- rings, p <- ps ])
2459
2460initializeMissingPEMFiles ::
2461 KeyRingOperation
2462 -> InputFileContext
2463 -> Maybe String
2464 -> Maybe MappedPacket
2465 -> (MappedPacket -> IO (KikiCondition Packet))
2466 -> KeyDB
2467 -> IO (KikiCondition ( (KeyDB,[( FilePath
2468 , Maybe String
2469 , [MappedPacket]
2470 , StreamInfo )])
2471 , [(FilePath,KikiReportAction)]))
2472initializeMissingPEMFiles operation ctx grip mwk decrypt db = do
2473 nonexistents <-
2474 filterM (fmap not . doesFileExist . fst)
2475 $ do (f,t) <- Map.toList (opFiles operation)
2476 f <- resolveInputFile ctx f
2477 return (f,t)
2478
2479 let (missing,notmissing) = partition (\(_,_,ns,_)->null (ns >>= snd)) $ do
2480 (fname,stream) <- nonexistents
2481 let internalInitializer StreamInfo
2482 { initializer = Internal _
2483 , spill = KF_Match tag } = Just tag
2484 internalInitializer _ = Nothing
2485 mutableTag
2486 | isMutable stream = usageFromFilter (fill stream)
2487 | otherwise = Nothing
2488 usage <- maybeToList $ internalInitializer stream `mplus` mutableTag
2489 -- TODO: Report error if generating without specifying usage tag.
2490 let (topspec,subspec) = parseSpec (fromMaybe "" grip) usage
2491 -- ms will contain duplicates if a top key has multiple matching
2492 -- subkeys. This is intentional.
2493 -- ms = map (keykey . fst) $ selectAll True (topspec,subspec) db
2494 -- ms = filterMatches topspec $ Map.toList db
2495 ns = do
2496 (kk,kd) <- filterMatches topspec $ Map.toList db
2497 return (kk , subkeysForExport subspec kd)
2498 return (fname,subspec,ns,stream)
2499 (exports0,ambiguous) = partition (\(_,_,ns,_)->null $ drop 1 $ (ns>>=snd))
2500 notmissing
2501 exports = map (\(f,subspec,ns,stream) -> (f,subspec,ns >>= snd,stream)) exports0
2502
2503 ambiguity (f,topspec,subspec,_) = do
2504 return $ AmbiguousKeySpec f
2505
2506 ifnotnull (x:xs) f g = f x
2507 ifnotnull _ f g = g
2508
2509 ifnotnull ambiguous ambiguity $ do
2510
2511 -- create nonexistent files via external commands
2512 do
2513 let cmds = mapMaybe getcmd missing
2514 where
2515 getcmd (fname,subspec,ms,stream) = do
2516 cmd <- case initializer stream of
2517 External str -> Just str
2518 _ -> Nothing
2519 return (fname,subspec,ms,stream,cmd)
2520 rs <- forM cmds $ \tup@(fname,subspec,ms,stream,cmd) -> do
2521 e <- systemEnv [ ("file",fname)
2522 , ("usage",fromMaybe "" subspec) ]
2523 cmd
2524 case e of
2525 ExitFailure num -> return (tup,FailedExternal num)
2526 ExitSuccess -> return (tup,ExternallyGeneratedFile)
2527
2528 v <- foldM (importSecretKey decrypt)
2529 (KikiSuccess (db,[])) $ do
2530 ((f,subspec,ms,stream,cmd),r) <- rs
2531 guard $ case r of
2532 ExternallyGeneratedFile -> True
2533 _ -> False
2534 return (f,subspec,map fst ms,stream,cmd)
2535
2536 try v $ \(db,import_rs) -> do
2537
2538 -- generateInternals
2539 let internals = mapMaybe getParams missing
2540 where
2541 getParams (fname,subspec,ms,stream) =
2542 case initializer stream of
2543 Internal p -> Just (p, stream)
2544 _ -> Nothing
2545 v <- generateInternals decrypt mwk db internals
2546
2547 try v $ \(db,internals_rs) -> do
2548
2549 return $ KikiSuccess ((db,exports), map (\((f,_,_,_,_),r)->(f,r)) rs
2550 ++ import_rs ++ internals_rs)
2551{-
2552interpretManip :: KeyData -> KeyRingAddress PacketUpdate -> IO KeyData
2553interpretManip kd (KeyRingAddress kk sk (InducerSignature ps)) = error "todo"
2554interpretManip kd manip = return kd
2555-}
2556
2557combineTransforms :: [Transform] -> KeyRingRuntime -> KeyData -> [PacketUpdate]
2558combineTransforms trans rt kd = updates
2559 where
2560 updates = -- kManip operation rt kd ++
2561 concatMap (\t -> resolveTransform t rt kd) sanitized
2562 sanitized = group (sort trans) >>= take 1
2563
2564isSubkeySignature (SubkeySignature {}) = True
2565isSubkeySignature _ = False
2566
2567-- Returned data is simmilar to getBindings but the Word8 codes
2568-- are ORed together.
2569accBindings ::
2570 Bits t =>
2571 [(t, (Packet, Packet), [a], [a1], [a2])]
2572 -> [(t, (Packet, Packet), [a], [a1], [a2])]
2573accBindings bs = as
2574 where
2575 gs = groupBy samePair . sortBy (comparing bindingPair) $ bs
2576 as = map (foldl1 combine) gs
2577 bindingPair (_,p,_,_,_) = pub2 p
2578 where
2579 pub2 (a,b) = (pub a, pub b)
2580 pub a = fingerprint_material a
2581 samePair a b = bindingPair a == bindingPair b
2582 combine (ac,p,akind,ahashed,aclaimaints)
2583 (bc,_,bkind,bhashed,bclaimaints)
2584 = (ac .|. bc,p,akind++bkind,ahashed++bhashed,aclaimaints++bclaimaints)
2585
2586
2587
2588verifyBindings keys nonkeys = (top ++ filter isSubkeySignature embedded,othersigs)
2589 where
2590 verified = do
2591 sig <- signatures (Message nonkeys)
2592 let v = verify (Message keys) sig
2593 guard (not . null $ signatures_over v)
2594 return v
2595 (top,othersigs) = partition isSubkeySignature verified
2596 embedded = do
2597 sub <- top
2598 let sigover = signatures_over sub
2599 unhashed = sigover >>= unhashed_subpackets
2600 subsigs = mapMaybe backsig unhashed
2601 -- This should consist only of 0x19 values
2602 -- subtypes = map signature_type subsigs
2603 -- trace ("subtypes = "++show subtypes) (return ())
2604 -- trace ("issuers: "++show (map signature_issuer subsigs)) (return ())
2605 sig <- signatures (Message ([topkey sub,subkey sub]++subsigs))
2606 let v = verify (Message [subkey sub]) sig
2607 guard (not . null $ signatures_over v)
2608 return v
2609
2610smallpr k = drop 24 $ fingerprint k
2611
2612disjoint_fp ks = {- concatMap group2 $ -} transpose grouped
2613 where
2614 grouped = groupBy samepr . sortBy (comparing smallpr) $ ks
2615 samepr a b = smallpr a == smallpr b
2616
2617 {-
2618 -- useful for testing
2619 group2 :: [a] -> [[a]]
2620 group2 (x:y:ys) = [x,y]:group2 ys
2621 group2 [x] = [[x]]
2622 group2 [] = []
2623 -}
2624
2625
2626getBindings ::
2627 [Packet]
2628 ->
2629 ( [([Packet],[SignatureOver])] -- other signatures with key sets
2630 -- that were used for the verifications
2631 , [(Word8,
2632 (Packet, Packet), -- (topkey,subkey)
2633 [String], -- usage flags
2634 [SignatureSubpacket], -- hashed data
2635 [Packet])] -- binding signatures
2636 )
2637getBindings pkts = (sigs,bindings)
2638 where
2639 (sigs,concat->bindings) = unzip $ do
2640 let (keys,_) = partition isKey pkts
2641 keys <- disjoint_fp keys
2642 let (bs,sigs) = verifyBindings keys pkts
2643 return . ((keys,sigs),) $ do
2644 b <- bs -- trace ("sigs = "++show (map (map signature_issuer . signatures_over) sigs)) bs
2645 i <- map signature_issuer (signatures_over b)
2646 i <- maybeToList i
2647 who <- maybeToList $ find_key fingerprint (Message keys) i
2648 let (code,claimants) =
2649 case () of
2650 _ | who == topkey b -> (1,[])
2651 _ | who == subkey b -> (2,[])
2652 _ -> (0,[who])
2653 let hashed = signatures_over b >>= hashed_subpackets
2654 kind = guard (code==1) >> hashed >>= maybeToList . usage
2655 return (code,(topkey b,subkey b), kind, hashed,claimants)
2656
2657resolveTransform :: Transform -> KeyRingRuntime -> KeyData -> [PacketUpdate]
2658resolveTransform Autosign rt kd@(KeyData k ksigs umap submap) = ops
2659 where
2660 ops = map (\u -> InducerSignature u []) us
2661 us = filter torStyle $ Map.keys umap
2662 torStyle str = and [ uid_topdomain parsed == "onion"
2663 , uid_realname parsed `elem` ["","Anonymous"]
2664 , uid_user parsed == "root"
2665 , fmap (match . fst) (lookup (packet k) torbindings)
2666 == Just True ]
2667 where parsed = parseUID str
2668 match = (==subdom) . take (fromIntegral len)
2669 subdom0 = L.fromChunks [encodeUtf8 (uid_subdomain parsed)]
2670 subdom = Char8.unpack subdom0
2671 len = T.length (uid_subdomain parsed)
2672 torbindings = getTorKeys (map packet $ flattenTop "" True kd)
2673 getTorKeys pub = do
2674 xs <- groupBindings pub
2675 (_,(top,sub),us,_,_) <- xs
2676 guard ("tor" `elem` us)
2677 let torhash = fromMaybe "" $ derToBase32 <$> derRSA sub
2678 return (top,(torhash,sub))
2679
2680 groupBindings pub = gs
2681 where (_,bindings) = getBindings pub
2682 bindings' = accBindings bindings
2683 code (c,(m,s),_,_,_) = (fingerprint_material m,-c)
2684 ownerkey (_,(a,_),_,_,_) = a
2685 sameMaster (ownerkey->a) (ownerkey->b)
2686 = fingerprint_material a==fingerprint_material b
2687 gs = groupBy sameMaster (sortBy (comparing code) bindings')
2688
2689
2690resolveTransform (DeleteSubKey fp) rt kd@(KeyData k ksigs umap submap) = fmap (SubKeyDeletion topk) subk
2691 where
2692 topk = keykey $ packet k -- key to master of key to be deleted
2693 subk = do
2694 (k,sub) <- Map.toList submap
2695 guard (map toUpper fp == fingerprint (packet (subkeyMappedPacket sub)))
2696 return k
2697
2698
2699-- | Load and update key files according to the specified 'KeyRingOperation'.
2700runKeyRing :: KeyRingOperation -> IO (KikiResult KeyRingRuntime)
2701runKeyRing operation = do
2702 homedir <- getHomeDir (opHome operation)
2703 let try' :: KikiCondition a -> (a -> IO (KikiResult b)) -> IO (KikiResult b)
2704 -- FIXME: try' should probably accept a list of KikiReportActions.
2705 -- This would be useful for reporting on disk writes that have already
2706 -- succeded prior to this termination.
2707 try' v body =
2708 case functorToEither v of
2709 Left e -> return $ KikiResult e []
2710 Right wkun -> body wkun
2711 try' homedir $ \(homedir,secring,pubring,grip0) -> do
2712 let ctx = InputFileContext secring pubring
2713 tolocks = filesToLock operation ctx
2714 secring <- return Nothing
2715 pubring <- return Nothing
2716 lks <- forM tolocks $ \f -> do
2717 lk <- dotlock_create f 0
2718 v <- flip (maybe $ return Nothing) lk $ \lk -> do
2719 e <- dotlock_take lk (-1)
2720 if e==0 then return $ Just lk
2721 else dotlock_destroy lk >> return Nothing
2722 return (v,f)
2723 let (lked, map snd -> failed_locks) = partition (isJust . fst) lks
2724 ret <-
2725 if not $ null failed_locks
2726 then return $ KikiResult (FailedToLock failed_locks) []
2727 else do
2728
2729 -- merge all keyrings, PEM files, and wallets
2730 bresult <- buildKeyDB ctx grip0 operation
2731 try' bresult $ \((db,grip,wk,hs,accs,decrypt,unspilled),report_imports) -> do
2732
2733 externals_ret <- initializeMissingPEMFiles operation
2734 ctx
2735 grip
2736 wk
2737 decrypt
2738 db
2739 try' externals_ret $ \((db,exports),report_externals) -> do
2740
2741 let rt = KeyRingRuntime
2742 { rtPubring = homepubPath ctx
2743 , rtSecring = homesecPath ctx
2744 , rtGrip = grip
2745 , rtWorkingKey = fmap packet wk
2746 , rtKeyDB = db
2747 , rtRingAccess = accs
2748 , rtPassphrases = decrypt
2749 }
2750
2751 r <- performManipulations decrypt
2752 rt
2753 wk
2754 (combineTransforms $ opTransforms operation)
2755 try' r $ \(rt,report_manips) -> do
2756
2757 r <- writeWalletKeys operation (rtKeyDB rt) (fmap packet wk)
2758 try' r $ \report_wallets -> do
2759
2760 r <- writeRingKeys operation rt unspilled report_manips
2761 try' r $ \report_rings -> do
2762
2763 r <- writePEMKeys decrypt (rtKeyDB rt) exports
2764 try' r $ \report_pems -> do
2765
2766 import_hosts <- writeHostsFiles operation ctx hs
2767
2768 return $ KikiResult (KikiSuccess rt)
2769 $ concat [ report_imports
2770 , report_externals
2771 , report_manips
2772 , report_wallets
2773 , report_rings
2774 , report_pems ]
2775
2776 forM_ lked $ \(Just lk, fname) -> dotlock_release lk
2777
2778 return ret
2779
2780parseOptionFile :: FilePath -> IO [String]
2781parseOptionFile fname = do
2782 xs <- fmap lines (readFile fname)
2783 let ys = filter notComment xs
2784 notComment ('#':_) = False
2785 notComment cs = not (all isSpace cs)
2786 return ys
2787
2788-- | returns ( home directory
2789-- , path to secret ring
2790-- , path to public ring
2791-- , fingerprint of working key
2792-- )
2793getHomeDir :: Maybe FilePath -> IO (KikiCondition (FilePath,FilePath,FilePath,Maybe String))
2794getHomeDir protohome = do
2795 homedir <- envhomedir protohome
2796 flip (maybe (return CantFindHome))
2797 homedir $ \homedir -> do
2798 -- putStrLn $ "homedir = " ++show homedir
2799 let secring = homedir ++ "/" ++ "secring.gpg"
2800 pubring = homedir ++ "/" ++ "pubring.gpg"
2801 -- putStrLn $ "secring = " ++ show secring
2802 workingkey <- getWorkingKey homedir
2803 return $ KikiSuccess (homedir,secring,pubring,workingkey)
2804 where
2805 envhomedir opt = do
2806 gnupghome <- fmap (mfilter (/="")) $ lookupEnv (homevar home)
2807 homed <- fmap (mfilter (/="") . Just) getHomeDirectory
2808 let homegnupg = (++('/':(appdir home))) <$> homed
2809 let val = (opt `mplus` gnupghome `mplus` homegnupg)
2810 return $ val
2811
2812 -- TODO: rename this to getGrip
2813 getWorkingKey homedir = do
2814 let o = Nothing
2815 h = Just homedir
2816 ofile <- fmap listToMaybe . flip (maybe (return [])) h $ \h ->
2817 let optfiles = map (second ((h++"/")++))
2818 (maybe optfile_alts' (:[]) o')
2819 optfile_alts' = zip (False:repeat True) (optfile_alts home)
2820 o' = fmap (False,) o
2821 in filterM (doesFileExist . snd) optfiles
2822 args <- flip (maybe $ return []) ofile $
2823 \(forgive,fname) -> parseOptionFile fname
2824 let config = map (topair . words) args
2825 where topair (x:xs) = (x,xs)
2826 return $ lookup "default-key" config >>= listToMaybe
2827
2828#if MIN_VERSION_base(4,6,0)
2829#else
2830lookupEnv :: String -> IO (Maybe String)
2831lookupEnv var =
2832 handleIO_ (return Nothing) $ fmap Just (getEnv var)
2833#endif
2834
2835isKey :: Packet -> Bool
2836isKey (PublicKeyPacket {}) = True
2837isKey (SecretKeyPacket {}) = True
2838isKey _ = False
2839
2840isUserID :: Packet -> Bool
2841isUserID (UserIDPacket {}) = True
2842isUserID _ = False
2843
2844isTrust :: Packet -> Bool
2845isTrust (TrustPacket {}) = True
2846isTrust _ = False
2847
2848sigpackets ::
2849 Monad m =>
2850 Word8 -> [SignatureSubpacket] -> [SignatureSubpacket] -> m Packet
2851sigpackets typ hashed unhashed = return $
2852 signaturePacket
2853 4 -- version
2854 typ -- 0x18 subkey binding sig, or 0x19 back-signature
2855 RSA
2856 SHA1
2857 hashed
2858 unhashed
2859 0 -- Word16 -- Left 16 bits of the signed hash value
2860 [] -- [MPI]
2861
2862secretToPublic :: Packet -> Packet
2863secretToPublic pkt@(SecretKeyPacket {}) =
2864 PublicKeyPacket { version = version pkt
2865 , timestamp = timestamp pkt
2866 , key_algorithm = key_algorithm pkt
2867 -- , ecc_curve = ecc_curve pkt
2868 , key = let seckey = key pkt
2869 pubs = public_key_fields (key_algorithm pkt)
2870 in filter (\(k,v) -> k `elem` pubs) seckey
2871 , is_subkey = is_subkey pkt
2872 , v3_days_of_validity = Nothing
2873 }
2874secretToPublic pkt = pkt
2875
2876
2877
2878slurpWIPKeys :: Posix.EpochTime -> L.ByteString -> ( [(Word8,Packet)], [L.ByteString])
2879slurpWIPKeys stamp "" = ([],[])
2880slurpWIPKeys stamp cs =
2881 let (b58,xs) = Char8.span (`elem` base58chars) cs
2882 mb = decode_btc_key stamp (Char8.unpack b58)
2883 in if L.null b58
2884 then let (ys,xs') = Char8.break (`elem` base58chars) cs
2885 (ks,js) = slurpWIPKeys stamp xs'
2886 in (ks,ys:js)
2887 else let (ks,js) = slurpWIPKeys stamp xs
2888 in maybe (ks,b58:js) (\(net,Message [k])->((net,k):ks,js)) mb
2889
2890
2891decode_btc_key ::
2892 Enum timestamp => timestamp -> String -> Maybe (Word8, Message)
2893decode_btc_key timestamp str = do
2894 (network_id,us) <- base58_decode str
2895 return . (network_id,) $ Message $ do
2896 let d = foldl' (\a b->a*256+b) 0 (map fromIntegral us :: [Integer])
2897 {-
2898 xy = secp256k1_G `pmul` d
2899 x = getx xy
2900 y = gety xy
2901 -- y² = x³ + 7 (mod p)
2902 y' = sqrtModP' (applyCurve secp256k1_curve x) (getp secp256k1_curve)
2903 y'' = sqrtModPList (applyCurve secp256k1_curve x) (getp secp256k1_curve)
2904 -}
2905 secp256k1 = ECC.getCurveByName ECC.SEC_p256k1
2906 ECC.Point x y = ECC.ecc_g $ ECC.common_curve secp256k1
2907 -- pub = cannonical_eckey x y
2908 -- hash = S.cons network_id . RIPEMD160.hash . SHA256.hash . S.pack $ pub
2909 -- address = base58_encode hash
2910 -- pubstr = concatMap (printf "%02x") $ pub
2911 -- _ = pubstr :: String
2912 return $ {- trace (unlines ["pub="++show pubstr
2913 ,"add="++show address
2914 ,"y ="++show y
2915 ,"y' ="++show y'
2916 ,"y''="++show y'']) -}
2917 SecretKeyPacket
2918 { version = 4
2919 , timestamp = toEnum (fromEnum timestamp)
2920 , key_algorithm = ECDSA
2921 , key = [ -- public fields...
2922 ('c',MPI secp256k1_id) -- secp256k1 (bitcoin curve)
2923 ,('l',MPI 256)
2924 ,('x',MPI x)
2925 ,('y',MPI y)
2926 -- secret fields
2927 ,('d',MPI d)
2928 ]
2929 , s2k_useage = 0
2930 , s2k = S2K 100 ""
2931 , symmetric_algorithm = Unencrypted
2932 , encrypted_data = ""
2933 , is_subkey = True
2934 }
2935
2936rsaKeyFromPacket :: Packet -> Maybe RSAPublicKey
2937rsaKeyFromPacket p | isKey p = do
2938 n <- lookup 'n' $ key p
2939 e <- lookup 'e' $ key p
2940 return $ RSAKey n e
2941
2942rsaKeyFromPacket _ = Nothing
2943
2944
2945readPacketsFromWallet ::
2946 Maybe Packet
2947 -> InputFile
2948 -> IO [(Packet,Packet,(Packet,Map.Map FilePath Packet))]
2949readPacketsFromWallet wk fname = do
2950 let ctx = InputFileContext "" ""
2951 timestamp <- getInputFileTime ctx fname
2952 input <- readInputFileL ctx fname
2953 let (ks,_) = slurpWIPKeys timestamp input
2954 unless (null ks) $ do
2955 -- decrypt wk
2956 -- create sigs
2957 -- return key/sig pairs
2958 return ()
2959 return $ do
2960 wk <- maybeToList wk
2961 guard (not $ null ks)
2962 let prep (tagbyte,k) = (wk,k,(k,Map.singleton tag wk))
2963 where tag = CryptoCoins.nameFromSecretByte tagbyte
2964 (wk,MarkerPacket,(MarkerPacket,Map.empty))
2965 :map prep ks
2966
2967readPacketsFromFile :: InputFileContext -> InputFile -> IO Message
2968readPacketsFromFile ctx fname = do
2969 -- warn $ fname ++ ": reading..."
2970 input <- readInputFileL ctx fname
2971#if MIN_VERSION_binary(0,7,0)
2972 return $
2973 case decodeOrFail input of
2974 Right (_,_,msg ) -> msg
2975 Left (_,_,_) ->
2976 -- FIXME
2977 -- trace (fname++": read fail") $
2978 Message []
2979#else
2980 return $ decode input
2981#endif
2982
2983-- | Get the time stamp of a signature.
2984--
2985-- Warning: This function checks unhashed_subpackets if no timestamp occurs in
2986-- the hashed section. TODO: change this?
2987--
2988signature_time :: SignatureOver -> Word32
2989signature_time ov = case (if null cs then ds else cs) of
2990 [] -> minBound
2991 xs -> maximum xs
2992 where
2993 ps = signatures_over ov
2994 ss = filter isSignaturePacket ps
2995 cs = concatMap (concatMap creationTime . hashed_subpackets) ss
2996 ds = concatMap (concatMap creationTime . unhashed_subpackets) ss
2997 creationTime (SignatureCreationTimePacket t) = [t]
2998 creationTime _ = []
2999
3000splitAtMinBy :: (t -> t -> Ordering) -> [t] -> ([t], [t])
3001splitAtMinBy comp xs = minimumBy comp' xxs
3002 where
3003 xxs = zip (inits xs) (tails xs)
3004 comp' (_,as) (_,bs) = compM (listToMaybe as) (listToMaybe bs)
3005 compM (Just a) (Just b) = comp a b
3006 compM Nothing mb = GT
3007 compM _ _ = LT
3008
3009
3010
3011-- | Given list of subpackets, a master key, one of its subkeys and a
3012-- list of signatures on that subkey, yields:
3013--
3014-- * preceding list of signatures
3015--
3016-- * The most recent valid signature made by the master key along with a
3017-- flag that indicates whether or not all of the supplied subpackets occur in
3018-- it or, if no valid signature from the working key is present, Nothing.
3019--
3020-- * following list of signatures
3021--
3022findTag ::
3023 [SignatureSubpacket]
3024 -> Packet
3025 -> Packet
3026 -> [(MappedPacket, b)]
3027 -> ([(MappedPacket, b)],
3028 Maybe (Bool, (MappedPacket, b)),
3029 [(MappedPacket, b)])
3030findTag tag topk subkey subsigs = (xs',minsig,ys')
3031 where
3032 vs = map (\sig ->
3033 (sig, do
3034 sig <- Just (packet . fst $ sig)
3035 guard (isSignaturePacket sig)
3036 guard $ flip isSuffixOf
3037 (fingerprint topk)
3038 . fromMaybe "%bad%"
3039 . signature_issuer
3040 $ sig
3041 listToMaybe $
3042 map (signature_time . verify (Message [topk]))
3043 (signatures $ Message [topk,subkey,sig])))
3044 subsigs
3045 (xs,ys) = splitAtMinBy (comparing (Down . snd)) vs
3046 xs' = map fst xs
3047 ys' = map fst $ if isNothing minsig then ys else drop 1 ys
3048 minsig = do
3049 (sig,ov) <- listToMaybe ys
3050 ov
3051 let hshed = hashed_subpackets $ packet $ fst sig
3052 return ( null $ tag \\ hshed, sig)
3053
3054mkUsage :: String -> SignatureSubpacket
3055mkUsage tag = NotationDataPacket
3056 { human_readable = True
3057 , notation_name = "usage@"
3058 , notation_value = tag
3059 }
3060
3061makeSig ::
3062 (MappedPacket -> IO (KikiCondition Packet))
3063 -> MappedPacket
3064 -> [Char]
3065 -> MappedPacket
3066 -> [SignatureSubpacket]
3067 -> Maybe (MappedPacket, Map.Map k a)
3068 -> IO (KikiCondition ((MappedPacket, Map.Map k a), [KikiReportAction]))
3069makeSig doDecrypt top fname subkey_p tags mbsig = do
3070 let wk = packet top
3071 wkun <- doDecrypt top
3072 try wkun $ \wkun -> do
3073 let grip = fingerprint wk
3074 addOrigin new_sig =
3075 flip (maybe $ return FailedToMakeSignature)
3076 (new_sig >>= listToMaybe . signatures_over)
3077 $ \new_sig -> do
3078 let mp' = mappedPacket fname new_sig
3079 return $ KikiSuccess (mp', Map.empty)
3080 parsedkey = [packet subkey_p]
3081 hashed0 = KeyFlagsPacket
3082 { certify_keys = False
3083 , sign_data = False
3084 , encrypt_communication = False
3085 , encrypt_storage = False
3086 , split_key = False
3087 , authentication = True
3088 , group_key = False }
3089 : tags
3090 -- implicitly added:
3091 -- , SignatureCreationTimePacket (fromIntegral timestamp)
3092 subgrip = fingerprint (head parsedkey)
3093
3094 back_sig <- pgpSign (Message parsedkey)
3095 (SubkeySignature wk
3096 (head parsedkey)
3097 (sigpackets 0x19
3098 hashed0
3099 [IssuerPacket subgrip]))
3100 (if key_algorithm (head parsedkey)==ECDSA
3101 then SHA256
3102 else SHA1)
3103 subgrip
3104 let iss = IssuerPacket (fingerprint wk)
3105 cons_iss back_sig = iss : map EmbeddedSignaturePacket (signatures_over back_sig)
3106 unhashed0 = maybe [iss] cons_iss back_sig
3107
3108 new_sig <- pgpSign (Message [wkun])
3109 (SubkeySignature wk
3110 (head parsedkey)
3111 (sigpackets 0x18
3112 hashed0
3113 unhashed0))
3114 SHA1
3115 grip
3116 let newSig = do
3117 r <- addOrigin new_sig
3118 return $ fmap (,[]) r
3119 flip (maybe newSig) mbsig $ \(mp,trustmap) -> do
3120 let sig = packet mp
3121 isCreation (SignatureCreationTimePacket {}) = True
3122 isCreation _ = False
3123 isExpiration (SignatureExpirationTimePacket {}) = True
3124 isExpiration _ = False
3125 (cs,ps) = partition isCreation (hashed_subpackets sig)
3126 (es,qs) = partition isExpiration ps
3127 stamp = listToMaybe . sortBy (comparing Down) $
3128 map unwrap cs where unwrap (SignatureCreationTimePacket x) = x
3129 exp = listToMaybe $ sort $
3130 map unwrap es where unwrap (SignatureExpirationTimePacket x) = x
3131 expires = liftA2 (+) stamp exp
3132 timestamp <- now
3133 if fmap ( (< timestamp) . fromIntegral) expires == Just True then
3134 return $ KikiSuccess ((mp,trustmap), [ UnableToUpdateExpiredSignature ] )
3135 else do
3136 let times = (:) (SignatureExpirationTimePacket (fromIntegral timestamp))
3137 $ maybeToList $ do
3138 e <- expires
3139 return $ SignatureExpirationTimePacket (e - fromIntegral timestamp)
3140 sig' = sig { hashed_subpackets = times ++ (qs `union` tags) }
3141 new_sig <- pgpSign (Message [wkun])
3142 (SubkeySignature wk
3143 (packet subkey_p)
3144 [sig'] )
3145 SHA1
3146 (fingerprint wk)
3147 newsig <- addOrigin new_sig
3148 return $ fmap (,[]) newsig
3149
3150
3151
3152data OriginFlags = OriginFlags {
3153 originallyPublic :: Bool,
3154 originalNum :: Int
3155 }
3156 deriving Show
3157type OriginMap = Map.Map FilePath OriginFlags
3158data MappedPacket = MappedPacket
3159 { packet :: Packet
3160 , locations :: OriginMap
3161 } deriving Show
3162
3163type TrustMap = Map.Map FilePath Packet
3164type SigAndTrust = ( MappedPacket
3165 , TrustMap ) -- trust packets
3166
3167type KeyKey = [ByteString]
3168data SubKey = SubKey MappedPacket [SigAndTrust] deriving Show
3169
3170-- | This is a GPG Identity which includes a master key and all its UIDs and
3171-- subkeys and associated signatures.
3172data KeyData = KeyData { keyMappedPacket :: MappedPacket -- main key
3173 , keySigAndTrusts :: [SigAndTrust] -- sigs on main key
3174 , keyUids :: (Map.Map String ([SigAndTrust],OriginMap)) -- uids
3175 , keySubKeys :: (Map.Map KeyKey SubKey) -- subkeys
3176 } deriving Show
3177
3178type KeyDB = Map.Map KeyKey KeyData
3179
3180origin :: Packet -> Int -> OriginFlags
3181origin p n = OriginFlags ispub n
3182 where
3183 ispub = case p of
3184 SecretKeyPacket {} -> False
3185 _ -> True
3186
3187mappedPacket :: FilePath -> Packet -> MappedPacket
3188mappedPacket filename p = MappedPacket
3189 { packet = p
3190 , locations = Map.singleton filename (origin p (-1))
3191 }
3192
3193mappedPacketWithHint :: FilePath -> Packet -> Int -> MappedPacket
3194mappedPacketWithHint filename p hint = MappedPacket
3195 { packet = p
3196 , locations = Map.singleton filename (origin p hint)
3197 }
3198
3199keykey :: Packet -> KeyKey
3200keykey key =
3201 -- Note: The key's timestamp is normally included in it's fingerprint.
3202 -- This is undesirable for kiki because it causes the same
3203 -- key to be imported multiple times and show as apparently
3204 -- distinct keys with different fingerprints.
3205 -- Thus, we will remove the timestamp.
3206 fingerprint_material (key {timestamp=0}) -- TODO: smaller key?
3207
3208uidkey :: Packet -> String
3209uidkey (UserIDPacket str) = str
3210
3211merge :: KeyDB -> InputFile -> Message -> KeyDB
3212merge db inputfile (Message ps) = merge_ db filename qs
3213 where
3214 filename = resolveForReport Nothing inputfile
3215
3216 qs = scanPackets filename ps
3217
3218 scanPackets :: FilePath -> [Packet] -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))]
3219 scanPackets filename [] = []
3220 scanPackets filename (p:ps) = scanl doit (doit (MarkerPacket,MarkerPacket,ret MarkerPacket) p) ps
3221 where
3222 ret p = (p,Map.empty)
3223 doit (top,sub,prev) p =
3224 case p of
3225 _ | isKey p && not (is_subkey p) -> (p,MarkerPacket,ret p)
3226 _ | isKey p && is_subkey p -> (top,p,ret p)
3227 _ | isUserID p -> (top,p,ret p)
3228 _ | isTrust p -> (top,sub,updateTrust top sub prev p)
3229 _ -> (top,sub,ret p)
3230
3231 updateTrust top (PublicKeyPacket {}) (pre,t) p = (pre,Map.insert filename p t) -- public
3232 updateTrust (PublicKeyPacket {}) _ (pre,t) p = (pre,Map.insert filename p t) -- public
3233 updateTrust _ _ (pre,t) p = (pre,Map.insert filename p t) -- secret
3234
3235
3236{-
3237onionName :: KeyData -> (SockAddr,L.ByteString)
3238onionName kd = (addr,name)
3239 where
3240 (addr,(name:_,_)) = getHostnames kd
3241-}
3242keyCompare :: String -> Packet -> Packet -> Ordering
3243keyCompare what (SecretKeyPacket {}) (PublicKeyPacket {}) = LT
3244keyCompare what (PublicKeyPacket {}) (SecretKeyPacket {}) = GT
3245keyCompare what a b | keykey a==keykey b = EQ
3246keyCompare what a b = error $ unlines ["Unable to merge "++what++":"
3247 , fingerprint a
3248 , PP.ppShow a
3249 , fingerprint b
3250 , PP.ppShow b
3251 ]
3252
3253mergeKeyPacket :: String -> MappedPacket -> MappedPacket -> MappedPacket
3254mergeKeyPacket what key p =
3255 key { packet = minimumBy (keyCompare what) [packet key,packet p]
3256 , locations = Map.union (locations key) (locations p)
3257 }
3258
3259
3260merge_ :: KeyDB -> FilePath -> [(Packet,Packet,(Packet,Map.Map FilePath Packet))]
3261 -> KeyDB
3262merge_ db filename qs = foldl mergeit db (zip [0..] qs)
3263 where
3264 asMapped n p = mappedPacketWithHint filename p n
3265 asSigAndTrust n (p,tm) = (asMapped n p,tm)
3266 emptyUids = Map.empty
3267 -- mergeit db (_,_,TrustPacket {}) = db -- Filter TrustPackets
3268 mergeit :: KeyDB -> (Int,(Packet,Packet,(Packet,Map.Map FilePath Packet))) -> KeyDB
3269 mergeit db (n,(top,sub,ptt@(p,trustmap))) | isKey top = Map.alter update (keykey top) db
3270 where
3271 -- NOTE:
3272 -- if a keyring file has both a public key packet and a secret key packet
3273 -- for the same key, then only one of them will survive, which ever is
3274 -- later in the file.
3275 --
3276 -- This is due to the use of statements like
3277 -- (Map.insert filename (origin p n) (locations key))
3278 --
3279 update :: Maybe KeyData -> Maybe KeyData
3280 update v | isKey p && not (is_subkey p)
3281 = case v of
3282 Nothing -> Just $ KeyData (asMapped n p) [] emptyUids Map.empty
3283 Just (KeyData key sigs uids subkeys) | keykey (packet key) == keykey p
3284 -> Just $ KeyData (mergeKeyPacket "master keys" key $ asMapped n p)
3285 sigs
3286 uids
3287 subkeys
3288 _ -> error . concat $ ["Unexpected master key merge error: "
3289 ,show (fingerprint top, fingerprint p)]
3290 update (Just (KeyData key sigs uids subkeys)) | isKey p && is_subkey p
3291 = Just $ KeyData key sigs uids (Map.alter (mergeSubkey n p) (keykey p) subkeys)
3292 update (Just (KeyData key sigs uids subkeys)) | isUserID p
3293 = Just $ KeyData key sigs (Map.alter (mergeUid n ptt) (uidkey p) uids)
3294 subkeys
3295 update (Just (KeyData key sigs uids subkeys))
3296 = case sub of
3297 MarkerPacket -> Just $ KeyData key (mergeSig n ptt sigs) uids subkeys
3298 UserIDPacket {} -> Just $ KeyData key
3299 sigs
3300 (Map.alter (mergeUidSig n ptt) (uidkey sub) uids)
3301 subkeys
3302 _ | isKey sub -> Just $ KeyData key
3303 sigs
3304 uids
3305 (Map.alter (mergeSubSig n ptt) (keykey sub) subkeys)
3306 _ -> error $ "Unexpected PGP packet 1: "++(words (show p) >>= take 1)
3307 update _ = error $ "Unexpected PGP packet 2: "++(words (show p) >>= take 1)
3308
3309 mergeit _ (_,(_,_,p)) = error $ "Unexpected PGP packet 3: "++whatP p
3310
3311 mergeSubkey :: Int -> Packet -> Maybe SubKey -> Maybe SubKey
3312 mergeSubkey n p Nothing = Just $ SubKey (asMapped n p) []
3313 mergeSubkey n p (Just (SubKey key sigs)) = Just $
3314 SubKey (mergeKeyPacket "subs" key $ asMapped n p)
3315 sigs
3316
3317 mergeUid :: Int ->(Packet,a) -> Maybe ([SigAndTrust],OriginMap) -> Maybe ([SigAndTrust],OriginMap)
3318 mergeUid n (UserIDPacket s,_) Nothing = Just ([],Map.singleton filename (origin MarkerPacket n))
3319 mergeUid n (UserIDPacket s,_) (Just (sigs,m)) = Just (sigs, Map.insert filename (origin MarkerPacket n) m)
3320 mergeUid n p _ = error $ "Unable to merge into UID record: " ++whatP p
3321
3322 whatP (a,_) = concat . take 1 . words . show $ a
3323
3324
3325 mergeSig :: Int -> (Packet,TrustMap) -> [SigAndTrust] -> [SigAndTrust]
3326 mergeSig n sig sigs =
3327 let (xs,ys) = break (isSameSig sig) sigs
3328 in if null ys
3329 then sigs++[first (asMapped n) sig]
3330 else let y:ys'=ys
3331 in xs ++ (mergeSameSig n sig y : ys')
3332 where
3333 isSameSig (a,_) (MappedPacket {packet=b},_) | isSignaturePacket a && isSignaturePacket b =
3334 a { unhashed_subpackets=[] } == b { unhashed_subpackets = [] }
3335 isSameSig (a,_) (MappedPacket {packet=b},_) = a==b
3336
3337 mergeSameSig :: Int -> (Packet,TrustMap) -> (MappedPacket,TrustMap) -> (MappedPacket, TrustMap)
3338 mergeSameSig n (a,ta) (m@(MappedPacket{packet=b,locations=locs}),tb)
3339 | isSignaturePacket a && isSignaturePacket b =
3340 ( m { packet = b { unhashed_subpackets =
3341 union (unhashed_subpackets b) (unhashed_subpackets a)
3342 }
3343 , locations = Map.insert filename (origin a n) locs }
3344 -- TODO: when merging items, we should delete invalidated origins
3345 -- from the orgin map.
3346 , tb `Map.union` ta )
3347
3348 mergeSameSig n a b = b -- trace ("discarding dup "++show a) b
3349
3350 mergeUidSig n sig (Just (sigs,m)) = Just (mergeSig n sig sigs, m)
3351 mergeUidSig n sig Nothing = Just ([asSigAndTrust n sig],Map.empty)
3352
3353 mergeSubSig n sig (Just (SubKey key sigs)) = Just $ SubKey key (mergeSig n sig sigs)
3354 mergeSubSig n sig Nothing = error $
3355 "Unable to merge subkey signature: "++(words (show sig) >>= take 1)
3356
3357unsig :: FilePath -> Bool -> SigAndTrust -> [MappedPacket]
3358unsig fname isPublic (sig,trustmap) =
3359 sig : map (asMapped (-1)) ( take 1 . Map.elems $ Map.filterWithKey f trustmap)
3360 where
3361 f n _ = n==fname -- && trace ("fname=n="++show n) True
3362 asMapped n p = let m = mappedPacket fname p
3363 in m { locations = fmap (\x->x {originalNum=n}) (locations m) }
3364
3365concatSort ::
3366 FilePath -> ([a] -> MappedPacket) -> (b -> [a]) -> [b] -> [a]
3367concatSort fname getp f = concat . sortByHint fname getp . map f
3368
3369sortByHint :: FilePath -> (a -> MappedPacket) -> [a] -> [a]
3370sortByHint fname f = sortBy (comparing gethint)
3371 where
3372 gethint = maybe defnum originalNum . Map.lookup fname . locations . f
3373 defnum = -1
3374
3375flattenKeys :: Bool -> KeyDB -> Message
3376flattenKeys isPublic db = Message $ concatMap (map packet . flattenTop "" isPublic . snd) (prefilter . Map.assocs $ db)
3377 where
3378 prefilter = if isPublic then id else filter isSecret
3379 where
3380 isSecret (_,(KeyData
3381 (MappedPacket { packet=(SecretKeyPacket {})})
3382 _
3383 _
3384 _)) = True
3385 isSecret _ = False
3386
3387
3388flattenTop :: FilePath -> Bool -> KeyData -> [MappedPacket]
3389flattenTop fname ispub (KeyData key sigs uids subkeys) =
3390 unk ispub key :
3391 ( flattenAllUids fname ispub uids
3392 ++ concatSort fname head (flattenSub fname ispub) (Map.elems subkeys))
3393
3394flattenSub :: FilePath -> Bool -> SubKey -> [MappedPacket]
3395flattenSub fname ispub (SubKey key sigs) = unk ispub key: concatSort fname head (unsig fname ispub) sigs
3396
3397unk :: Bool -> MappedPacket -> MappedPacket
3398unk isPublic = if isPublic then toPacket secretToPublic else id
3399 where toPacket f mp@(MappedPacket {packet=p}) = mp {packet=(f p)}
3400
3401flattenAllUids :: FilePath -> Bool -> Map.Map String ([SigAndTrust],OriginMap) -> [MappedPacket]
3402flattenAllUids fname ispub uids =
3403 concatSort fname head (flattenUid fname ispub) (Map.assocs uids)
3404
3405flattenUid :: FilePath -> Bool -> (String,([SigAndTrust],OriginMap)) -> [MappedPacket]
3406flattenUid fname ispub (str,(sigs,om)) =
3407 (mappedPacket "" $ UserIDPacket str) {locations=om} : concatSort fname head (unsig fname ispub) sigs
3408
3409getCrossSignedSubkeys :: Packet -> Map.Map KeyKey SubKey -> String -> [Packet]
3410getCrossSignedSubkeys topk subs tag = do
3411 SubKey k sigs <- Map.elems subs
3412 let subk = packet k
3413 let sigs' = do
3414 torsig <- filter (has_tag tag) $ map (packet . fst) sigs
3415 sig <- (signatures $ Message [topk,subk,torsig])
3416 let v = verify (Message [topk]) sig
3417 -- Require parent's signature
3418 guard (not . null $ signatures_over v)
3419 let unhashed = unhashed_subpackets torsig
3420 subsigs = mapMaybe backsig unhashed
3421 -- This should consist only of 0x19 values
3422 -- subtypes = map signature_type subsigs
3423 sig' <- signatures . Message $ [topk,subk]++subsigs
3424 let v' = verify (Message [subk]) sig'
3425 -- Require subkey's signature
3426 guard . not . null $ signatures_over v'
3427 return torsig
3428 guard (not $ null sigs')
3429 return subk
3430
3431has_tag tag p = isSignaturePacket p
3432 && or [ tag `elem` mapMaybe usage (hashed_subpackets p)
3433 , tag `elem` map usageString (mapMaybe keyflags (hashed_subpackets p)) ]
3434
3435
3436-- |
3437-- Returns (ip6 fingerprint address,(onion names,other host names))
3438--
3439-- Requires a validly cross-signed tor key for each onion name returned.
3440-- (Signature checks are performed.)
3441getHostnames :: KeyData -> (SockAddr, ([L.ByteString],[L.ByteString]))
3442getHostnames (KeyData topmp _ uids subs) = (addr,(onames,othernames))
3443 where
3444 othernames = do
3445 mp <- flattenAllUids "" True uids
3446 let p = packet mp
3447 guard $ isSignaturePacket p
3448 uh <- unhashed_subpackets p
3449 case uh of
3450 NotationDataPacket True "hostname@" v
3451 -> return $ Char8.pack v
3452 _ -> mzero
3453
3454 addr = fingerdress topk
3455 -- name = fromMaybe "" $ listToMaybe onames -- TODO: more than one tor key?
3456 topk = packet topmp
3457 torkeys = getCrossSignedSubkeys topk subs "tor"
3458
3459 -- subkeyPacket (SubKey k _ ) = k
3460 onames :: [L.ByteString]
3461 onames = map ( (<> ".onion")
3462 . Char8.pack
3463 . take 16
3464 . torhash )
3465 torkeys
3466
3467hasFingerDress :: KeyDB -> SockAddr -> Bool
3468hasFingerDress db addr | socketFamily addr/=AF_INET6 = False
3469hasFingerDress db addr = pre=="fd" && isJust (selectPublicKey (KeyGrip g',Nothing) db)
3470 where
3471 (pre,g) = splitAt 2 $ filter (/=':') $ Hosts.inet_ntop addr
3472 g' = map toUpper g
3473
3474-- We return into IO in case we want to make a signature here.
3475setHostnames :: (SockAddr -> Bool) -> Hosts.Hosts -> KeyData -> IO KeyData
3476setHostnames pred hosts kd@(KeyData topmp topsigs uids subs) =
3477 -- TODO: we are removing the origin from the UID OriginMap,
3478 -- when we should be removing origins from the locations
3479 -- field of the sig's MappedPacket records.
3480 -- Call getHostnames and compare to see if no-op.
3481 if not (pred addr) || names0 == names \\ onions
3482 then {- trace (unlines [ "setHostnames NO-OP: gpg: "++show (map Char8.unpack onions, map Char8.unpack names0)
3483 , " file: "++show (map Char8.unpack names)
3484 , " pred: "++show (pred addr)]) -}
3485 (return kd)
3486 else do
3487 -- We should be sure to remove origins so that the data is written
3488 -- (but only if something changed).
3489 -- Filter all hostnames present in uids
3490 -- Write notations into first uid
3491 {-
3492 trace (unlines [ "setHostnames ACTION: gpg: "++show (map Char8.unpack onions, map Char8.unpack names0)
3493 , " file: "++show (map Char8.unpack names) ]) $ do
3494 -}
3495 return $ KeyData topmp topsigs uids1 subs
3496 where
3497 topk = packet topmp
3498 addr = fingerdress topk
3499 names :: [Char8.ByteString]
3500 names = Hosts.namesForAddress addr hosts
3501 (_,(onions,names0)) = getHostnames kd
3502 notations = map (NotationDataPacket True "hostname@" . Char8.unpack) (names \\ onions)
3503 isName (NotationDataPacket True "hostname@" _) = True
3504 isName _ = False
3505 uids0 = fmap zapIfHasName uids
3506 fstuid = head $ do
3507 p <- map packet $ flattenAllUids "" True uids
3508 guard $ isUserID p
3509 return $ uidkey p
3510 uids1 = Map.adjust addnames fstuid uids0
3511 addnames (sigs,om) = (fmap f ss ++ ts, om ) -- XXX: removed om=Map.empty, preserve UserId origin
3512 where
3513 (ss,ts) = splitAt 1 sigs
3514 f (sig,tm) = if isSignaturePacket (packet sig) then (sig { packet = p', locations=Map.empty }, tm)
3515 else (sig, tm)
3516 where p' = (packet sig) { unhashed_subpackets=uh }
3517 uh = unhashed_subpackets (packet sig) ++ notations
3518 zapIfHasName (sigs,om) = if or bs then (sigs',om) -- XXX: removed om=Map.empty to preserve UserID origin
3519 else (sigs,om)
3520 where
3521 (bs, sigs') = unzip $ map unhash sigs
3522
3523 unhash (sig,tm) = ( not (null ns)
3524 , ( sig { packet = p', locations = Map.empty }
3525 , tm ) )
3526 where
3527 psig = packet sig
3528 p' = if isSignaturePacket psig then psig { unhashed_subpackets = ps }
3529 else psig
3530 uh = unhashed_subpackets psig
3531 (ns,ps) = partition isName uh
3532
3533fingerdress :: Packet -> SockAddr
3534fingerdress topk = fromMaybe zero $ Hosts.inet_pton addr_str
3535 where
3536 zero = SockAddrInet 0 0
3537 addr_str = colons $ "fd" ++ drop 10 (map toLower $ fingerprint topk)
3538 colons (a:b:c:d:xs@(_:_)) = [a,b,c,d,':'] ++ colons xs
3539 colons xs = xs
3540
3541backsig :: SignatureSubpacket -> Maybe Packet
3542backsig (EmbeddedSignaturePacket s) = Just s
3543backsig _ = Nothing
3544
3545socketFamily :: SockAddr -> Family
3546socketFamily (SockAddrInet _ _) = AF_INET
3547socketFamily (SockAddrInet6 {}) = AF_INET6
3548socketFamily (SockAddrUnix _) = AF_UNIX
3549
3550#if ! MIN_VERSION_unix(2,7,0)
3551setFdTimesHiRes :: Posix.Fd -> POSIXTime -> POSIXTime -> IO ()
3552setFdTimesHiRes (Posix.Fd fd) atime mtime =
3553 withArray [toCTimeSpec atime, toCTimeSpec mtime] $ \times ->
3554 throwErrnoIfMinus1_ "setFdTimesHiRes" (c_futimens fd times)
3555
3556data CTimeSpec = CTimeSpec Posix.EpochTime CLong
3557instance Storable CTimeSpec where
3558 sizeOf _ = (16)
3559 alignment _ = alignment (undefined :: CInt)
3560 poke p (CTimeSpec sec nsec) = do
3561 ((\hsc_ptr -> pokeByteOff hsc_ptr 0)) p sec
3562 ((\hsc_ptr -> pokeByteOff hsc_ptr 8)) p nsec
3563 peek p = do
3564 sec <- (\hsc_ptr -> peekByteOff hsc_ptr 0) p
3565 nsec <- (\hsc_ptr -> peekByteOff hsc_ptr 8) p
3566 return $ CTimeSpec sec nsec
3567
3568toCTimeSpec :: POSIXTime -> CTimeSpec
3569toCTimeSpec t = CTimeSpec (CTime sec) (truncate $ 10^(9::Int) * frac)
3570 where
3571 (sec, frac) = if (frac' < 0) then (sec' - 1, frac' + 1) else (sec', frac')
3572 (sec', frac') = properFraction $ toRational t
3573
3574foreign import ccall unsafe "futimens"
3575 c_futimens :: CInt -> Ptr CTimeSpec -> IO CInt
3576#endif
3577
3578onionNameForContact :: KeyKey -> KeyDB -> Maybe String
3579onionNameForContact kk db = do
3580 contact <- Map.lookup kk db
3581 case getHostnames contact of
3582 (_,(name:_,_)) -> Just $ Char8.unpack name
3583 _ -> Nothing