signify-hs-0.1.0.1: src/Main.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE NoImplicitPrelude #-}
module Main where
import Options.Applicative as O
import Data.Maybe (fromMaybe,isNothing,Maybe(..))
import Data.Either (Either(..))
import Data.Bool (Bool(..),(||),otherwise)
import Data.String (String)
import Data.List ((++),drop)
import Data.Eq ((==))
import Data.Function (($))
import Crypto.ECC.Signify
import qualified Crypto.ECC.Ed25519.Sign as Ed
import qualified Data.ByteString as B
import Control.Monad
import Data.Semigroup ((<>))
import System.Exit
import System.Environment (getProgName)
import System.FilePath (takeBaseName)
import System.IO (hSetEcho,stdin,stdout,hFlush,IO(),FilePath,putStrLn,putStr,print)
-- import System.FileArchive.GZip
#ifndef mingw32_HOST_OS
import qualified Data.ByteString.Lazy.Char8 as BS8
#else
import qualified Crypto.Random as R
import Prelude (show)
#endif
data Opts = Opts
{
checksum :: Bool
, generate :: Bool
, sign :: Bool
, verify :: Bool
, pubkey :: Maybe FilePath
, seckey :: Maybe FilePath
, sigfile :: Maybe FilePath
, message :: Maybe String
, quiet :: Bool
, comment :: String
, embedmsg :: Bool
, nopassphrase :: Bool
, keytype :: Maybe String
, gzipembed :: Bool
, files :: [FilePath]
}
options :: Parser Opts
options = Opts
<$> switch
(short 'C'
<> help "Verify a signed checksum list, and then verify the checksum for each file." )
<*> switch
(short 'G'
<> help "Generate a new key pair." )
<*> switch
(short 'S'
<> help "Sign the specified message file and create a signature." )
<*> switch
(short 'V'
<> help "Verify the message and signature match." )
<*> optional (strOption
(short 'p'
<> help "Public key"
<> metavar "pubkey"))
<*> optional (strOption
(short 's'
<> help "Secret Key"
<> metavar "seckey"))
<*> optional (strOption
(short 'x'
<> help "Signature file to create or verify"
<> metavar "sigfile"))
<*> optional (strOption
(short 'm'
<> help "message"
<> metavar "message"))
<*> switch
(short 'q'
<> help "Whether to be quiet" )
<*> strOption
(short 'c'
<> value "signify"
<> help "comment"
<> metavar "comment")
<*> switch
(short 'e'
<> help "when signing, embed message after signature" )
<*> switch
(short 'n'
<> help "don't force a passphrase" )
<*> optional (strOption
(short 't'
<> help "keytypes???"
<> metavar "keytype"))
<*> switch
(short 'z'
<> help "sign and verify gzip archives, signing data embedded in the header" )
<*> many (argument str (metavar "file ..."))
-- The horror of option combinations is contained here
main :: IO ()
-- main = do signify =<< customExecParser (prefs showHelpOnError) opts
main = signify =<< execParser opts
where
opts = info (options <**> helper)
( fullDesc
<> progDesc "cryptographically sign and verify files"
<> header "signify-hs, a Haskell clone of signify-openbsd" )
signify :: Opts -> IO ()
signify (Opts _ _ _ _ _ _ _ _ _ _ _ _ _ True _)
= do putStrLn "gzip embedding not supported, yet"
wrong
signify (Opts True _ _ _ _ _ Nothing _ q _ _ _ _ _ _)
= do unless q $ putStrLn "must specify sigfile"
wrong
signify (Opts True _ _ _ pub _ (Just sigfile') _ q _ _ _ _ _ files')
= do putStrLn "Checksum file mode not supported, yet"
wrong
signify (Opts _ True _ _ pub sec _ _ _ comment' _ nopass _ _ _)
| isNothing pub || isNothing sec = putStrLn "must specify pubkey and seckey" >> wrong
| otherwise = do
passphrase <- if nopass then return B.empty else getcreatepassphrase
keys <- Ed.genkeys
case keys of
Left e -> print e >> exitFailure
Right (seck,pubk) ->
do
#ifndef mingw32_HOST_OS
bytes <- BS8.readFile "/dev/urandom"
let salt = BS8.toStrict $ BS8.take 16 bytes
keyID = BS8.toStrict $ BS8.take 8 $ BS8.drop 16 bytes
#else
g <- R.getStdGen
let bytes = R.randoms g
salt = BS.pack $ BS8.take 16 bytes
keyID = BS8.toStrict $ BS8.take 8 $ BS8.drop 16 bytes
#endif
B.writeFile (fromMaybe "" pub) $ printPubKey keyID pubk comment'
B.writeFile (fromMaybe "" sec) $ printSecKey keyID passphrase salt seck pubk comment'
signify (Opts _ _ True _ _ sec sig message' _ _ embed nopass _ gzipembed' _)
| isNothing sec || isNothing message' = putStrLn "must specify message and seckey" >> wrong
signify (Opts _ _ True _ _ (Just secfile) sig (Just messagefile) _ _ embed nopass _ gzipembed' _) = do
let targetfile = if isNothing sig then messagefile ++ ".sig" else let (Just sigfile') = sig in sigfile'
passphrase <- if nopass then return B.empty else getpassphrase
seccontent <- parseSecKey passphrase <$> B.readFile secfile
case seccontent of
Left e -> putStrLn e >> exitFailure
Right (_, keyid, secKeyAndPubKey) -> do
msgs <- B.readFile messagefile
let comment' = "verify with " ++ takeBaseName secfile ++ ".pub"
s = Ed.dsign secKeyAndPubKey msgs
case s of
Right sigs -> B.writeFile targetfile $ printSignature keyid sigs comment' `B.append` if embed then msgs else B.empty
Left e -> putStrLn e >> exitFailure
signify (Opts _ _ _ True _ _ _ Nothing _ _ _ _ _ _ _)
= putStrLn "must specify message" >> wrong
signify (Opts _ _ _ True pub _ sig (Just msgfile) q _ embed _ _ gzipembed' _)
= do
sigs <- case sig of
Nothing -> parseSignature <$> B.readFile (msgfile ++ ".sig")
Just sigfile' -> parseSignature <$> B.readFile sigfile'
msgs <- B.readFile msgfile
case sigs of
Left _ -> wrong
Right (comments, keyids, sig') -> do
pubs <- case pub of
Nothing -> let filep = drop 12 comments
in parsePubKey <$> B.readFile ("/etc/signify/" ++ filep)
Just pubfile -> parsePubKey <$> B.readFile pubfile
case pubs of
Left _ -> wrong
Right (commentp, keyidp, pub') ->
let x = Ed.dverify pub' sig' msgs
in case x of
Right Ed.SigOK -> unless q $ putStrLn "Signature Verified"
_ -> do unless q $ putStrLn "signature verification failed"
exitFailure
signify _ = wrong
wrong :: IO ()
wrong = printUsage >> exitFailure
printUsage :: IO ()
printUsage = do
putStrLn "usage:\tsignify-hs -C [-q] [-p pubkey] [-t keytype] -x sigfile [file ...]"
putStrLn "\tsignify-hs -G [-n] [-c comment] -p pubkey -s seckey"
putStrLn "\tsignify-hs -S [-enz] [-x sigfile] -s seckey -m message"
putStrLn "\tsignify-hs -V [-eqz] [-p pubkey] [-t keytype] [-x sigfile] -m message"
-- putStrLn "help:\tsignify-hs [-h|--help]"
getcreatepassphrase :: IO B.ByteString
getcreatepassphrase = do
hSetEcho stdin False
passphrase <- getpassphrase
putStr "confirm passphrase: "
hFlush stdout
passphrase2 <- B.getLine
putStrLn ""
if passphrase == passphrase2
then return passphrase
else do name <- getProgName
putStrLn $ name ++ ": passwords don't match"
exitFailure
getpassphrase :: IO B.ByteString
getpassphrase = do
hSetEcho stdin False
putStr "passphrase: "
hFlush stdout
passphrase <- B.getLine
putStrLn ""
return passphrase