/* RPCSecurity.c
   RPC: Authentication and security facilities
   Last modified by D. Swinehart, November 11, 1982 10:24 am
   L. Stewart December 27, 1982  3:14 PM, flush nested delcarations
   L. Stewart January 1, 1983  4:17 PM, flush AllocZero
   L. Stewart January 1, 1983  4:17 PM, formatting, right assoc, flush Alloc
 */
   
/* Authentication is done by calling RPC Authentication server */

#include <Env.h>
#include <Signal.h>
#include <Context.h>
#include <Queue.h>
#include <rpc.h>
#include <rpcinternal.h>
#include <rpclupine.h>
#include <rpcpkt.h>
#include <rpcbind.h>

    /* From RPCPkt, RPC, RPCInternal */
extern int PktExchange();
extern SetupResponse();
extern struct ExportInstance *exportTable;
extern union Machine myHost;
extern int *mySoc;

    /* From Pup package */
extern SendPup();
extern struct PBI *GetPBI();
extern int MultEq();
extern int DoubleEq();
extern ReleasePBI();

  /* From DES"Face" */
extern CryptData();
extern DecryptBlock();
extern CBCCheckDecrypt();

    /* From Utils */
extern int DoubleComp();
extern int DoubleInc();
extern Move2();
extern int StringSize();
extern int StringSizeN();
extern struct ShortSTRING *ShallString();

extern int AuthenticateFailed;

    /* From Timer, Time/date */
extern ReadCalendar();
extern Timer();

    /* From OS */
extern int *GetFixed();
extern MoveBlock();

/* Encryption control codes */
#define dirEncrypt 1
#define dirDecrypt 0

/* ******** Encryption primitives ******** */

struct ConversationID *lastConversation;
struct PktConversationID *firstConversation;

static struct Conversation *conversations[0];
  /* POINTER TO ARRAY ConvHashKey OF POINTER TO Conversation[Object]; */
int UnknownConversation;  /* SIGNAL */

static struct EncryptionKey *ka;
static struct EncryptionKey *kb;
static struct EncryptionKey privateKey; /* null, for now */

struct Block {
  byte blockElt[8];
  };
#define lenBlock (sizeof (struct Block)/2)

/* constructed and used in "marshalled" (Mesa machine) form */
struct RFARequest {
  struct PktConversationID callerConv;
  struct PktID callerPktID; /* still encrypted , */
  word nonceID[2]; /* nominated by requester*/
  };
#define lenRFARequest (sizeof (struct RFARequest)/2)

 /* constructed and used in "marshalled" (Mesa machine) form */

struct RFAResponse {
  struct IV iv;
  struct ConnectionID connection;
  struct CallCount callCt;
  word nonceID[2];
  /* ends here if callee originated the conversation */
  word level;
  /* ends here if level = none -- */
  struct Authenticator authenticator;
  };
  /* Followed by Callee principal name. */
#define lenRFAResponse ((sizeof (struct RFAResponse)/2)-(sizeof (struct Authenticator)/2)+lenAuthenticator)

#define rfaDataLength lenRFAResponse + 2*(64/2)
  /* .. + StringSizeN(RPC.maxPrincipalLength) */
#define responseCKBlocks (12/encryptionBlockSize)
#define lenCheck 2
  
struct EncryptionKey nullKeyB; /* = table { 0; 0; 0; 0; } */
struct EncryptionKey nullSeedB; /* = table { 0; 0; 0; 0; } */
#define nullKey &nullKeyB
#define nullSeed &nullSeedB

struct Conversation
*GenerateConversation() {
  return (EntryGenerate(slNone, nullKey, 0, 0, nullKey, 0));
  };

static struct Conversation
*EntryGenerate(level, iv, originator, responder, convKey, auth)
  int /* SecurityLevel */ level;
  struct IV *iv;
  struct ShortSTRING *originator;
  struct ShortSTRING *responder;
  struct EncryptionKey *convKey;
  struct Authenticator *auth;
  {
  DoubleInc(&lastConversation->count, 1);
  lastConversation->count.fields &= notCalleeMask;
  return (AddConversation(lastConversation, level, iv, originator, responder, convKey, auth));
  };

