Definitions of principals involved:

U:
user
W:
WWW server user U wants to access
C:
certification authority (holds credentials that A vouches for; on the net; public key Kc is well known; largely untrusted)
A:
authentication server (produces credentials, not accessible via the network; trusted by W; public key Ka is well known)

Short version of protocol with no explanations:

Part 1: U gets W's public key Kw from C

 U -> C : { request(W), nc }_Kc
 C -> U : {{ W, Kw }_K-a }_nc

Part 2: U gets the desired document from W

  U -> W : { A, {U, Ku}_K-a, {request, t, {cert}_K-a', ..., nu, Ks }_K-u }_Kw
  W -> U : { response, nu }_Ks
subsequent requests in the same session take the simpler form
  U -> W: { request, t, nu' }_K-s

Explanations of abbreviations:

E -> F : m
means that principal E sends message m to principal F.
{m}_K
is the message resulting from encrypting message m with key K.
Kx
is a public key (usually X's public key).
K-x
is the private key matching Kx.
nx
is a nonce created by or for x. A nonce is a random number used only once, to guarantee that a response matches a particular request.
request(W)
is a request for the public key of W.
t
is a timestamp.

Assumptions:

  1. W trusts A and A', i.e., it believes credentials signed by them (that is, credentials encrypted with their private keys).
  2. W and U know Ka and Kc. To bootstrap the system, there must be at least one trusted credential authority and authentication server, whose public keys are known to all.
  3. Before any message is encrypted, a checksum is added to it, so that the decryptor will be able to tell if the msg has been altered in transit. The checksums are not written out explicitly in the protocol.
  4. Public key cryptography works like this (eg RSA):
    {{ m }_K }_K-1 = m
    {{ m }_K-1 }_K = m
  5. Symmetric cryptography works like this: let n be a random number with as many bits as m. Then {m}_n = (n xor m), and {{m}_n}_n = m. Symmetric cryptography is not secure unless n is as long as m, so it cannot be used with long messages. Also, it may be hard for both sender and recipient to know what the key is without others knowing and compromising security. However, symmetric encryption (eg DES) is faster and cheaper than public key encryption, so we should use it on the rare occasions we can.
  6. Public keys can be handed out freely to anyone who asks for them. (If this assumption is false, a different protocol must be used for Part 1.)

Protocols with explanations:

PART 1: PROTOCOL TO GET PUBLIC KEY FOR W.

  1. U -> C : { request(W), nc }_Kc
    -
    Encrypting the message with the well known public key Kc of the certification authority C allows privacy in requesting a public key. This may or may not be needed depending on the application.
  2. C gets the message and:
    -
    decrypts with K-c to get request(W) and nc
    -
    C looks up the public key of W in its database of public keys. These entries may or may not be encrypted with C's public key. Encryption of these entries would hide the existence of W as a server and could also be used to prevent just anyone from getting W's public key (although we don't do that in this protocol). The entries in the table look something like {x, Kx}_K-a, meaning that A certifies that X's public key is Kx. These certificates cannot be forged since only A knows A's private key and (thank heavens) A is off line and so can't be attacked by hostile parties.

    If C gets compromised, C can't forge certificates. It could like and say that it doesn't have any, or give out outdated ones, but these behaviors are really minor mischief.

    If A were on line and were successfully attacked, then A might give out false public keys which could allow messages to W to be intercepted, read, and answered falsely.

  3. C responds with:
    C -> U: { {W, Kw}_K-a }_nc

    U decrypts the message with nc and then again with Ka, to find that W's public key is Kw.

    U knows this message is a "fresh" response to its request (not a replayed response from some hostile attacker) because it's encrypted with nc, and only C could have known about nc.

    No one but U can read the response, because it is encrypted with nc. Thus U's request is private, and so is the response.

    Finally, C can't have sent a false certificate because the certificate is still signed by A. (If public keys change regularly, it would be good to include an expiration date inside the certificate.)

PART 2: U GETS A DOCUMENT FROM W.

  1. U -> W:
    {A, {U, Ku}_K-a, {request, t, {cert}_K-a', ..., nu, Ks }_K-u }_Kw
    

    request is the request U is making to W.

  2. W gets the message and:
    Decrypts the entire message with K-w (its own private key). This means that only W can read the request, thus giving U privacy.

    To read most of the rest of the message, W needs U's public key, which has thoughtfully been included in the message: {U, Ku}_K-a means that A certifies that Ku is the public key for U. If this were not included, W would have to ask C for U's public key, which would waste W's time since U already knows what it is.

    Since W might trust several authorities, the message includes A as well as {U, Ku}_K-a, so that W knows what public key to use (Ka) to decrypt the certificate and get U's public key. It would not be good enough to include just {Ku}_K-a, since we need to know *who* has public key Ku (otherwise the message might really be from V instead of U). Similarly, it would not be good enough to include just (U and) Ku, since the message might really be from V pretending to be U.

    In sum, W knows A's public key Ka and decrypts {U, Ku}_K-a to find that U's public key is Ku.

    Next W decrypts {request,t,{cert}_K-a', ..., n_u, Ks }_K-u using Ku. If the decryption is successful ("successful decryption" means that the checksum matches the message contents) then W knows sender is in possession of U's private key and therefore W believes the message really is from U.

    t, the timestamp, helps alleviate malicious hogging of system resources. To wit: perhaps an attacker keeps a copy of U's message to W, and replays it over and over to hog W and drive U crazy. Once the current time is sufficiently past t, W can ignore any requests containing t without feeling guilty.

    nu prevents problems from malicious replay of responses. In other words, suppose U's request is intercepted by V (who can't read it) and V sends back a fabricated response. To look realistic, the response will have to be signed by W, i.e., encrypted with W's private key. Thus the fabricated response will have to be an old response of W's. But by including a new random number nu in every request, which W is to include in the response, U can tell whether a response is a bona fide response or a replay of an old response (saying, e.g., that the requested document does not exist).

    Ks is called a "session key." U wants W to encrypt the response with Ks. Only U knows K-s so only U will be able to read the response. (One could achieve the same effect by encrypting the response with Ku, but there are other advantages to using Ks.) U created both Ks and K-s just for the current interaction with W (or perhaps for a bit longer if U is feeling reckless).

    {cert}_K-a', ... are other certificates that W requires before it will grant a request, authenticating things other than U's public key, generated by servers A' that W trusts and that (I hope) are not available on the net for attack. For example, W might insist on a certificate showing that U is an undergrad, in order to receive a certain service.

  3. W responds to U's request:
    W -> U : { response, nu }_Ks
    

    U decrypts the message with K-s, which only it knows, giving privacy for the response. U verifies that nu is included, so that the response is not a replay. Since only W and U know Ks, the response must really have come from W.

    We do not need Ks when only two messages (one exchange of messages) take place. In that case use the following:

    W -> U: {{ response, nu}_K-w}_Ku
    
  4. If U wants to send another message to W in the same session, the following format should be used for its requests:
    U -> W: {request, t, nu'}_K-s
    

    W knows that the message must come from U, since only U knows K-s. W decrypts the message with Ks.

    We assume that W knows *which* Ks goes with the message because W remembers which Ks is associated with the socket on which the message was received. Thus any message coming in on that socket gets decrypted with Ks. If the decryption is unsuccessful then the message was garbled in transit or is from an impostor.

    The timestamp t is included to help with malicious replay of requests. A new nonce nu' is included so that U will know that the response is "fresh."

    The advantage of using Ks is that the heavy overhead of creating and processing the first message from U to W is avoided in future exchanges. The drawback of using Ks is that if K-s is compromised during the session, the attacker can talk to W using K-s. To help with this, W can use a timeout (explicit or implicit) with Ks, so that U must present its credentials again after a while.

    W's second and subsequent responses in a session all look just like its first response:
    W -> U: {response, n_u'}_Ks