< prev index next >

src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java

Print this page

        

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -24,121 +24,323 @@
  */
 
 package sun.security.ssl;
 
 import java.io.IOException;
-import javax.net.ssl.SSLProtocolException;
 import java.security.MessageDigest;
 import java.security.SecureRandom;
+import java.util.Arrays;
+import static sun.security.ssl.ClientHello.ClientHelloMessage;
 
-import sun.security.ssl.HandshakeMessage.ClientHello;
-
-/*
- * HelloVerifyRequest cookie manager
+/**
+ *  (D)TLS handshake cookie manager
  */
-final class HelloCookieManager {
-    // the cookie secret life time
-    private static long COOKIE_TIMING_WINDOW = 3600000;     // in milliseconds
-    private static int  COOKIE_MAX_LENGTH_DTLS10 = 32;      // 32 bytes
-    private static int  COOKIE_MAX_LENGTH_DTLS12 = 0xFF;    // 2^8 -1 bytes
-
-    private final SecureRandom          secureRandom;
-    private final MessageDigest         cookieDigest;
+class HelloCookieManager {
+    final SecureRandom secureRandom;
 
-    private int                         cookieVersion;      // allow to wrap
-    private long                        secretLifetime;
-    private byte[]                      cookieSecret;
-
-    private int                         prevCookieVersion;
-    private byte[]                      prevCookieSecret;
+    private volatile D10HelloCookieManager d10HelloCookieManager;
+    private volatile D13HelloCookieManager d13HelloCookieManager;
+    private volatile T13HelloCookieManager t13HelloCookieManager;
 
     HelloCookieManager(SecureRandom secureRandom) {
         this.secureRandom = secureRandom;
-        this.cookieDigest = JsseJce.getMessageDigest("SHA-256");
+    }
 
-        this.cookieVersion = secureRandom.nextInt();
-        this.secretLifetime = 0;
-        this.cookieSecret = null;
+    HelloCookieManager valueOf(ProtocolVersion protocolVersion) {
+        if (protocolVersion.isDTLS) {
+            if (protocolVersion.useTLS13PlusSpec()) {
+                if (d13HelloCookieManager != null) {
+                    return d13HelloCookieManager;
+                }
 
-        this.prevCookieVersion = 0;
-        this.prevCookieSecret = null;
+                synchronized (this) {
+                    if (d13HelloCookieManager == null) {
+                        d13HelloCookieManager =
+                                new D13HelloCookieManager(secureRandom);
+                    }
     }
 
-    // Used by server side to generate cookies in HelloVerifyRequest message.
-    synchronized byte[] getCookie(ClientHello clientHelloMsg) {
-        if (secretLifetime < System.currentTimeMillis()) {
-            if (cookieSecret != null) {
-                prevCookieVersion = cookieVersion;
-                prevCookieSecret = cookieSecret.clone();
+                return d13HelloCookieManager;
             } else {
-                cookieSecret = new byte[32];
+                if (d10HelloCookieManager != null) {
+                    return d10HelloCookieManager;
             }
 
-            cookieVersion++;
+                synchronized (this) {
+                    if (d10HelloCookieManager == null) {
+                        d10HelloCookieManager =
+                                new D10HelloCookieManager(secureRandom);
+                    }
+                }
+
+                return d10HelloCookieManager;
+            }
+        } else {
+            if (protocolVersion.useTLS13PlusSpec()) {
+                if (t13HelloCookieManager != null) {
+                    return t13HelloCookieManager;
+                }
+
+                synchronized (this) {
+                    if (t13HelloCookieManager == null) {
+                        t13HelloCookieManager =
+                                new T13HelloCookieManager(secureRandom);
+                    }
+                }
+
+                return t13HelloCookieManager;
+            }
+        }
+
+        return null;
+    }
+
+    byte[] createCookie(ConnectionContext context,
+                ClientHelloMessage clientHello) throws IOException {
+        throw new UnsupportedOperationException(
+                "Not yet supported handshake cookie manager");
+    }
+
+    boolean isCookieValid(ConnectionContext context,
+            ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+        throw new UnsupportedOperationException(
+                "Not yet supported handshake cookie manager");
+    }
+
+    // DTLS 1.0/1.2
+    private static final
+            class D10HelloCookieManager extends HelloCookieManager {
+        private int         cookieVersion;  // allow to wrap, version + sequence
+        private byte[]      cookieSecret;
+        private byte[]      legacySecret;
+
+        D10HelloCookieManager(SecureRandom secureRandom) {
+            super(secureRandom);
+
+            this.cookieVersion = secureRandom.nextInt();
+            this.cookieSecret = new byte[32];
+            this.legacySecret = new byte[32];
+
             secureRandom.nextBytes(cookieSecret);
-            secretLifetime = System.currentTimeMillis() + COOKIE_TIMING_WINDOW;
+            System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
         }
 
-        clientHelloMsg.updateHelloCookie(cookieDigest);
-        byte[] cookie = cookieDigest.digest(cookieSecret);      // 32 bytes
-        cookie[0] = (byte)((cookieVersion >> 24) & 0xFF);
-        cookie[1] = (byte)((cookieVersion >> 16) & 0xFF);
-        cookie[2] = (byte)((cookieVersion >> 8) & 0xFF);
-        cookie[3] = (byte)(cookieVersion & 0xFF);
+        @Override
+        byte[] createCookie(ConnectionContext context,
+                ClientHelloMessage clientHello) throws IOException {
+            int version;
+            byte[] secret;
 
-        return cookie;
+            synchronized (this) {
+                version = cookieVersion;
+                secret = cookieSecret;
+
+                // the cookie secret usage limit is 2^24
+                if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
+                    System.arraycopy(cookieSecret, 0, legacySecret, 0, 32);
+                    secureRandom.nextBytes(cookieSecret);
+                }
+
+                cookieVersion++;
     }
 
-    // Used by server side to check the cookie in ClientHello message.
-    synchronized boolean isValid(ClientHello clientHelloMsg) {
-        byte[] cookie = clientHelloMsg.cookie;
+            MessageDigest md = JsseJce.getMessageDigest("SHA-256");
+            byte[] helloBytes = clientHello.getHelloCookieBytes();
+            md.update(helloBytes);
+            byte[] cookie = md.digest(secret);      // 32 bytes
+            cookie[0] = (byte)((version >> 24) & 0xFF);
 
+            return cookie;
+        }
+
+        @Override
+        boolean isCookieValid(ConnectionContext context,
+            ClientHelloMessage clientHello, byte[] cookie) throws IOException {
         // no cookie exchange or not a valid cookie length
         if ((cookie == null) || (cookie.length != 32)) {
             return false;
         }
 
-        int version = ((cookie[0] & 0xFF) << 24) |
-                      ((cookie[1] & 0xFF) << 16) |
-                      ((cookie[2] & 0xFF) << 8) |
-                       (cookie[3] & 0xFF);
-
         byte[] secret;
-        if (version == cookieVersion) {
+            synchronized (this) {
+                if (((cookieVersion >> 24) & 0xFF) == cookie[0]) {
             secret = cookieSecret;
-        } else if (version == prevCookieVersion) {
-            secret = prevCookieSecret;
         } else {
-            return false;       // may be out of the timing window
+                    secret = legacySecret;  // including out of window cookies
+                }
         }
 
-        clientHelloMsg.updateHelloCookie(cookieDigest);
-        byte[] target = cookieDigest.digest(secret);            // 32 bytes
+            MessageDigest md = JsseJce.getMessageDigest("SHA-256");
+            byte[] helloBytes = clientHello.getHelloCookieBytes();
+            md.update(helloBytes);
+            byte[] target = md.digest(secret);      // 32 bytes
+            target[0] = cookie[0];
 
-        for (int i = 4; i < 32; i++) {
-            if (cookie[i] != target[i]) {
-                return false;
+            return Arrays.equals(target, cookie);
             }
         }
 
-        return true;
+    private static final
+            class D13HelloCookieManager extends HelloCookieManager {
+        D13HelloCookieManager(SecureRandom secureRandom) {
+            super(secureRandom);
+        }
+
+        @Override
+        byte[] createCookie(ConnectionContext context,
+                ClientHelloMessage clientHello) throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        @Override
+        boolean isCookieValid(ConnectionContext context,
+            ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+    }
+
+    private static final
+            class T13HelloCookieManager extends HelloCookieManager {
+        private int             cookieVersion;      // version + sequence
+        private final byte[]    cookieSecret;
+        private final byte[]    legacySecret;
+
+        T13HelloCookieManager(SecureRandom secureRandom) {
+            super(secureRandom);
+
+            this.cookieVersion = secureRandom.nextInt();
+            this.cookieSecret = new byte[64];
+            this.legacySecret = new byte[64];
+
+            secureRandom.nextBytes(cookieSecret);
+            System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
+        }
+
+        @Override
+        byte[] createCookie(ConnectionContext context,
+                ClientHelloMessage clientHello) throws IOException {
+            int version;
+            byte[] secret;
+
+            synchronized (this) {
+                version = cookieVersion;
+                secret = cookieSecret;
+
+                // the cookie secret usage limit is 2^24
+                if ((cookieVersion & 0xFFFFFF) == 0) {  // reset the secret
+                    System.arraycopy(cookieSecret, 0, legacySecret, 0, 64);
+                    secureRandom.nextBytes(cookieSecret);
+                }
+
+                cookieVersion++;        // allow wrapped version number
+            }
+
+            // happens in server side only
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            MessageDigest md = JsseJce.getMessageDigest(
+                    shc.negotiatedCipherSuite.hashAlg.name);
+            byte[] headerBytes = clientHello.getHeaderBytes();
+            md.update(headerBytes);
+            byte[] headerCookie = md.digest(secret);
+
+            // hash of ClientHello handshake message
+            shc.handshakeHash.update();
+            byte[] clientHelloHash = shc.handshakeHash.digest();
+
+            // version and cipher suite
+            //
+            // Store the negotiated cipher suite in the cookie as well.
+            // cookie[0]/[1]: cipher suite
+            // cookie[2]: cookie version
+            // + (hash length): Mac(ClientHello header)
+            // + (hash length): Hash(ClientHello)
+            byte[] prefix = new byte[] {
+                    (byte)((shc.negotiatedCipherSuite.id >> 8) & 0xFF),
+                    (byte)(shc.negotiatedCipherSuite.id & 0xFF),
+                    (byte)((version >> 24) & 0xFF)
+                };
+
+            byte[] cookie = Arrays.copyOf(prefix,
+                prefix.length + headerCookie.length + clientHelloHash.length);
+            System.arraycopy(headerCookie, 0, cookie,
+                prefix.length, headerCookie.length);
+            System.arraycopy(clientHelloHash, 0, cookie,
+                prefix.length + headerCookie.length, clientHelloHash.length);
+
+            return cookie;
+        }
+
+        @Override
+        boolean isCookieValid(ConnectionContext context,
+            ClientHelloMessage clientHello, byte[] cookie) throws IOException {
+            // no cookie exchange or not a valid cookie length
+            if ((cookie == null) || (cookie.length <= 32)) {    // 32: roughly
+                return false;
+            }
+
+            int csId = ((cookie[0] & 0xFF) << 8) | (cookie[1] & 0xFF);
+            CipherSuite cs = CipherSuite.valueOf(csId);
+            if (cs == null || cs.hashAlg == null || cs.hashAlg.hashLength == 0) {
+                return false;
     }
 
-    // Used by client side to check the cookie in HelloVerifyRequest message.
-    static void checkCookie(ProtocolVersion protocolVersion,
-            byte[] cookie) throws IOException {
-        if (cookie != null && cookie.length != 0) {
-            int limit = COOKIE_MAX_LENGTH_DTLS12;
-            if (protocolVersion.v == ProtocolVersion.DTLS10.v) {
-                limit = COOKIE_MAX_LENGTH_DTLS10;
+            int hashLen = cs.hashAlg.hashLength;
+            if (cookie.length != (3 + hashLen * 2)) {
+                return false;
             }
 
-            if (cookie.length > COOKIE_MAX_LENGTH_DTLS10) {
-                throw new SSLProtocolException(
-                        "Invalid HelloVerifyRequest.cookie (length = " +
-                         cookie.length + " bytes)");
+            byte[] prevHeadCookie =
+                    Arrays.copyOfRange(cookie, 3, 3 + hashLen);
+            byte[] prevClientHelloHash =
+                    Arrays.copyOfRange(cookie, 3 + hashLen, cookie.length);
+
+            byte[] secret;
+            synchronized (this) {
+                if ((byte)((cookieVersion >> 24) & 0xFF) == cookie[2]) {
+                    secret = cookieSecret;
+                } else {
+                    secret = legacySecret;  // including out of window cookies
             }
         }
 
-        // Otherwise, no cookie exchange.
+            // happens in server side only
+            ServerHandshakeContext shc = (ServerHandshakeContext)context;
+
+            MessageDigest md = JsseJce.getMessageDigest(cs.hashAlg.name);
+            byte[] headerBytes = clientHello.getHeaderBytes();
+            md.update(headerBytes);
+            byte[] headerCookie = md.digest(secret);
+
+            if (!Arrays.equals(headerCookie, prevHeadCookie)) {
+                return false;
+            }
+
+            // Use the ClientHello hash in the cookie for transtript
+            // hash calculation for stateless HelloRetryRequest.
+            //
+            // Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) =
+            //   Hash(message_hash ||    /* Handshake type */
+            //     00 00 Hash.length ||  /* Handshake message length (bytes) */
+            //     Hash(ClientHello1) || /* Hash of ClientHello1 */
+            //     HelloRetryRequest || ... || Mn)
+
+            // Reproduce HelloRetryRequest handshake message
+            byte[] hrrMessage =
+                    ServerHello.hrrReproducer.produce(context, clientHello);
+            shc.handshakeHash.push(hrrMessage);
+
+            // Construct the 1st ClientHello message for transcript hash
+            byte[] hashedClientHello = new byte[4 + hashLen];
+            hashedClientHello[0] = SSLHandshake.MESSAGE_HASH.id;
+            hashedClientHello[1] = (byte)0x00;
+            hashedClientHello[2] = (byte)0x00;
+            hashedClientHello[3] = (byte)(hashLen & 0xFF);
+            System.arraycopy(prevClientHelloHash, 0,
+                    hashedClientHello, 4, hashLen);
+
+            shc.handshakeHash.push(hashedClientHello);
+
+            return true;
+        }
     }
 }
< prev index next >