Warmup question: - Suppose you're allowed to buy two kinds of network connections: insecure Ethernet, and "secure" direct lines. Ethernet links can be used to build any-to-any networks, but "secure" wires can only go between two endpoints (think of a telephone wire). - You want to support the goals of Kerberos. How to most effectively do so? - Simple answer: Buy n^2 secure wires between all pairs of hosts. Problem: very expensive. - Better answer: Establish a central trusted host, A. Buy n secure wires and n insecure wires: one secure wire from every host to A, and one insecure Ethernet connection for every host. If client C wants to talk to server S, client C should contact A, ask A to create a session key k for communication with S, then A tells k to C and S (all this is done over the secure wires); finally, C and S can set up a secure connection over the insecure Ethernet by using the crypto key k. Followup question: - Suppose C and S share a session key k, and want to establish a secure connection. - C constructs an "authenticator" using k, sends it to S: C->S: {C,address_C}_k S constructs another "authenticator", returns it to C: S->C: {S,address_S}_k Once C and S confirm each other's identity, they continue the TCP connection. - Q: Is this secure? - A: replay attacks, reflection attacks, TCP hijacking, etc. Crypto notation: {x}_k x encrypted under key k [x]_k x protected by a MAC under key k [{x}]_k x protected by both encryption and a MAC using shared key k (x,y) concatenation of x and y Caveats: - Encryption might not protect integrity - Early protocol papers get this wrong; they often implicitly assumed that encryption provides integrity (wrong in practice), and so never made a distinction between secrecy and integrity - You'll rarely see [x]_k or [{x}]_k in the literature; that's my own preferred notation, though - Concatenation should be done so that x and y can be uniquely, unambiguously recovered from (x,y) - For [{x}]_k, we should use separate keys for encryption and MAC; e.g., let (k0,k1) = hash(k), then [{x}]_k = {x}_k1, [{x}_k1]_k2 Setting up a secure channel: - What we'd like: If C wants to send message M to S, a magic fairy carries M to S in a way that noone else can see the message, C knows that the message will always reach S (and only S), and S knows that the message came from C (and only C). Repeated messages will be delivered reliably and in-order. Sadly, magic fairies don't exist... - Given a shared key K_{C,S}, we can set up the abstraction of a reliable, unordered, tamperproof, confidential communication channel between C and S using crypto +-+ +-+ |C|<------>|S| +-+ +-+ - when C wants to send a message M over this abstract channel, we actually send something like C->S: [{M}]_{K_{C,S}} where [{...}]_k denotes encryption + MAC under key k - Q: why doesn't the above quite work? A: messages can be replayed A: messages can be deleted A: direction is not clear (reflection attacks) - fix: C->S: [{C sends "M" to S with seq # s}]_... where s is a non-repeating seq # (recipient must check that they are in-order and none are missed!) S->C: [{C sends "M" to S at time t}]_... where t is a timestamp (recipient must check that timestamp is within some window around the current time; and, must cache old messages within that window and avoid repeats of a ciphertext) - note: seq #'s are stateful; timestamping is stateless - Q: btw, the above is still not quite perfect; why? A: still possible to truncate a communication stream ("an undetected DoS") fixes are possible (have recipient ACK receipt; then DoS can be detected, if not prevented), but not always necessary - Lesson: If you have a shared key, you can set up a secure channel Motivation for session keys: - Though a single shared key suffices for communication, might not want to use just one - Forward secrecy: Compromise of just one key shouldn't reveal too much - Multiplexing: Logically, we often have multiple different connections, and we will want to avoid getting confused about which message goes with which connection - A common solution: Use a session key - Used just for a single session - Time-limited, then deleted; prevents after-the-fact compromise (rough quote from the Walker trial: Judge: "Why would anyone care about a used key?" Witness: "But, Judge, a used key is the most valuable kind.") How to establish a session key: - Assume we've got a secure channel; how can we establish a fresh session key? - C picks k, and sends C->S: "Let's use k for a session between C<->S with lifetime T_0 .. T_exp" (over the secure channel) - Then use k to establish a new secure channel - Property: At most C,S now have access to the new secure channel - If confirmation is required (so that we know that both C and S do indeed have access to the new secure channel), both parties can send an ACK over the newly established channel - challenge-response: C->S: ""Show me you're listening by repeating N"" (N is a nonce) S->C: ""Ok; I heard you send N"" - time-stamping S->C: ""I can send messages at time T"" (all messages above over the new secure channel) - Note: Once you've got a secure channel, it doesn't matter how the underlying encrypted content makes its way to your communicant - Lesson: If you've got a secure channel, you can establish a fresh session key Generalizing to n>2 entities: - A long-term key K_{C,S} for each pair (C,S) of entities (O(n^2) keys) - C knows K_{C,S} for each S (he knows O(n) keys) - Problem: Doesn't scale too well; lots of keys Better scaling, with a central trusted third party: - A long-term key K_{A,e} for each entity e - e knows 1 key, K_{A,e} - A knows n keys, i.e., all the K_{A,e}'s +-+ /|A|\ / +-+ \ +-+/ \+-+ |C|<------->|S| +-+ +-+ - Note that C (resp. S) can use K_{A,C} (K_{A,S}) to establish a secure channel with A - If C and S trust A to pick a session key for them, they can use their secure channels to tell them what key to use to talk to each other - e.g., C->A: "I, C, want a session with S; dated, T_0" A->C: "As requested by C at T_0, k is good for C<->S for T_0 .. T_exp" A->S: "As requested by C at T_0, k is good for C<->S for T_0 .. T_exp" (all messages over secure channel C<->A or S<->A) - C and S can now use k to establish a private secure channel C<->S - They should probably confirm - Note: much better scaling Optimizing the above: - Triangle routing: the A->S message can be routed through C C->A: "I want a session..." A->C: "Here's k...; also, please send the following to S: ``Here's k...''" C->S: S says ``Here's k''; also, `I can send messages...' S->C: `I can send messages too...' where "" = secure channel for A<->C ``'' = secure channel for A<->S `' = new, fresh secure channel for C<->S - Bi-level authentication: - Use A to get a secure channel to a ticket-granting-service TGS - Use TGS to get a secure channel to other services -- Why? (So only TGS needs to know about the list of all servers; a level of indirection; and for key management -- you get a master ticket which lets you get access to more tickets for specific servers) - By the way, this is usually written in some abstract mathematical notation that leaves out all the words and hopes you'll be able to infer the intent - Q: Why? (A: performance optimization? shorter descriptions? I dunno) - You can probably tell what I think of this idea - Combine all the above, and you get Kerberos - Notice the power of the abstraction of a "secure channel"; Kerberos designers apparently worked without this abstraction, and their task was made significantly harder as a consequence (witness the reading for today) Testing your understanding: -- Q: what's in the TCB? A: AS, TGS, timeserver, end hosts Experience with Kerberos: - The real Kerberos actually gives you just an authenticated channel by default; encryption is optional. - Q: why? - A: performance optimization - Kerberos shows its age here; today encryption is almost free and doesn't hurt, so you'd better have a really good reason to omit the encryption - Single point of failure: if A goes down, whole network is unavailable - Q: how do you fix it? A: replication - Q: how do you ensure consistency? A: everything is stateless - Timeouts were an extremely good idea -- Ticket only good for (say) 12 hours -- Limits impact of a potential host compromise -- Revocation -- Easy: just go disable the account at the AS (and potentially TGS) -- If the user has an outstanding ticket, you may have to wait 12 hours; otherwise, that's it -- But note that cost of setup is high -- Comparison to public-key crypto -- Pubkey allows for same O(n) scaling without global trust in the server -- except that it is trusted not to impersonate folks, but otherwise it can't read encrypted traffic. Why is the distinction important? (Maybe there's not much difference if the server is malicious, but there's a big difference if the server is temporarily hacked.) -- However, revocation is much harder with pubkey stuff -- This suggests a rule of thumb: (cost of setup) + (cost of revocation) = constant -- Threat model -- What can the AS+TGS do if they are compromised for a few days? (Defeat authentication for those few days; decrypt all past traffic (and possibly future traffic, if keys aren't changed)) -- But that sucks! -- Q: So why did they use a single trusted central server, anyway? (A: The n^2 scaling problem; public-key stuff not really mature yet; revocation; because authentication, not confidentiality, was primary original focus; and because it was designed for scenarios where all hosts are in the same administrative domain and thus have to trust some central set of machines anyway) -- Kerberos is missing forward secrecy and backward secrecy... Implementation: -- Servers check IP address, so it's not enough to break Kerberos, you also have to do IP spoofing. Seems like a useful property (and ensures that Kerberos can't make things any *worse*, no matter what happens). -- But bad interactions with multi-homing. -- Gets MACs wrong. Uses encrypted checksum, a big no-no. And guess what -- there are attacks. Fix: Use a MAC. -- Dictionary attacks. If you can guess K_{C<->AS} (e.g. via a wordlist), you can test your guesses offline and eliminate wrong ones. Don't even need to intercept an encrypted {T_{C<->TGS}}_{K_{C<->AS}}, why? (Can just ask the AS nicely for it.) -- But, you need to know a username and realm name. However, there was a bug ("buffer underflow") that discloses usernames: you send it a malformed packet, and it sends back a packet containing an error message. However, the packet contains left-over data from some unsanitized data structures; and that contains the name of the last user to request a TGT as well as the realm. -- Time stamp woes. -- If you can hack NTP, what breaks? (Can replay old authenticators, if can move S's clock back. If can move AS's and TGS's and C's clock forward, also can do attacks.) -- Replays. Endpoints usually accept a clock skew of up to 5 minutes; but this has security implications. Original paper suggested that there was no chance of replay within 5 minutes -- clearly bogus. (Why?) What's the right thing to do? (Store a copy of all the "authenticators" you got in the past 5 minutes, check that list for replays, time out entries after 5 minutes. This was actually suggested but apparently not implemented.) But this "windowing" idea is a pretty general technique; you'll see it again in e.g. IP security. -- Not easy to do secure network time service... -- Simple fix? (Challenge-response.) But challenge-response involves one more roundtrip; timestamps were used for performance fine-tuning. This is a pretty general trade-off. In general, I think the tendency today is to go for the challenge-response protocols, since then you don't need to trust any timeservers. -- Key generation: used bad randomness (getpid(), time of day). Was totally breakable in K4. A failure of both software engineering and social processes... -- And other attacks found by a student as part of a class project the last time we gave this class. (By the way, she got her paper published! I have confidence that you can do the same.) -- Summary -- K5 is in NT and Win2000. Is used in Wall Street. Etc. -- Sort of the ancient dinosaur we all view with respect, awe, and a little bit of fear. -- For reference: the Protocol -- N = nonce (random challenge) -- T_{C<->S} = (K_{C<->S}, C, S, ip address, timestamp, lifetime, N) -- The basic protocol: 1. C->AS: C, S, expiration date, N 2. AS->C: {T_{C<->S}}_{K_{C<->AS}}, {T_{C<->S}}_{K_{S<->AS}} 3. C->S: {C,S,timestamp}_{K_{C<->S}}, {T_{C<->S}}_{K_{S<->AS}} 4. S->C: {C,S,timestamp+1}_{K_{C<->S}} (optional) At the end, S and C share a good key k. S knows it is speaking to C. If they did msg 4, C knows it is talking to S. -- The complete protocol: A. Do 1--4 above to get a key for C<->TGS from AS. B. Do 1--4 above to get a key for C<->S from TGS. (Note: can piggyback A3 on B1 and A4 on B2.) -- T_{C<->TGS} is known as a TGT (ticket granting ticket). -- Note: K_{C<->AS} is your password. -- {C,S,timestamp}_{K_{C<->S}} also called an "authenticator". -- If you leave out "authenticator", what breaks? (Replays.) -- If you leave out the name of the client in the ticket, what breaks? (Client can request a ticket from TGS under one name C, then can give it to server under another name C'.) -- If you leave out the name of the server in the ticket, what breaks? (Answer: man in the middle attack, where client asks TGS for ticket for server S, but M modifies changes the S to a S', and client gets back a ticket for S'; then when client sends the ticket to S, M redirects it to S'; and the client thinks he is speaking to S even though he is really talking to S'.) -- If a key K_{C<->S} ever gets compromised, what happens? (You can replay it, if you managed to the handshake messages; and you can create new authenticators, because you know the key they are encrypted under.) Extra stuff from last time: -- Chosen-plaintext attacks. -- Same key used for both authentication and message encryption, and for multiple sessions; if you can get a message of your choosing encrypted, you can spoof future sessions. -- How would you do this on a PDA? (Split proxy.) How do you implement the split proxy? (Charon.) -- K_{C<->AS} (your password) and K_{C<->TGS} live on the PDA; session keys and service tickets T_{C<->S} live on the proxy. -- How does this help? (Less trust in proxy.) How about giving K_{C<->TGS} to the proxy? (More trust in proxy.) The way it currently is, proxy just has to be at least as secure as the server S you're using. -- Protocol: 1. Client gets a TGT from AS; the proxy just copies bits. 2. Client gets a secure connection with proxy, by making proxy a kerberized service. Remaining messages go over this link. 3. To access a server S, client talks to the TGS, gets a ticket and a session key, then gives the session key and ticket T_{C<->S} to the proxy (over the secure link established in 2), and the proxy can access the service on the client's behalf. -- Cross-realm issues -- Realm = set of hosts under one AS/TGS -- How do you decide which hosts to put in which realms? (By administrative domain, usually, because all clients + servers have to trust the AS+TGS) -- But then what if you want to connect to a host in another realm? -- Can ensure that all realm-pairs share a set of keys (K4), and set up "roaming agreements"; but this has n^2 scaling -- K5: multi-hop authentication, + hierarchical structure on realms. Disadvantages? (Global trust in the root, inflexibility of hierarchical structure, transitive trust. This one sucks: it has all the disadvantages of the worst of hierarchical and peer-to-peer models, and none of the advantages. Yuck.)