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