From 03138ec18ed552ed90793ccc2a87f3ca75d7574f Mon Sep 17 00:00:00 2001 From: "Henry D. Case" Date: Tue, 26 Jun 2018 14:25:45 +0100 Subject: [PATCH] TLSv1.3 -draft23: Implementation of signature_algorithms_cert Tris uses signature_algorithms_cert in order to advertise that it doesn't support RSA-PSS. See GH#86 for more detailed discussion. --- 13.go | 13 +++ common.go | 33 ++++---- example_test.go | 8 +- handshake_client.go | 1 + handshake_messages.go | 186 +++++++++++++++++++++++++----------------- 5 files changed, 147 insertions(+), 94 deletions(-) diff --git a/13.go b/13.go index d1897e8..be4abcc 100644 --- a/13.go +++ b/13.go @@ -225,6 +225,7 @@ CurvePreferenceLoop: certReq := new(certificateRequestMsg13) // extension 'signature_algorithms' MUST be specified certReq.supportedSignatureAlgorithms = supportedSignatureAlgorithms13 + certReq.supportedSignatureAlgorithmsCert = supportedSigAlgorithmsCert(supportedSignatureAlgorithms13) hs.keySchedule.write(certReq.marshal()) if _, err := hs.c.writeRecord(recordTypeHandshake, certReq.marshal()); err != nil { return err @@ -1114,3 +1115,15 @@ func (hs *clientHandshakeState) doTLS13Handshake() error { c.in.setCipher(c.vers, appServerCipher) return nil } + +// supportedSigAlgorithmsCert iterates over schemes and filters out those algorithms +// which are not supported for certificate verification. +func supportedSigAlgorithmsCert(schemes []SignatureScheme) (ret []SignatureScheme) { + for _, sig := range schemes { + // X509 doesn't support PSS signatures + if !signatureSchemeIsPSS(sig) { + ret = append(ret, sig) + } + } + return +} diff --git a/common.go b/common.go index e261573..5fbe17a 100644 --- a/common.go +++ b/common.go @@ -80,22 +80,23 @@ const ( // TLS extension numbers const ( - extensionServerName uint16 = 0 - extensionStatusRequest uint16 = 5 - extensionSupportedCurves uint16 = 10 // Supported Groups in 1.3 nomenclature - extensionSupportedPoints uint16 = 11 - extensionSignatureAlgorithms uint16 = 13 - extensionALPN uint16 = 16 - extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6 - extensionSessionTicket uint16 = 35 - extensionPreSharedKey uint16 = 41 - extensionEarlyData uint16 = 42 - extensionSupportedVersions uint16 = 43 - extensionPSKKeyExchangeModes uint16 = 45 - extensionCAs uint16 = 47 - extensionKeyShare uint16 = 51 - extensionNextProtoNeg uint16 = 13172 // not IANA assigned - extensionRenegotiationInfo uint16 = 0xff01 + extensionServerName uint16 = 0 + extensionStatusRequest uint16 = 5 + extensionSupportedCurves uint16 = 10 // Supported Groups in 1.3 nomenclature + extensionSupportedPoints uint16 = 11 + extensionSignatureAlgorithms uint16 = 13 + extensionALPN uint16 = 16 + extensionSCT uint16 = 18 // https://tools.ietf.org/html/rfc6962#section-6 + extensionSessionTicket uint16 = 35 + extensionPreSharedKey uint16 = 41 + extensionEarlyData uint16 = 42 + extensionSupportedVersions uint16 = 43 + extensionPSKKeyExchangeModes uint16 = 45 + extensionCAs uint16 = 47 + extensionSignatureAlgorithmsCert uint16 = 50 + extensionKeyShare uint16 = 51 + extensionNextProtoNeg uint16 = 13172 // not IANA assigned + extensionRenegotiationInfo uint16 = 0xff01 ) // TLS signaling cipher suite values diff --git a/example_test.go b/example_test.go index 60136ae..3f91def 100644 --- a/example_test.go +++ b/example_test.go @@ -155,8 +155,8 @@ func ExampleConfig_keyLogWriter_TLS13() { // preferences. // Output: - // CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 ab02b68658d18ef1a4056b3094fe511b43084d40e9a6518753a7f832da724292 - // SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 d2e96648d170e2524bee07b651f4cca932a52247493ca33cc0714260a7424b2d - // SERVER_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 371fab23269e3cd73496e0e78f3dbc487f7cd5a563cc9f8c1a71be242268c375 - // CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 ca30484e48ec9a6f3b05b41c7492dbed8dea8e92d2abece2824a96052ac8ed8d + // CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 a829dab06ccbe9323e0ad6cf331cd64d9a499f7f4b1e0b52d0dfaba90c07f275 + // SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 5f6a288b5b5aba5cfc65d9966b279e911ac58bd7f81abbb67b10427106f01940 + // SERVER_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 1f202d1c1d43e74a8c4f46a56eae2cef9de417ff9f5f3927195eacc168f459b3 + // CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 67ece7874ec4f661fe1f06fb4240decd25f54062d78f0784844bf222d1967c20 } diff --git a/handshake_client.go b/handshake_client.go index 6b97c32..ca1921f 100644 --- a/handshake_client.go +++ b/handshake_client.go @@ -106,6 +106,7 @@ NextCipherSuite: hello.vers = VersionTLS12 hello.supportedVersions = config.getSupportedVersions() hello.supportedSignatureAlgorithms = supportedSignatureAlgorithms13 + hello.supportedSignatureAlgorithmsCert = supportedSigAlgorithmsCert(supportedSignatureAlgorithms13) } return hello, nil diff --git a/handshake_messages.go b/handshake_messages.go index 83bbbdb..9da37af 100644 --- a/handshake_messages.go +++ b/handshake_messages.go @@ -6,81 +6,97 @@ package tls import ( "bytes" + "encoding/binary" "strings" ) +// signAlgosCertList helper function returns either list of signature algorithms in case +// signature_algorithms_cert extension should be marshalled or nil in the other case. +// signAlgos is a list of algorithms from signature_algorithms extension. signAlgosCert is a list +// of algorithms from signature_algorithms_cert extension. +func signAlgosCertList(signAlgos, signAlgosCert []SignatureScheme) []SignatureScheme { + if eqSignatureAlgorithms(signAlgos, signAlgosCert) { + // ensure that only supported_algorithms extension is send if supported_algorithms_cert + // has identical content + return nil + } + return signAlgosCert +} + type clientHelloMsg struct { - raw []byte - rawTruncated []byte // for PSK binding - vers uint16 - random []byte - sessionId []byte - cipherSuites []uint16 - compressionMethods []uint8 - nextProtoNeg bool - serverName string - ocspStapling bool - scts bool - supportedCurves []CurveID - supportedPoints []uint8 - ticketSupported bool - sessionTicket []uint8 - supportedSignatureAlgorithms []SignatureScheme - secureRenegotiation []byte - secureRenegotiationSupported bool - alpnProtocols []string - keyShares []keyShare - supportedVersions []uint16 - psks []psk - pskKeyExchangeModes []uint8 - earlyData bool + raw []byte + rawTruncated []byte // for PSK binding + vers uint16 + random []byte + sessionId []byte + cipherSuites []uint16 + compressionMethods []uint8 + nextProtoNeg bool + serverName string + ocspStapling bool + scts bool + supportedCurves []CurveID + supportedPoints []uint8 + ticketSupported bool + sessionTicket []uint8 + supportedSignatureAlgorithms []SignatureScheme + supportedSignatureAlgorithmsCert []SignatureScheme + secureRenegotiation []byte + secureRenegotiationSupported bool + alpnProtocols []string + keyShares []keyShare + supportedVersions []uint16 + psks []psk + pskKeyExchangeModes []uint8 + earlyData bool } // Helpers - -// Marshalling of signature_algorithms extension see https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 -// for more details. Extension is serialized in data buffer +// Function used for signature_algorithms and signature_algorithrms_cert extensions only +// (for more details, see TLS 1.3 draft 28, 4.2.3) // Function advances data slice and returns it, so that it can be used for further processing -func marshalExtensionSignatureAlgorithms(data []byte, sigSchemes []SignatureScheme) []byte { - data[0] = byte(extensionSignatureAlgorithms >> 8) - data[1] = byte(extensionSignatureAlgorithms) - l := 2 + 2*len(sigSchemes) - data[2] = byte(l >> 8) - data[3] = byte(l) - data = data[4:] +func marshalExtensionSignatureAlgorithms(extension uint16, data []byte, schemes []SignatureScheme) []byte { + algNum := uint16(len(schemes)) + if algNum == 0 { + return data + } - l -= 2 - data[0] = byte(l >> 8) - data[1] = byte(l) + binary.BigEndian.PutUint16(data, extension) data = data[2:] - for _, sigAlgo := range sigSchemes { - data[0] = byte(sigAlgo >> 8) - data[1] = byte(sigAlgo) + binary.BigEndian.PutUint16(data, (2*algNum)+2) // +1 for length + data = data[2:] + binary.BigEndian.PutUint16(data, (2 * algNum)) + data = data[2:] + + for _, algo := range schemes { + binary.BigEndian.PutUint16(data, uint16(algo)) data = data[2:] } return data } -// Unmrshalling of signature_algorithms extension see https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 -// for more details. +// Function used for unmarshalling signature_algorithms or signature_algorithms_cert extensions only +// (for more details, see TLS 1.3 draft 28, 4.2.3) // In case of error function returns alertDecoderError otherwise filled SignatureScheme slice and alertSuccess func unmarshalExtensionSignatureAlgorithms(data []byte, length int) ([]SignatureScheme, alert) { + if length < 2 || length&1 != 0 { return nil, alertDecodeError } - l := int(data[0])<<8 | int(data[1]) - if l != length-2 { + algLen := binary.BigEndian.Uint16(data) + idx := 2 + + if int(algLen) != length-2 { return nil, alertDecodeError } - n := l / 2 - d := data[2:] - sigSchemes := make([]SignatureScheme, n) - for i := range sigSchemes { - sigSchemes[i] = SignatureScheme(d[0])<<8 | SignatureScheme(d[1]) - d = d[2:] + + schemes := make([]SignatureScheme, algLen/2) + for i := range schemes { + schemes[i] = SignatureScheme(binary.BigEndian.Uint16(data[idx:])) + idx += 2 } - return sigSchemes, alertSuccess + return schemes, alertSuccess } func (m *clientHelloMsg) equal(i interface{}) bool { @@ -104,6 +120,7 @@ func (m *clientHelloMsg) equal(i interface{}) bool { m.ticketSupported == m1.ticketSupported && bytes.Equal(m.sessionTicket, m1.sessionTicket) && eqSignatureAlgorithms(m.supportedSignatureAlgorithms, m1.supportedSignatureAlgorithms) && + eqSignatureAlgorithms(m.supportedSignatureAlgorithmsCert, m1.supportedSignatureAlgorithmsCert) && m.secureRenegotiationSupported == m1.secureRenegotiationSupported && bytes.Equal(m.secureRenegotiation, m1.secureRenegotiation) && eqStrings(m.alpnProtocols, m1.alpnProtocols) && @@ -120,6 +137,8 @@ func (m *clientHelloMsg) marshal() []byte { length := 2 + 32 + 1 + len(m.sessionId) + 2 + len(m.cipherSuites)*2 + 1 + len(m.compressionMethods) numExtensions := 0 extensionsLength := 0 + + // Indicates wether to send signature_algorithms_cert extension if m.nextProtoNeg { numExtensions++ } @@ -147,6 +166,10 @@ func (m *clientHelloMsg) marshal() []byte { extensionsLength += 2 + 2*len(m.supportedSignatureAlgorithms) numExtensions++ } + if m.getSignatureAlgorithmsCert() != nil { + extensionsLength += 2 + 2*len(m.getSignatureAlgorithmsCert()) + numExtensions++ + } if m.secureRenegotiationSupported { extensionsLength += 1 + len(m.secureRenegotiation) numExtensions++ @@ -305,26 +328,15 @@ func (m *clientHelloMsg) marshal() []byte { copy(z, m.sessionTicket) z = z[len(m.sessionTicket):] } - if len(m.supportedSignatureAlgorithms) > 0 { - // https://tools.ietf.org/html/rfc5246#section-7.4.1.4.1 - // https://tools.ietf.org/html/draft-ietf-tls-tls13-18#section-4.2.3 - z[0] = byte(extensionSignatureAlgorithms >> 8) - z[1] = byte(extensionSignatureAlgorithms) - l := 2 + 2*len(m.supportedSignatureAlgorithms) - z[2] = byte(l >> 8) - z[3] = byte(l) - z = z[4:] - l -= 2 - z[0] = byte(l >> 8) - z[1] = byte(l) - z = z[2:] - for _, sigAlgo := range m.supportedSignatureAlgorithms { - z[0] = byte(sigAlgo >> 8) - z[1] = byte(sigAlgo) - z = z[2:] - } + if len(m.supportedSignatureAlgorithms) > 0 { + z = marshalExtensionSignatureAlgorithms(extensionSignatureAlgorithms, z, m.supportedSignatureAlgorithms) } + if m.getSignatureAlgorithmsCert() != nil { + // Ensure only one list of algorithms is sent if supported_algorithms and supported_algorithms_cert are the same + z = marshalExtensionSignatureAlgorithms(extensionSignatureAlgorithmsCert, z, m.getSignatureAlgorithmsCert()) + } + if m.secureRenegotiationSupported { z[0] = byte(extensionRenegotiationInfo >> 8) z[1] = byte(extensionRenegotiationInfo & 0xff) @@ -743,6 +755,10 @@ func (m *clientHelloMsg) unmarshal(data []byte) alert { return alertSuccess } +func (m *clientHelloMsg) getSignatureAlgorithmsCert() []SignatureScheme { + return signAlgosCertList(m.supportedSignatureAlgorithms, m.supportedSignatureAlgorithmsCert) +} + type serverHelloMsg struct { raw []byte vers uint16 @@ -2074,15 +2090,17 @@ func (m *certificateRequestMsg) unmarshal(data []byte) alert { if len(data) != 0 { return alertDecodeError } + return alertSuccess } type certificateRequestMsg13 struct { raw []byte - requestContext []byte - supportedSignatureAlgorithms []SignatureScheme - certificateAuthorities [][]byte + requestContext []byte + supportedSignatureAlgorithms []SignatureScheme + supportedSignatureAlgorithmsCert []SignatureScheme + certificateAuthorities [][]byte } func (m *certificateRequestMsg13) equal(i interface{}) bool { @@ -2091,7 +2109,8 @@ func (m *certificateRequestMsg13) equal(i interface{}) bool { bytes.Equal(m.raw, m1.raw) && bytes.Equal(m.requestContext, m1.requestContext) && eqByteSlices(m.certificateAuthorities, m1.certificateAuthorities) && - eqSignatureAlgorithms(m.supportedSignatureAlgorithms, m1.supportedSignatureAlgorithms) + eqSignatureAlgorithms(m.supportedSignatureAlgorithms, m1.supportedSignatureAlgorithms) && + eqSignatureAlgorithms(m.supportedSignatureAlgorithmsCert, m1.supportedSignatureAlgorithmsCert) } func (m *certificateRequestMsg13) marshal() (x []byte) { @@ -2104,6 +2123,11 @@ func (m *certificateRequestMsg13) marshal() (x []byte) { numExtensions := 1 extensionsLength := 2 + 2*len(m.supportedSignatureAlgorithms) + if m.getSignatureAlgorithmsCert() != nil { + numExtensions += 1 + extensionsLength += 2 + 2*len(m.getSignatureAlgorithmsCert()) + } + casLength := 0 if len(m.certificateAuthorities) > 0 { for _, ca := range m.certificateAuthorities { @@ -2131,7 +2155,11 @@ func (m *certificateRequestMsg13) marshal() (x []byte) { z = z[2:] // TODO: this function should be reused by CH - z = marshalExtensionSignatureAlgorithms(z, m.supportedSignatureAlgorithms) + z = marshalExtensionSignatureAlgorithms(extensionSignatureAlgorithms, z, m.supportedSignatureAlgorithms) + + if m.getSignatureAlgorithmsCert() != nil { + z = marshalExtensionSignatureAlgorithms(extensionSignatureAlgorithmsCert, z, m.getSignatureAlgorithmsCert()) + } // certificate_authorities if casLength > 0 { z[0] = byte(extensionCAs >> 8) @@ -2204,6 +2232,12 @@ func (m *certificateRequestMsg13) unmarshal(data []byte) alert { if err != alertSuccess { return err } + case extensionSignatureAlgorithmsCert: + var err alert + m.supportedSignatureAlgorithmsCert, err = unmarshalExtensionSignatureAlgorithms(data, length) + if err != alertSuccess { + return err + } case extensionCAs: // TODO DRY: share code with CH if length < 2 { @@ -2240,6 +2274,10 @@ func (m *certificateRequestMsg13) unmarshal(data []byte) alert { return alertSuccess } +func (m *certificateRequestMsg13) getSignatureAlgorithmsCert() []SignatureScheme { + return signAlgosCertList(m.supportedSignatureAlgorithms, m.supportedSignatureAlgorithmsCert) +} + type certificateVerifyMsg struct { raw []byte hasSignatureAndHash bool