| // Copyright 2025 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package runner | |
| import ( | |
| "bytes" | |
| "crypto/rand" | |
| "fmt" | |
| "strconv" | |
| ) | |
| const ( | |
| shrinkingCompressionAlgID = 0xff01 | |
| expandingCompressionAlgID = 0xff02 | |
| randomCompressionAlgID = 0xff03 | |
| ) | |
| var ( | |
| // shrinkingPrefix is the first two bytes of a Certificate message. | |
| shrinkingPrefix = []byte{0, 0} | |
| // expandingPrefix is just some arbitrary byte string. This has to match the | |
| // value in the shim. | |
| expandingPrefix = []byte{1, 2, 3, 4} | |
| ) | |
| var shrinkingCompression = CertCompressionAlg{ | |
| Compress: func(uncompressed []byte) []byte { | |
| if !bytes.HasPrefix(uncompressed, shrinkingPrefix) { | |
| panic(fmt.Sprintf("cannot compress certificate message %x", uncompressed)) | |
| } | |
| return uncompressed[len(shrinkingPrefix):] | |
| }, | |
| Decompress: func(out []byte, compressed []byte) bool { | |
| if len(out) != len(shrinkingPrefix)+len(compressed) { | |
| return false | |
| } | |
| copy(out, shrinkingPrefix) | |
| copy(out[len(shrinkingPrefix):], compressed) | |
| return true | |
| }, | |
| } | |
| var expandingCompression = CertCompressionAlg{ | |
| Compress: func(uncompressed []byte) []byte { | |
| ret := make([]byte, 0, len(expandingPrefix)+len(uncompressed)) | |
| ret = append(ret, expandingPrefix...) | |
| return append(ret, uncompressed...) | |
| }, | |
| Decompress: func(out []byte, compressed []byte) bool { | |
| if !bytes.HasPrefix(compressed, expandingPrefix) { | |
| return false | |
| } | |
| copy(out, compressed[len(expandingPrefix):]) | |
| return true | |
| }, | |
| } | |
| var randomCompression = CertCompressionAlg{ | |
| Compress: func(uncompressed []byte) []byte { | |
| ret := make([]byte, 1+len(uncompressed)) | |
| if _, err := rand.Read(ret[:1]); err != nil { | |
| panic(err) | |
| } | |
| copy(ret[1:], uncompressed) | |
| return ret | |
| }, | |
| Decompress: func(out []byte, compressed []byte) bool { | |
| if len(compressed) != 1+len(out) { | |
| return false | |
| } | |
| copy(out, compressed[1:]) | |
| return true | |
| }, | |
| } | |
| func addCertCompressionTests() { | |
| for _, ver := range tlsVersions { | |
| if ver.version < VersionTLS12 { | |
| continue | |
| } | |
| // Duplicate compression algorithms is an error, even if nothing is | |
| // configured. | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "DuplicateCertCompressionExt-" + ver.name, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| Bugs: ProtocolBugs{ | |
| DuplicateCompressedCertAlgs: true, | |
| }, | |
| }, | |
| shouldFail: true, | |
| expectedError: ":ERROR_PARSING_EXTENSION:", | |
| }) | |
| // With compression algorithms configured, an duplicate values should still | |
| // be an error. | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "DuplicateCertCompressionExt2-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| Bugs: ProtocolBugs{ | |
| DuplicateCompressedCertAlgs: true, | |
| }, | |
| }, | |
| shouldFail: true, | |
| expectedError: ":ERROR_PARSING_EXTENSION:", | |
| }) | |
| if ver.version < VersionTLS13 { | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionIgnoredBefore13-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| expandingCompressionAlgID: expandingCompression, | |
| }, | |
| }, | |
| }) | |
| continue | |
| } | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionExpands-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| expandingCompressionAlgID: expandingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: expandingCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionShrinks-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: shrinkingCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| // Test that the shim behaves consistently if the compression function | |
| // is non-deterministic. This is intended to model version differences | |
| // between the shim and handshaker with handshake hints, but it is also | |
| // useful in confirming we only call the callbacks once. | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionRandom-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| randomCompressionAlgID: randomCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: randomCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| // With both algorithms configured, the server should pick its most | |
| // preferable. (Which is expandingCompressionAlgID.) | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionPriority-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| expandingCompressionAlgID: expandingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: expandingCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| // With no common algorithms configured, the server should decline | |
| // compression. | |
| testCases = append(testCases, testCase{ | |
| testType: serverTest, | |
| name: "CertCompressionNoCommonAlgs-" + ver.name, | |
| flags: []string{"-install-one-cert-compression-alg", strconv.Itoa(shrinkingCompressionAlgID)}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| expandingCompressionAlgID: expandingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectUncompressedCert: true, | |
| }, | |
| }, | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: clientTest, | |
| name: "CertCompressionExpandsClient-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| expandingCompressionAlgID: expandingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: expandingCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: clientTest, | |
| name: "CertCompressionShrinksClient-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: shrinkingCompressionAlgID, | |
| }, | |
| }, | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: clientTest, | |
| name: "CertCompressionBadAlgIDClient-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: shrinkingCompressionAlgID, | |
| SendCertCompressionAlgID: 1234, | |
| }, | |
| }, | |
| shouldFail: true, | |
| expectedError: ":UNKNOWN_CERT_COMPRESSION_ALG:", | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: clientTest, | |
| name: "CertCompressionTooSmallClient-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: shrinkingCompressionAlgID, | |
| SendCertUncompressedLength: 12, | |
| }, | |
| }, | |
| shouldFail: true, | |
| expectedError: ":CERT_DECOMPRESSION_FAILED:", | |
| }) | |
| testCases = append(testCases, testCase{ | |
| testType: clientTest, | |
| name: "CertCompressionTooLargeClient-" + ver.name, | |
| flags: []string{"-install-cert-compression-algs"}, | |
| config: Config{ | |
| MinVersion: ver.version, | |
| MaxVersion: ver.version, | |
| CertCompressionAlgs: map[uint16]CertCompressionAlg{ | |
| shrinkingCompressionAlgID: shrinkingCompression, | |
| }, | |
| Bugs: ProtocolBugs{ | |
| ExpectedCompressedCert: shrinkingCompressionAlgID, | |
| SendCertUncompressedLength: 1 << 20, | |
| }, | |
| }, | |
| shouldFail: true, | |
| expectedError: ":UNCOMPRESSED_CERT_TOO_LARGE:", | |
| }) | |
| } | |
| } |