struct Conversation
*StartConversation(caller, key, callee, level)
  struct ShortSTRING *caller;
  struct EncryptionKey *key;
  struct ShortSTRING *callee;
  int /* SecurityLevel */ level;
  { /* RETURNS {conversation: Conversation;}; */
  struct Authenticator *authenticator;
  struct IV iv;
  struct EncryptionKey convKey;
  if (level == slNone) SIGNAL(ERROR); /* may relax this constraint later, to allow multi clear convs. */
  authenticator = Authenticate(caller, key, callee, &convKey, &iv);
  return (EntryGenerate(level, &iv, caller, callee, &convKey, authenticator));
  };

struct Authenticator
*Authenticate(caller, key, callee, /* more results */ convKey, iv)
  struct ShortSTRING *caller;
  struct EncryptionKey *key;
  struct ShortSTRING *callee;
  struct EncryptionKey *convKey;
  struct IV *iv;
  {
  /* Uses a remote simulation of a secure R-Server protocol! */
  /* Does not at present use the key for anything. */
  int nBlks, authLength;
  int nonceId[2];
  int args[5];
  struct Authenticator *authenticator;
  struct Authentication *authentication;
  struct EncryptionKey kx;
  struct ShortSTRING *b;
  int nonceOK;
  ReadCalendar(nonceId);
  authentication = AgentAuthenticate(nonceId, caller, callee, &authenticator);
  if (authentication==0) SIGNAL(AuthenticateFailed, badCommunications);  /* MORE analysis! */
  nBlks = DESBlocks(authentication->length);
  DecryptBlock(ka, &authentication->kx, &kx);
  CBCCheckDecrypt(&kx, nBlks-2, &authentication->ck, &authentication->ck, nullSeed);
  nonceOK = (nonceId[0] == swab(authentication->nonceId[0]) &&
          nonceId[1] == swab(authentication->nonceId[1]));
  b = ShallString(0, &authentication->b.length, 2);
  if ((!nonceOK) || !EquivalentStrings(callee, b)) SIGNAL(AuthenticateFailed, badKey);
  DecryptBlock(ka, &authentication->ck, convKey);
  nBlks = DESBlocks(authenticator->length);
  CBCCheckDecrypt(&kx, nBlks, &authenticator->ky, &authenticator->ky, nullSeed);
/*
  Free(myZone, authentication);
  Free(myZone, b);
*/
  GetRandomIV(iv);
  return (authenticator);
  };

static struct Conversation
 *AddConversation(id, level, iv, originator, responder, convKey, auth)
  struct ConversationID *id;
  int /* SecurityLevel */ level;
  struct IV *iv;
  struct ShortSTRING *originator;
  struct ShortSTRING *responder;
  struct EncryptionKey *convKey;
  struct Authenticator *auth;
  {
  /* TEMP: Assume we can't keep iv, originator,
       responder strings, convKey, but we can keep authenticator
    */
  struct Conversation *conversation, *dataPtr;
  dataPtr = (struct Conversation *) &conversations[swab(id->count.LS) & 127];
  while (conversation=dataPtr->next) {
    if (MultEq(&conversation->id, id, lenConversationID))  return conversation;
    dataPtr = conversation;
    };
  conversation = (struct Conversation *) GetFixed(lenConversationObject);
  MoveBlock(&conversation->id, id, lenConversationID);
  conversation->level = level;
  MoveBlock(&conversation->key, convKey, lenEncryptionKey);
  MoveBlock(&conversation->iv, iv, lenIV);
  conversation->originator = ShallString(0, originator, 0);
  conversation->responder = ShallString(0, responder, 0);
  conversation->authenticator = auth; /* Assume I can keep it. */
  dataPtr->next = conversation;
  Block();
  return (conversation);
  };

