packages feed

scyther-proof-0.4.0: src/Scyther/Typing.hs

{-# LANGUAGE TypeSynonymInstances, DeriveDataTypeable, FlexibleInstances #-}
-- | Building typing invariants for security protocol in order to enable
-- verification in an untyped model.
module Scyther.Typing (
  -- * Type
    Type(..)
  , normType

  -- ** Type annotations
  , TypeAnn
  , typeAnnTIDs
  , typeAnnAMIDs
  , substTypeAnn

  -- ** Type assertions
  , Typing
  , mscTyping

  -- ** Pretty Printing
  , isaType
  , sptType
  , sptTypeAnn
  , sptTyping
) where

import qualified Data.Set as S
import qualified Data.Map as M
import Data.DAG.Simple
import Data.Data

import Control.Basics
import Control.Monad.State

import Scyther.Protocol
import Scyther.Message
import qualified Scyther.Equalities as E
import Text.Isar


data Type =
    AgentT 
  | ConstT Id
  | NonceT Role Id
  | HashT Type
  | EncT  Type Type
  | TupT Type Type
  | SymKT Type Type
  | AsymPKT Type
  | AsymSKT Type
  | KnownT RoleStep
  | SumT Type Type
  deriving( Eq, Ord, Show, Data, Typeable )

-- | Partial normalization: rewrites with 
--
-- > SumT ty (KnownT _) = SumT (KnownT _) ty
--
normType :: Type -> Type
normType (SumT ty1 ty2@(KnownT _)) = SumT ty2 (normType ty1)
normType (SumT ty1 ty2)            = SumT (normType ty1) (normType ty2)
normType (EncT ty1 ty2)            = EncT (normType ty1) (normType ty2)
normType (TupT ty1 ty2)            = TupT (normType ty1) (normType ty2)
normType (SymKT ty1 ty2)           = SymKT (normType ty1) (normType ty2)
normType (HashT ty)                = HashT ty
normType (AsymPKT ty)              = AsymPKT ty
normType (AsymSKT ty)              = AsymSKT ty
normType ty                        = ty


------------------------------------------------------------------------------
-- Type annotations on messages
------------------------------------------------------------------------------

-- | A type annotation states that a message is a member of a type interpreted
-- with respect to a specific thread.
type TypeAnn = (Message, Type, TID)

-- | The TIDs of a type annotation.
typeAnnTIDs :: TypeAnn -> [TID]
typeAnnTIDs (m, _, tid) = tid : msgTIDs m 

-- | The AMIDs of a type annotation.
typeAnnAMIDs :: TypeAnn -> [ArbMsgId]
typeAnnAMIDs (m, _, _) = msgAMIDs m 

-- | Substitute a type annotation.
substTypeAnn :: E.Equalities -> TypeAnn -> TypeAnn
substTypeAnn eqs (m, ty, tid) = (E.substMsg eqs m, ty, E.substTID eqs tid)


------------------------------------------------------------------------------
-- Typings
------------------------------------------------------------------------------

-- | A type assignment for variables of several roles.
type Typing = M.Map (Id, Role) Type

-- | Compute a typing from the message sequence chart of the protocol
-- implicitly given by the corresponding labels.
--
-- FIXME: This is quite a hack and could be done much better: do it!
mscTyping :: Protocol -> Maybe Typing
mscTyping proto = 
    (`execStateT` M.empty) $ foldM typeStep E.empty steps
  where
    roleeqs = M.fromList $ zip [1..] (protoRoles proto)
    rolemap = M.fromList $ zip (protoRoles proto) [1..]
    steps = map (second (rolemap M.!)) . toposort $ protoOrd proto

    typeStep eqs (Send _ _, _) = do
        return eqs
    typeStep eqs (recv@(Recv lR ptR), tidR) =
        case [ send | send@(Send lS _, _) <- steps, lS == lR ] of
          []                   -> do
              -- no matching send: assume intruder knows all message variables
              -- in the received pattern that are not yet mapped.
              sequence_ $ do PMVar v <- S.toList $ subpatterns ptR
                             return (knownAtRecv v)
              return eqs
          ((Send _ ptS, tidS):_) -> do
              eqs' <- lift $ E.solve [E.MsgEq (inst tidR ptR, inst tidS ptS)] eqs
              mapM_ typeMVar (E.getMVarEqs eqs')
              -- variables not mapped yet are mapped to they known type.
              sequence_ $ do PMVar v <- S.toList $ subpatterns ptR
                             return (knownAtRecv v)
              return eqs'
          _ -> error "mscTyping: the impossible happened"
      where
        typeMVar (MVar (LocalId (v, vTid)), m) 
          | vTid == tidR && PMVar v `S.member` splitpatterns ptR =
              noteType (KnownT recv)
          | otherwise = 
              noteType $ maybe (KnownT recv) (SumT (KnownT recv)) (typeMsg m)
          where
            noteType = modify . M.insertWith keepSimpler (v, roleeqs M.! vTid)
            -- prefer non-sum-types and then the existing one
            keepSimpler _  x@(KnownT _) = x
            keepSimpler x@(KnownT _) _  = x
            keepSimpler _            x  = x

        knownAtRecv v = 
            modify (M.insertWith keepExisting (v, roleeqs M.! tidR) (KnownT recv))
          where
            keepExisting _new old = old
 
        typeMsg (MFresh (Fresh fr)) = pure $ NonceT (roleeqs M.! lidTID fr) (lidId fr)
        typeMsg (MConst c)    = pure $ ConstT c
        typeMsg (MAVar _)     = pure $ AgentT
        typeMsg (MMVar _)     = Nothing
        typeMsg (MHash m)     = HashT   <$> typeMsg m
        typeMsg (MTup m1 m2)  = TupT    <$> typeMsg m1 <*> typeMsg m2
        typeMsg (MEnc m1 m2)  = EncT    <$> typeMsg m1 <*> typeMsg m2
        typeMsg (MSymK m1 m2) = SymKT   <$> typeMsg m1 <*> typeMsg m2
        typeMsg (MAsymPK m)   = AsymPKT <$> typeMsg m
        typeMsg (MAsymSK m)   = AsymSKT <$> typeMsg m
        typeMsg (MArbMsg _)    = error $ "mscTyping: agent variable encountered"
        typeMsg (MInvKey _)   = error $ "mscTyping: key inversion encountered"
        typeMsg (MShrK _ _)   = error $ "mscTyping: bi-directional shared key encountered"


------------------------------------------------------------------------------
-- Pretty printing
------------------------------------------------------------------------------

-- | Pretty print a type in Isar syntax; paramatrized over the role of the
-- variable that this type describes. This role is used for abbreviating role
-- steps by the 'role_label' constant symbols defined in Isabelle.
isaType :: IsarConf -> Maybe Role -> Type -> Doc
isaType conf optRole = go
  where
  go ty = case ty of
    AgentT        -> text "AgentT"
    ConstT i      -> parens $ text "ConstT" <-> isar conf i
    NonceT role i -> parens $ text "NonceT" <-> text (roleName role) <-> isar conf i
    HashT ty1     -> parens $ text "HashT" <-> go ty1
    EncT ty1 ty2  -> parens $ text "EncT" <-> go ty1 <-> go ty2
    TupT ty1 ty2  -> parens $ text "TupT" <-> go ty1 <-> go ty2
    AsymPKT ty1   -> parens $ text "PKT" <-> go ty1
    AsymSKT ty1   -> parens $ text "SKT" <-> go ty1
    SymKT ty1 ty2 -> parens $ text "KT" <-> go ty1 <-> go ty2
    KnownT step   -> parens $ text "KnownT" <-> isaRoleStep conf optRole step
    SumT ty1 ty2  -> parens $ text "SumT" <-> go ty1 <-> go ty2

-- | Pretty print a type in security protocol theory format. If the role is
-- given then the type describes a variable of this role. The steps of this
-- role are abbreviated accordingly.
sptType :: Maybe Role -> Type -> Doc
sptType optRole = go
  where
  go ty = case ty of
    AgentT        -> text "Agent"
    ConstT i      -> text $ "'" ++ getId i ++ "'"
    NonceT role i -> text $ getId i ++ "@" ++ roleName role
    HashT ty1     -> text "h" <> parens (go ty1)
    EncT ty1 ty2  -> braces (go ty1) <> go ty2
    TupT ty1 ty2  -> parens (go ty1 <> comma <-> go ty2)
    AsymPKT ty1   -> text "pk" <> parens (go ty1)
    AsymSKT ty1   -> text "sk" <> parens (go ty1)
    SymKT ty1 ty2 -> text "k"  <> parens (go ty1 <> comma <-> go ty2)
    KnownT step   -> text "Known" <> parens (sptRoleStep optRole step)
    SumT ty1 ty2  -> parens (sep [go ty1 <-> text "|", go ty2])

sptTypeAnn :: (TID -> Maybe Role) -> TypeAnn -> Doc
sptTypeAnn tidToRole (m,ty,i) = hsep 
    [sptMessage m, text "::", parens (sptType (tidToRole i) ty) <> sptTID i]

sptTyping :: Typing -> Doc
sptTyping = vcat . map ppTyEq . M.toList 
  where
    ppTyEq ((v,role),ty) = 
      sep [ text $ getId v ++ "@" ++ roleName role
          , nest 2 $ text "::" <-> sptType (Just role) ty ]

instance Isar Type where
  isar conf = isaType conf Nothing

instance Isar Typing where
  isar conf = 
      ($$ rbrack) . vcat . zipWith (<->) seps . map ppTyEq . M.toList
    where
      seps = map char $ '[' : repeat ','
      ppTyEq ((v,role),ty) = parens $
        sep [ parens (text (roleName role) <> comma <-> isar conf v) <> comma
            , isaType conf (Just role) ty ]