DIRECTORY AuthenticationP14V2 USING [AuthenticationError, CallError, CallProblem, ChangeSimpleKey, ChangeStrongKey, CheckSimpleCredentials, CreateSimpleKey, CreateStrongKey, CredentialsPackage, DeleteSimpleKey, DeleteStrongKey, GetStrongCredentials, HashedPassword, Problem, SeqWords, SeqWordsObject, StrongCredentials, StrongVerifier, Which], Basics USING [BITXOR, LongNumber, ShortNumber], BasicTime USING [earliestGMT, FromNSTime, GetClockPulses, GMT, latestGMT, Now, Period, ToNSTime], CrRPC USING [CreateClientHandle, DestroyClientHandle, Error, Handle, MarshalledROPEHWords], DESFace USING [Block, Blocks, CBCCheckDecrypt, CBCCheckEncrypt, CorrectParity, EncryptBlock, Key, nullKey], Process USING [Detach, Pause, SecondsToTicks], ProcessorFace USING [processorID], -- there ought to be a better place to get this! RefText USING [InlineAppendChar, ObtainScratch, ReleaseScratch], Rope USING [Equal, FromRefText, InlineFetch, Length, ROPE], TimeP15V2 USING [Time], XNS USING [Address, Host, unknownHost, unknownSocket], XNSAuth USING [Credentials, CredentialsType, HashedPassword, Key, Name, Verifier], XNSRouter USING [GetHops, Hops], XNSServerLocation USING [EachAddressProc, LocateServers], XNSWKS USING [authenticationInfo] ; XNSAuthImpl: CEDAR MONITOR LOCKS lock USING lock: Lock IMPORTS AuthenticationP14V2, Basics, BasicTime, CrRPC, DESFace, Process, ProcessorFace, RefText, Rope, XNSRouter, XNSServerLocation EXPORTS XNSAuth ~ { OPEN Auth: AuthenticationP14V2, Time: TimeP15V2; authPgmNum: CARD ~ 14; authVersionNum: CARDINAL ~ 2; SeqWords: TYPE ~ Auth.SeqWords; SeqWordsObject: TYPE ~ Auth.SeqWordsObject; GMT: TYPE ~ BasicTime.GMT; Block: TYPE ~ DESFace.Block; ROPE: TYPE ~ Rope.ROPE; NSTime: TYPE ~ Time.Time; Credentials: TYPE ~ XNSAuth.Credentials; CredentialsType: TYPE ~ XNSAuth.CredentialsType; HashedPassword: TYPE ~ XNSAuth.HashedPassword; Key: TYPE ~ XNSAuth.Key; Name: TYPE ~ XNSAuth.Name; Verifier: TYPE ~ XNSAuth.Verifier; HostNumber: TYPE ~ XNS.Host; authSvcName: Name _ ["CHServers", "CHServers", "Authentication Service"]; myHostNumber: HostNumber _ LOOPHOLE[ProcessorFace.processorID]; Lock: TYPE ~ REF LockObject; LockObject: TYPE ~ MONITORED RECORD []; AuthenticationError: PUBLIC ERROR [problem: Auth.Problem] _ Auth.AuthenticationError; CallError: PUBLIC ERROR[problem: Auth.CallProblem, whichArg: Auth.Which] _ Auth.CallError; MarshallError: ERROR ~ CODE; CantContactAuthServer: PROC ~ { ERROR }; AuthServerError: PROC [what: ROPE] ~ { ERROR }; SimpleIdentity: TYPE ~ REF SimpleIdentityObject; SimpleIdentityObject: TYPE ~ RECORD [ name: Name, password: ROPE, credentials: Credentials, verifier: Verifier ]; StrongIdentity: TYPE ~ REF StrongIdentityObject; StrongIdentityObject: TYPE ~ RECORD [ next: StrongIdentity, conversations: StrongConversation, name: Name, password: ROPE, -- optional, if present must agree with key key: Key ]; Identity: TYPE ~ REF; -- SimpleIdentityObject U StrongIdentityObject initiatorCacheLock: Lock ~ NEW[LockObject _ []]; strongIdentities: StrongIdentity _ NIL; AddNewStrongIdentity: ENTRY PROC [lock: Lock _ initiatorCacheLock, newID: StrongIdentity] ~ { newID.next _ strongIdentities; strongIdentities _ newID }; RemoveOldStrongIdentity: ENTRY PROC [lock: Lock _ initiatorCacheLock, oldID: StrongIdentity] ~ { p, prev: StrongIdentity; p _ strongIdentities; prev _ NIL; WHILE (p # NIL) AND (p # oldID) DO prev _ p; p _ p.next ENDLOOP; IF p # NIL THEN { IF prev # NIL THEN prev.next _ p.next ELSE strongIdentities _ p.next }; }; MakeIdentity: PUBLIC PROC[name: Name, password: ROPE, credentialsType: CredentialsType _ strong, check: BOOL _ TRUE] RETURNS [identity: Identity] ~ { SELECT credentialsType FROM simple => { simpleID: SimpleIdentity; simpleID _ NEW[SimpleIdentityObject _ [name~name, password~password, credentials~CredentialsFromName[name], verifier~VerifierFromHashedPassword[SimpleKeyFromPassword[password]]]]; identity _ simpleID; IF check THEN CheckSimple[simpleID.credentials, simpleID.verifier]; }; strong => { strongID: StrongIdentity _ NEW[StrongIdentityObject _ [name~name, password~password, key~StrongKeyFromPassword[password]]]; AddNewStrongIdentity[newID~strongID]; identity _ strongID; IF check THEN Terminate[Initiate[identity, authSvcName]]; }; ENDCASE => ERROR; }; MakeStrongIdentityUsingKey: PUBLIC PROC[name: Name, key: Key, check: BOOL _ TRUE] RETURNS [identity: Identity] ~ { strongID: StrongIdentity _ NEW[StrongIdentityObject _ [name~name, password~NIL, key~key]]; AddNewStrongIdentity[newID~strongID]; identity _ strongID; IF check THEN Terminate[Initiate[identity, authSvcName]]; }; SimpleConversation: TYPE ~ REF SimpleConversationObject; SimpleConversationObject: TYPE ~ RECORD [ initiatorID: SimpleIdentity, recipientName: Name ]; StrongConversation: TYPE ~ REF StrongConversationObject; StrongConversationObject: TYPE ~ RECORD [ next: StrongConversation, initiatorID: StrongIdentity, timeStamp: GMT, recipientName: Name, recipientHostNumber: HostNumber, credentials: Credentials, conversationKey: Key, lastStrongVerifier: Auth.StrongVerifier ]; Conversation: TYPE ~ REF; -- SimpleConversationObject U StrongConversationObject StrongConversationCacheGet: ENTRY PROC[lock: Lock _ initiatorCacheLock, id: StrongIdentity, name: Name] RETURNS[StrongConversation] ~ { ENABLE UNWIND => NULL; p, prev: StrongConversation; prev _ NIL; p _ id.conversations; WHILE (p # NIL) AND (NOT EqualNames[name, p.recipientName]) DO prev _ p; p _ p.next; ENDLOOP; IF p # NIL THEN { IF prev # NIL THEN prev.next _ p.next ELSE id.conversations _ p.next; p.next _ NIL }; RETURN [p] }; StrongConversationCachePut: ENTRY PROC[lock: Lock _ initiatorCacheLock, c: StrongConversation, id: StrongIdentity] ~ { ENABLE UNWIND => NULL; c.next _ id.conversations; id.conversations _ c }; strongConversationCacheTimeout: INT ~ 300; -- ???? what's the right number ???? SweepStrongConversationCache: ENTRY PROC[lock: Lock _ initiatorCacheLock] ~ { ENABLE UNWIND => NULL; p, prev: StrongConversation; now: GMT ~ BasicTime.Now[]; FOR strongID: StrongIdentity _ strongIdentities, strongID.next WHILE strongID # NIL DO p _ strongID.conversations; prev _ NIL; WHILE p # NIL DO IF BasicTime.Period[from~p.timeStamp, to~now] < strongConversationCacheTimeout THEN { prev _ p; p _ p.next } ELSE { p _ p.next; IF prev = NIL THEN strongID.conversations _ p ELSE prev.next _ p }; ENDLOOP; ENDLOOP; }; secondsBetweenRefreshes: CARDINAL ~ 300; -- ???? what's the right number ???? initialStrongVerifier: Auth.StrongVerifier ~ [BasicTime.ToNSTime[BasicTime.earliestGMT], 0]; Initiate: PUBLIC PROC [identity: Identity, recipientName: Name] RETURNS [conversation: Conversation] ~ { WITH identity SELECT FROM simpleID: SimpleIdentity => { c: SimpleConversation _ NEW[SimpleConversationObject _ [initiatorID~simpleID, recipientName~recipientName]]; conversation _ c }; strongID: StrongIdentity => { c: StrongConversation; c _ StrongConversationCacheGet[id~strongID, name~recipientName]; IF c = NIL THEN c _ NEW[StrongConversationObject _ [timeStamp~BasicTime.earliestGMT, recipientName~recipientName, recipientHostNumber~XNS.unknownHost, credentials~[strong,NIL], conversationKey~, lastStrongVerifier~initialStrongVerifier]]; c.initiatorID _ strongID; IF BasicTime.Period[from~c.timeStamp, to~BasicTime.Now[]] > secondsBetweenRefreshes THEN Refresh[c]; conversation _ c }; ENDCASE => ERROR; }; Refresh: PUBLIC PROC [conversation: Conversation] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { NULL }; strongC: StrongConversation => { seqWords: SeqWords; credentialsPackage: Auth.CredentialsPackage; nonce: CARD ~ BasicTime.GetClockPulses[]; DoGetStrongCredentials: PROC [h: CrRPC.Handle, host: HostNumber] ~ { seqWords _ Auth.GetStrongCredentials[h, strongC.initiatorID.name, strongC.recipientName, nonce] }; CallRemote[DoGetStrongCredentials]; seqWords _ DecryptSeqWords[strongC.initiatorID.key, seqWords]; [cp~credentialsPackage] _ FetchCredentialsPackageFromSeqWords[seqWords, 0 ! MarshallError => GOTO Bad]; IF credentialsPackage.nonce # nonce THEN GOTO Bad; strongC.recipientName _ credentialsPackage.recipient; strongC.credentials _ credentialsPackage.credentials; strongC.conversationKey _ credentialsPackage.conversationKey; strongC.lastStrongVerifier _ initialStrongVerifier; strongC.timeStamp _ BasicTime.Now[]; EXITS Bad => ERROR AuthenticationError[credentialsInvalid]; }; ENDCASE => ERROR; }; Terminate: PUBLIC PROC [conversation: Conversation] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { NULL }; strongC: StrongConversation => { strongID: StrongIdentity ~ strongC.initiatorID; strongC.initiatorID _ NIL; StrongConversationCachePut[c~strongC, id~strongID] }; ENDCASE => ERROR; }; GetCredentials: PUBLIC PROC [conversation: Conversation] RETURNS [credentials: Credentials] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { credentials _ simpleC.initiatorID.credentials }; strongC: StrongConversation => { credentials _ strongC.credentials }; ENDCASE => ERROR; }; SetRecipientHostNumber: PUBLIC PROC [conversation: Conversation, recipientHostNumber: HostNumber] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { NULL }; strongC: StrongConversation => { strongC.recipientHostNumber _ recipientHostNumber }; ENDCASE => ERROR; }; GetNextVerifier: PUBLIC PROC [conversation: Conversation] RETURNS [verifier: Verifier] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { verifier _ simpleC.initiatorID.verifier }; strongC: StrongConversation => { now: NSTime ~ BasicTime.ToNSTime[BasicTime.Now[]]; IF strongC.lastStrongVerifier.timeStamp = now THEN strongC.lastStrongVerifier _ IncrementStrongVerifier[strongC.lastStrongVerifier] ELSE strongC.lastStrongVerifier.ticks _ 0; verifier _ EncryptedVerifierFromStrongVerifier[strongC.conversationKey, LOOPHOLE[strongC.recipientHostNumber], strongC.lastStrongVerifier]; }; ENDCASE => ERROR; }; ReplyVerifierChecks: PUBLIC PROC [conversation: Conversation, verifier: Verifier] RETURNS [ok: BOOL] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { ERROR AuthenticationError[inappropriateCredentials] }; strongC: StrongConversation => { ENABLE MarshallError => { ok _ FALSE; CONTINUE }; sV: Auth.StrongVerifier ~ StrongVerifierFromEncryptedVerifier[ strongC.conversationKey, LOOPHOLE[strongC.recipientHostNumber], verifier]; ok _ (sV = IncrementStrongVerifier[strongC.lastStrongVerifier]); IF ok THEN strongC.lastStrongVerifier _ sV }; ENDCASE => ERROR; }; RSimpleConversation: TYPE ~ REF RSimpleConversationObject; RSimpleConversationObject: TYPE ~ RECORD [ next: RSimpleConversation, referenced: BOOL _ TRUE, initiatorName: Name, credentials: Credentials, verifier: Verifier ]; rSimpleConversationCacheLock: Lock _ NEW[LockObject _ []]; rSimpleConversationCache: RSimpleConversation _ NIL; RSimpleConversationCacheGet: ENTRY PROC [ lock: Lock _ rSimpleConversationCacheLock, credentials: Credentials] RETURNS [RSimpleConversation] ~ { ENABLE UNWIND => NULL; p, prev: RSimpleConversation; IF credentials.type # simple THEN ERROR; prev _ NIL; p _ rSimpleConversationCache; WHILE (p # NIL) AND (NOT EqualSeqWords[credentials.value, p.credentials.value]) DO prev _ p; p _ p.next; ENDLOOP; IF p # NIL THEN { IF prev # NIL THEN prev.next _ p.next ELSE rSimpleConversationCache _ p.next; p.next _ NIL; p.referenced _ TRUE }; RETURN [p] }; RSimpleConversationCachePut: ENTRY PROC[ lock: Lock _ rSimpleConversationCacheLock, conversation: RSimpleConversation] ~ { ENABLE UNWIND => NULL; conversation.next _ rSimpleConversationCache; rSimpleConversationCache _ conversation }; SweepRSimpleConversationCache: ENTRY PROC[lock: Lock _ rSimpleConversationCacheLock] ~ { ENABLE UNWIND => NULL; p, prev: RSimpleConversation; p _ rSimpleConversationCache; prev _ NIL; WHILE p # NIL DO IF p.referenced THEN { p.referenced _ FALSE; prev _ p; p _ p.next } ELSE { p _ p.next; IF prev = NIL THEN rSimpleConversationCache _ p ELSE prev.next _ p }; ENDLOOP; }; RStrongConversation: TYPE ~ REF RStrongConversationObject; RStrongConversationObject: TYPE ~ RECORD [ next: RStrongConversation, referenced: BOOL _ TRUE, credentials: Credentials, decryptedStrongCredentials: Auth.StrongCredentials, lastStrongVerifier: Auth.StrongVerifier ]; rStrongConversationCacheLock: Lock _ NEW[LockObject _ []]; rStrongConversationCache: RStrongConversation _ NIL; RStrongConversationCacheGet: ENTRY PROC [ lock: Lock _ rStrongConversationCacheLock, credentials: Credentials] RETURNS [RStrongConversation] ~ { ENABLE UNWIND => NULL; p, prev: RStrongConversation; IF credentials.type # strong THEN ERROR; prev _ NIL; p _ rStrongConversationCache; WHILE (p # NIL) AND (NOT EqualSeqWords[credentials.value, p.credentials.value]) DO prev _ p; p _ p.next; ENDLOOP; IF p # NIL THEN { IF prev # NIL THEN prev.next _ p.next ELSE rStrongConversationCache _ p.next; p.next _ NIL; p.referenced _ TRUE }; RETURN [p] }; RStrongConversationCachePut: ENTRY PROC[ lock: Lock _ rStrongConversationCacheLock, conversation: RStrongConversation] ~ { ENABLE UNWIND => NULL; conversation.next _ rStrongConversationCache; rStrongConversationCache _ conversation }; SweepRStrongConversationCache: ENTRY PROC[lock: Lock _ rStrongConversationCacheLock] ~ { ENABLE UNWIND => NULL; p, prev: RStrongConversation; p _ rStrongConversationCache; prev _ NIL; WHILE p # NIL DO IF p.referenced THEN { p.referenced _ FALSE; prev _ p; p _ p.next } ELSE { p _ p.next; IF prev = NIL THEN rStrongConversationCache _ p ELSE prev.next _ p }; ENDLOOP; }; Authenticate: PUBLIC PROC [myIdentity: Identity, hisCredentials: Credentials, hisVerifier: Verifier] RETURNS [hisName: Name] ~ { SELECT hisCredentials.type FROM simple => { hisName _ AuthenticateSimple[hisCredentials, hisVerifier] }; strong => { [hisName~hisName] _ AuthenticateStrong[myIdentity~myIdentity, hisCredentials~hisCredentials, hisVerifier~hisVerifier, useExpiredCredentials~FALSE, computeReplyVerifier~FALSE] }; ENDCASE => ERROR; }; AuthenticateWithExpiredCredentials: PUBLIC PROC [myIdentity: Identity, hisCredentials: Credentials, hisVerifier: Verifier] RETURNS [hisName: Name] ~ { SELECT hisCredentials.type FROM simple => { hisName _ AuthenticateSimple[hisCredentials, hisVerifier] }; strong => { [hisName~hisName] _ AuthenticateStrong[myIdentity~myIdentity, hisCredentials~hisCredentials, hisVerifier~hisVerifier, useExpiredCredentials~TRUE, computeReplyVerifier~FALSE] }; ENDCASE => ERROR; }; AuthenticateAndReply: PUBLIC PROC [myIdentity: Identity, hisCredentials: Credentials, hisVerifier: Verifier] RETURNS [hisName: Name, replyVerifier: Verifier] ~ { SELECT hisCredentials.type FROM simple => { ERROR AuthenticationError[inappropriateCredentials]; }; strong => { [hisName, replyVerifier] _ AuthenticateStrong[myIdentity~myIdentity, hisCredentials~hisCredentials, hisVerifier~hisVerifier, useExpiredCredentials~FALSE, computeReplyVerifier~TRUE]; }; ENDCASE => ERROR; }; AuthenticateSimple: PROC [hisCredentials: Credentials, hisVerifier: Verifier] RETURNS [hisName: Name] ~ { rSimpleC: RSimpleConversation; rSimpleC _ RSimpleConversationCacheGet[credentials~hisCredentials]; IF (rSimpleC = NIL) OR (NOT EqualSeqWords[hisVerifier, rSimpleC.verifier]) THEN { CheckSimple[hisCredentials, hisVerifier]; rSimpleC _ NEW[RSimpleConversationObject _ [initiatorName~FetchNameFromSeqWords[hisCredentials.value, 0].name, credentials~hisCredentials, verifier~hisVerifier]]; }; hisName _ rSimpleC.initiatorName; RSimpleConversationCachePut[conversation~rSimpleC]; }; verifierTimeout: INT ~ 60; AuthenticateStrong: PROC [myIdentity: Identity, hisCredentials: Credentials, hisVerifier: Verifier, useExpiredCredentials: BOOL _ FALSE, computeReplyVerifier: BOOL _ FALSE] RETURNS [hisName: Name, replyVerifier: Verifier _ NIL] ~ { rStrongC: RStrongConversation; WITH myIdentity SELECT FROM myStrongID: StrongIdentity => { rStrongC _ RStrongConversationCacheGet[credentials~hisCredentials]; IF rStrongC = NIL THEN { ENABLE MarshallError => CONTINUE; temp: SeqWords _ DecryptSeqWords[key~myStrongID.key, seqWords~hisCredentials.value]; rStrongC _ NEW[RStrongConversationObject _ [credentials~hisCredentials, decryptedStrongCredentials~FetchStrongCredentialsFromSeqWords[temp,0].sc, lastStrongVerifier~initialStrongVerifier]]; }; IF rStrongC = NIL THEN ERROR AuthenticationError[credentialsInvalid]; IF NOT useExpiredCredentials THEN { expired: BOOL ~ (BasicTime.Period[ from~BasicTime.Now[], to~BasicTime.FromNSTime[rStrongC.decryptedStrongCredentials.expirationTime ]] >= 0); IF expired THEN ERROR AuthenticationError[credentialsExpired] }; { ENABLE MarshallError => GOTO Bad; temp: Auth.StrongVerifier _ StrongVerifierFromEncryptedVerifier[ rStrongC.decryptedStrongCredentials.conversationKey, LOOPHOLE[myHostNumber], hisVerifier]; IF NOT StrongVerifierGT[new~temp, old~rStrongC.lastStrongVerifier] THEN ERROR AuthenticationError[verifierReused]; IF NOT useExpiredCredentials THEN { IF StrongVerifierExpired[temp] THEN ERROR AuthenticationError[verifierExpired]; }; IF computeReplyVerifier THEN { rStrongC.lastStrongVerifier _ IncrementStrongVerifier[temp]; replyVerifier _ EncryptedVerifierFromStrongVerifier[ rStrongC.decryptedStrongCredentials.conversationKey, LOOPHOLE[myHostNumber], rStrongC.lastStrongVerifier] } ELSE { rStrongC.lastStrongVerifier _ temp }; EXITS Bad => ERROR AuthenticationError[verifierInvalid]; }; hisName _ rStrongC.decryptedStrongCredentials.initiator; RStrongConversationCachePut[conversation: rStrongC]; }; mySimpleID: SimpleIdentity => { ERROR AuthenticationError[inappropriateCredentials] }; ENDCASE => ERROR; }; CheckSimple: PROC[credentials: Credentials, verifier: Verifier] ~ { okay: BOOL _ FALSE; DoCheckSimple: PROC [h: CrRPC.Handle, host: HostNumber] ~ { okay _ Auth.CheckSimpleCredentials[h, credentials, verifier] }; CallRemote[DoCheckSimple]; IF NOT okay -- can this happen? -- THEN ERROR AuthenticationError[problem~credentialsInvalid]; }; ChangeMyPasswords: PUBLIC PROC [identity: Identity, newPassword: ROPE, changeStrong: BOOL _ TRUE, changeSimple: BOOL _ TRUE] ~ { IF changeStrong THEN { newKey: Key _ StrongKeyFromPassword[newPassword]; ChangeMyStrongKey[identity, newKey] }; IF changeSimple THEN { newKey: HashedPassword _ SimpleKeyFromPassword[newPassword]; ChangeMySimpleKey[identity, newKey] }; }; CreateStrongKey: PUBLIC PROC [identity: Identity, name: Name, newKey: Key] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.CreateStrongKey[h, GetCredentials[c], GetNextVerifier[c], name, newKey] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; ChangeMyStrongKey: PUBLIC PROC [identity: Identity, newKey: Key] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.ChangeStrongKey[h, GetCredentials[c], GetNextVerifier[c], newKey] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; DeleteStrongKey: PUBLIC PROC [identity: Identity, name: Name] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.DeleteStrongKey[h, GetCredentials[c], GetNextVerifier[c], name] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; CreateSimpleKey: PUBLIC PROC [identity: Identity, name: Name, newKey: HashedPassword] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.CreateSimpleKey[h, GetCredentials[c], GetNextVerifier[c], name, newKey] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; ChangeMySimpleKey: PUBLIC PROC [identity: Identity, newKey: HashedPassword] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.ChangeSimpleKey[h, GetCredentials[c], GetNextVerifier[c], newKey] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; DeleteSimpleKey: PUBLIC PROC [identity: Identity, name: Name] ~ { c: Conversation _ Initiate[identity, authSvcName]; DoIt: PROC [h: CrRPC.Handle, host: HostNumber] ~ { SetRecipientHostNumber[c, host]; Auth.DeleteSimpleKey[h, GetCredentials[c], GetNextVerifier[c], name] }; { ENABLE UNWIND => Terminate[c]; CallRemote[DoIt] }; Terminate[c]; }; StrongKeyFromPassword: PUBLIC PROC [password: ROPE] RETURNS [key: Key] ~ { index: INT; passwordLen: INT ~ Rope.Length[password]; ki: DESFace.Key _ DESFace.nullKey; bi: Block; biPlus1: Block; FOR index _ 0, index + 4 WHILE index < passwordLen DO len: INT ~ MIN[4, passwordLen-index]; TRUSTED { StoreSubropeInBlock[password, index, len, @bi, TRUE]; DESFace.CorrectParity[@ki]; DESFace.EncryptBlock[key~ki, from~@bi, to~@biPlus1]; ki _ LOOPHOLE[biPlus1] }; ENDLOOP; TRUSTED { DESFace.CorrectParity[@ki] }; RETURN [ LOOPHOLE[ki] ]; }; GetRandomStrongKey: PUBLIC PROC RETURNS [key: Key] ~ { ERROR }; SimpleKeyFromPassword: PUBLIC PROC [password: ROPE] RETURNS [hashVal: HashedPassword _ 0] ~ { c: CHAR; acc: Basics.LongNumber; FOR i: INT IN [0 .. Rope.Length[password]) DO c _ Rope.InlineFetch[password, i]; SELECT c FROM IN ['A .. 'Z] => c _ 'a + (c - 'A); ENDCASE; acc.hi _ hashVal; acc.lo _ ORD[c]; hashVal _ acc.lc MOD 65357; ENDLOOP; }; GetCredentialsType: PUBLIC PROC [credentials: Credentials] RETURNS [CredentialsType] ~ { RETURN [credentials.type] }; GetConversationDetails: PUBLIC PROC [conversation: Conversation] RETURNS [ recipientName: Name, recipientHostNumber: HostNumber, credentials: Credentials, conversationKey: Key, owner: Identity] ~ { WITH conversation SELECT FROM simpleC: SimpleConversation => { recipientName _ simpleC.recipientName; recipientHostNumber _ XNS.unknownHost; credentials _ simpleC.initiatorID.credentials; conversationKey _ ALL[0]; owner _ simpleC.initiatorID }; strongC: StrongConversation => { recipientName _ strongC.recipientName; recipientHostNumber _ strongC.recipientHostNumber; credentials _ strongC.credentials; conversationKey _ strongC.conversationKey; owner _ strongC.initiatorID }; ENDCASE => ERROR; }; GetIdentityDetails: PUBLIC PROC [identity: Identity] RETURNS [ name: Name, password: ROPE, credentialsType: CredentialsType] ~ { WITH identity SELECT FROM simpleI: SimpleIdentity => { name _ simpleI.name; password _ simpleI.password; credentialsType _ simple }; strongI: StrongIdentity => { name _ strongI.name; password _ strongI.password; credentialsType _ strong }; ENDCASE => ERROR; }; GetCredentialsDetails: PUBLIC PROC [myKey: Key, hisCredentials: Credentials] RETURNS [ok: BOOL, credentialsType: CredentialsType, conversationKey: Key, expirationTime: GMT, hisName: Name] ~ { ok _ FALSE; SELECT hisCredentials.type FROM simple => { ENABLE MarshallError => CONTINUE; credentialsType _ simple; conversationKey _ ALL[0]; expirationTime _ BasicTime.latestGMT; hisName _ FetchNameFromSeqWords[hisCredentials.value, 0].name; ok _ TRUE; }; strong => { ENABLE MarshallError => CONTINUE; rStrongC: RStrongConversation; sC: Auth.StrongCredentials; credentialsType _ strong; rStrongC _ RStrongConversationCacheGet[credentials~hisCredentials]; IF rStrongC # NIL THEN { sC _ rStrongC.decryptedStrongCredentials; RStrongConversationCachePut[conversation: rStrongC] } ELSE { temp: SeqWords _ DecryptSeqWords[key~myKey, seqWords~hisCredentials.value]; [sc~sC] _ FetchStrongCredentialsFromSeqWords[temp,0] }; conversationKey _ sC.conversationKey; expirationTime _ BasicTime.FromNSTime[sC.expirationTime]; hisName _ sC.initiator; ok _ TRUE; }; ENDCASE => ERROR; }; secondsBetweenSweeps: CARDINAL ~ 300; Daemon: PROC ~ { DO Process.Pause[Process.SecondsToTicks[secondsBetweenSweeps]]; SweepStrongConversationCache[]; SweepRStrongConversationCache[]; ENDLOOP; }; EqualNames: PROC [a: Name, b: Name] RETURNS [BOOL] ~ { RETURN[Rope.Equal[a.organization, b.organization, FALSE] AND Rope.Equal[a.domain, b.domain, FALSE] AND Rope.Equal[a.object, b.object, FALSE]] }; EqualSeqWords: PROC [a, b: SeqWords] RETURNS [BOOL] ~ { IF a.length # b.length THEN RETURN [FALSE]; FOR i: CARDINAL IN [0..a.length) DO IF a.body[i] # b.body[i] THEN RETURN [FALSE]; ENDLOOP; RETURN [TRUE] }; IncrementStrongVerifier: PROC [v: Auth.StrongVerifier] RETURNS [result: Auth.StrongVerifier] ~ { result.timeStamp _ v.timeStamp; IF (result.ticks _ v.ticks + 1) = 0 THEN result.timeStamp _ result.timeStamp + 1; }; StrongVerifierGT: PROC [new, old: Auth.StrongVerifier] RETURNS [gt: BOOL] ~ { deltaT: INT ~ BasicTime.Period[from~BasicTime.FromNSTime[old.timeStamp], to~BasicTime.FromNSTime[new.timeStamp]]; SELECT deltaT FROM > 0 => RETURN [TRUE]; < 0 => RETURN [FALSE]; 0 => RETURN [new.ticks > old.ticks]; ENDCASE; }; StrongVerifierExpired: PROC [v: Auth.StrongVerifier] RETURNS [expired: BOOL] ~ { deltaT: INT ~ BasicTime.Period[from~BasicTime.FromNSTime[v.timeStamp], to~BasicTime.Now[]]; RETURN [ deltaT > verifierTimeout ] }; FetchRopeFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, r: ROPE] ~ { i, count, seqLen: CARDINAL; w: Basics.ShortNumber; buf: REF TEXT; seqLen _ s.length; IF where >= seqLen THEN ERROR MarshallError; count _ s.body[where]; where _ where + 1; buf _ RefText.ObtainScratch[count]; i _ 0; WHILE i < count DO IF where >= seqLen THEN ERROR MarshallError; w.lc _ s.body[where]; where _ where + 1; buf _ RefText.InlineAppendChar[buf, VAL[w.hi]]; i _ i + 1; IF i < count THEN { buf _ RefText.InlineAppendChar[buf, VAL[w.lo]]; i _ i + 1 }; ENDLOOP; newWhere _ where; r _ Rope.FromRefText[buf]; RefText.ReleaseScratch[buf] }; StoreRopeInSeqWords: PROC [r: ROPE, s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL] ~ { i, length, seqLen: CARDINAL; w: Basics.ShortNumber; length _ Rope.Length[r]; seqLen _ s.length; IF where >= seqLen THEN ERROR MarshallError; s.body[where] _ length; where _ where + 1; i _ 0; WHILE i < length DO w.hi _ ORD[Rope.InlineFetch[r,i]]; i _ i + 1; w.lo _ (IF i < length THEN ORD[Rope.InlineFetch[r,i]] ELSE 0); i _ i + 1; IF where >= seqLen THEN ERROR MarshallError; s.body[where] _ LOOPHOLE[w]; where _ where + 1; ENDLOOP; RETURN [where] }; FetchSeqWordsFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, result: SeqWords] ~ { n, seqLen: CARDINAL; IF where >= seqLen THEN ERROR MarshallError; n _ s.body[where]; where _ where + 1; IF (n+where) >= seqLen THEN ERROR MarshallError; result _ NEW[SeqWordsObject[n]]; FOR i: CARDINAL IN [0..n) DO result.body[i] _ s.body[where]; where _ where + 1; ENDLOOP; newWhere _ where; }; StoreSeqWordsInSeqWords: PROC [s: SeqWords, arg: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL] ~ { n: CARDINAL ~ arg.length; IF (where+n+1) >= s.length THEN ERROR MarshallError; s.body[where] _ n; where _ where + 1; FOR i: CARDINAL IN [0..n) DO s.body[where] _ arg.body[i]; where _ where + 1; ENDLOOP; newWhere _ where; }; FetchBlockFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, block: Block] ~ { IF (newWhere _ where+4) >= s.length THEN ERROR MarshallError; block[0] _ s.body[where]; block[1] _ s.body[where+1]; block[2] _ s.body[where+2]; block[3] _ s.body[where+3]; }; StoreBlockInSeqWords: PROC [s: SeqWords, block: Block, where: CARDINAL] RETURNS [newWhere: CARDINAL] ~ { IF (newWhere _ where+4) >= s.length THEN ERROR MarshallError; s.body[where] _ block[0]; s.body[where+1] _ block[1]; s.body[where+2] _ block[2]; s.body[where+3] _ block[3]; }; FetchCredentialsFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, c: Credentials] ~ { type: CARDINAL; IF where >= s.length THEN ERROR MarshallError; type _ s.body[where]; where _ where + 1; SELECT type FROM ORD[CredentialsType.strong] => c.type _ strong; ORD[CredentialsType.simple] => c.type _ simple; ENDCASE => ERROR MarshallError; [newWhere~newWhere, result~c.value] _ FetchSeqWordsFromSeqWords[s, where]; }; FetchCARDFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, result: CARD] ~ { n: Basics.LongNumber; IF(newWhere _ where+2) >= s.length THEN ERROR MarshallError; n.hi _ s.body[where]; n.lo _ s.body[where+1]; result _ n.lc }; FetchCredentialsPackageFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, cp: Auth.CredentialsPackage] ~ { [where, cp.credentials] _ FetchCredentialsFromSeqWords[s, where]; IF cp.credentials.type # strong THEN ERROR AuthenticationError[inappropriateCredentials]; [where, cp.nonce] _ FetchCARDFromSeqWords[s, where]; [where, cp.recipient] _ FetchNameFromSeqWords[s, where]; [where, cp.conversationKey] _ FetchBlockFromSeqWords[s, where]; newWhere _ where }; FetchStrongCredentialsFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, sc: Auth.StrongCredentials] ~ { [where, sc.conversationKey] _ FetchBlockFromSeqWords[s, where]; [where, sc.expirationTime] _ FetchCARDFromSeqWords[s, where]; [where, sc.initiator] _ FetchNameFromSeqWords[s, where]; newWhere _ where }; StoreSubropeInBlock: PROC [r: ROPE, index, len: INT, to: LONG POINTER TO Block, mapCase: BOOL _ TRUE] ~ { c: CHAR; blockIndex: INT _ 0; WHILE blockIndex < len DO c _ Rope.InlineFetch[r, index]; IF mapCase THEN SELECT c FROM IN ['A .. 'Z] => c _ 'a + (c - 'A); ENDCASE; TRUSTED { to[blockIndex] _ ORD[c] }; index _ index + 1; blockIndex _ blockIndex + 1; ENDLOOP; WHILE blockIndex < 4 DO TRUSTED { to[blockIndex] _ 0 }; blockIndex _ blockIndex + 1; ENDLOOP; }; FetchNameFromSeqWords: PROC [s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL, name: Name] ~ { [where, name.organization] _ FetchRopeFromSeqWords[s, where]; [where, name.domain] _ FetchRopeFromSeqWords[s, where]; [where, name.object] _ FetchRopeFromSeqWords[s, where]; newWhere _ where }; StoreNameInSeqWords: PROC [name: Name, s: SeqWords, where: CARDINAL] RETURNS [newWhere: CARDINAL] ~ { where _ StoreRopeInSeqWords[name.organization, s, where]; where _ StoreRopeInSeqWords[name.domain, s, where]; where _ StoreRopeInSeqWords[name.object, s, where]; RETURN [where] }; MarshalledNameHWORDs: PROC [name: Name] RETURNS [hWords: CARDINAL] ~ { RETURN [CrRPC.MarshalledROPEHWords[name.organization] + CrRPC.MarshalledROPEHWords[name.domain] + CrRPC.MarshalledROPEHWords[name.object]] }; SeqWordsFromName: PROC [name: Name] RETURNS [SeqWords] ~ { s: SeqWords _ NEW[SeqWordsObject[MarshalledNameHWORDs[name]]]; [] _ StoreNameInSeqWords[name, s, 0]; RETURN [s] }; CredentialsFromName: PROC [name: Name] RETURNS [credentials: Credentials] ~ { RETURN[ [type~simple, value~SeqWordsFromName[name]] ] }; VerifierFromHashedPassword: PROC [hashedPassword: HashedPassword] RETURNS [verifier: Verifier] ~ { verifier _ NEW[SeqWordsObject[1]]; verifier.body[0] _ hashedPassword }; EncryptSeqWords: PROC [key: Key, seqWords: SeqWords] RETURNS [result: SeqWords] ~ { nBlocks: CARDINAL _ (seqWords.length + SIZE[Block] - 1) / SIZE[Block]; roundedLength: CARDINAL _ nBlocks * SIZE[Block]; result _ NEW[SeqWordsObject[roundedLength]]; IF roundedLength # seqWords.length THEN { FOR i: CARDINAL IN [0..seqWords.length) DO result.body[i] _ seqWords.body[i] ENDLOOP; FOR i: CARDINAL IN [seqWords.length..roundedLength) DO result.body[i] _ 0 ENDLOOP; seqWords _ result }; TRUSTED { blocksFrom: DESFace.Blocks ~ LOOPHOLE[ LOOPHOLE[seqWords, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; blocksTo: DESFace.Blocks ~ LOOPHOLE[ LOOPHOLE[result, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; DESFace.CBCCheckEncrypt[LOOPHOLE[key], nBlocks, blocksFrom, blocksTo, ALL[0]]; }; }; DecryptSeqWords: PROC [key: Key, seqWords: SeqWords] RETURNS [result: SeqWords] ~ { nBlocks: CARDINAL ~ seqWords.length / SIZE[Block]; roundedLength: CARDINAL _ nBlocks * SIZE[Block]; IF seqWords.length # roundedLength THEN ERROR MarshallError; result _ NEW[SeqWordsObject[roundedLength]]; TRUSTED { blocksFrom: DESFace.Blocks ~ LOOPHOLE[ LOOPHOLE[seqWords, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; blocksTo: DESFace.Blocks ~ LOOPHOLE[ LOOPHOLE[result, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; DESFace.CBCCheckDecrypt[LOOPHOLE[key], nBlocks, blocksFrom, blocksTo, ALL[0]]; }; }; EncryptedVerifierFromStrongVerifier: PROC [conversationKey: Key, hostNumber: MACHINE DEPENDENT RECORD [a, b, c: WORD], strongVerifier: Auth.StrongVerifier] RETURNS [verifier: Verifier] ~ { seqWords: SeqWords ~ NEW[SeqWordsObject[SIZE[Auth.StrongVerifier]]]; TRUSTED { p: LONG POINTER TO Auth.StrongVerifier _ LOOPHOLE[ LOOPHOLE[seqWords, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; p^ _ strongVerifier }; seqWords.body[0] _ Basics.BITXOR[seqWords.body[0], hostNumber.a]; seqWords.body[1] _ Basics.BITXOR[seqWords.body[0], hostNumber.b]; seqWords.body[2] _ Basics.BITXOR[seqWords.body[0], hostNumber.c]; verifier _ EncryptSeqWords[key~LOOPHOLE[conversationKey], seqWords~seqWords]; }; StrongVerifierFromEncryptedVerifier: PROC [conversationKey: Key, hostNumber: MACHINE DEPENDENT RECORD [a, b, c: WORD], verifier: Verifier] RETURNS [strongVerifier: Auth.StrongVerifier] ~ { IF verifier.length # SIZE[Auth.StrongVerifier] THEN ERROR MarshallError; verifier _ DecryptSeqWords[key~LOOPHOLE[conversationKey], seqWords~verifier]; verifier.body[0] _ Basics.BITXOR[verifier.body[0], hostNumber.a]; verifier.body[1] _ Basics.BITXOR[verifier.body[0], hostNumber.b]; verifier.body[2] _ Basics.BITXOR[verifier.body[0], hostNumber.c]; TRUSTED { p: LONG POINTER TO Auth.StrongVerifier _ LOOPHOLE[ LOOPHOLE[verifier, LONG POINTER] + SIZE[SeqWordsObject[0]] ]; strongVerifier _ p^ }; }; ServerInfo: TYPE ~ REF ServerInfoObject; ServerInfoObject: TYPE ~ RECORD [ next: ServerInfo, address: XNS.Address, hops: XNSRouter.Hops ]; serverInfoLock: Lock _ NEW[LockObject _ []]; serverBroadcastLock: Lock _ NEW[LockObject _ []]; serverInfo: ServerInfo _ NIL; serverInfoTimestamp: GMT; defaultServerHops: XNSRouter.Hops ~ 3; maxServerHops: XNSRouter.Hops ~ 6; secondsBetweenBroadcasts: INT ~ 180; DeleteServer: ENTRY PROC [lock: Lock _ serverInfoLock, it: ServerInfo] ~ { ENABLE UNWIND => NULL; IF it = serverInfo THEN { serverInfo _ it.next; RETURN }; FOR p: ServerInfo _ serverInfo, p.next WHILE p # NIL DO IF p.next = it THEN { p.next _ it.next; RETURN }; ENDLOOP; }; AddServerAddress: ENTRY PROC [lock: Lock _ serverInfoLock, addr: XNS.Address, hops: XNSRouter.Hops] ~ { ENABLE UNWIND => NULL; p, prev, new: ServerInfo; prev _ NIL; p _ serverInfo; WHILE (p # NIL) AND (p.address # addr) DO { prev _ p; p _ p.next } ENDLOOP; IF p = NIL THEN { new _ NEW[ServerInfoObject _ [next~NIL, address~addr, hops~hops]] } ELSE { IF prev # NIL THEN prev.next _ p.next ELSE serverInfo _ p.next; new _ p; new.hops _ hops }; prev _ NIL; p _ serverInfo; WHILE (p # NIL) AND (p.hops < hops) DO { prev _ p; p _ p.next } ENDLOOP; new.next _ p; IF prev # NIL THEN prev.next _ new ELSE serverInfo _ new; }; BroadcastForServers: ENTRY PROC [lock: Lock _ serverBroadcastLock, maxHops: XNSRouter.Hops, firstResponseOnly: BOOL] ~ { ENABLE UNWIND => NULL; EachAddress: XNSServerLocation.EachAddressProc -- [addr: XNS.Address] -- ~ { hops: XNSRouter.Hops; addr.socket _ XNS.unknownSocket; hops _ XNSRouter.GetHops[addr.net]; AddServerAddress[addr~addr, hops~hops] }; XNSServerLocation.LocateServers[socket~XNSWKS.authenticationInfo, remotePgm~authPgmNum, remotePgmVersion~authVersionNum, eachAddress~EachAddress, maxHops~maxHops, firstResponseOnly~firstResponseOnly]; serverInfoTimestamp _ BasicTime.Now[]; }; GetBestServer: PROC RETURNS [ServerInfo] ~ { p: ServerInfo _ serverInfo; -- ATOMIC SELECT TRUE FROM (p = NIL) => { BroadcastForServers[maxHops~maxServerHops, firstResponseOnly~TRUE]; p _ serverInfo; TRUSTED { Process.Detach[ FORK BroadcastForServers[maxHops~defaultServerHops, firstResponseOnly~FALSE] ] }; }; ((p.hops > 0) AND (BasicTime.Period[from~serverInfoTimestamp, to~BasicTime.Now[]] > secondsBetweenBroadcasts)) => TRUSTED { Process.Detach[ FORK BroadcastForServers[maxHops~MIN[(p.hops-1), defaultServerHops], firstResponseOnly~FALSE] ] }; ENDCASE; RETURN [p]; }; CallRemote: PROC [proc: PROC [CrRPC.Handle, HostNumber]] ~ { h: CrRPC.Handle _ NIL; p: ServerInfo; maxTries: CARDINAL ~ 10; CleanUp: PROC ~ { IF h # NIL THEN { CrRPC.DestroyClientHandle[h]; h _ NIL }}; FOR try: CARDINAL IN [1..maxTries] DO BEGIN ENABLE { UNWIND => CleanUp[]; CrRPC.Error => { CleanUp[]; DeleteServer[it~p]; LOOP }}; IF (p _ GetBestServer[]) = NIL THEN EXIT; h _ CrRPC.CreateClientHandle[class~$SPP, remote~p.address]; proc[h, p.address.host]; CleanUp[]; RETURN; END; ENDLOOP; CleanUp[]; CantContactAuthServer[]; }; [] _ GetBestServer[]; -- load the cache TRUSTED { Process.Detach[ FORK Daemon[]] }; }. "XNSAuthImpl.mesa Demers, October 9, 1986 5:59:45 pm PDT ToDo: Finalization code for strong identities and conversations. The daemon. Initiator Identities Conversations Note there is no simple conversation cache on the initiator's side, since creating a simple conversation does not require contacting the authentication service. Public Procedures Recipient Simple Conversations Strong Conversations Public Procedures Private Utilities Just return if okay, else raise CallError or AuthenticationError. Key and Password Administration Public Utilities DESFace.EncryptBlock[key~ki, from~@bi, to~LOOPHOLE[LONG[@ki]]] }; Daemon and Finalization Private Utilities Store len chars in block using 16-bit encoding. ???? WORD SIZE DEPENDENT ???? ???? Word size dependent. Should use BYTES in here instead of SIZE ???? ???? Word size dependent. Should use BYTES in here instead of SIZE ???? Locating Authentication Servers and Performing Remote Calls Delete old entry for this address (hops may have changed) Add new entry in correct position (sorted by hops) Κ%ι˜™J™&J™šœ™J™:J™ ——Icode˜šΟk ˜ Kšœœ΄˜ΝKšœœœ˜/Kšœ œ+œ$˜aKšœœP˜[Kšœœ^˜kKšœœ!˜.KšœœΟc0˜SKšœœ3˜@Kšœœ+œ˜;Kšœ œ˜Kšœœ-˜6KšœœE˜RK˜ Kšœœ"˜9Kšœœ˜!Kšœ˜K˜—šΟn œœ˜Kšœœ ˜Kšœœf˜ƒKšœ˜K˜KšœŸœŸœ ˜0K˜Kšœ œ˜Kšœœ˜K˜Kšœ œ˜Kšœœ˜+Kšœœ œ˜Kšœœ˜Kšœœœ˜Kšœœ ˜Kšœ œ˜(Kšœœ˜0Kšœœ˜.Kšœœ˜Kšœœ˜Kšœ œ˜"Kšœ œœ˜K˜K˜IKšœœ˜?K˜Kšœœœ ˜Kšœ œ œœ˜'K˜šŸœ œ˜9Kšœ˜—šŸ œ œ1˜HK˜—KšŸ œœœ˜K˜šŸœœ˜Kšœ˜K˜—šŸœœœ˜&Kšœ˜—head™ ™ Kšœœœ˜0šœœœ˜%K˜ Kšœ œ˜K˜K˜K˜K˜—Kšœœœ˜0šœœœ˜%Kšœ˜K˜"K˜ Kšœ œž+˜;Kšœ˜K˜K˜—Kšœ œœžΠcmž˜DK˜Kšœœ˜0Kšœ#œ˜'K˜šŸœ œ=˜]K˜K˜K˜—šŸœœœ=˜`K˜Kšœœ˜"Kš œœœ œœ˜@šœœœ˜Kšœœœœ˜G—K˜K˜—š Ÿ œ œœ4œœœ˜‘K˜šœ˜˜ Kšœ˜Kšœ œ₯˜³Kšœ˜Kšœœ6˜CK˜—˜ Kšœœ]˜{Kšœ%˜%Kšœ˜Kšœœ,˜9K˜—Kšœœ˜—K˜K˜—š Ÿœ œœœœ˜nK˜Kšœœ-œ ˜ZKšœ%˜%Kšœ˜Kšœœ,˜9K˜——™ Kšœœœ˜8šœœœ˜)Kšœ˜K˜K˜K˜—Kšœœœ˜8šœœœ˜)Kšœ˜Kšœ˜Kšœ œ˜K˜Kšœ ˜ K˜K˜K˜'K˜—K˜Kšœœœž ž˜PK˜K™ K˜šŸœœœBœ˜ƒK˜Kšœœœ˜Kšœ˜Kšœœ˜"š œœœœ$˜>K˜Kšœ˜—šœœœ˜Kšœœœœ˜EKšœ œ˜—Kšœ˜ K˜—šŸœœœL˜rK˜Kšœœœ˜K˜Kšœ˜K˜—Kšœ œž$˜OK˜šŸœœœ%˜MKšœœœ˜K˜Kšœœ˜šœ<œ œ˜VKšœ#œ˜'šœœ˜šœL˜Nšœ˜Kšœ ˜ Kšœ ˜ —šœ˜Kšœ ˜ Kšœœœœ˜C——Kšœ˜—Kšœ˜—K˜——™Kšœœž$˜MKšœ\˜\K˜šŸœ œ+œ˜dKšœ˜šœ œ˜˜KšœœQ˜mK˜—˜K˜Kšœ@˜@šœœ˜Kšœœ”œ@˜ή—Kšœ˜KšœRœ ˜dK˜—Kšœœ˜—K˜K˜—šŸœ œ˜1Kšœ˜šœœ˜šœ ˜ Kšœ˜—šœ!˜!K˜K˜,Kšœœ˜)šŸœœ(˜DK˜b—Kšœ#˜#Kšœ>˜>˜IKšœœ˜—Kšœ"œœ˜2Kšœ5˜5Kšœ5˜5Kšœ=˜=Kšœ3˜3K˜$š˜Kšœœ)˜5—K˜—Kšœœ˜—K˜K˜—šŸ œ œ˜3Kšœ˜šœœ˜šœ ˜ Jšœ˜—šœ!˜!Kšœ/˜/Kšœœ˜Kšœ5˜5—Kšœœ˜—K˜K˜—šŸœ œœ˜[Kšœ˜šœœ˜šœ ˜ Kšœ0˜0—šœ!˜!Kšœ$˜$—Kšœœ˜—K˜K˜—šŸœœœ>˜aK˜šœœ˜šœ ˜ Kšœ˜—šœ!˜!Kšœ4˜4—Kšœœ˜—K˜K˜—šŸœœœœ˜VKšœ˜šœœ˜šœ ˜ Kšœ*˜*—šœ ˜ Kšœ2˜2šœ+˜-KšœQ˜UKšœ&˜*—KšœHœ;˜‹Kšœ˜—Kšœœ˜—K˜K˜—šŸœ œ2œœ˜dKšœ˜šœœ˜šœ ˜ Kšœ1˜6—šœ ˜ Kšœœœ˜1KšœXœ)˜‰Kšœ@˜@Kšœœ#˜-—Kšœœ˜—K˜———™ ™Kšœœœ˜:šœœœ˜*K˜Jšœ œœ˜K˜K˜K˜K˜K˜—Kšœ%œ˜:Kšœ0œ˜4K˜šŸœ œHœ˜Kšœœœ˜Kšœ˜Kšœœœ˜(Kšœœ ˜*š œœœœ8˜RK˜Kšœ˜—šœœœ˜Kšœœœœ#˜MKšœ œ˜ Kšœœ˜—Kšœ˜ K˜—šŸœœœS˜zKšœœœ˜Kšœ-˜-Kšœ*˜*K˜—šŸœœœ/˜XKšœœœ˜K˜Kšœ%œ˜)šœœ˜šœ ˜šœ˜Kšœœ˜Kšœ ˜ Kšœ ˜ —šœ˜Kšœ ˜ Kšœœœœ˜E——Kšœ˜—K˜—K˜—™Kšœœœ˜:šœœœ˜*Kšœ˜Jšœ œœ˜K˜J˜3K˜'K˜K˜—Kšœ%œ˜:Kšœ0œ˜4K˜šŸœ œHœ˜Kšœœœ˜Kšœ˜Kšœœœ˜(Kšœœ ˜*š œœœœ8˜RK˜Kšœ˜—šœœœ˜Kšœœœœ#˜MKšœ œ˜ Kšœœ˜—Kšœ˜ K˜—šŸœœœS˜zKšœœœ˜Kšœ-˜-Kšœ*˜*K˜—šŸœœœ/˜XKšœœœ˜Kšœ˜Kšœ%œ˜)šœœ˜šœ ˜šœ˜Kšœœ˜Kšœ ˜ Kšœ ˜ —šœ˜Kšœ ˜ Kšœœœœ˜E——Kšœ˜—K˜—K˜K˜—™šŸ œ œLœ˜|K˜šœ˜˜ Kšœ<˜<—˜ KšœŒœœ˜±—Kšœœ˜—K˜K˜—šŸ"œœœLœ˜–šœ˜˜ Kšœ<˜<—˜ KšœŒœœ˜°—Kšœœ˜—K˜K˜—šŸœœœLœ-˜‘šœ˜˜ Jšœ/˜4K˜—˜ Kšœ“œœ˜΅K˜—Kšœœ˜—K˜K˜——™šŸœœ6œ˜eK˜K˜K˜Cš œ œœœ0œ˜QKšœ)˜)Kšœ œ”˜’K˜—Kšœ!˜!Kšœ3˜3Kšœ˜K˜—Kšœœ˜K˜šŸœœcœœœœœ+œ˜ηKšœ˜šœ œ˜˜K˜Cšœ œœ˜Kšœœ˜!KšœT˜TKšœ œ―˜½K˜—Kšœ œœœ)˜Ešœœœ˜#šœ œ˜"Kšœ˜KšœJ˜JKšœ ˜ —Kšœ œœ+˜@—šœœœ˜#Kšœvœ˜›Kšœœ= œ%˜ršœœœ˜#šœ˜#Kšœ&˜+—K˜—šœ˜šœ?˜CKšœjœ.˜ —šœ˜Kšœ%˜%——š˜Kšœœ&˜2—K˜—Kšœ8˜8Kšœ4˜4K˜—˜Kšœ1˜6—Kšœœ˜—K˜K˜—šŸ œœ2˜CKšœA™AKšœœœ˜šŸ œœ$˜7KšœC˜C—Kšœ˜šœœžœ˜'Kšœ1˜6—K˜———™šŸœ œ#œœœœœ˜€šœœ˜Kšœ1˜1Kšœ&˜&—šœœ˜Kšœ<˜˜>Kšœœ˜ K˜—˜ Kšœœ˜!Kšœ˜K˜K˜K˜Cšœ ˜šœ˜Kšœ)˜)Kšœ5˜5—šœ˜KšœK˜KKšœ7˜7——Kšœ%˜%Kšœ9˜9Kšœ˜Kšœœ˜ Kšœ˜—Kšœœ˜—K˜——™K˜%šŸœœ˜š˜K˜Kšœ%˜%Kšœ˜ K˜—šŸœœœ˜MKšœ2˜8K˜—šŸœœ"œ˜bKšœ™Kšœ œ˜"Kšœ$˜$K˜—šŸœœ œ˜SK™HKšœ œœœ˜FKšœœ œ˜0Kšœ œ ˜,šœ!œ˜)šœœœ˜'Kšœ#œ˜-—šœœœ!˜3Kšœœ˜—Kšœ˜—šœ˜ Kš œœœ œœœ˜dKš œœœ œœœ˜`Kšœœ&œ˜NK˜—Kšœ˜K˜—šŸœœ œ˜SK™HKšœ œœ˜2Kšœœ œ˜0Kšœ!œœ˜