EndConversation(conversation)
  struct Conversation *conversation;
  {
  struct Conversation *dataPtr;
  struct Conversation *this;
  dataPtr = (struct Conversation *) &conversations[swab(conversation->id.count.LS) & 127];
  this = dataPtr->next;
  if (conversation->id.originator.w == myHost.w)
  while (this) {
    if (this == conversation)  {
      dataPtr->next = this->next;
     /* TEMP: Remember, we assumed we could have originator, responder, */
   /* TEMP:  so we are responsible for zapping them. */
/*
      if (this->authenticator) Free(myZone, this->authenticator);
      if (this->originator)  Free(myZone, this->originator);
      if (this->responder)  Free(myZone, this->responder);
      Free(myZone, this);
 */
      Block();
      return;
      };
    dataPtr = this; this=this->next;
    };
  SIGNAL(UnknownConversation);
  };

InvalidateConversations()
  {
/* invalidate conversations for which we are the originator,
  such that client will get an error if it uses them.
 */
  int i;
  struct Conversation *conv;
  for (i=0; i<128; ++i) {
    conv = conversations[i];
    while (conv) {
      if (conv->id.originator.w == myHost.w) conv->id.originator.w = 0;
      conv = conv->next;
      };
    };
  };
    
AttachConversation(handle, conversation)
  struct ImportInstance *handle;
  struct Conversation *conversation;
  {
  handle->currentConversation = conversation;
  };

struct ConversationID
*GetConversationID(conversation)
  struct Conversation *conversation;
  { 
  return((conversation)? &conversation->id: 0);
  };

struct ShortSTRING
*GetCaller(conversation)
  struct Conversation *conversation;
  {
  return((conversation)? conversation->originator: 0);
  };

int /* SecurityLevel */
*GetLevel(conversation)
  struct Conversation *conversation;
  {
  return((conversation)? conversation->level: slNone);
  };
  
/* ************ Packet encryption and decryption ************* */

#define encryptedHeaderLength (lenPktID+lenDispatcherDetails)

int /* DataLength */
EncryptPkt(pkt, ln)
  struct PBI *pkt;
  int ln;
  {
  struct Header *header;
  struct Conversation *conv;
  int words, nBlks, *checkPtr;
  struct Block blocks[];
  header = pkt->pup;
  conv = pkt->convHandle;
  if ((conv != 0) && (conv->level != slNone) && (conv->level != slAuthOnly)) {
    words = ln  /* stub data words */ + encryptedHeaderLength + 2 /* SIZE[Check] */;
    nBlks = (words + encryptionBlockSize - 1)/encryptionBlockSize;
    blocks = (struct Block *) &header->pktID;
    checkPtr = ((int *) (&blocks[nBlks])) - lenCheck;
    checkPtr[0] = swab(nBlks*encryptionBlockSize-words); /* Roundoff adj? */
    checkPtr[1] = 0;
    CryptData(&conv->key, nBlks, blocks, 0, dirEncrypt, conv->level, &conv->iv);
    return (((nBlks*encryptionBlockSize) - encryptedHeaderLength) + pktLengthOverhead);
    };
  return (ln+pktLengthOverhead);
  };

int /* BOOL */
DecryptPkt(header, convHandle, /* results */ lvL)
  struct Header *header;
  struct Conversation *convHandle;
  int *lvL;
  {
  int /*BOOL*/ ok, pktLen;
  int nBlks, *checkPtr, cp;
  struct Block blocks[];
  ok=true;
  pktLen = (swab(header->length)>>1) - pktLengthOverhead;
  if (convHandle != 0 && convHandle->level != slNone && convHandle->level != slAuthOnly) {
    nBlks = (pktLen + encryptedHeaderLength)/encryptionBlockSize;
    blocks = (struct Block *) &header->pktID;
    checkPtr = ((int *) (&blocks[nBlks])) - lenCheck;
    CryptData(&convHandle->key, nBlks, blocks, 0, dirDecrypt,
      convHandle->level, &convHandle->iv);
    cp = swab(checkPtr[0]);
    if (0 > cp || cp >= encryptionBlockSize) ok = false;
    else *lvL = (((nBlks*encryptionBlockSize) - cp) - lenCheck) - encryptedHeaderLength;
    };
  else *lvL = pktLen;
  return (ok);
  };

