Test that ALPN is preferred over NPN.

Change-Id: Ia9d10f672c8a83f507b46f75869b7c00fe1a4fda
Reviewed-on: https://boringssl-review.googlesource.com/1755
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index acaa89c..7dbc1f0 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -167,6 +167,7 @@
 	CipherSuite                uint16                // cipher suite in use (TLS_RSA_WITH_RC4_128_SHA, ...)
 	NegotiatedProtocol         string                // negotiated next protocol (from Config.NextProtos)
 	NegotiatedProtocolIsMutual bool                  // negotiated protocol was advertised by server
+	NegotiatedProtocolFromALPN bool                  // protocol negotiated with ALPN
 	ServerName                 string                // server name requested by client, if any (server side only)
 	PeerCertificates           []*x509.Certificate   // certificate chain presented by remote peer
 	VerifiedChains             [][]*x509.Certificate // verified chains built from PeerCertificates
@@ -454,6 +455,11 @@
 	// ExpectServerName, if not empty, is the hostname the client
 	// must specify in the server_name extension.
 	ExpectServerName string
+
+	// SwapNPNAndALPN switches the relative order between NPN and
+	// ALPN on the server. This is to test that server preference
+	// of ALPN works regardless of their relative order.
+	SwapNPNAndALPN bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index 47b6e61..9f0c328 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -47,6 +47,7 @@
 
 	clientProtocol         string
 	clientProtocolFallback bool
+	usedALPN               bool
 
 	channelID *ecdsa.PublicKey
 
@@ -1105,6 +1106,7 @@
 		state.NegotiatedProtocol = c.clientProtocol
 		state.DidResume = c.didResume
 		state.NegotiatedProtocolIsMutual = !c.clientProtocolFallback
+		state.NegotiatedProtocolFromALPN = c.usedALPN
 		state.CipherSuite = c.cipherSuite
 		state.PeerCertificates = c.peerCertificates
 		state.VerifiedChains = c.verifiedChains
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index d61c6d2..d78e767 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -69,6 +69,7 @@
 		alpnProtocols:       c.config.NextProtos,
 		duplicateExtension:  c.config.Bugs.DuplicateExtension,
 		channelIDSupported:  c.config.ChannelID != nil,
+		npnLast:             c.config.Bugs.SwapNPNAndALPN,
 	}
 
 	if c.config.Bugs.SendClientVersion != 0 {
@@ -601,6 +602,7 @@
 	if serverHasALPN {
 		c.clientProtocol = hs.serverHello.alpnProtocol
 		c.clientProtocolFallback = false
+		c.usedALPN = true
 	}
 
 	if !hs.hello.channelIDSupported && hs.serverHello.channelIDRequested {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index f0a1493..136360d 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -27,6 +27,7 @@
 	alpnProtocols       []string
 	duplicateExtension  bool
 	channelIDSupported  bool
+	npnLast             bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -54,7 +55,8 @@
 		m.secureRenegotiation == m1.secureRenegotiation &&
 		eqStrings(m.alpnProtocols, m1.alpnProtocols) &&
 		m.duplicateExtension == m1.duplicateExtension &&
-		m.channelIDSupported == m1.channelIDSupported
+		m.channelIDSupported == m1.channelIDSupported &&
+		m.npnLast == m1.npnLast
 }
 
 func (m *clientHelloMsg) marshal() []byte {
@@ -160,7 +162,7 @@
 		z[1] = 0xff
 		z = z[4:]
 	}
-	if m.nextProtoNeg {
+	if m.nextProtoNeg && !m.npnLast {
 		z[0] = byte(extensionNextProtoNeg >> 8)
 		z[1] = byte(extensionNextProtoNeg & 0xff)
 		// The length is always 0
@@ -305,6 +307,12 @@
 		z[1] = byte(extensionChannelID & 0xff)
 		z = z[4:]
 	}
+	if m.nextProtoNeg && m.npnLast {
+		z[0] = byte(extensionNextProtoNeg >> 8)
+		z[1] = byte(extensionNextProtoNeg & 0xff)
+		// The length is always 0
+		z = z[4:]
+	}
 	if m.duplicateExtension {
 		// Add a duplicate bogus extension at the beginning and end.
 		z[0] = 0xff
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index 7f6b521..45e300d 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -225,6 +225,7 @@
 		if selectedProto, fallback := mutualProtocol(hs.clientHello.alpnProtocols, c.config.NextProtos); !fallback {
 			hs.hello.alpnProtocol = selectedProto
 			c.clientProtocol = selectedProto
+			c.usedALPN = true
 		}
 	} else {
 		// Although sending an empty NPN extension is reasonable, Firefox has
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 25e7626..7a87b91 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -97,6 +97,11 @@
 	dtls
 )
 
+const (
+	alpn = 1
+	npn  = 2
+)
+
 type testCase struct {
 	testType      testType
 	protocol      protocol
@@ -116,6 +121,9 @@
 	// expectedNextProto controls whether the connection should
 	// negotiate a next protocol via NPN or ALPN.
 	expectedNextProto string
+	// expectedNextProtoType, if non-zero, is the expected next
+	// protocol negotiation mechanism.
+	expectedNextProtoType int
 	// messageLen is the length, in bytes, of the test message that will be
 	// sent.
 	messageLen int
@@ -523,6 +531,12 @@
 		}
 	}
 
+	if test.expectedNextProtoType != 0 {
+		if (test.expectedNextProtoType == alpn) != tlsConn.ConnectionState().NegotiatedProtocolFromALPN {
+			return fmt.Errorf("next proto type mismatch")
+		}
+	}
+
 	if test.shimWritesFirst {
 		var buf [5]byte
 		_, err := io.ReadFull(tlsConn, buf[:])
@@ -1143,8 +1157,9 @@
 					MaxHandshakeRecordLength: maxHandshakeRecordLength,
 				},
 			},
