-- Copyright (C) 1984 by Xerox Corporation. All rights reserved. -- RPCSecurity.mesa, HGM, 21-Jan-84 20:41:01 -- Cedar 5, HGM, 21-Jan-84 20:40:58 -- RPC: Authentication and security facilities --RPCSecurity.mesa -- Andrew Birrell September 7, 1982 1:47 pm DIRECTORY Buffer USING [PupBuffer, ReturnBuffer], DESFace, Heap USING [FreeNode, MakeNode, systemZone], Inline USING [LongCOPY, LowHalf], NameInfoDefs USING [AuthenticateKey, CheckStamp], PSB USING [PsbIndex, PsbNull], PupDefs USING [GetLocalPupAddress, PupRouterSendThis], PupTypes USING [PupAddress], MesaRPC USING [ AuthenticateFailed, Conversation, ConversationLevel, EncryptionKey, maxPrincipalLength, Principal, SecurityLevel, unencrypted], RPCInternal USING [ConversationObject], MesaRPCLupine USING [ DataLength, Dispatcher, GetStubPkt, Header, pktOverhead, RPCPkt, StubPkt], RPCPkt USING [ CallCount, ConnectionID, ConversationID, DispatcherDetails, Header, Machine, PktConversationID, PktExchange, PktID, pktLengthOverhead, SetupResponse], RPCPrivate USING [rpcSocket], System USING [GetClockPulses, GetGreenwichMeanTime, SecondsSinceEpoch]; RPCSecurity: MONITOR IMPORTS Buffer, DESFace, Heap, Inline, NameInfoDefs, PupDefs, MesaRPC, MesaRPCLupine, RPCPkt, System EXPORTS MesaRPC --encryption stuff-- , RPCInternal --GetAuthenticator-- , MesaRPCLupine SHARES MesaRPCLupine = BEGIN Header: PUBLIC TYPE = RPCPkt.Header; ConcreteHeader: PROC [abstract: LONG POINTER TO MesaRPCLupine.Header] RETURNS [LONG POINTER TO Header] = INLINE {RETURN[abstract]}; GiveBackBuffer: PROC [b: Buffer.PupBuffer] = -- NOTE: calls of this must be made outside our monitor, because -- RPCPrivate.ReturnBuffer acquires the EthernetDriver monitor, -- and the EthernetDriver may call EnqueueRecvd which acquires -- our monitor! Buffer.ReturnBuffer; CopyPrincipal: PROC [from: MesaRPC.Principal, to: LONG POINTER] = { Inline.LongCOPY[from: from, to: to, nwords: SIZE[StringBody [from.length]]]}; AllocPrincipal: PROC [from: MesaRPC.Principal] RETURNS [to: MesaRPC.Principal] = BEGIN IF from = NIL THEN RETURN[NIL]; to ← Heap.MakeNode[n: SIZE[StringBody [from.length]]]; CopyPrincipal[from, to]; END; CopyAuth: PROC [from: Authenticator, to: LONG POINTER] = { Inline.LongCOPY[from: BASE[from], to: to, nwords: LENGTH[from]]}; AllocAuth: PROC [from: Authenticator] RETURNS [to: Authenticator] = BEGIN IF BASE[from] = NIL THEN RETURN[DESCRIPTOR[NIL, 0]]; to ← DESCRIPTOR[Heap.MakeNode[n: LENGTH[from]], LENGTH[from]]; CopyAuth[from, BASE[to]]; END; -- ******** Conversation management ******** -- ConversationID: PUBLIC TYPE = RPCPkt.ConversationID; ConversationObject: PUBLIC TYPE = RPCInternal.ConversationObject; Conversation: TYPE = LONG POINTER TO ConversationObject; lastConversation: ConversationID; firstConversation: PUBLIC RPCPkt.PktConversationID; GenerateConversation: PUBLIC PROC RETURNS [conversation: Conversation] = { RETURN[EntryGenerate[none, , NIL, NIL, DESFace.nullKey, DESCRIPTOR[NIL, 0]]]}; EntryGenerate: ENTRY PROC [ level: MesaRPC.SecurityLevel, iv: DESFace.IV, originator, responder: MesaRPC.Principal, convKey: DESFace.Key, auth: Authenticator] RETURNS [conversation: Conversation] = BEGIN lastConversation.count.ls ← lastConversation.count.ls + 1; IF lastConversation.count.ls = 0 THEN lastConversation.count.ms ← lastConversation.count.ms + 1; conversation ← AddConversation[ lastConversation, level, iv, originator, responder, convKey, auth]; END; Authenticator: TYPE = LONG DESCRIPTOR FOR ARRAY OF WORD; -- for A talking to B using key CK, contains: -- {KY}KB, spare, { {CK}KB, spare, time, A }KY . -- The keys are single cipher blocks. -- The rest is encrypted with CBC-check using a zero IV -- AuthenticatorLayout: TYPE = MACHINE DEPENDENT RECORD [ ky(0): DESFace.Key, kySpare(4): DESFace.Key, -- space for larger keys! -- ck(8): DESFace.Key, ckSpare(12): DESFace.Key, -- space for larger keys! -- time(16): LONG CARDINAL, a(18): StringBody]; BadConversationLevel: ERROR = CODE; StartConversation: PUBLIC PROC [ caller: MesaRPC.Principal, key: MesaRPC.EncryptionKey, callee: MesaRPC.Principal, level: MesaRPC.ConversationLevel] RETURNS [conversation: Conversation] = BEGIN authenticator: Authenticator; convKey: DESFace.Key; iv: DESFace.IV; IF level NOT IN MesaRPC.ConversationLevel THEN ERROR BadConversationLevel[]; [authenticator, convKey, iv] ← Authenticate[caller, key, callee]; RETURN[EntryGenerate[level, iv, caller, callee, convKey, authenticator]] END; Authenticate: PROC [ caller: MesaRPC.Principal, key: MesaRPC.EncryptionKey, callee: MesaRPC.Principal] RETURNS [authenticator: Authenticator, convKey: DESFace.Key, iv: DESFace.IV] = BEGIN -- TEMP: This entire procedure should use a secure R-Server protocol! -- nBlks: CARDINAL = (SIZE[AuthenticatorLayout] - SIZE[StringBody [0]] + SIZE[ StringBody [caller.length]] + SIZE[DESFace.Block] - 1 --rounding-- )/SIZE[DESFace.Block]; authLength: CARDINAL = nBlks*SIZE[DESFace.Block]; -- TEMP: copy into MDS for GrapevineUser's benefit -- buff: STRING = [MesaRPC.maxPrincipalLength]; IF caller.length > buff.maxlength THEN ERROR MesaRPC.AuthenticateFailed[badCaller]; IF callee.length > buff.maxlength THEN ERROR MesaRPC.AuthenticateFailed[badCallee]; Inline.LongCOPY[ from: caller, to: buff, nwords: SIZE[StringBody [caller.length]]]; SELECT NameInfoDefs.AuthenticateKey[buff, key] FROM group, notFound => ERROR MesaRPC.AuthenticateFailed[badCaller]; allDown => ERROR MesaRPC.AuthenticateFailed[communications]; badPwd => ERROR MesaRPC.AuthenticateFailed[badKey]; ENDCASE => NULL; Inline.LongCOPY[ from: callee, to: buff, nwords: SIZE[StringBody [callee.length]]]; SELECT NameInfoDefs.CheckStamp[buff] FROM group, notFound => ERROR MesaRPC.AuthenticateFailed[badCallee]; allDown => ERROR MesaRPC.AuthenticateFailed[communications]; ENDCASE => NULL; iv ← DESFace.GetRandomIV[]; authenticator ← DESCRIPTOR[Heap.MakeNode[n: authLength], authLength]; BEGIN -- TEMP: construct authenticator in clear ourselves! -- kb: DESFace.Key ← DESFace.nullKey -- !!! -- ; authRec: LONG POINTER TO AuthenticatorLayout = LOOPHOLE[BASE[authenticator]]; authRec.ky ← DESFace.GetRandomKey[]; authRec.ck ← convKey ← DESFace.GetRandomKey[]; authRec.time ← 0; Inline.LongCOPY[ from: caller, to: @(authRec.a), nwords: SIZE[StringBody [caller.length]]]; DESFace.CorrectParity[@kb]; DESFace.EncryptBlock[ key: kb, from: LOOPHOLE[@authRec.ck], to: LOOPHOLE[@authRec.ck]]; DESFace.CBCCheckEncrypt[ key: authRec.ky, nBlks: nBlks - 2, from: LOOPHOLE[@authRec.ck], to: LOOPHOLE[@authRec.ck], seed: [0, 0, 0, 0]]; DESFace.EncryptBlock[ key: kb, from: LOOPHOLE[@authRec.ky], to: LOOPHOLE[@authRec.ky]]; END; END; ConvHashKey: TYPE = [0..127]; conversations: LONG POINTER TO ARRAY ConvHashKey OF Conversation = Heap.systemZone.NEW[ARRAY ConvHashKey OF Conversation ← ALL[NIL]]; InconsistentHashTable: ERROR = CODE; EntryAddConversation: ENTRY PROC [ id: ConversationID, level: MesaRPC.SecurityLevel, iv: DESFace.IV, originator, responder: MesaRPC.Principal, convKey: DESFace.Key, auth: Authenticator] RETURNS [conversation: Conversation] = INLINE { RETURN[AddConversation[id, level, iv, originator, responder, convKey, auth]]}; AddConversation: INTERNAL PROC [ id: ConversationID, level: MesaRPC.SecurityLevel, iv: DESFace.IV, originator, responder: MesaRPC.Principal, convKey: DESFace.Key, auth: Authenticator] RETURNS [conversation: Conversation] = BEGIN dataPtr: LONG POINTER TO Conversation ← @conversations[ id.count.ls MOD SUCC[LAST[ConvHashKey]]]; DO SELECT TRUE FROM dataPtr↑ = NIL => BEGIN conversation ← Heap.systemZone.NEW[ ConversationObject ← [ NIL, id, level, convKey, iv, AllocPrincipal[originator], AllocPrincipal[responder], auth]]; dataPtr↑ ← conversation; EXIT; END; dataPtr↑.id = id => {conversation ← dataPtr↑; EXIT -- already there -- }; ENDCASE => dataPtr ← @(dataPtr↑.next); ENDLOOP; END; UnknownConversation: ERROR = CODE; EndConversation: PUBLIC ENTRY PROC [conversation: Conversation] = BEGIN dataPtr: LONG POINTER TO Conversation ← @conversations[ conversation.id.count.ls MOD SUCC[LAST[ConvHashKey]]]; IF conversation.id.originator # myHost THEN RETURN WITH ERROR UnknownConversation[]; DO SELECT TRUE FROM dataPtr↑ = NIL => RETURN WITH ERROR UnknownConversation[]; dataPtr↑ = conversation => BEGIN dataPtr↑ ← dataPtr↑.next; IF conversation.originator # NIL THEN Heap.FreeNode[p: conversation.originator]; IF conversation.responder # NIL THEN Heap.FreeNode[p: conversation.responder]; IF BASE[conversation.authenticator] # NIL THEN Heap.FreeNode[p: BASE[conversation.authenticator]]; Heap.systemZone.FREE[@conversation]; EXIT; END; ENDCASE => dataPtr ← @(dataPtr↑.next); ENDLOOP; END; GetConversationID: PUBLIC ENTRY PROC [conversation: Conversation] RETURNS [id: ConversationID] = { RETURN[IF conversation = NIL THEN [[[0], [0]], [0, 0]] ELSE conversation.id]}; GetCaller: PUBLIC ENTRY PROC [conversation: Conversation] RETURNS [MesaRPC.Principal] = { RETURN[IF conversation = NIL THEN NIL ELSE conversation.originator]}; GetLevel: PUBLIC ENTRY PROC [conversation: Conversation] RETURNS [level: MesaRPC.SecurityLevel] = { RETURN[IF conversation = NIL THEN none ELSE conversation.level]}; -- ******** Packet encryption and decryption ******** -- encryptedHeaderLength: CARDINAL = SIZE[RPCPkt.PktID] + SIZE[RPCPkt.DispatcherDetails]; Check: TYPE = LONG CARDINAL; -- cipher checksum at end of packet -- -- bottom 2 bits indicate block rounding; remaining 30 bits should be 0 -- EncryptPkt: PUBLIC PROC [pkt: MesaRPCLupine.RPCPkt, l: MesaRPCLupine.DataLength] RETURNS [CARDINAL] = BEGIN header: LONG POINTER TO RPCPkt.Header = @pkt.header; conv: Conversation = pkt.convHandle; IF conv # NIL AND conv.level # none AND conv.level # authOnly THEN BEGIN words: CARDINAL = l -- stub data words -- + encryptedHeaderLength + SIZE[Check]; nBlks: CARDINAL = (words + SIZE[DESFace.Block] - 1 -- round up -- )/SIZE[DESFace.Block]; blocks: DESFace.Blocks = LOOPHOLE[@header.pktID]; checkPtr: LONG POINTER TO Check = LOOPHOLE[@blocks[nBlks] - SIZE[Check]]; checkPtr↑ ← nBlks*SIZE[DESFace.Block] - words; SELECT conv.level FROM ECB => DESFace.ECBEncrypt[conv.key, nBlks, blocks, blocks]; CBC => DESFace.CBCEncrypt[conv.key, nBlks, blocks, blocks, conv.iv]; CBCCheck => DESFace.CBCCheckEncrypt[conv.key, nBlks, blocks, blocks, conv.iv]; ENDCASE => ERROR; RETURN[ nBlks*SIZE[DESFace.Block] - encryptedHeaderLength + RPCPkt.pktLengthOverhead] END ELSE RETURN[l + RPCPkt.pktLengthOverhead] END; DecryptPkt: PUBLIC PROC [ header: LONG POINTER TO RPCPkt.Header, convHandle: Conversation] RETURNS [ok: BOOLEAN, l: MesaRPCLupine.DataLength] = BEGIN ok ← TRUE; IF convHandle # NIL AND convHandle.level # none AND convHandle.level # authOnly THEN BEGIN nBlks: CARDINAL = (header.length - RPCPkt.pktLengthOverhead + encryptedHeaderLength)/SIZE[ DESFace.Block]; blocks: DESFace.Blocks = LOOPHOLE[@header.pktID]; checkPtr: LONG POINTER TO Check = LOOPHOLE[@blocks[nBlks] - SIZE[Check]]; SELECT convHandle.level FROM ECB => DESFace.ECBDecrypt[convHandle.key, nBlks, blocks, blocks]; CBC => DESFace.CBCDecrypt[ convHandle.key, nBlks, blocks, blocks, convHandle.iv]; CBCCheck => DESFace.CBCCheckDecrypt[ convHandle.key, nBlks, blocks, blocks, convHandle.iv]; ENDCASE => ERROR; IF checkPtr↑ IN [0..SIZE[DESFace.Block]) THEN l ← Inline.LowHalf[ nBlks*SIZE[DESFace.Block] - checkPtr↑ - SIZE[Check] - encryptedHeaderLength] ELSE ok ← FALSE; END ELSE l ← header.length - RPCPkt.pktLengthOverhead; END; -- ******** Requests-For-Authenticator's (RFA's) ******** -- RFARequest: TYPE = MACHINE DEPENDENT RECORD [ -- conversationID, {pktID}CK, nonceID callerConv(0): RPCPkt.PktConversationID, callerPktID(2): RPCPkt.PktID -- still encrypted -- , nonceID(6): LONG CARDINAL -- nominated by requester -- ]; RFAResponse: TYPE = MACHINE DEPENDENT RECORD [ -- {iv, connectionID, callCount, nonceID+1}CK, -- null, authScheme, authLength, authenticator, callee. -- The initial part is encrypted with CBC using a zero IV. iv(0): DESFace.IV, connection(4): RPCPkt.ConnectionID, call(8): RPCPkt.CallCount, nonceID(10): LONG CARDINAL, -- ends here if callee originated the conversation -- level(12): MesaRPC.SecurityLevel, -- ends here if level = none -- authLength(13): CARDINAL, -- length of authenticator (words) -- authenticator(14): AuthenticatorLayout -- followed by Callee principal name -- ]; responseCKBlocks: CARDINAL = 12/SIZE[DESFace.Block]; -- Size of {iv, connectionID, callCount, nonceID+1} rfaDataLength: MesaRPCLupine.DataLength = SIZE[RFAResponse] - SIZE[StringBody [0]] + 2*SIZE[StringBody [MesaRPC.maxPrincipalLength]]; myHost: RPCPkt.Machine; GetConnectionState: PUBLIC PROC [decrypted: BOOLEAN, callPkt: MesaRPCLupine.RPCPkt] RETURNS [ ok: BOOLEAN, id: RPCPkt.ConnectionID, call: RPCPkt.CallCount, conv: Conversation, l: MesaRPCLupine.DataLength] = BEGIN -- 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. callHeader: LONG POINTER TO Header = @callPkt.header; rfaPktSpace: ARRAY [1..rfaDataLength + MesaRPCLupine.pktOverhead] OF WORD; rfaPkt: MesaRPCLupine.StubPkt = MesaRPCLupine.GetStubPkt[@rfaPktSpace]; myNonceID: LONG CARDINAL = System.GetClockPulses[]; request: LONG POINTER TO RFARequest = LOOPHOLE[LONG[@(rfaPkt[0])]]; response: LONG POINTER TO RFAResponse = LOOPHOLE[LONG[@(rfaPkt[0])]]; rfaHeader: LONG POINTER TO Header = @rfaPkt.header; request.callerConv ← callHeader.conv; request.callerPktID ← callHeader.pktID; --still encrypted-- request.nonceID ← myNonceID; rfaPkt.convHandle ← MesaRPC.unencrypted; rfaHeader↑ ← ConcreteHeader[@callPkt.header]↑; RPCPkt.SetupResponse[rfaHeader]; rfaHeader.conv ← firstConversation; rfaHeader.pktID.activity ← 0; -- rfaHeader.pktID.callSeq will be set by PktExchange -- rfaHeader.pktID.pktSeq ← 0; BEGIN responseLength: MesaRPCLupine.DataLength ← RPCPkt.PktExchange[ rfaPkt, SIZE[RFARequest], rfaDataLength, authReq].newLength; IF responseLength < SIZE[RFAResponse] - SIZE[AuthenticatorLayout] THEN RETURN[FALSE, , , , ]; END; IF response.level = none THEN { l ← callHeader.length - RPCPkt.pktLengthOverhead; conv ← NIL} ELSE BEGIN -- response = {IV,cond,call,nonce}CK,,{KY}KB,{{CK}KB,time,A}KY --TEMP-- kb: DESFace.Key ← DESFace.nullKey -- !!! -- ; DESFace.CorrectParity[@kb]; DESFace.DecryptBlock[ key: kb, from: LOOPHOLE[@response.authenticator.ky], to: LOOPHOLE[@response.authenticator.ky]]; DESFace.CBCCheckDecrypt[ key: response.authenticator.ky, nBlks: response.authLength/SIZE[DESFace.Block] - 2, from: LOOPHOLE[@response.authenticator.ck], to: LOOPHOLE[@response.authenticator.ck], seed: [0, 0, 0, 0]]; DESFace.DecryptBlock[ key: kb, from: LOOPHOLE[@response.authenticator.ck], to: LOOPHOLE[@response.authenticator.ck]]; DESFace.CBCCheckDecrypt[ key: response.authenticator.ck, nBlks: responseCKBlocks, from: LOOPHOLE[response], to: LOOPHOLE[response], seed: [0, 0, 0, 0]]; conv ← EntryAddConversation[ -- Construct a ConversationID from a PktConversationID -- [ IF response.connection.conv.originator = caller THEN response.connection.caller ELSE myHost, [ response.connection.conv.ls, response.connection.conv.ms]], response.level, response.iv, @response.authenticator.a, NIL, response.authenticator.ck, DESCRIPTOR[NIL, 0]]; callPkt.convHandle ← conv; IF NOT decrypted THEN BEGIN ok: BOOLEAN; [ok, l] ← DecryptPkt[callHeader, conv]; IF NOT ok THEN RETURN[FALSE, , , , ]; END; END; -- correctness of nonceID means he knows CK. IF response.nonceID # myNonceID + 1 THEN RETURN[FALSE, , , , ]; IF response.connection # [ callHeader.conv, callHeader.srceHost, callHeader.pktID.activity] THEN RETURN[FALSE, , , , ]; IF response.call # callHeader.pktID.callSeq THEN RETURN[FALSE, , , , ]; RETURN[TRUE, response.connection, response.call, conv, l]; END; ReplyToRFA: PUBLIC PROC [ b: Buffer.PupBuffer, callHeader: LONG POINTER TO RPCPkt.Header, -- encrypted -- callPktID: RPCPkt.PktID -- clear -- , convHandle: Conversation] RETURNS [BOOLEAN] = BEGIN -- Remember Buffer.PupBuffer's don't look like MesaRPCLupine.RPCPkt's! -- recvdHeader: LONG POINTER TO RPCPkt.Header = LOOPHOLE[@b.pup.pupLength]; -- copy request out of buffer to avoid overwriting our args! -- request: RFARequest = LOOPHOLE[recvdHeader + SIZE[RPCPkt.Header], LONG POINTER TO RFARequest]↑; response: LONG POINTER TO RFAResponse = LOOPHOLE[recvdHeader + SIZE[RPCPkt.Header]]; used: CARDINAL ← SIZE[RFAResponse] - SIZE[AuthenticatorLayout]; IF request.callerConv # callHeader.conv OR request.callerPktID # callHeader.pktID THEN { GiveBackBuffer[b]; RETURN[FALSE]}; response.connection ← [ callHeader.conv, callHeader.srceHost, callPktID.activity]; response.call ← callPktID.callSeq; response.nonceID ← request.nonceID + 1; IF callHeader.conv = firstConversation THEN response.level ← none ELSE BEGIN IF callHeader.conv.originator = callee THEN --TEMP-- ERROR; response.level ← convHandle.level; response.iv ← convHandle.iv; DESFace.CBCCheckEncrypt[ key: convHandle.key, nBlks: responseCKBlocks, from: LOOPHOLE[response], to: LOOPHOLE[response], seed: [0, 0, 0, 0]]; response.authLength ← LENGTH[convHandle.authenticator]; CopyAuth[from: convHandle.authenticator, to: @response.authenticator]; used ← used + LENGTH[convHandle.authenticator]; CopyPrincipal[ from: convHandle.originator, to: @response.authenticator + LENGTH[convHandle.authenticator]]; used ← used + SIZE[StringBody [convHandle.originator.length]]; END; BEGIN header: Header ← recvdHeader↑; RPCPkt.SetupResponse[@header]; header.length ← RPCPkt.pktLengthOverhead + used; header.oddByte ← no; header.type ← [0, rpc, end, dontAck, data]; header.pktID.pktSeq ← header.pktID.pktSeq + 1; header.srceHost ← myHost; header.srceSoc ← RPCPrivate.rpcSocket; header.srcePSB ← PSB.PsbNull; recvdHeader↑ ← header; END; PupDefs.PupRouterSendThis[b]; RETURN[TRUE] END; -- Initialization -- Initialize: ENTRY PROC = BEGIN myAddr: PupTypes.PupAddress ← PupDefs.GetLocalPupAddress[ RPCPrivate.rpcSocket, NIL]; now: MACHINE DEPENDENT RECORD [ls: CARDINAL, spare: [0..1], ms: [0..77777B]] = LOOPHOLE[System.SecondsSinceEpoch[System.GetGreenwichMeanTime[]]]; firstHandle: Conversation; myHost ← [myAddr.net, myAddr.host]; lastConversation ← [originator: myHost, count: [ls: now.ls, ms: now.ms]]; -- Use first available conversationID for unencrypted conversations. firstHandle ← AddConversation[ lastConversation, none, , NIL, NIL, DESFace.nullKey, DESCRIPTOR[NIL, 0]]; firstConversation ← [ ls: firstHandle.id.count.ls, originator: caller, ms: firstHandle.id.count.ms]; END; Initialize[]; END.