/* 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();
};