{-# LANGUAGE RecordWildCards #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE ScopedTypeVariables #-} -- | This module provide an IO-based API. The /ks/ executable provides -- some keystore management functions that can be used from the shell -- and "Data.KeyStore.KeyStore" provides the underlying functional model. module Data.KeyStore.IO ( readSettings , CtxParams(..) , IC(..) , module Data.KeyStore.Types , module Data.KeyStore.KS.KS , keyStoreBytes , keyStoreFromBytes , settingsFromBytes , defaultSettingsFilePath , settingsFilePath , defaultKeyStoreFilePath , defaultCtxParams , instanceCtx , instanceCtx_ , newKeyStore , store , listSettings , settings , updateSettings , listTriggers , triggers , addTrigger , addTrigger' , rmvTrigger , createRSAKeyPair , createKey , adjustKey , rememberKey , rememberKey_ , secureKey , loadKey , showIdentity , showComment , showDate , showHash , showHashComment , showHashSalt , showPublic , showSecret , keys , list , keyInfo , deleteKeys , encrypt , encrypt_ , encrypt__ , decrypt , decrypt_ , decrypt__ , sign , sign_ , verify , verify_ , run , getKeystore , getState , getCtxState , putCtxState ) where import Data.KeyStore.IO.IC import Data.KeyStore.KS import Data.KeyStore.KS.KS import Data.KeyStore.Types import Data.API.Types import Data.IORef import Data.Aeson import qualified Data.Text as T import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LBS import qualified Data.ByteString.Base64 as B64 import qualified Data.Map as Map import Data.Time import Text.Printf import Control.Applicative import qualified Control.Exception as X import qualified Control.Lens as L import Control.Monad import System.IO import System.Locale -- | Generate a new keystore located in the given file with the given global -- settings. newKeyStore :: FilePath -> Settings -> IO () newKeyStore str_fp stgs = do ei <- X.try $ B.readFile str_fp :: IO (Either X.SomeException B.ByteString) either (const $ return ()) (const $ errorIO "keystore file exists") ei g <- newGenerator let state = State { st_keystore = emptyKeyStore $ defaultConfiguration stgs , st_cprng = g } LBS.writeFile str_fp $ keyStoreBytes $ st_keystore state -- | Given 'CtxParams' describing the location of the keystore, etc., generate -- an IC for use in the following keystore access functions that will allow -- context to be cached between calls to these access functions. instanceCtx :: CtxParams -> IO IC instanceCtx cp = do ctx_st <- getCtxState $ instanceCtx_ cp IC cp . Just <$> newIORef ctx_st -- | This functional method will generate an IC that will not cache any -- state between calls. instanceCtx_ :: CtxParams -> IC instanceCtx_ cp = IC cp Nothing -- | the filepath of the loaded store store :: IC -> IO FilePath store ic = run ic storeKS -- | List the JSON settings on stdout. listSettings :: IC -> IO () listSettings ic = settings ic >>= LBS.putStrLn . encode . _Settings -- | Return the settings associated with the keystore. settings :: IC -> IO Settings settings ic = run ic $ _cfg_settings <$> getConfig -- | Update the global settings of a keystore from the given JSON settings. updateSettings :: IC -> FilePath -> IO () updateSettings ic fp = do bs <- LBS.readFile fp stgs <- e2io $ settingsFromBytes bs run ic $ modConfig $ L.over cfg_settings $ const stgs -- | List the triggers set up in the keystore on stdout. listTriggers :: IC -> IO () listTriggers ic = triggers ic >>= putStr . unlines . map fmt where fmt Trigger{..} = printf "%-12s : %12s => %s" id_s pat_s stgs_s where id_s = T.unpack $ _TriggerID _trg_id pat_s = _pat_string _trg_pattern stgs_s = LBS.unpack $ encode $ Object $ _Settings _trg_settings -- | Returns the striggers setup on the keystore. triggers :: IC -> IO [Trigger] triggers ic = run ic $ Map.elems . _cfg_triggers <$> getConfig -- | addTrigger' cariant that erads the setting from a file. addTrigger :: IC -> TriggerID -> Pattern -> FilePath -> IO () addTrigger ic tid pat fp = do bs <- LBS.readFile fp stgs <- e2io $ settingsFromBytes bs addTrigger' ic tid pat stgs -- | Set up a named trigger on a keystore that will fire when a key matches the -- given pattern establishing the settings. addTrigger' :: IC -> TriggerID -> Pattern -> Settings -> IO () addTrigger' ic tid pat stgs = run ic $ modConfig $ L.over cfg_triggers $ Map.insert tid $ Trigger tid pat stgs -- | Remove the named trigger from the keystore. rmvTrigger :: IC -> TriggerID -> IO () rmvTrigger ic tid = run ic $ modConfig $ L.over cfg_triggers $ Map.delete tid -- | Create an RSA key pair, encoding the private key in the named Safeguards. createRSAKeyPair :: IC -> Name -> Comment -> Identity -> [Safeguard] -> IO () createRSAKeyPair ic nm cmt ide sgs = run ic $ createRSAKeyPairKS nm cmt ide sgs -- | Create a symmetric key, possibly auto-loaded from an environment variable. createKey :: IC -> Name -> Comment -> Identity -> Maybe EnvVar -> Maybe B.ByteString -> IO () createKey ic nm cmt ide mb_ev mb_bs = run ic $ createKeyKS nm cmt ide mb_ev (ClearText . Binary <$> mb_bs) -- | Adjust a named key. adjustKey :: IC -> Name -> (Key->Key) -> IO () adjustKey ic nm adj = run ic $ adjustKeyKS nm adj -- | Load a named key from the named file. rememberKey :: IC -> Name -> FilePath -> IO () rememberKey ic nm fp = B.readFile fp >>= rememberKey_ ic nm -- | Load the named key. rememberKey_ :: IC -> Name -> B.ByteString -> IO () rememberKey_ ic nm bs = run ic $ rememberKeyKS nm $ ClearText $ Binary bs -- | Encrypt and store the key with the named safeguard. secureKey :: IC -> Name -> Safeguard -> IO () secureKey ic nm nms = run ic $ secureKeyKS nm nms -- | Try and retrieve the secret text for a given key. loadKey :: IC -> Name -> IO Key loadKey ic nm = run ic $ loadKeyKS nm -- | Return the identity of a key. showIdentity :: IC -> Bool -> Name -> IO B.ByteString showIdentity ic = show_it' ic "identity" (Just . _key_identity) (B.pack . T.unpack . _Identity) -- | Return the comment associated with a key. showComment :: IC -> Bool -> Name -> IO B.ByteString showComment ic = show_it' ic "comment" (Just . _key_comment) (B.pack . T.unpack . _Comment ) -- | Return the creation UTC of a key. showDate :: IC -> Bool -> Name -> IO B.ByteString showDate ic = show_it' ic "date" (Just . _key_created_at) (B.pack . formatTime defaultTimeLocale fmt) where fmt = "%F-%TZ" -- | Return the hash of a key. showHash :: IC -> Bool -> Name -> IO B.ByteString showHash ic = show_it ic "hash" (fmap _hash_hash . _key_hash) _HashData -- | Return the hash comment of a key/ showHashComment :: IC -> Bool -> Name -> IO B.ByteString showHashComment ic = show_it' ic "hash" _key_hash cmt where cmt = B.pack . T.unpack . _Comment . _hashd_comment . _hash_description -- | Retuen the hash salt of a key. showHashSalt :: IC -> Bool -> Name -> IO B.ByteString showHashSalt ic = show_it ic "hash" (fmap (_hashd_salt . _hash_description) . _key_hash) _Salt -- | (For public key pairs only) return the public key. showPublic :: IC -> Bool -> Name -> IO B.ByteString showPublic ic = show_it ic "public" (fmap encodePublicKeyDER . _key_public) _ClearText -- | Return the secret text of a key (will be the private key for a public key pair). showSecret :: IC -> Bool -> Name -> IO B.ByteString showSecret ic = show_it ic "secret" _key_clear_text _ClearText show_it :: IC -> String -> (Key->Maybe a) -> (a->Binary) -> Bool -> Name -> IO B.ByteString show_it ic lbl prj_1 prj_2 aa nm = show_it' ic lbl prj_1 (_Binary . prj_2) aa nm show_it' :: IC -> String -> (Key->Maybe a) -> (a->B.ByteString) -> Bool -> Name -> IO B.ByteString show_it' ic lbl prj_1 prj_2 aa nm = do key <- loadKey ic nm case prj_2 <$> prj_1 key of Nothing -> errorIO $ printf "%s: %s not present" (_name nm) lbl Just bs -> return $ armr bs where armr = if aa then B64.encode else id -- | List a summary of all of the keys on stdout. list :: IC -> IO () list ic = run ic $ listKS -- Summarize a single key on stdout. keyInfo :: IC -> Name -> IO () keyInfo ic nm = run ic $ keyInfoKS nm -- | Return all of the keys in the keystore. keys :: IC -> IO [Key] keys ic = Map.elems . _ks_keymap <$> getKeystore ic -- | Delete a list of keys from the keystore. deleteKeys :: IC -> [Name] -> IO () deleteKeys ic nms = run ic $ deleteKeysKS nms -- Encrypt a file with a named key. encrypt :: IC -> Name -> FilePath -> FilePath -> IO () encrypt ic nm s_fp d_fp = do bs <- B.readFile s_fp bs' <- encrypt_ ic nm bs B.writeFile d_fp bs' -- | Encrypt a 'B.ByteString' with a named key. encrypt_ :: IC -> Name -> B.ByteString -> IO B.ByteString encrypt_ ic nm bs = _Binary . _EncryptionPacket <$> (run ic $ encryptWithRSAKeyKS nm $ ClearText $ Binary bs) -- | Encrypt a 'B.ByteString' with a named key to produce a 'RSASecretData'. encrypt__ :: IC -> Name -> B.ByteString -> IO RSASecretData encrypt__ ic nm bs = run ic $ encryptWithRSAKeyKS_ nm $ ClearText $ Binary bs -- | Decrypt a file with the named key (whose secret text must be accessible). decrypt :: IC -> FilePath -> FilePath -> IO () decrypt ic s_fp d_fp = do bs <- B.readFile s_fp bs' <- decrypt_ ic bs B.writeFile d_fp bs' -- | Decrypt a 'B.ByteString' with the named key -- (whose secret text must be accessible). decrypt_ :: IC -> B.ByteString -> IO B.ByteString decrypt_ ic bs = _Binary . _ClearText <$> (run ic $ decryptWithRSAKeyKS $ EncryptionPacket $ Binary bs) -- | Decrypt a 'B.ByteString' from a 'RSASecretData' with the named key -- (whose secret text must be accessible). decrypt__ :: IC -> Name -> RSASecretData -> IO B.ByteString decrypt__ ic nm rsd = _Binary . _ClearText <$> (run ic $ decryptWithRSAKeyKS_ nm rsd) -- | Sign a file with the named key (whose secret text must be accessible) -- to produce a detached signature in the named file. sign :: IC -> Name -> FilePath -> FilePath -> IO () sign ic nm s_fp d_fp = do bs <- B.readFile s_fp bs' <- sign_ ic nm bs B.writeFile d_fp bs' -- | Sign a 'B.ByteString' with the named key (whose secret text must be accessible) -- to produce a detached signature. sign_ :: IC -> Name -> B.ByteString -> IO B.ByteString sign_ ic nm m_bs = _Binary . _SignaturePacket <$> (run ic $ signWithRSAKeyKS nm $ ClearText $ Binary m_bs) -- | Verify that a signature for a file via the named public key. verify :: IC -> FilePath -> FilePath -> IO Bool verify ic m_fp s_fp = do m_bs <- B.readFile m_fp s_bs <- B.readFile s_fp ok <- verify_ ic m_bs s_bs case ok of True -> return () False -> report "signature does not match the data" return ok -- | Verify that a signature for a 'B.ByteString' via the named public key. verify_ :: IC -> B.ByteString -> B.ByteString -> IO Bool verify_ ic m_bs s_bs = run ic $ verifyWithRSAKeyKS (ClearText $ Binary m_bs) (SignaturePacket $ Binary s_bs) -- | Run a KS function in an IO context, dealing with keystore updates, output, -- debug logging and errors. run :: IC -> KS a -> IO a run ic p = do (ctx,st0) <- getCtxState ic st1 <- scan_env ctx st0 let msg = "[Keystore: " ++ ctx_store ctx ++"]\n" (e,st2,les) = run_ ctx st1 $ debugLog msg >> p r <- e2io e mapM_ (logit ctx) les st' <- backup_env ctx st2 putCtxState ic ctx st' return r scan_env :: Ctx -> State -> IO State scan_env ctx st0 = do (ks,les) <- scanEnv ks0 mapM_ (logit ctx) les return st0 { st_keystore = ks } where ks0 = st_keystore st0 backup_env :: Ctx -> State -> IO State backup_env ctx st0 = do mapM_ (logit ctx) les' e2io e return st' where (e,st',les') = run_ ctx st0 backupKeysKS getKeystore :: IC -> IO KeyStore getKeystore ic = st_keystore <$> getState ic getState :: IC -> IO State getState ic = snd <$> getCtxState ic getCtxState :: IC -> IO (Ctx,State) getCtxState IC{..} = case ic_cache of Nothing -> determineCtx ic_ctx_params Just rf -> readIORef rf putCtxState :: IC -> Ctx -> State -> IO () putCtxState IC{..} ctx st = do maybe (return ()) (flip writeIORef (ctx,st)) ic_cache when (not $ maybe False id $ cp_readonly ic_ctx_params) $ LBS.writeFile (ctx_store ctx) $ keyStoreBytes $ st_keystore st report :: String -> IO () report = hPutStrLn stderr