static int Rlse(sig,code,sel)
  int sig, code;
  struct Seal1 *sel;
  {
  ReleasePBI(sel->data[0]);
  return (sig);
  };

int /* ok: BOOLEAN */
GetConnectionState(decrypted, callPkt, /* more results */ id, callCt, lvConv, lvNewLength)
  int /*BOOL*/ decrypted;
  struct PBI *callPkt;
  struct ConversationID *id;
  struct CallCount *callCt;
  struct Conversation *lvConv[]; 
  int *lvNewLength;
  {
/* The RFA packet looks a lot like the first
   packet of a new call. The response, a data packet,
    will look a lot like the result of this call.
 */
  
  struct PBI *rfaPkt;
  struct Seal1 sel;
  
  word myNonceID[2];
  struct Header *callHeader;
  struct Header *rfaHeader;
  struct RFARequest *request;
  struct RFAResponse *response;
  int responseLength;
  struct ConversationID convID;
  int ok;
  struct EncryptionKey *ky, *ck;
  
  callHeader = callPkt->pup;
  rfaPkt = GetPBI(mySoc);
  sel.data[0] = (int) rfaPkt;
  ENABLE(UNWIND, &Rlse, &sel);
  
  Timer(myNonceID);
  rfaHeader = rfaPkt->pup;
  request = (struct RFARequest *) &rfaHeader->callData;
  response = (struct RFAResponse *) request;
  MoveBlock(&request->callerConv,
            &callHeader->conv, lenPktConversationID+lenPktID); /* PktID still encrypted */
  Move2(&request->nonceID[0], myNonceID);
  rfaPkt->convHandle = unencrypted;
  MoveBlock(rfaHeader, callHeader, lenHeader);
  SetupResponse(rfaHeader);
  Move2(&rfaHeader->conv, firstConversation);
  rfaHeader->pktID.activity = 0;  
  /* rfaHeader.pktID.callSeq will be set by PktExchange */
  rfaHeader->pktID.pktSeq = 0;
  responseLength = PktExchange(rfaPkt, lenRFARequest, rfaDataLength, authReq);
  if (responseLength < lenRFAResponse-lenAuthenticator) return (false);
  ok = true;
  if (response->level == slNone) {
    *lvNewLength = (swab(callHeader->length)>>1) - pktLengthOverhead; *lvConv = 0; };
  else { /* response = {IV, conn, call, nonce}CK,,{KY}KB,{{CK}KB, time, A}KY */
      /* TEMP kb is nullKey, parity-corrected!!! */
    ky = &response->authenticator.ky;
    ck = &response->authenticator.ck;
    DecryptBlock(kb, ky, 0);
    CBCCheckDecrypt(ky,
    (swab(response->authenticator.length)/encryptionBlockSize)-2, ck, 0, nullSeed);
    DecryptBlock(kb, ck, 0);
    CBCCheckDecrypt(ck, responseCKBlocks, response, 0, nullSeed);
    Move2(&convID.count, &response->connection.conv);
  convID.count.fields &= notCalleeMask;
  convID.originator =
      ((response->connection.conv.fields&originatorField) != callerField)? myHost.w
      :response->connection.caller.w;
  response->authenticator.a.length = swab(response->authenticator.a.length);
  *lvConv = AddConversation( /* Construct a ConversationID from a PktConversationID */
      &convID, response->level, &response->iv, &response->authenticator.a, 0,
      &response->authenticator.ck, 0);
  callPkt->convHandle = *lvConv;
  if (decrypted==false && DecryptPkt(callHeader, *lvConv, lvNewLength)==false) ok=false;
    }
  /* Correctness of nonceID means he knows CK. */
  if (ok) if (DoubleComp(&response->nonceID[0], myNonceID) != 1) ok=false;
  else if (DoubleEq(&response->connection.conv, &callHeader->conv)==false) ok=false;
  else if (response->connection.caller != callHeader->srceHost) ok=false;
  else if (response->connection.activity != callHeader->pktID.activity) ok=false;
  else if (DoubleEq(&response->callCt, &callHeader->pktID.callCt) == false) ok=false;
  if (ok) { MoveBlock(id, &response->connection, lenConnectionID);
         Move2(callCt, &response->callCt); };
  Block();
  return (Rlse(ok, 0, &sel));
  };

 
