cpsa-4.4.1: src/CPSA/Roletran/Derivation.hs
-- Constructs a procedure from a role using derivations
-- Copyright (c) 2020 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.Roletran.Derivation (derive) where
import Control.Monad (foldM)
import qualified Data.Map.Strict as M
import Data.Map.Strict (Map)
import CPSA.Lib.SExpr (Pos)
import CPSA.Roletran.Algebra
import CPSA.Roletran.Protocol
import CPSA.Roletran.Emitter
import CPSA.Roletran.Displayer (displayTerm)
-- At this stage, program variables are represented as integers. We
-- call this representation of a program variable a variable index.
-- The compile time store is a map from terms to variable indices. At
-- runtime, the variable will be bound to a data object that is
-- represented by the term.
type CompStore = Map Term Vari
-- Entry point for compilation by derivation
derive :: MonadFail m => Role -> m Proc
derive r =
do
-- Construct the parameter bindings.
let (fresh, bindings, ins) = bindInputs (rinputs r)
-- Construct the initial state.
let st = (fresh, M.fromList bindings, [])
-- Construct a list of the return types.
let outs = map kind (routputs r)
-- Construct the statements that form the body of the procedure.
stmts <- deriveStmts r st
return $ mkProc
(rname r)
(rpos r)
(reverse ins)
outs
(reverse stmts)
-- Allocate variable indices to inputs and create procedure
-- declarations.
bindInputs :: [Term] -> (Vari, [(Term, Vari)], [Decl])
bindInputs ts =
foldl f (0, [], []) ts
where
f (fresh, binding, ins) t =
(fresh + 1, (t, fresh) : binding, (fresh, kind t) : ins)
-- The state association with compilation
type State = (Vari, CompStore, [Stmt])
-- Accessors for the state
-- The first component is the next available variable index.
freshVar :: State -> Vari
freshVar (fresh, _, _) = fresh
-- The second component is the compile time store.
compStore :: State -> CompStore
compStore (_, cs, _) = cs
-- The third component is the current list of statements.
statements :: State -> [Stmt]
statements (_, _, stmts) = stmts
-- Compile the trace and the outputs.
deriveStmts :: MonadFail m => Role -> State -> m [Stmt]
deriveStmts r st =
do
st <- foldM (deriveEvent (runiques r))
st
(zip (rtrace r) [0..])
deriveOutputs st r
-- Compile an event.
deriveEvent :: MonadFail m => [(Term, Int)] ->
State -> (Event, Int) -> m State
-- Compile a send event. Variable i holds the position of the event
-- in the trace. It used to determine when to bind uniques to nonces.
deriveEvent uniques st ((Out pos ch t), i) =
case receivable t of
Nothing -> -- t is receivable.
do
-- Create uniques and synthesize the channel
(st, chan) <- build pos (deriveUniques uniques i (sendCmt pos st)) ch
-- Synthesize the message
(st, v) <- build pos st t
return (
freshVar st,
compStore st,
Send chan (v, kind t) : statements st)
Just t -> -- t is the offending term
fail (shows pos ("Message not receivable " ++ show (displayTerm t)))
-- Compile a recv event.
deriveEvent _ st ((In pos ch t), _) =
case receivable t of
Nothing -> -- t is receivable.
do
-- Synthesize the channel.
(st, chan) <- build pos (recvCmt pos st) ch
let (fresh, cs, stmts) = st
let recv = Recv (fresh, kind t) chan
let st' = (fresh + 1, cs, recv : stmts)
-- Associate fresh with the received message.
reduce pos st' [(t, fresh)]
Just t -> -- t is the offending term
fail (shows pos ("Message not receivable " ++ show (displayTerm t)))
-- Comments for events
sendCmt :: Pos -> State -> State
sendCmt pos (fresh, cs, stmts) =
(fresh, cs, Comment ("Send (" ++ displayPos pos ++ ")") : stmts)
recvCmt :: Pos -> State -> State
recvCmt pos (fresh, cs, stmts) =
(fresh, cs, Comment ("Recv (" ++ displayPos pos ++ ")") : stmts)
-- Add uniques as appropriate.
deriveUniques :: [(Term, Int)] -> Int -> State -> State
deriveUniques uniques i st =
foldl f st uniques
where
f st@(fresh, cs, stmts) (t, j)
| i == j = (fresh + 1,
M.insert t fresh cs, -- Bind fresh to a nonce.
Bind (fresh, kind t) (Frsh (kind t)) : stmts)
| otherwise = st
-- Synthesize a term and fail when it can't be built.
build :: MonadFail m => Pos -> State -> Term -> m (State, Vari)
build pos st t =
case synth st t of
Nothing -> fail (shows pos ("Cannot build " ++ show (displayTerm t)))
Just result -> return result
-- Synthesize a term and return Nothing when it can't be synthesized.
synth :: State -> Term -> Maybe (State, Vari)
synth st t =
case M.lookup t (compStore st) of
Just v -> Just (st, v) -- Apply the Mem rule
Nothing ->
case t of
Pr x y -> synthPair st t x y -- Apply the Pair rule
En x y -> synthEncr st t x y -- Apply the Encr rule
Hsh x -> synthHash st t x -- Apply the Hash rule
Tag s -> synthTag st t s -- Apply the Tag rule
_ -> Nothing
synthPair :: State -> Term -> Term -> Term -> Maybe (State, Vari)
synthPair st t x y =
do
(st, v) <- synth st x
((fresh, cs, stmts), u) <- synth st y
return (
(fresh + 1,
M.insert t fresh cs, -- Bind fresh to the pair t.
Bind (fresh, kind t) (Pair (v, kind x) (u, kind y)) : stmts),
fresh)
synthEncr :: State -> Term -> Term -> Term -> Maybe (State, Vari)
synthEncr st t x y =
do
(st, v) <- synth st x
((fresh, cs, stmts), u) <- synth st y
return (
(fresh + 1,
M.insert t fresh cs, -- Bind fresh to the encryption t.
Bind (fresh, kind t) (Encr (v, kind x) (u, kind y)) : stmts),
fresh)
synthHash :: State -> Term -> Term -> Maybe (State, Vari)
synthHash st t x =
do
((fresh, cs, stmts), v) <- synth st x
return (
(fresh + 1,
M.insert t fresh cs, -- Bind fresh to the hash t.
Bind (fresh, kind t) (Hash (v, kind x)) : stmts),
fresh)
synthTag :: State -> Term -> String -> Maybe (State, Vari)
synthTag (fresh, cs, stmts) t s =
return (
(fresh + 1,
M.insert t fresh cs, -- Bind fresh to the tag.
Bind (fresh, kind t) (Quot s) : stmts),
fresh)
-- Reduce a received term. This is by far the trickiest code. The
-- reason is there is a loop that repeats as long as progress is being
-- made.
reduce :: MonadFail m => Pos -> State -> [(Term, Vari)] -> m State
reduce pos st cs =
loop pos st False cs []
-- The loop parameters are
-- pos: the position of the received term in the source file
-- st: the current state
-- more: a boolean that is true when there is more to be done
-- recvd: unprocessed received terms and their variable indices
-- todo: received terms and their variables put off for later processing
loop :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] -> m State
loop _ st _ [] [] = return st
loop pos st True [] todo = -- More todo
loop pos st False todo []
loop pos _ False [] (_ : _) = -- No progress can be made
fail (shows pos "Received term cannot be fully destructured")
loop pos st more ((t, v) : recvd) todo =
case t of -- Dispatch on the form of the term
Pr x y -> loopPair pos st recvd todo t v x y
En x y -> loopEncr pos st more recvd todo t v x y
Hsh _ -> loopHash pos st more recvd todo t v
Sky (Ltk x y) -> loopLtk pos st more recvd todo t v x y
Aky k -> loopAsym pos st more recvd todo t v k
Iky k -> loopAsym pos st more recvd todo t v k
_ -> loopOther pos st more recvd todo t v
-- Reduce a pair. Adds two instructions and allocates two variables.
-- Applies the Frst and Scnd rules
loopPair :: MonadFail m => Pos -> State ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> Term -> Term -> m State
loopPair pos (fresh, cs, stmts) recvd todo t v x y =
do
let stmtX = Bind (fresh, kind x) (Frst (kind x) v)
let stmtY = Bind (fresh + 1, kind y) (Scnd (kind y) v)
let st = (
fresh + 2,
M.insert t v cs, -- Add pair to the compile time store
stmtY : stmtX : stmts)
loop pos st True recvd ((x, fresh) : (y, fresh + 1) : todo)
-- Reduce an encryption. If the inverse key can be synthesized, it
-- adds two instructions and allocates two variables.
-- Applies the Decr rule
loopEncr :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> Term -> Term -> m State
loopEncr pos st more recvd todo t v x y =
case synth st (inv y) of
Nothing -> -- Can't synthesize key -- no progress
loop pos st more recvd ((t, v) : todo)
Just ((fresh, cs, stmts), k) ->
do
let stmt = Bind (fresh, kind x) (Decr (kind x) v (k, kind y))
let st = (
fresh + 1,
M.insert t v cs, -- Add encryption to the compile time store
stmt : stmts)
loop pos st True recvd ((x, fresh) : todo)
loopHash :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> m State
loopHash pos st more recvd todo t v =
case synth st t of
Nothing -> -- Can't synthesize hash -- no progress
loop pos st more recvd ((t, v) : todo)
Just ((fresh, cs, stmts), h) ->
do
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st True recvd todo
-- Reduce a long term key.
loopLtk :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> Var -> Var -> m State
loopLtk pos st@(fresh, cs, stmts) more recvd todo t v x y =
case synth st t of
Nothing ->
case M.lookup (Nam x) (compStore st) of
Nothing -> -- Create variable reference to x
let st = (
fresh + 1,
cs,
stmts) in
loop pos st True recvd ((Nam x, fresh) : (t, v) : todo)
Just u ->
case M.lookup (Nam y) (compStore st) of
Nothing -> -- Create variable reference to y
let st = (
fresh + 1,
cs,
stmts) in
loop pos st True recvd ((Nam y, fresh) : (t, v) : todo)
Just w ->
let st = (
fresh,
M.insert t v cs,
Ltkp v u w : stmts) in
loop pos st True recvd todo
Just ((fresh, cs, stmts), h) ->
do -- Otherwise, check sameness
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st more recvd todo
-- Reduce an asymmtric key.
loopAsym :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> Akey -> m State
loopAsym pos st@(fresh, cs, stmts) more recvd todo t v (AVar _) =
case synth st t of
Nothing ->
case synth st (inv t) of
Nothing -> -- Add new term
loop pos (fresh, M.insert t v cs, stmts) True recvd todo
Just ((fresh, cs, stmts), h) ->
do
let stmt = Invp (kind t) v h
let st = (fresh, M.insert t v cs, stmt : stmts)
loop pos st True recvd todo
Just ((fresh, cs, stmts), h) ->
do -- Otherwise, check sameness
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st more recvd todo
loopAsym pos st@(fresh, cs, stmts) more recvd todo t v (Pubk x) =
case synth st t of
Nothing ->
case M.lookup (Nam x) (compStore st) of
Nothing -> -- Create variable reference to x
let st = (
fresh + 1,
cs,
stmts) in
loop pos st True recvd ((Nam x, fresh) : (t, v) : todo)
Just u ->
do
let stmt = Namp (kind t) v u
let st = (fresh, M.insert t v cs, stmt : stmts)
loop pos st True recvd todo
Just ((fresh, cs, stmts), h) ->
do -- Otherwise, check sameness
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st more recvd todo
loopAsym pos st@(fresh, cs, stmts) more recvd todo t v (Pubk2 q x) =
case synth st t of
Nothing ->
case M.lookup (Nam x) (compStore st) of
Nothing -> -- Create variable reference to x
let st = (
fresh + 1,
cs,
stmts) in
loop pos st True recvd ((Nam x, fresh) : (t, v) : todo)
Just u ->
case synth st (Tag q) of
Just ((fresh, cs, stmts), h) ->
do
let stmt = Nm2p (kind t) v h u
let st = (fresh, M.insert t v cs, stmt : stmts)
loop pos st True recvd todo
Nothing ->
fail (shows pos ("Tag not synthesized " ++ show (displayTerm t)))
Just ((fresh, cs, stmts), h) ->
do -- Otherwise, check sameness
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st more recvd todo
-- Reduce terms other than pairs, encryptions, hashes, long term keys,
-- and asymmetric keys.
loopOther :: MonadFail m => Pos -> State -> Bool ->
[(Term, Vari)] -> [(Term, Vari)] ->
Term -> Vari -> m State
loopOther pos st@(fresh, cs, stmts) more recvd todo t v =
case synth st t of
Nothing -> -- Add new term
loop pos (fresh, M.insert t v cs, stmts) True recvd todo
Just ((fresh, cs, stmts), h) ->
do -- Otherwise, check sameness
let stmt = Same (kind t) v h
let st = (fresh, cs, stmt : stmts)
loop pos st more recvd todo
deriveOutputs :: MonadFail m => State -> Role -> m [Stmt]
deriveOutputs st r =
do
(st, vs) <-
foldM (deriveOutput $ rpos r) (st, []) (routputs r)
return $ Return (reverse vs) : statements st
-- Synthesize each output and add it to the list
deriveOutput :: MonadFail m => Pos -> (State, [Vari]) ->
Term -> m (State, [Vari])
deriveOutput pos (st, vs) t =
case receivable t of
Nothing -> -- t is receivable.
do
(st, v) <- build pos st t
return (st, v : vs)
Just t -> -- t is the offending term
fail (shows pos ("Message not receivable " ++ show (displayTerm t)))