{-# LANGUAGE CPP #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ScopedTypeVariables #-}
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 qualified Control.Exception as X
import qualified Control.Lens as L
import Control.Monad
import System.IO
#if MIN_VERSION_time(1,5,0)
#else
import System.Locale (defaultTimeLocale)
#endif
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
instanceCtx :: CtxParams -> IO IC
instanceCtx cp =
do ctx_st <- getCtxState $ instanceCtx_ cp
IC cp . Just <$> newIORef ctx_st
instanceCtx_ :: CtxParams -> IC
instanceCtx_ cp = IC cp Nothing
store :: IC -> IO FilePath
store ic = run ic storeKS
listSettings :: IC -> IO ()
listSettings ic = settings ic >>= LBS.putStrLn . encode . _Settings
settings :: IC -> IO Settings
settings ic = run ic $ _cfg_settings <$> getConfig
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
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
triggers :: IC -> IO [Trigger]
triggers ic = run ic $ Map.elems . _cfg_triggers <$> getConfig
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
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
rmvTrigger :: IC -> TriggerID -> IO ()
rmvTrigger ic tid = run ic $ modConfig $ L.over cfg_triggers $ Map.delete tid
createRSAKeyPair :: IC -> Name -> Comment -> Identity -> [Safeguard] -> IO ()
createRSAKeyPair ic nm cmt ide sgs = run ic $ createRSAKeyPairKS nm cmt ide sgs
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)
adjustKey :: IC -> Name -> (Key->Key) -> IO ()
adjustKey ic nm adj = run ic $ adjustKeyKS nm adj
rememberKey :: IC -> Name -> FilePath -> IO ()
rememberKey ic nm fp = B.readFile fp >>= rememberKey_ ic nm
rememberKey_ :: IC -> Name -> B.ByteString -> IO ()
rememberKey_ ic nm bs = run ic $ rememberKeyKS nm $ ClearText $ Binary bs
secureKey :: IC -> Name -> Safeguard -> IO ()
secureKey ic nm nms = run ic $ secureKeyKS nm nms
loadKey :: IC -> Name -> IO Key
loadKey ic nm = run ic $ loadKeyKS nm
showIdentity :: IC -> Bool -> Name -> IO B.ByteString
showIdentity ic = show_it' ic "identity" (Just . _key_identity) (B.pack . T.unpack . _Identity)
showComment :: IC -> Bool -> Name -> IO B.ByteString
showComment ic = show_it' ic "comment" (Just . _key_comment) (B.pack . T.unpack . _Comment )
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"
showHash :: IC -> Bool -> Name -> IO B.ByteString
showHash ic = show_it ic "hash" (fmap _hash_hash . _key_hash) _HashData
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
showHashSalt :: IC -> Bool -> Name -> IO B.ByteString
showHashSalt ic = show_it ic "hash" (fmap (_hashd_salt . _hash_description) . _key_hash) _Salt
showPublic :: IC -> Bool -> Name -> IO B.ByteString
showPublic ic = show_it ic "public" (fmap encodePublicKeyDER . _key_public) _ClearText
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 :: IC -> IO ()
list ic = run ic $ listKS
keyInfo :: IC -> Name -> IO ()
keyInfo ic nm = run ic $ keyInfoKS nm
keys :: IC -> IO [Key]
keys ic = Map.elems . _ks_keymap <$> getKeystore ic
deleteKeys :: IC -> [Name] -> IO ()
deleteKeys ic nms = run ic $ deleteKeysKS nms
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_ :: IC -> Name -> B.ByteString -> IO B.ByteString
encrypt_ ic nm bs = _Binary . _EncryptionPacket <$>
(run ic $ encryptWithRSAKeyKS nm $ ClearText $ Binary bs)
encrypt__ :: IC -> Name -> B.ByteString -> IO RSASecretData
encrypt__ ic nm bs = run ic $ encryptWithRSAKeyKS_ nm $ ClearText $ Binary bs
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_ :: IC -> B.ByteString -> IO B.ByteString
decrypt_ ic bs = _Binary . _ClearText <$>
(run ic $ decryptWithRSAKeyKS $ EncryptionPacket $ Binary bs)
decrypt__ :: IC -> Name -> RSASecretData -> IO B.ByteString
decrypt__ ic nm rsd = _Binary . _ClearText <$> (run ic $ decryptWithRSAKeyKS_ nm rsd)
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_ :: IC -> Name -> B.ByteString -> IO B.ByteString
sign_ ic nm m_bs = _Binary . _SignaturePacket <$>
(run ic $ signWithRSAKeyKS nm $ ClearText $ Binary m_bs)
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_ :: 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 :: 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