int /*BOOLEAN*/
ReplyToRFA(recvdPkt, callHeader/*encrypted*/, callPktID /* clear */, convHandle)
  struct PBI *recvdPkt;
  struct Header *callHeader;
  struct PktID *callPktID;
  struct Conversation *convHandle;
  {
  struct Header *recvdHeader;
  struct RFARequest request;
  struct RFAResponse *response;
  int used;
  int authLen;
  struct Authenticator *auth;
  int *resp;
  recvdHeader = recvdPkt->pup;
  response = (struct RFAResponse *) &recvdHeader->callData;
  MoveBlock(&request, response, lenRFARequest);
  used = lenRFAResponse-lenAuthenticator;
  if (MultEq(&request.callerConv,
    &callHeader->conv, lenPktConversationID+lenPktID) == 0) {
      ReleasePBI(recvdPkt);
      return (false);
      };
  Move2(&response->connection.conv, &callHeader->conv);
  response->connection.caller = callHeader->srceHost;
  response->connection.activity = callPktID->activity;
  Move2(&response->callCt, &callPktID->callCt);
  Move2(&response->nonceID[0], &request.nonceID[0]);
  DoubleInc(&response->nonceID[0], 1);
  if (DoubleEq(&callHeader->conv, firstConversation)) {
    response->level = slNone;
    ++used;
    };
  else {
    if ((callHeader->conv.fields&originatorField) == calleeField) SIGNAL(ERROR); /* TEMP */
    response->level = convHandle->level;
    MoveBlock(&response->iv, &convHandle->iv, lenIV);
  /* next line is CBCCheckEncrypt(...); */
       CryptData(&convHandle->key, responseCKBlocks,
      response, 0, dirEncrypt, slCBCCheck, nullSeed);
    auth = convHandle->authenticator;
    authLen = auth->length+1;
    MoveBlock(&response->authenticator, auth, authLen);
    response->authenticator.length = swab(auth->length);
    used += authLen;
    resp = (int *) response;
    ShallString(&resp[used], convHandle->originator, 1);
    used += StringSize(convHandle->originator);
    }; 
  SetupResponse(recvdHeader);
  recvdHeader->length = swab((pktLengthOverhead + used) << 1);
  recvdHeader->type.fields = typeData;
  recvdHeader->pktID.pktSeq += swapped1;
  recvdHeader->srceHost.w = myHost.w;
  recvdHeader->srceSoc.LS = rpcSocket;
  recvdHeader->srcePSB = nullPSB;
  SendPup(recvdPkt);
  Block();
  return (true);
  };

/* Initialization */

SecurityInitialize() {
  /* Use first available conversationID
      for unencrypted conversations.
   */
  firstConversation =
  (struct PktConversationID *) GetFixed(lenPktConversationID);
  ReadCalendar(firstConversation);
  firstConversation->fields &= notCalleeMask; /* time unpredictable in 32 bits */
  lastConversation = 
  (struct ConversationID *) GetFixed(lenConversationID);
  lastConversation->originator.w = myHost.w;
  Move2(&lastConversation->count, firstConversation);
  conversations = (struct Conversation **) GetFixed(128);
  UnknownConversation = CODE();
  /* let firstHandle = */
  AddConversation(lastConversation, slNone, nullSeed, 0, 0, nullKey, 0);
  kb = ka = &privateKey;
  CorrectParity(ka);
  };
 
SecurityRestart()
  {
  InvalidateConversations();
  };