Add SSL_get_tls_unique.

SSL_get_tls_unique returns the tls-unique channel-binding value as
defined in https://tools.ietf.org/html/rfc5929#section-3.1.

Change-Id: Id9644328a7db8a91cf3ff0deee9dd6ce0d3e00ba
Reviewed-on: https://boringssl-review.googlesource.com/4984
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 2b25d35..bd03cb1 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -201,6 +201,9 @@
 	// flags, if not empty, contains a list of command-line flags that will
 	// be passed to the shim program.
 	flags []string
+	// testTLSUnique, if true, causes the shim to send the tls-unique value
+	// which will be compared against the expected value.
+	testTLSUnique bool
 }
 
 var testCases = []testCase{
@@ -1246,6 +1249,17 @@
 		}
 	}
 
+	if test.testTLSUnique {
+		var peersValue [12]byte
+		if _, err := io.ReadFull(tlsConn, peersValue[:]); err != nil {
+			return err
+		}
+		expected := tlsConn.ConnectionState().TLSUnique
+		if !bytes.Equal(peersValue[:], expected) {
+			return fmt.Errorf("tls-unique mismatch: peer sent %x, but %x was expected", peersValue[:], expected)
+		}
+	}
+
 	if test.shimWritesFirst {
 		var buf [5]byte
 		_, err := io.ReadFull(tlsConn, buf[:])
@@ -1431,6 +1445,10 @@
 		flags = append(flags, "-expect-session-miss")
 	}
 
+	if test.testTLSUnique {
+		flags = append(flags, "-tls-unique")
+	}
+
 	flags = append(flags, test.flags...)
 
 	var shim *exec.Cmd
@@ -3369,6 +3387,59 @@
 	})
 }
 
+func addTLSUniqueTests() {
+	for _, isClient := range []bool{false, true} {
+		for _, isResumption := range []bool{false, true} {
+			for _, hasEMS := range []bool{false, true} {
+				var suffix string
+				if isResumption {
+					suffix = "Resume-"
+				} else {
+					suffix = "Full-"
+				}
+
+				if hasEMS {
+					suffix += "EMS-"
+				} else {
+					suffix += "NoEMS-"
+				}
+
+				if isClient {
+					suffix += "Client"
+				} else {
+					suffix += "Server"
+				}
+
+				test := testCase{
+					name:          "TLSUnique-" + suffix,
+					testTLSUnique: true,
+					config: Config{
+						Bugs: ProtocolBugs{
+							NoExtendedMasterSecret: !hasEMS,
+						},
+					},
+				}
+
+				if isResumption {
+					test.resumeSession = true
+					test.resumeConfig = &Config{
+						Bugs: ProtocolBugs{
+							NoExtendedMasterSecret: !hasEMS,
+						},
+					}
+				}
+
+				if isResumption && !hasEMS {
+					test.shouldFail = true
+					test.expectedError = "failed to get tls-unique"
+				}
+
+				testCases = append(testCases, test)
+			}
+		}
+	}
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -3467,6 +3538,7 @@
 	addFastRadioPaddingTests()
 	addDTLSRetransmitTests()
 	addExportKeyingMaterialTests()
+	addTLSUniqueTests()
 	for _, async := range []bool{false, true} {
 		for _, splitHandshake := range []bool{false, true} {
 			for _, protocol := range []protocol{tls, dtls} {