-- Makes the characteristic skeleton of a security goal -- Copyright (c) 2015 The MITRE Corporation -- -- This program is free software: you can redistribute it and/or -- modify it under the terms of the BSD License as published by the -- University of California. module CPSA.Characteristic (Conj, characteristic) where import Control.Monad import qualified Data.List as L import CPSA.Lib.Utilities import CPSA.Lib.ReturnFail import CPSA.Lib.SExpr import CPSA.Algebra import CPSA.Protocol import CPSA.Strand {-- import System.IO.Unsafe z :: Show a => a -> b -> b z x y = unsafePerformIO (print x >> return y) --} -- Relocating this to Protocol.hs: -- type Conj = [(Pos, AForm)] -- Entry point. Takes a position, a protocol, a variable generator, a -- goal, and a skeleton comment and makes a skeleton or fails. This -- function extracts the anecedent and univesally quantified variable. characteristic :: MonadFail m => Pos -> Prot -> [Goal] -> Gen -> Conj -> [SExpr ()] -> m Preskel characteristic pos prot goals g antec comment = equalsForm pos prot goals g antec comment -- Checks for equals in an antecedent and fails if it finds one. One -- could use unification to solve the equality, and then apply the -- result to the remaining parts of the formula. equalsForm :: MonadFail m => Pos -> Prot -> [Goal] -> Gen -> Conj -> [SExpr ()] -> m Preskel equalsForm pos _ _ _ as _ | any isEquals as = fail (shows pos "Equals not allowed in antecedent") equalsForm pos prot goals g as comment = splitForm pos prot goals g as comment isEquals :: (Pos, AForm) -> Bool isEquals (_, Equals _ _) = True isEquals _ = False -- Split the formula into instance formulas and skeleton formulas. -- The instance formulas are used to generate the skeleton's -- instances, and the skeleton formulas generate the rest. Make the -- instances, and then make the rest. splitForm :: MonadFail m => Pos -> Prot -> [Goal] -> Gen -> Conj -> [SExpr ()] -> m Preskel splitForm pos prot goals g as comment = do (nmap, g, insts) <- mkInsts g is mkSkel pos prot goals nmap g insts ks comment where -- is is the instance formulas and (is, ks) = L.partition instForm as -- ks is the skeleton formulas -- Instance formulas are role length and parameter predicates. instForm :: (Pos, AForm) -> Bool instForm (_, Length _ _ _) = True instForm (_, Param _ _ _ _ _) = True instForm _ = False -- Make the instances from the instance predicates mkInsts :: MonadFail m => Gen -> Conj -> m ([(Term, Sid)], Gen, [Instance]) mkInsts g as = do srl <- strdRoleLength as -- Compute role-length of each strand (g, insts) <- foldInsts g as srl -- Construct instances let strdMap = zip (map fst srl) [0..] -- Construct strand map return (strdMap, g, insts) -- Construct node map for later use -- Computes a map from strands to roles and lengths strdRoleLength :: MonadFail m => Conj -> m [(Term, (Role, Int))] strdRoleLength as = foldM f [] as where f srl (pos, Length r z ht) = case indxLookup emptyEnv ht of Nothing -> fail (shows pos "Index is variable, not integer") Just h -> (case lookup z srl of Nothing -> return ((z, (r, h)) : srl) Just (r', h') | rname r' /= rname r -> fail (shows pos "Strand occurs in more than one role length atom") | h <= h' -> return srl -- Use original binding | otherwise -> return ((z, (r, h)) : srl)) f srl _ = return srl -- Construct instances foldInsts :: MonadFail m => Gen -> Conj -> [(Term, (Role, Int))] -> m (Gen, [Instance]) foldInsts g _ [] = return (g, []) foldInsts g as ((z, (r, h)) : srs) = do (g, inst) <- mkInst g as z r h (g, insts) <- foldInsts g as srs return (g, inst : insts) -- Construct an instance by extracting maplets from the parameter -- predicates associated with the strand. mkInst :: MonadFail m => Gen -> Conj -> Term -> Role -> Int -> m (Gen, Instance) mkInst g as z r h = do (g, env) <- foldM (mkMaplet r z) (g, emptyEnv) as return (mkInstance g r env h) -- Add match from a maplet mkMaplet :: MonadFail m => Role -> Term -> (Gen, Env) -> (Pos, AForm) -> m (Gen, Env) mkMaplet role z env (pos, Param r v _ z' t) | z == z' = if rname role == rname r then -- Ensure role matches the one case match v t env of -- used to create instance env : _ -> return env [] -> fail (shows pos "Domain does not match range") else fail (shows pos "Role in parameter pred differs from role position pred") mkMaplet _ _ env _ = return env -- Use this lookup when lookup must succeed, that is when loader makes -- the check. nMapLookup :: NodeTerm -> [(Term, Sid)] -> Node nMapLookup (z, ht) nmap = case (lookup z nmap, indxLookup emptyEnv ht) of (Just s, Just i) -> (s, i) (Nothing, _) -> error "Characteristic.nMapLookup: Bad lookup" (_, Nothing) -> error "Characteristic.nMapLookup: Bad height term" -- Create a skeleton given a list of instances mkSkel :: MonadFail m => Pos -> Prot -> [Goal] -> [(Term, Sid)] -> Gen -> [Instance] -> Conj -> [SExpr ()] -> m Preskel mkSkel pos p goals nmap g insts as comment = do let o = foldr (mkPrec nmap) [] as let nr = foldr mkNon [] as let ar = foldr mkPnon [] as let ur = foldr mkUniq [] as let ug = foldr mkUgen [] as let gs = foldr mkGenSt [] as let cf = foldr mkConf [] as let au = foldr mkAuth [] as let (nr', ar', ur', ug', cf', au') = foldl addInstOrigs (nr, ar, ur, ug, cf, au) insts let fs = foldr (mkFact nmap) [] as let prios = [] let k = mkPreskel g p goals insts o nr' ar' ur' ug' [] [] gs cf' au' fs prios comment mapM_ (checkUniqAt nmap k) as mapM_ (checkUgenAt nmap k) as case termsWellFormed $ nr' ++ ar' ++ ur' ++ ug' ++ kterms k of False -> fail (shows pos "Terms in skeleton not well formed") True -> return () case verbosePreskelWellFormed k of Return () -> return k Fail msg -> fail $ shows pos $ showString "Skeleton not well formed: " msg addInstOrigs :: ([Term], [Term], [Term], [Term], [Term], [Term]) -> Instance -> ([Term], [Term], [Term], [Term], [Term], [Term]) addInstOrigs (nr, ar, ur, ug, cf, au) i = (foldl (flip adjoin) nr $ inheritRnon i, foldl (flip adjoin) ar $ inheritRpnon i, foldl (flip adjoin) ur $ inheritRunique i, foldl (flip adjoin) ug $ inheritRuniqgen i, foldl (flip adjoin) au $ inheritRconf i, foldl (flip adjoin) cf $ inheritRauth i) mkPrec :: [(Term, Sid)] -> (Pos, AForm) -> [Pair] -> [Pair] mkPrec nmap (_, Prec n n') o = (nMapLookup n nmap, nMapLookup n' nmap) : o mkPrec _ _ o = o mkNon :: (Pos, AForm) -> [Term] -> [Term] mkNon (_, Non t) ts = t : ts mkNon _ ts = ts mkPnon :: (Pos, AForm) -> [Term] -> [Term] mkPnon (_, Pnon t) ts = t : ts mkPnon _ ts = ts mkUniq :: (Pos, AForm) -> [Term] -> [Term] mkUniq (_, Uniq t) ts = t : ts mkUniq (_, UniqAt t _) ts = t : ts mkUniq _ ts = ts mkUgen :: (Pos, AForm) -> [Term] -> [Term] mkUgen (_, Ugen t) ts = t : ts mkUgen (_, UgenAt t _) ts = t : ts mkUgen _ ts = ts mkGenSt :: (Pos, AForm) -> [Term] -> [Term] mkGenSt (_, GenStV t) ts = t : ts mkGenSt _ ts = ts mkConf :: (Pos, AForm) -> [Term] -> [Term] mkConf (_, Conf t) ts = t : ts mkConf _ ts = ts mkAuth :: (Pos, AForm) -> [Term] -> [Term] mkAuth (_, Auth t) ts = t : ts mkAuth _ ts = ts mkFact :: [(Term, Sid)] -> (Pos, AForm) -> [Fact] -> [Fact] mkFact nmap (_, AFact name fs) ts = Fact name (map f fs) : ts where f t = case lookup t nmap of Just s -> FSid s Nothing -> FTerm t mkFact _ _ ts = ts checkUniqAt :: MonadFail m => [(Term, Sid)] -> Preskel -> (Pos, AForm) -> m () checkUniqAt nmap k (pos, UniqAt t n) = case lookup t $ korig k of Nothing -> fail (shows pos "Atom not unique at node") Just ns | elem (nMapLookup n nmap) ns -> return () | otherwise -> fail (shows pos "Atom not unique at node") checkUniqAt _ _ _ = return () checkUgenAt :: MonadFail m => [(Term, Sid)] -> Preskel -> (Pos, AForm) -> m () checkUgenAt nmap k (pos, UgenAt t n) = case lookup t $ kugen k of Nothing -> fail (shows pos "Atom not uniq gen at node") Just ns | elem (nMapLookup n nmap) ns -> return () | otherwise -> fail (shows pos "Atom not uniq gen at node") checkUgenAt _ _ _ = return ()