{-# LANGUAGE CPP #-}
module Hackage.Security.Key (
Ed25519
, Key(..)
, PublicKey(..)
, PrivateKey(..)
, KeyType(..)
, somePublicKey
, somePublicKeyType
, someKeyId
, publicKey
, privateKey
, createKey
, createKey'
, KeyId(..)
, HasKeyId(..)
, sign
, verify
) where
import Control.Monad
import Data.Functor.Identity
import Data.Typeable (Typeable)
import Text.JSON.Canonical
import qualified Crypto.Hash.SHA256 as SHA256
import qualified Crypto.Sign.Ed25519 as Ed25519
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BS.C8
import qualified Data.ByteString.Base16 as Base16
import qualified Data.ByteString.Lazy as BS.L
#if !MIN_VERSION_base(4,7,0)
import qualified Data.Typeable as Typeable
#endif
import Hackage.Security.Util.JSON
import Hackage.Security.Util.Some
import Hackage.Security.Util.TypedEmbedded
import qualified Hackage.Security.Util.Base64 as B64
data Ed25519
data Key a where
KeyEd25519 :: Ed25519.PublicKey -> Ed25519.SecretKey -> Key Ed25519
deriving (Typeable)
data PublicKey a where
PublicKeyEd25519 :: Ed25519.PublicKey -> PublicKey Ed25519
deriving (Typeable)
data PrivateKey a where
PrivateKeyEd25519 :: Ed25519.SecretKey -> PrivateKey Ed25519
deriving (Typeable)
deriving instance Show (Key typ)
deriving instance Show (PublicKey typ)
deriving instance Show (PrivateKey typ)
deriving instance Eq (Key typ)
deriving instance Eq (PublicKey typ)
deriving instance Eq (PrivateKey typ)
instance SomeShow Key where someShow = DictShow
instance SomeShow PublicKey where someShow = DictShow
instance SomeShow PrivateKey where someShow = DictShow
instance SomeEq Key where someEq = DictEq
instance SomeEq PublicKey where someEq = DictEq
instance SomeEq PrivateKey where someEq = DictEq
publicKey :: Key a -> PublicKey a
publicKey (KeyEd25519 pub _pri) = PublicKeyEd25519 pub
privateKey :: Key a -> PrivateKey a
privateKey (KeyEd25519 _pub pri) = PrivateKeyEd25519 pri
data KeyType typ where
KeyTypeEd25519 :: KeyType Ed25519
deriving instance Show (KeyType typ)
deriving instance Eq (KeyType typ)
instance SomeShow KeyType where someShow = DictShow
instance SomeEq KeyType where someEq = DictEq
instance Unify KeyType where
unify KeyTypeEd25519 KeyTypeEd25519 = Just Refl
type instance TypeOf Key = KeyType
type instance TypeOf PublicKey = KeyType
type instance TypeOf PrivateKey = KeyType
instance Typed Key where
typeOf (KeyEd25519 _ _) = KeyTypeEd25519
instance Typed PublicKey where
typeOf (PublicKeyEd25519 _) = KeyTypeEd25519
instance Typed PrivateKey where
typeOf (PrivateKeyEd25519 _) = KeyTypeEd25519
somePublicKey :: Some Key -> Some PublicKey
somePublicKey (Some key) = Some (publicKey key)
somePublicKeyType :: Some PublicKey -> Some KeyType
somePublicKeyType (Some pub) = Some (typeOf pub)
someKeyId :: HasKeyId key => Some key -> KeyId
someKeyId (Some a) = keyId a
createKey :: KeyType key -> IO (Key key)
createKey KeyTypeEd25519 = uncurry KeyEd25519 <$> Ed25519.createKeypair
createKey' :: KeyType key -> IO (Some Key)
createKey' = liftM Some . createKey
newtype KeyId = KeyId { keyIdString :: String }
deriving (Show, Eq, Ord)
instance Monad m => ToObjectKey m KeyId where
toObjectKey = return . keyIdString
instance Monad m => FromObjectKey m KeyId where
fromObjectKey = return . Just . KeyId
class HasKeyId key where
keyId :: key typ -> KeyId
instance HasKeyId PublicKey where
keyId = KeyId
. BS.C8.unpack
. Base16.encode
. SHA256.hashlazy
. renderCanonicalJSON
. runIdentity
. toJSON
instance HasKeyId Key where
keyId = keyId . publicKey
sign :: PrivateKey typ -> BS.L.ByteString -> BS.ByteString
sign (PrivateKeyEd25519 pri) =
Ed25519.unSignature . dsign pri . BS.concat . BS.L.toChunks
where
#if MIN_VERSION_ed25519(0,0,4)
dsign = Ed25519.dsign
#else
dsign = Ed25519.sign'
#endif
verify :: PublicKey typ -> BS.L.ByteString -> BS.ByteString -> Bool
verify (PublicKeyEd25519 pub) inp sig =
dverify pub (BS.concat $ BS.L.toChunks inp) (Ed25519.Signature sig)
where
#if MIN_VERSION_ed25519(0,0,4)
dverify = Ed25519.dverify
#else
dverify = Ed25519.verify'
#endif
instance Monad m => ToJSON m (Key typ) where
toJSON key = case key of
KeyEd25519 pub pri ->
enc "ed25519" (Ed25519.unPublicKey pub) (Ed25519.unSecretKey pri)
where
enc :: String -> BS.ByteString -> BS.ByteString -> m JSValue
enc tag pub pri = mkObject [
("keytype", return $ JSString tag)
, ("keyval", mkObject [
("public", toJSON (B64.fromByteString pub))
, ("private", toJSON (B64.fromByteString pri))
])
]
instance ReportSchemaErrors m => FromJSON m (Some Key) where
fromJSON enc = do
(tag, pub, pri) <- dec enc
case tag of
"ed25519" -> return . Some $
KeyEd25519 (Ed25519.PublicKey pub) (Ed25519.SecretKey pri)
_otherwise ->
expected "valid key type" (Just tag)
where
dec :: JSValue -> m (String, BS.ByteString, BS.ByteString)
dec obj = do
tag <- fromJSField obj "keytype"
val <- fromJSField obj "keyval"
pub <- fromJSField val "public"
pri <- fromJSField val "private"
return (tag, B64.toByteString pub, B64.toByteString pri)
instance Monad m => ToJSON m (PublicKey typ) where
toJSON key = case key of
PublicKeyEd25519 pub ->
enc "ed25519" (Ed25519.unPublicKey pub)
where
enc :: String -> BS.ByteString -> m JSValue
enc tag pub = mkObject [
("keytype", return $ JSString tag)
, ("keyval", mkObject [
("public", toJSON (B64.fromByteString pub))
])
]
instance Monad m => ToJSON m (Some Key) where toJSON (Some a) = toJSON a
instance Monad m => ToJSON m (Some PublicKey) where toJSON (Some a) = toJSON a
instance Monad m => ToJSON m (Some KeyType) where toJSON (Some a) = toJSON a
instance ReportSchemaErrors m => FromJSON m (Some PublicKey) where
fromJSON enc = do
(tag, pub) <- dec enc
case tag of
"ed25519" -> return . Some $
PublicKeyEd25519 (Ed25519.PublicKey pub)
_otherwise ->
expected "valid key type" (Just tag)
where
dec :: JSValue -> m (String, BS.ByteString)
dec obj = do
tag <- fromJSField obj "keytype"
val <- fromJSField obj "keyval"
pub <- fromJSField val "public"
return (tag, B64.toByteString pub)
instance Monad m => ToJSON m (KeyType typ) where
toJSON KeyTypeEd25519 = return $ JSString "ed25519"
instance ReportSchemaErrors m => FromJSON m (Some KeyType) where
fromJSON enc = do
tag <- fromJSON enc
case tag of
"ed25519" -> return . Some $ KeyTypeEd25519
_otherwise -> expected "valid key type" (Just tag)
#if !MIN_VERSION_base(4,7,0)
tyConKey, tyConPublicKey, tyConPrivateKey :: Typeable.TyCon
tyConKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "Key"
tyConPublicKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PublicKey"
tyConPrivateKey = Typeable.mkTyCon3 "hackage-security" "Hackage.Security.Key" "PrivateKey"
instance Typeable (Some Key) where
typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConKey []]
instance Typeable (Some PublicKey) where
typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPublicKey []]
instance Typeable (Some PrivateKey) where
typeOf _ = Typeable.mkTyConApp tyConSome [Typeable.mkTyConApp tyConPrivateKey []]
#endif