-- 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.