signify-hs-0.1.0.1: src/Crypto/ECC/Signify.hs
{-# LANGUAGE NoImplicitPrelude #-}
module Crypto.ECC.Signify ( parsePubKey
, parseSignature
, parseSecKey
, printPubKey
, printSignature
, printSecKey
) where
import Text.Parsec
import Data.Either (Either(..))
import Data.String (String)
import Data.Function (($))
import Data.List ((++),drop,map)
import Data.Eq ((==),(/=))
import Data.Bool ((&&))
import Text.Show (show)
import Control.Monad
import qualified Data.ByteString as B
import Data.ByteString.Internal (c2w)
import Data.ByteString.Base64
import Crypto.ECC.Ed25519.Sign
import qualified Crypto.ECC.Ed25519.Internal.Ed25519 as DANGER
import Crypto.KDF.BCryptPBKDF
import Data.Bits
import qualified Crypto.Hash.SHA512 as H
type KeyID = B.ByteString
type Comment = String
type FileContent = B.ByteString
type Passphrase = B.ByteString
type Errormsg = String
type Salt = B.ByteString
parsePubKey :: FileContent -> Either Errormsg (Comment, KeyID, PubKey)
parsePubKey = parsePubOrSig
parseSignature :: FileContent -> Either Errormsg (Comment, KeyID, Signature)
parseSignature = parsePubOrSig
parseSecKey :: Passphrase -> FileContent -> Either Errormsg (Comment, KeyID, SecKey)
parseSecKey pass file = do
(comment, rest) <- parseSignifyFileContent file
let (kdfalg,rest2) = B.splitAt 2 rest
(kdfrounds,rest3) = B.splitAt 4 rest2
(salt,rest4) = B.splitAt 16 rest3
(cksum,rest5) = B.splitAt 8 rest4
(keyid,encbytes) = B.splitAt 8 rest5
-- rounds = fromBytes $ B.reverse kdfrounds
rounds = 42 -- magic number, TODO: get rid of
params = Parameters {iterCounts = rounds, outputLength = B.length encbytes}
hashpw = generate params pass salt
secbytes = B.pack $ B.zipWith xor encbytes hashpw
resultbytes = if pass == B.empty then encbytes else secbytes
if B.take 8 (H.hash resultbytes) == cksum
then return (comment, keyid, DANGER.SecKeyBytes resultbytes)
else Left "signify-hs: incorrect passphrase"
printPubKey :: KeyID -> PubKey -> Comment -> FileContent
printPubKey keyID pubKey comment = B.pack (map c2w ("untrusted comment: " ++ comment ++ " public key")) `B.append`
B.pack (map c2w "\n") `B.append`
encode (
B.pack (map c2w "Ed") `B.append` -- signify file format magic
keyID `B.append`
pubKey
) `B.append`
B.pack (map c2w "\n")
printSignature :: KeyID -> Signature -> Comment -> FileContent
printSignature keyID sig comment = B.pack (map c2w ("untrusted comment: " ++ comment)) `B.append`
B.pack (map c2w "\n") `B.append`
encode (
B.pack (map c2w "Ed") `B.append` -- signify file format magic
keyID `B.append`
sig
) `B.append`
B.pack (map c2w "\n")
printSecKey :: KeyID -> Passphrase -> Salt -> SecKey -> PubKey -> Comment -> FileContent
printSecKey keyID passphrase salt (DANGER.SecKeyBytes secKeyBytes) pubKeyBytes comment =
let rounds = 42
longkey = secKeyBytes `B.append` pubKeyBytes
params = Parameters {iterCounts = rounds, outputLength = B.length longkey}
hashpw = generate params passphrase salt
secdata = B.pack $ B.zipWith xor longkey hashpw
cksum = B.take 8 $ H.hash longkey
fulldata = B.pack (map c2w "Ed") `B.append` -- signify file format magic
B.pack (map c2w "BK") `B.append` -- signify file format magic
B.pack (map c2w (if passphrase /= B.empty then "\NUL\NUL\NUL*" else "\NUL\NUL\NUL\NUL")) `B.append` -- manually hack rounds magic number 42 for now, TODO cleanly
salt `B.append`
cksum `B.append`
keyID `B.append`
(if passphrase /= B.empty then secdata else longkey)
in B.pack (map c2w ("untrusted comment: " ++ comment ++ " secret key")) `B.append`
B.pack (map c2w "\n") `B.append`
encode fulldata `B.append`
B.pack (map c2w "\n")
{-
-- Read bytes in big-endian order (most significant byte first)
-- Little-endian order is fromBytes . BS.reverse
fromBytes :: (Bits a, Num a) => B.ByteString -> a
fromBytes = B.foldl' f 0
where
f a b = a `shiftL` 8 .|. fromIntegral b
toBytes :: (Bits a, Num a) => a -> B.ByteString
toBytes = undefined
-- -}
parsePubOrSig :: FileContent -> Either Errormsg (Comment, KeyID, B.ByteString)
parsePubOrSig file = do
(comment, rest) <- parseSignifyFileContent file
let (keyid, signifydata) = B.splitAt 8 rest
return (comment, keyid, signifydata)
parseSignifyFileContent :: FileContent -> Either Errormsg (Comment, B.ByteString)
parseSignifyFileContent file = do
let res = parse signifyFile "(unknown)" file
case res of
Left s -> Left $ show s
Right (comment,bytes) -> do
let (alg,rest) = B.splitAt 2 bytes
if alg /= B.pack (map c2w "Ed") && alg /= B.pack (map c2w "ED")
then Left "currently unsupported signing algorithm"
else return (drop 19 comment, rest)
signifyFile :: Parsec FileContent u (Comment, B.ByteString)
signifyFile = do
comment <- many (noneOf "\r\n")
_ <- endOfLine
base64data <- many (noneOf "\r\n")
_ <- endOfLine
let base64decoded = decode $ B.pack $ map c2w base64data
case base64decoded of
Left s -> parserFail s
Right dat -> return (comment,dat)