module Hackage.Security.TUF.Signed (
Signed(..)
, Signatures(..)
, Signature(..)
, unsigned
, withSignatures
, withSignatures'
, signRendered
, verifySignature
, signedFromJSON
, verifySignatures
, UninterpretedSignatures(..)
, PreSignature(..)
, fromPreSignature
, fromPreSignatures
, toPreSignature
, toPreSignatures
) where
import Control.Monad
import Data.Functor.Identity
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BS.L
import qualified Data.Set as Set
import Hackage.Security.JSON
import Hackage.Security.Key
import Hackage.Security.TUF.Layout.Repo
import Hackage.Security.Util.Some
import Text.JSON.Canonical
import qualified Hackage.Security.Util.Base64 as B64
data Signed a = Signed {
signed :: a
, signatures :: Signatures
}
newtype Signatures = Signatures [Signature]
data Signature = Signature {
signature :: BS.ByteString
, signatureKey :: Some PublicKey
}
unsigned :: a -> Signed a
unsigned a = Signed { signed = a, signatures = Signatures [] }
withSignatures :: ToJSON WriteJSON a => RepoLayout -> [Some Key] -> a -> Signed a
withSignatures repoLayout keys doc = Signed {
signed = doc
, signatures = signRendered keys $ renderJSON repoLayout doc
}
withSignatures' :: ToJSON Identity a => [Some Key] -> a -> Signed a
withSignatures' keys doc = Signed {
signed = doc
, signatures = signRendered keys $ renderJSON_NoLayout doc
}
signRendered :: [Some Key] -> BS.L.ByteString -> Signatures
signRendered keys rendered = Signatures $ map go keys
where
go :: Some Key -> Signature
go (Some key) = Signature {
signature = sign (privateKey key) rendered
, signatureKey = Some $ publicKey key
}
verifySignature :: BS.L.ByteString -> Signature -> Bool
verifySignature inp Signature{signature = sig, signatureKey = Some pub} =
verify pub inp sig
instance (Monad m, ToJSON m a) => ToJSON m (Signed a) where
toJSON Signed{..} = mkObject [
("signed" , toJSON signed)
, ("signatures" , toJSON signatures)
]
instance Monad m => ToJSON m Signatures where
toJSON = toJSON . toPreSignatures
instance MonadKeys m => FromJSON m Signatures where
fromJSON = fromPreSignatures <=< fromJSON
signedFromJSON :: (MonadKeys m, FromJSON m a) => JSValue -> m (Signed a)
signedFromJSON envelope = do
enc <- fromJSField envelope "signed"
signed <- fromJSON enc
signatures <- fromJSField envelope "signatures"
validate "signatures" $ verifySignatures enc signatures
return Signed{..}
verifySignatures :: JSValue -> Signatures -> Bool
verifySignatures parsed (Signatures sigs) =
all (verifySignature $ renderCanonicalJSON parsed) sigs
data UninterpretedSignatures a = UninterpretedSignatures {
uninterpretedSigned :: a
, uninterpretedSignatures :: [PreSignature]
}
deriving (Show)
data PreSignature = PreSignature {
presignature :: BS.ByteString
, presigMethod :: Some KeyType
, presigKeyId :: KeyId
}
deriving (Show)
fromPreSignature :: MonadKeys m => PreSignature -> m Signature
fromPreSignature PreSignature{..} = do
key <- lookupKey presigKeyId
validate "key type" $ typecheckSome key presigMethod
return Signature {
signature = presignature
, signatureKey = key
}
toPreSignature :: Signature -> PreSignature
toPreSignature Signature{..} = PreSignature {
presignature = signature
, presigMethod = somePublicKeyType signatureKey
, presigKeyId = someKeyId signatureKey
}
fromPreSignatures :: MonadKeys m => [PreSignature] -> m Signatures
fromPreSignatures sigs = do
validate "all signatures made with different keys" $
Set.size (Set.fromList (map presigKeyId sigs)) == length sigs
Signatures <$> mapM fromPreSignature sigs
toPreSignatures :: Signatures -> [PreSignature]
toPreSignatures (Signatures sigs) = map toPreSignature sigs
instance ReportSchemaErrors m => FromJSON m PreSignature where
fromJSON enc = do
kId <- fromJSField enc "keyid"
method <- fromJSField enc "method"
sig <- fromJSField enc "sig"
return PreSignature {
presignature = B64.toByteString sig
, presigMethod = method
, presigKeyId = KeyId kId
}
instance Monad m => ToJSON m PreSignature where
toJSON PreSignature{..} = mkObject [
("keyid" , return $ JSString . keyIdString $ presigKeyId)
, ("method" , toJSON $ presigMethod)
, ("sig" , toJSON $ B64.fromByteString presignature)
]
instance ( ReportSchemaErrors m
, FromJSON m a
) => FromJSON m (UninterpretedSignatures a) where
fromJSON envelope = do
enc <- fromJSField envelope "signed"
uninterpretedSigned <- fromJSON enc
uninterpretedSignatures <- fromJSField envelope "signatures"
return UninterpretedSignatures{..}
instance (Monad m, ToJSON m a) => ToJSON m (UninterpretedSignatures a) where
toJSON UninterpretedSignatures{..} = mkObject [
("signed" , toJSON uninterpretedSigned)
, ("signatures" , toJSON uninterpretedSignatures)
]