-			flags:             append(flags, "-select-next-proto", "foo"),
-			expectedNextProto: "foo",
+			flags:                 append(flags, "-select-next-proto", "foo"),
+			expectedNextProto:     "foo",
+			expectedNextProtoType: npn,
 		})
 		testCases = append(testCases, testCase{
 			protocol: protocol,
@@ -1159,7 +1174,8 @@
 			flags: append(flags,
 				"-advertise-npn", "\x03foo\x03bar\x03baz",
 				"-expect-next-proto", "bar"),
-			expectedNextProto: "bar",
+			expectedNextProto:     "bar",
+			expectedNextProtoType: npn,
 		})
 
 		// Client does False Start and negotiates NPN.
@@ -1446,8 +1462,9 @@
 			"-advertise-alpn", "\x03foo\x03bar\x03baz",
 			"-expect-alpn", "foo",
 		},
-		expectedNextProto: "foo",
-		resumeSession:     true,
+		expectedNextProto:     "foo",
+		expectedNextProtoType: alpn,
+		resumeSession:         true,
 	})
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -1459,8 +1476,43 @@
 			"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
 			"-select-alpn", "foo",
 		},
-		expectedNextProto: "foo",
-		resumeSession:     true,
+		expectedNextProto:     "foo",
+		expectedNextProtoType: alpn,
+		resumeSession:         true,
+	})
+	// Test that the server prefers ALPN over NPN.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ALPNServer-Preferred",
+		config: Config{
+			NextProtos: []string{"foo", "bar", "baz"},
+		},
+		flags: []string{
+			"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+			"-select-alpn", "foo",
+			"-advertise-npn", "\x03foo\x03bar\x03baz",
+		},
+		expectedNextProto:     "foo",
+		expectedNextProtoType: alpn,
+		resumeSession:         true,
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ALPNServer-Preferred-Swapped",
+		config: Config{
+			NextProtos: []string{"foo", "bar", "baz"},
+			Bugs: ProtocolBugs{
+				SwapNPNAndALPN: true,
+			},
+		},
+		flags: []string{
+			"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+			"-select-alpn", "foo",
+			"-advertise-npn", "\x03foo\x03bar\x03baz",
+		},
+		expectedNextProto:     "foo",
+		expectedNextProtoType: alpn,
+		resumeSession:         true,
 	})
 }