├── .github └── workflows │ ├── go.yml │ └── test.yaml ├── LICENSE.md ├── README.md ├── SECURITY.md ├── codec.go ├── codec_test.go ├── go.mod ├── go.sum ├── messages.go ├── messages_test.go ├── odoh-flow.png ├── odoh.go ├── odoh_test.go └── test-vectors.json /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.x 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: '>=1.20.0' 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: | 27 | go get -v -t -d ./... 28 | if [ -f Gopkg.toml ]; then 29 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 30 | dep ensure 31 | fi 32 | 33 | - name: Build 34 | run: go build -v . 35 | 36 | - name: Test 37 | run: go test -v . 38 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | name: Test with Coverage 12 | runs-on: ubuntu-latest 13 | steps: 14 | 15 | - name: Set up Go 16 | uses: actions/setup-go@v1 17 | with: 18 | go-version: '>=1.20.0' 19 | 20 | - name: Check out code 21 | uses: actions/checkout@v2 22 | 23 | - name: Install dependencies 24 | run: | 25 | go mod download 26 | 27 | - name: Run Unit tests 28 | run: | 29 | go test -race -covermode atomic -coverprofile=covprofile ./... 30 | 31 | - name: Install goveralls 32 | env: 33 | GO111MODULE: off 34 | run: go get github.com/mattn/goveralls 35 | 36 | - name: Send coverage 37 | env: 38 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | run: $(go env GOPATH)/bin/goveralls -coverprofile=covprofile -service=github -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # odoh-go 2 | 3 | [![Coverage Status](https://coveralls.io/repos/github/cloudflare/odoh-go/badge.svg?branch=master)](https://coveralls.io/github/cloudflare/odoh-go?branch=master) 4 | [![GoDoc](https://godoc.org/github.com/cloudflare/odoh-go?status.svg)](https://godoc.org/github.com/cloudflare/odoh-go) 5 | 6 | This library implements version 0x0001 of [Oblivious DoH](https://tfpauly.github.io/draft-pauly-adaptive-dns-privacy/draft-pauly-dprive-oblivious-doh.html). It is based on the original implementation [available here](https://github.com/chris-wood/odoh). 7 | 8 | ![protocol overview](odoh-flow.png) 9 | 10 | ## Test vector generation 11 | 12 | To generate test vectors, run: 13 | 14 | ``` 15 | $ ODOH_TEST_VECTORS_OUT=test-vectors.json go test -v -run TestVectorGenerate 16 | ``` 17 | 18 | To check test vectors, run: 19 | 20 | ``` 21 | $ ODOH_TEST_VECTORS_IN=test-vectors.json go test -v -run TestVectorVerify 22 | ``` 23 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Vulnerabilities 2 | 3 | Please see [this page](https://www.cloudflare.com/.well-known/security.txt) for information on how to report a vulnerability to Cloudflare. Thanks! 4 | -------------------------------------------------------------------------------- /codec.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "encoding/binary" 27 | "fmt" 28 | ) 29 | 30 | func encodeLengthPrefixedSlice(slice []byte) []byte { 31 | result := make([]byte, 2) 32 | binary.BigEndian.PutUint16(result, uint16(len(slice))) 33 | return append(result, slice...) 34 | } 35 | 36 | func decodeLengthPrefixedSlice(slice []byte) ([]byte, int, error) { 37 | if len(slice) < 2 { 38 | return nil, 0, fmt.Errorf("Expected at least 2 bytes of length encoded prefix") 39 | } 40 | 41 | length := binary.BigEndian.Uint16(slice) 42 | if int(2+length) > len(slice) { 43 | return nil, 0, fmt.Errorf("Insufficient data. Expected %d, got %d", 2+length, len(slice)) 44 | } 45 | 46 | return slice[2 : 2+length], int(2 + length), nil 47 | } 48 | -------------------------------------------------------------------------------- /codec_test.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "bytes" 27 | "testing" 28 | ) 29 | 30 | func TestEncodeEmptySlice(t *testing.T) { 31 | expectedBytes := []byte{0x00, 0x00} 32 | if !bytes.Equal(encodeLengthPrefixedSlice(nil), expectedBytes) { 33 | t.Fatalf("Result mismatch.") 34 | } 35 | } 36 | 37 | func TestEncodeLengthPrefixedSlice(t *testing.T) { 38 | testData := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} 39 | result := encodeLengthPrefixedSlice(testData) 40 | expectedBytes := []byte{0x00, 0x09, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} 41 | 42 | if !bytes.Equal(result, expectedBytes) { 43 | t.Fatalf("Result mismatch.") 44 | } 45 | } 46 | 47 | func TestDecodeLengthPrefixedSlice(t *testing.T) { 48 | testData := []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} 49 | result := encodeLengthPrefixedSlice(testData) 50 | decodedBytes, length, err := decodeLengthPrefixedSlice(result) 51 | if err != nil { 52 | t.Fatalf("Raised an error. Decoding error.") 53 | } 54 | if !bytes.Equal(testData, decodedBytes) { 55 | t.Fatalf("Decoding result mismatch.") 56 | } 57 | if len(testData)+2 != length { 58 | t.Fatalf("Incorrect length in the encoded message.") 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudflare/odoh-go 2 | 3 | go 1.20 4 | 5 | require github.com/cisco/go-hpke v0.0.0-20230407100446-246075f83609 6 | 7 | require ( 8 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b // indirect 9 | github.com/cloudflare/circl v1.3.3 // indirect 10 | golang.org/x/crypto v0.13.0 // indirect 11 | golang.org/x/sys v0.12.0 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cisco/go-hpke v0.0.0-20230407100446-246075f83609 h1:+zUH9Y9OFBb59WFBFAQJTK25GGTby/g0DU7P+Pz1WaI= 2 | github.com/cisco/go-hpke v0.0.0-20230407100446-246075f83609/go.mod h1:RJ2C6TWlNvW2BlTT+YcexuRwIyzXter42/IRyb2sOTg= 3 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b h1:Ves2turKTX7zruivAcUOQg155xggcbv3suVdbKCBQNM= 4 | github.com/cisco/go-tls-syntax v0.0.0-20200617162716-46b0cfb76b9b/go.mod h1:0AZAV7lYvynZQ5ErHlGMKH+4QYMyNCFd+AiL9MlrCYA= 5 | github.com/cloudflare/circl v1.0.0/go.mod h1:MhjB3NEEhJbTOdLLq964NIUisXDxaE1WkQPUxtgZXiY= 6 | github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= 7 | github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= 8 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 13 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 14 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 15 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 16 | golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 17 | golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= 18 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 19 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 20 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 21 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 22 | golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 23 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 24 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 27 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 28 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 29 | -------------------------------------------------------------------------------- /messages.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "encoding/binary" 27 | "fmt" 28 | ) 29 | 30 | type ObliviousMessageType uint8 31 | 32 | const ( 33 | QueryType ObliviousMessageType = 0x01 34 | ResponseType ObliviousMessageType = 0x02 35 | ) 36 | 37 | // struct { 38 | // opaque dns_message<1..2^16-1>; 39 | // opaque padding<0..2^16-1>; 40 | // } ObliviousDoHQueryBody; 41 | type ObliviousDNSMessageBody struct { 42 | DnsMessage []byte 43 | Padding []byte 44 | } 45 | 46 | func (m ObliviousDNSMessageBody) Marshal() []byte { 47 | return append(encodeLengthPrefixedSlice(m.DnsMessage), encodeLengthPrefixedSlice(m.Padding)...) 48 | } 49 | 50 | func UnmarshalMessageBody(data []byte) (ObliviousDNSMessageBody, error) { 51 | messageLength := binary.BigEndian.Uint16(data) 52 | if int(2+messageLength) > len(data) { 53 | return ObliviousDNSMessageBody{}, fmt.Errorf("Invalid DNS message length") 54 | } 55 | message := data[2 : 2+messageLength] 56 | 57 | paddingLength := binary.BigEndian.Uint16(data[2+messageLength:]) 58 | if int(2+messageLength+2+paddingLength) > len(data) { 59 | return ObliviousDNSMessageBody{}, fmt.Errorf("Invalid DNS padding length") 60 | } 61 | 62 | padding := data[2+messageLength+2 : 2+messageLength+2+paddingLength] 63 | return ObliviousDNSMessageBody{ 64 | DnsMessage: message, 65 | Padding: padding, 66 | }, nil 67 | } 68 | 69 | func (m ObliviousDNSMessageBody) Message() []byte { 70 | return m.DnsMessage 71 | } 72 | 73 | type ObliviousDNSQuery struct { 74 | ObliviousDNSMessageBody 75 | } 76 | 77 | func CreateObliviousDNSQuery(query []byte, paddingBytes uint16) *ObliviousDNSQuery { 78 | msg := ObliviousDNSMessageBody{ 79 | DnsMessage: query, 80 | Padding: make([]byte, int(paddingBytes)), 81 | } 82 | return &ObliviousDNSQuery{ 83 | msg, 84 | } 85 | } 86 | 87 | func UnmarshalQueryBody(data []byte) (*ObliviousDNSQuery, error) { 88 | msg, err := UnmarshalMessageBody(data) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return &ObliviousDNSQuery{msg}, nil 94 | } 95 | 96 | type ObliviousDNSResponse struct { 97 | ObliviousDNSMessageBody 98 | } 99 | 100 | func CreateObliviousDNSResponse(response []byte, paddingBytes uint16) *ObliviousDNSResponse { 101 | msg := ObliviousDNSMessageBody{ 102 | DnsMessage: response, 103 | Padding: make([]byte, int(paddingBytes)), 104 | } 105 | return &ObliviousDNSResponse{ 106 | msg, 107 | } 108 | } 109 | 110 | func UnmarshalResponseBody(data []byte) (*ObliviousDNSResponse, error) { 111 | msg, err := UnmarshalMessageBody(data) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | return &ObliviousDNSResponse{msg}, nil 117 | } 118 | 119 | // struct { 120 | // uint8 message_type; 121 | // opaque key_id<0..2^16-1>; 122 | // opaque encrypted_message<1..2^16-1>; 123 | // } ObliviousDoHMessage; 124 | type ObliviousDNSMessage struct { 125 | MessageType ObliviousMessageType 126 | KeyID []byte 127 | EncryptedMessage []byte 128 | } 129 | 130 | func (m ObliviousDNSMessage) Type() ObliviousMessageType { 131 | return m.MessageType 132 | } 133 | 134 | func CreateObliviousDNSMessage(messageType ObliviousMessageType, keyID []byte, encryptedMessage []byte) *ObliviousDNSMessage { 135 | return &ObliviousDNSMessage{ 136 | MessageType: messageType, 137 | KeyID: keyID, 138 | EncryptedMessage: encryptedMessage, 139 | } 140 | } 141 | 142 | func (m ObliviousDNSMessage) Marshal() []byte { 143 | encodedKey := encodeLengthPrefixedSlice(m.KeyID) 144 | encodedMessage := encodeLengthPrefixedSlice(m.EncryptedMessage) 145 | 146 | result := append([]byte{uint8(m.MessageType)}, encodedKey...) 147 | result = append(result, encodedMessage...) 148 | 149 | return result 150 | } 151 | 152 | func UnmarshalDNSMessage(data []byte) (ObliviousDNSMessage, error) { 153 | if len(data) < 1 { 154 | return ObliviousDNSMessage{}, fmt.Errorf("Invalid data length: %d", len(data)) 155 | } 156 | 157 | messageType := data[0] 158 | keyID, messageOffset, err := decodeLengthPrefixedSlice(data[1:]) 159 | if err != nil { 160 | return ObliviousDNSMessage{}, err 161 | } 162 | encryptedMessage, _, err := decodeLengthPrefixedSlice(data[1+messageOffset:]) 163 | if err != nil { 164 | return ObliviousDNSMessage{}, err 165 | } 166 | 167 | return ObliviousDNSMessage{ 168 | MessageType: ObliviousMessageType(messageType), 169 | KeyID: keyID, 170 | EncryptedMessage: encryptedMessage, 171 | }, nil 172 | } 173 | -------------------------------------------------------------------------------- /messages_test.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "bytes" 27 | "testing" 28 | ) 29 | 30 | func TestObliviousMessageMarshalEmptyKeyId(t *testing.T) { 31 | testMessage := []byte{0x06, 0x07, 0x08, 0x09} 32 | message := ObliviousDNSMessage{ 33 | MessageType: 0xFF, 34 | KeyID: nil, 35 | EncryptedMessage: testMessage, 36 | } 37 | 38 | serializedMessage := message.Marshal() 39 | expectedBytes := []byte{0xFF} 40 | expectedBytes = append(expectedBytes, []byte{0x00, 0x00}...) // empty key ID 41 | expectedBytes = append(expectedBytes, []byte{0x00, 0x04}...) // non-empty message 42 | expectedBytes = append(expectedBytes, testMessage...) 43 | if !bytes.Equal(serializedMessage, expectedBytes) { 44 | t.Fatalf("Marshalling mismatch in the encoding. Got %x, received %x", serializedMessage, expectedBytes) 45 | } 46 | } 47 | 48 | func TestObliviousMessageMarshalEmptyMessage(t *testing.T) { 49 | testKeyId := []byte{0x02, 0x03} 50 | message := ObliviousDNSMessage{ 51 | MessageType: 0xFF, 52 | KeyID: testKeyId, 53 | EncryptedMessage: nil, 54 | } 55 | 56 | serializedMessage := message.Marshal() 57 | expectedBytes := []byte{0xFF} 58 | expectedBytes = append(expectedBytes, []byte{0x00, 0x02}...) // non-empty key ID 59 | expectedBytes = append(expectedBytes, testKeyId...) 60 | expectedBytes = append(expectedBytes, []byte{0x00, 0x00}...) // empty message 61 | if !bytes.Equal(serializedMessage, expectedBytes) { 62 | t.Fatalf("Marshalling mismatch in the encoding. Got %x, received %x", serializedMessage, expectedBytes) 63 | } 64 | } 65 | 66 | func TestObliviousMessageMarshalNonEmptyKeyId(t *testing.T) { 67 | testMessage := []byte{0x06, 0x07, 0x08, 0x09} 68 | testKeyId := []byte{0x02, 0x03} 69 | message := ObliviousDNSMessage{ 70 | MessageType: 0xFF, 71 | KeyID: testKeyId, 72 | EncryptedMessage: testMessage, 73 | } 74 | 75 | serializedMessage := message.Marshal() 76 | expectedBytes := []byte{0xFF} 77 | expectedBytes = append(expectedBytes, []byte{0x00, 0x02}...) // non-empty key ID 78 | expectedBytes = append(expectedBytes, testKeyId...) 79 | expectedBytes = append(expectedBytes, []byte{0x00, 0x04}...) // non-empty message 80 | expectedBytes = append(expectedBytes, testMessage...) 81 | if !bytes.Equal(serializedMessage, expectedBytes) { 82 | t.Fatalf("Marshalling mismatch in the encoding. Got %x, received %x", serializedMessage, expectedBytes) 83 | } 84 | } 85 | 86 | func TestObliviousDoHQueryNoPaddingMarshal(t *testing.T) { 87 | dnsMessage := []byte{0x06, 0x07, 0x08, 0x09} 88 | query := CreateObliviousDNSQuery(dnsMessage, 0) 89 | 90 | serializedMessage := query.Marshal() 91 | expectedBytes := []byte{ 92 | 0x00, 0x04, 93 | 0x06, 0x07, 0x08, 0x09, 94 | 0x00, 0x00} 95 | if !bytes.Equal(serializedMessage, expectedBytes) { 96 | t.Fatalf("Marshalling mismatch in the encoding.") 97 | } 98 | } 99 | 100 | func TestObliviousDoHQueryPaddingMarshal(t *testing.T) { 101 | dnsMessage := []byte{0x06, 0x07, 0x08, 0x09} 102 | 103 | paddingLength := uint16(8) 104 | paddedBytes := make([]byte, paddingLength) 105 | query := CreateObliviousDNSQuery(dnsMessage, paddingLength) 106 | 107 | serializedMessage := query.Marshal() 108 | expectedBytes := []byte{ 109 | 0x00, 0x04, 110 | 0x06, 0x07, 0x08, 0x09, 111 | 0x00, uint8(paddingLength)} 112 | expectedBytes = append(expectedBytes, paddedBytes...) 113 | if !bytes.Equal(serializedMessage, expectedBytes) { 114 | t.Fatalf("Marshalling mismatch in the encoding.") 115 | } 116 | } 117 | 118 | func TestObliviousDoHMessage_Marshal(t *testing.T) { 119 | messageType := QueryType 120 | keyId := []byte{0x00, 0x01, 0x02, 0x03, 0x04} 121 | encryptedMessage := []byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 122 | 123 | odnsMessage := ObliviousDNSMessage{ 124 | MessageType: messageType, 125 | KeyID: keyId, 126 | EncryptedMessage: encryptedMessage, 127 | } 128 | 129 | serializedMessage := odnsMessage.Marshal() 130 | expectedBytes := []byte{0x01, 131 | 0x00, 0x05, 132 | 0x00, 0x01, 0x02, 0x03, 0x04, 133 | 0x00, 0x0B, 134 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 135 | 136 | if !bytes.Equal(serializedMessage, expectedBytes) { 137 | t.Fatalf("Failed to serialize correctly. Got %x, expected %x", serializedMessage, expectedBytes) 138 | } 139 | } 140 | 141 | func TestObliviousDoHMessage_Unmarshal(t *testing.T) { 142 | messageType := QueryType 143 | keyId := []byte{0x00, 0x01, 0x02, 0x03, 0x04} 144 | encryptedMessage := []byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 145 | 146 | odnsMessage := ObliviousDNSMessage{ 147 | MessageType: messageType, 148 | KeyID: keyId, 149 | EncryptedMessage: encryptedMessage, 150 | } 151 | 152 | expectedBytes := []byte{0x01, 153 | 0x00, 0x05, 154 | 0x00, 0x01, 0x02, 0x03, 0x04, 155 | 0x00, 0x0B, 156 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 157 | 158 | deserializedMessage, err := UnmarshalDNSMessage(expectedBytes) 159 | 160 | if err != nil { 161 | t.Fatalf("Failed to unmarshal ObliviousDNSMessage") 162 | } 163 | 164 | if !(deserializedMessage.MessageType == odnsMessage.MessageType) { 165 | t.Fatalf("Message type mismatch after unmarshaling") 166 | } 167 | 168 | if !bytes.Equal(deserializedMessage.KeyID, odnsMessage.KeyID) { 169 | t.Fatalf("Failed to unmarshal the KeyID correctly.") 170 | } 171 | 172 | if !bytes.Equal(deserializedMessage.EncryptedMessage, odnsMessage.EncryptedMessage) { 173 | t.Fatalf("Failed to unmarshal the Encrypted Message Correctly.") 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /odoh-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/odoh-go/f39fa019b017510690599e895c20cd02ae138742/odoh-flow.png -------------------------------------------------------------------------------- /odoh.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "crypto/rand" 27 | "crypto/subtle" 28 | "encoding/binary" 29 | "errors" 30 | "fmt" 31 | 32 | "github.com/cisco/go-hpke" 33 | ) 34 | 35 | const ( 36 | ODOH_VERSION = uint16(0x0001) 37 | ODOH_SECRET_LENGTH = 32 38 | ODOH_PADDING_BYTE = uint8(0) 39 | ODOH_LABEL_KEY_ID = "odoh key id" 40 | ODOH_LABEL_KEY = "odoh key" 41 | ODOH_LABEL_NONCE = "odoh nonce" 42 | ODOH_LABEL_QUERY = "odoh query" 43 | ODOH_LABEL_RESPONSE = "odoh response" 44 | ODOH_DEFAULT_KEMID hpke.KEMID = hpke.DHKEM_X25519 45 | ODOH_DEFAULT_KDFID hpke.KDFID = hpke.KDF_HKDF_SHA256 46 | ODOH_DEFAULT_AEADID hpke.AEADID = hpke.AEAD_AESGCM128 47 | ) 48 | 49 | type ObliviousDoHConfigContents struct { 50 | KemID hpke.KEMID 51 | KdfID hpke.KDFID 52 | AeadID hpke.AEADID 53 | PublicKeyBytes []byte 54 | } 55 | 56 | func CreateObliviousDoHConfigContents(kemID hpke.KEMID, kdfID hpke.KDFID, aeadID hpke.AEADID, publicKeyBytes []byte) (ObliviousDoHConfigContents, error) { 57 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 58 | if err != nil { 59 | return ObliviousDoHConfigContents{}, err 60 | } 61 | 62 | _, err = suite.KEM.DeserializePublicKey(publicKeyBytes) 63 | if err != nil { 64 | return ObliviousDoHConfigContents{}, err 65 | } 66 | 67 | return ObliviousDoHConfigContents{ 68 | KemID: kemID, 69 | KdfID: kdfID, 70 | AeadID: aeadID, 71 | PublicKeyBytes: publicKeyBytes, 72 | }, nil 73 | } 74 | 75 | func (k ObliviousDoHConfigContents) KeyID() []byte { 76 | suite, err := hpke.AssembleCipherSuite(k.KemID, k.KdfID, k.AeadID) 77 | if err != nil { 78 | return nil 79 | } 80 | 81 | identifiers := make([]byte, 8) 82 | binary.BigEndian.PutUint16(identifiers[0:], uint16(k.KemID)) 83 | binary.BigEndian.PutUint16(identifiers[2:], uint16(k.KdfID)) 84 | binary.BigEndian.PutUint16(identifiers[4:], uint16(k.AeadID)) 85 | binary.BigEndian.PutUint16(identifiers[6:], uint16(len(k.PublicKeyBytes))) 86 | config := append(identifiers, k.PublicKeyBytes...) 87 | 88 | prk := suite.KDF.Extract(nil, config) 89 | identifier := suite.KDF.Expand(prk, []byte(ODOH_LABEL_KEY_ID), suite.KDF.OutputSize()) 90 | 91 | return identifier 92 | } 93 | 94 | func (k ObliviousDoHConfigContents) Marshal() []byte { 95 | identifiers := make([]byte, 8) 96 | binary.BigEndian.PutUint16(identifiers[0:], uint16(k.KemID)) 97 | binary.BigEndian.PutUint16(identifiers[2:], uint16(k.KdfID)) 98 | binary.BigEndian.PutUint16(identifiers[4:], uint16(k.AeadID)) 99 | binary.BigEndian.PutUint16(identifiers[6:], uint16(len(k.PublicKeyBytes))) 100 | 101 | response := append(identifiers, k.PublicKeyBytes...) 102 | return response 103 | } 104 | 105 | func UnmarshalObliviousDoHConfigContents(buffer []byte) (ObliviousDoHConfigContents, error) { 106 | if len(buffer) < 8 { 107 | return ObliviousDoHConfigContents{}, errors.New("Invalid serialized ObliviousDoHConfigContents") 108 | } 109 | 110 | kemId := binary.BigEndian.Uint16(buffer[0:]) 111 | kdfId := binary.BigEndian.Uint16(buffer[2:]) 112 | aeadId := binary.BigEndian.Uint16(buffer[4:]) 113 | publicKeyLength := binary.BigEndian.Uint16(buffer[6:]) 114 | 115 | if len(buffer[8:]) < int(publicKeyLength) { 116 | return ObliviousDoHConfigContents{}, errors.New("Invalid serialized ObliviousDoHConfigContents") 117 | } 118 | 119 | publicKeyBytes := buffer[8 : 8+publicKeyLength] 120 | 121 | var KemID hpke.KEMID 122 | var KdfID hpke.KDFID 123 | var AeadID hpke.AEADID 124 | 125 | switch kemId { 126 | case 0x0010: 127 | KemID = hpke.DHKEM_P256 128 | case 0x0012: 129 | KemID = hpke.DHKEM_P521 130 | case 0x0020: 131 | KemID = hpke.DHKEM_X25519 132 | case 0x0021: 133 | KemID = hpke.DHKEM_X448 134 | default: 135 | return ObliviousDoHConfigContents{}, fmt.Errorf("Unsupported KEMID: %04x", kemId) 136 | } 137 | 138 | switch kdfId { 139 | case 0x0001: 140 | KdfID = hpke.KDF_HKDF_SHA256 141 | case 0x0002: 142 | KdfID = hpke.KDF_HKDF_SHA384 143 | case 0x0003: 144 | KdfID = hpke.KDF_HKDF_SHA512 145 | default: 146 | return ObliviousDoHConfigContents{}, fmt.Errorf("Unsupported KDFID: %04x", kdfId) 147 | } 148 | 149 | switch aeadId { 150 | case 0x0001: 151 | AeadID = hpke.AEAD_AESGCM128 152 | case 0x0002: 153 | AeadID = hpke.AEAD_AESGCM256 154 | case 0x0003: 155 | AeadID = hpke.AEAD_CHACHA20POLY1305 156 | default: 157 | return ObliviousDoHConfigContents{}, fmt.Errorf("Unsupported AEADID: %04x", aeadId) 158 | } 159 | 160 | suite, err := hpke.AssembleCipherSuite(KemID, KdfID, AeadID) 161 | if err != nil { 162 | return ObliviousDoHConfigContents{}, errors.New("Unsupported HPKE ciphersuite") 163 | } 164 | 165 | _, err = suite.KEM.DeserializePublicKey(publicKeyBytes) 166 | if err != nil { 167 | return ObliviousDoHConfigContents{}, errors.New("Invalid HPKE public key bytes") 168 | } 169 | 170 | return ObliviousDoHConfigContents{ 171 | KemID: KemID, 172 | KdfID: KdfID, 173 | AeadID: AeadID, 174 | PublicKeyBytes: publicKeyBytes, 175 | }, nil 176 | } 177 | 178 | func (k ObliviousDoHConfigContents) PublicKey() []byte { 179 | return k.PublicKeyBytes 180 | } 181 | 182 | func (k ObliviousDoHConfigContents) CipherSuite() (hpke.CipherSuite, error) { 183 | return hpke.AssembleCipherSuite(k.KemID, k.KdfID, k.AeadID) 184 | } 185 | 186 | type ObliviousDoHConfig struct { 187 | Version uint16 188 | Contents ObliviousDoHConfigContents 189 | } 190 | 191 | func CreateObliviousDoHConfig(contents ObliviousDoHConfigContents) ObliviousDoHConfig { 192 | return ObliviousDoHConfig{ 193 | Version: ODOH_VERSION, 194 | Contents: contents, 195 | } 196 | } 197 | 198 | func (c ObliviousDoHConfig) Marshal() []byte { 199 | marshalledConfig := c.Contents.Marshal() 200 | 201 | buffer := make([]byte, 4) 202 | binary.BigEndian.PutUint16(buffer[0:], uint16(c.Version)) 203 | binary.BigEndian.PutUint16(buffer[2:], uint16(len(marshalledConfig))) 204 | 205 | configBytes := append(buffer, marshalledConfig...) 206 | return configBytes 207 | } 208 | 209 | func parseConfigHeader(buffer []byte) (uint16, uint16, error) { 210 | if len(buffer) < 4 { 211 | return uint16(0), uint16(0), errors.New("Invalid ObliviousDoHConfig encoding") 212 | } 213 | 214 | version := binary.BigEndian.Uint16(buffer[0:]) 215 | length := binary.BigEndian.Uint16(buffer[2:]) 216 | return version, length, nil 217 | } 218 | 219 | func isSupportedConfigVersion(version uint16) bool { 220 | return version == ODOH_VERSION 221 | } 222 | 223 | func UnmarshalObliviousDoHConfig(buffer []byte) (ObliviousDoHConfig, error) { 224 | version, length, err := parseConfigHeader(buffer) 225 | if err != nil { 226 | return ObliviousDoHConfig{}, err 227 | } 228 | 229 | if !isSupportedConfigVersion(version) { 230 | return ObliviousDoHConfig{}, fmt.Errorf("Unsupported version: %04x", version) 231 | } 232 | if len(buffer[4:]) < int(length) { 233 | return ObliviousDoHConfig{}, fmt.Errorf("Invalid serialized ObliviousDoHConfig, expected %v bytes, got %v", length, len(buffer[4:])) 234 | } 235 | 236 | configContents, err := UnmarshalObliviousDoHConfigContents(buffer[4:]) 237 | if err != nil { 238 | return ObliviousDoHConfig{}, err 239 | } 240 | 241 | return ObliviousDoHConfig{ 242 | Version: version, 243 | Contents: configContents, 244 | }, nil 245 | } 246 | 247 | type ObliviousDoHConfigs struct { 248 | Configs []ObliviousDoHConfig 249 | } 250 | 251 | func CreateObliviousDoHConfigs(configs []ObliviousDoHConfig) ObliviousDoHConfigs { 252 | return ObliviousDoHConfigs{ 253 | Configs: configs, 254 | } 255 | } 256 | 257 | func (c ObliviousDoHConfigs) Marshal() []byte { 258 | serializedConfigs := make([]byte, 0) 259 | for _, config := range c.Configs { 260 | serializedConfigs = append(serializedConfigs, config.Marshal()...) 261 | } 262 | 263 | buffer := make([]byte, 2) 264 | binary.BigEndian.PutUint16(buffer[0:], uint16(len(serializedConfigs))) 265 | 266 | result := append(buffer, serializedConfigs...) 267 | return result 268 | } 269 | 270 | func UnmarshalObliviousDoHConfigs(buffer []byte) (ObliviousDoHConfigs, error) { 271 | if len(buffer) < 2 { 272 | return ObliviousDoHConfigs{}, errors.New("Invalid ObliviousDoHConfigs encoding") 273 | } 274 | 275 | configs := make([]ObliviousDoHConfig, 0) 276 | length := binary.BigEndian.Uint16(buffer[0:]) 277 | offset := uint16(2) 278 | 279 | for { 280 | configVersion, configLength, err := parseConfigHeader(buffer[offset:]) 281 | if err != nil { 282 | return ObliviousDoHConfigs{}, errors.New("Invalid ObliviousDoHConfigs encoding") 283 | } 284 | 285 | if uint16(len(buffer[offset:])) < configLength { 286 | // The configs vector is encoded incorrectly, so discard the whole thing 287 | return ObliviousDoHConfigs{}, fmt.Errorf("Invalid serialized ObliviousDoHConfig, expected %v bytes, got %v", length, len(buffer[offset:])) 288 | } 289 | 290 | if isSupportedConfigVersion(configVersion) { 291 | config, err := UnmarshalObliviousDoHConfig(buffer[offset:]) 292 | if err == nil { 293 | configs = append(configs, config) 294 | } 295 | } // else skip over unsupported versions 296 | 297 | offset += 4 + configLength 298 | if offset >= 2+length { 299 | // Stop reading 300 | break 301 | } 302 | } 303 | 304 | return CreateObliviousDoHConfigs(configs), nil 305 | } 306 | 307 | type ObliviousDoHKeyPair struct { 308 | Config ObliviousDoHConfig 309 | secretKey hpke.KEMPrivateKey 310 | Seed []byte 311 | } 312 | 313 | func CreateKeyPairFromSeed(kemID hpke.KEMID, kdfID hpke.KDFID, aeadID hpke.AEADID, ikm []byte) (ObliviousDoHKeyPair, error) { 314 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 315 | if err != nil { 316 | return ObliviousDoHKeyPair{}, err 317 | } 318 | 319 | sk, pk, err := suite.KEM.DeriveKeyPair(ikm) 320 | if err != nil { 321 | return ObliviousDoHKeyPair{}, err 322 | } 323 | 324 | configContents, err := CreateObliviousDoHConfigContents(kemID, kdfID, aeadID, suite.KEM.SerializePublicKey(pk)) 325 | if err != nil { 326 | return ObliviousDoHKeyPair{}, err 327 | } 328 | 329 | config := CreateObliviousDoHConfig(configContents) 330 | 331 | return ObliviousDoHKeyPair{ 332 | Config: config, 333 | secretKey: sk, 334 | Seed: ikm, 335 | }, nil 336 | } 337 | 338 | func CreateDefaultKeyPairFromSeed(seed []byte) (ObliviousDoHKeyPair, error) { 339 | return CreateKeyPairFromSeed(ODOH_DEFAULT_KEMID, ODOH_DEFAULT_KDFID, ODOH_DEFAULT_AEADID, seed) 340 | } 341 | 342 | func CreateKeyPair(kemID hpke.KEMID, kdfID hpke.KDFID, aeadID hpke.AEADID) (ObliviousDoHKeyPair, error) { 343 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 344 | if err != nil { 345 | return ObliviousDoHKeyPair{}, err 346 | } 347 | 348 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 349 | _, err = rand.Reader.Read(ikm) 350 | if err != nil { 351 | return ObliviousDoHKeyPair{}, err 352 | } 353 | sk, pk, err := suite.KEM.DeriveKeyPair(ikm) 354 | if err != nil { 355 | return ObliviousDoHKeyPair{}, err 356 | } 357 | 358 | configContents, err := CreateObliviousDoHConfigContents(kemID, kdfID, aeadID, suite.KEM.SerializePublicKey(pk)) 359 | if err != nil { 360 | return ObliviousDoHKeyPair{}, err 361 | } 362 | 363 | config := CreateObliviousDoHConfig(configContents) 364 | 365 | return ObliviousDoHKeyPair{ 366 | Config: config, 367 | secretKey: sk, 368 | Seed: ikm, 369 | }, nil 370 | } 371 | 372 | func CreateDefaultKeyPair() (ObliviousDoHKeyPair, error) { 373 | return CreateKeyPair(ODOH_DEFAULT_KEMID, ODOH_DEFAULT_KDFID, ODOH_DEFAULT_AEADID) 374 | } 375 | 376 | type QueryContext struct { 377 | query []byte 378 | suite hpke.CipherSuite 379 | secret []byte 380 | publicKey ObliviousDoHConfigContents 381 | } 382 | 383 | func (c QueryContext) DecryptResponse(message ObliviousDNSMessage) ([]byte, error) { 384 | responseNonceSize := c.suite.AEAD.KeySize() 385 | if responseNonceSize < c.suite.AEAD.NonceSize() { 386 | responseNonceSize = c.suite.AEAD.NonceSize() 387 | } 388 | 389 | if len(message.KeyID) != responseNonceSize { 390 | return nil, fmt.Errorf("Invalid response key ID length: expected %v, got %v", responseNonceSize, len(message.KeyID)) 391 | } 392 | 393 | encodedResponseNonce := encodeLengthPrefixedSlice(message.KeyID) 394 | salt := append(c.query, encodedResponseNonce...) 395 | prk := c.suite.KDF.Extract(salt, c.secret) 396 | key := c.suite.KDF.Expand(prk, []byte(ODOH_LABEL_KEY), c.suite.AEAD.KeySize()) 397 | nonce := c.suite.KDF.Expand(prk, []byte(ODOH_LABEL_NONCE), c.suite.AEAD.NonceSize()) 398 | 399 | aead, err := c.suite.AEAD.New(key) 400 | if err != nil { 401 | return nil, err 402 | } 403 | 404 | aad := append([]byte{byte(ResponseType)}, encodedResponseNonce...) 405 | return aead.Open(nil, nonce, message.EncryptedMessage, aad) 406 | } 407 | 408 | type ResponseContext struct { 409 | query []byte 410 | suite hpke.CipherSuite 411 | secret []byte 412 | } 413 | 414 | func (c ResponseContext) encryptResponseWithNonce(response *ObliviousDNSResponse, responseNonce []byte) (ObliviousDNSMessage, error) { 415 | encodedResponseNonce := encodeLengthPrefixedSlice(responseNonce) 416 | salt := append(c.query, encodedResponseNonce...) 417 | prk := c.suite.KDF.Extract(salt, c.secret) 418 | key := c.suite.KDF.Expand(prk, []byte(ODOH_LABEL_KEY), c.suite.AEAD.KeySize()) 419 | nonce := c.suite.KDF.Expand(prk, []byte(ODOH_LABEL_NONCE), c.suite.AEAD.NonceSize()) 420 | 421 | aead, err := c.suite.AEAD.New(key) 422 | if err != nil { 423 | return ObliviousDNSMessage{}, err 424 | } 425 | 426 | aad := append([]byte{byte(ResponseType)}, encodedResponseNonce...) 427 | ciphertext := aead.Seal(nil, nonce, response.Marshal(), aad) 428 | 429 | odohMessage := ObliviousDNSMessage{ 430 | KeyID: responseNonce, 431 | MessageType: ResponseType, 432 | EncryptedMessage: ciphertext, 433 | } 434 | 435 | return odohMessage, nil 436 | } 437 | 438 | func (c ResponseContext) EncryptResponse(response *ObliviousDNSResponse) (ObliviousDNSMessage, error) { 439 | responseNonceSize := c.suite.AEAD.KeySize() 440 | if responseNonceSize < c.suite.AEAD.NonceSize() { 441 | responseNonceSize = c.suite.AEAD.NonceSize() 442 | } 443 | 444 | responseNonce := make([]byte, responseNonceSize) 445 | read, err := rand.Read(responseNonce) 446 | if err != nil { 447 | return ObliviousDNSMessage{}, err 448 | } 449 | if read != responseNonceSize { 450 | return ObliviousDNSMessage{}, fmt.Errorf("Failed to read %v bytes", responseNonceSize) 451 | } 452 | 453 | return c.encryptResponseWithNonce(response, responseNonce) 454 | } 455 | 456 | func (targetKey ObliviousDoHConfigContents) EncryptQuery(query *ObliviousDNSQuery) (ObliviousDNSMessage, QueryContext, error) { 457 | suite, err := hpke.AssembleCipherSuite(targetKey.KemID, targetKey.KdfID, targetKey.AeadID) 458 | if err != nil { 459 | return ObliviousDNSMessage{}, QueryContext{}, err 460 | } 461 | 462 | pkR, err := suite.KEM.DeserializePublicKey(targetKey.PublicKeyBytes) 463 | if err != nil { 464 | return ObliviousDNSMessage{}, QueryContext{}, err 465 | } 466 | 467 | enc, ctxI, err := hpke.SetupBaseS(suite, rand.Reader, pkR, []byte(ODOH_LABEL_QUERY)) 468 | if err != nil { 469 | return ObliviousDNSMessage{}, QueryContext{}, err 470 | } 471 | 472 | secret := ctxI.Export([]byte(ODOH_LABEL_RESPONSE), suite.AEAD.KeySize()) 473 | 474 | keyID := targetKey.KeyID() 475 | keyIDLength := make([]byte, 2) 476 | binary.BigEndian.PutUint16(keyIDLength, uint16(len(keyID))) 477 | aad := append([]byte{byte(QueryType)}, keyIDLength...) 478 | aad = append(aad, keyID...) 479 | 480 | encodedMessage := query.Marshal() 481 | ct := ctxI.Seal(aad, encodedMessage) 482 | 483 | return ObliviousDNSMessage{ 484 | KeyID: targetKey.KeyID(), 485 | MessageType: QueryType, 486 | EncryptedMessage: append(enc, ct...), 487 | }, QueryContext{ 488 | secret: secret, 489 | suite: suite, 490 | query: query.Marshal(), 491 | publicKey: targetKey, 492 | }, nil 493 | } 494 | 495 | func validateMessagePadding(padding []byte) bool { 496 | validPadding := 1 497 | for _, v := range padding { 498 | validPadding &= subtle.ConstantTimeByteEq(v, ODOH_PADDING_BYTE) 499 | } 500 | return validPadding == 1 501 | } 502 | 503 | func (privateKey ObliviousDoHKeyPair) DecryptQuery(message ObliviousDNSMessage) (*ObliviousDNSQuery, ResponseContext, error) { 504 | if message.MessageType != QueryType { 505 | return nil, ResponseContext{}, errors.New("message is not a query") 506 | } 507 | 508 | suite, err := hpke.AssembleCipherSuite(privateKey.Config.Contents.KemID, privateKey.Config.Contents.KdfID, privateKey.Config.Contents.AeadID) 509 | if err != nil { 510 | return nil, ResponseContext{}, err 511 | } 512 | 513 | keySize := suite.KEM.PublicKeySize() 514 | enc := message.EncryptedMessage[0:keySize] 515 | ct := message.EncryptedMessage[keySize:] 516 | 517 | ctxR, err := hpke.SetupBaseR(suite, privateKey.secretKey, enc, []byte(ODOH_LABEL_QUERY)) 518 | if err != nil { 519 | return nil, ResponseContext{}, err 520 | } 521 | 522 | secret := ctxR.Export([]byte(ODOH_LABEL_RESPONSE), suite.AEAD.KeySize()) 523 | 524 | keyID := privateKey.Config.Contents.KeyID() 525 | keyIDLength := make([]byte, 2) 526 | binary.BigEndian.PutUint16(keyIDLength, uint16(len(keyID))) 527 | aad := append([]byte{byte(QueryType)}, keyIDLength...) 528 | aad = append(aad, keyID...) 529 | 530 | dnsMessage, err := ctxR.Open(aad, ct) 531 | if err != nil { 532 | return nil, ResponseContext{}, err 533 | } 534 | 535 | query, err := UnmarshalQueryBody(dnsMessage) 536 | if err != nil { 537 | return nil, ResponseContext{}, err 538 | } 539 | 540 | if !validateMessagePadding(query.Padding) { 541 | return nil, ResponseContext{}, errors.New("invalid padding") 542 | } 543 | 544 | responseContext := ResponseContext{ 545 | suite: suite, 546 | query: query.Marshal(), 547 | secret: secret, 548 | } 549 | 550 | return query, responseContext, nil 551 | } 552 | 553 | func SealQuery(dnsQuery []byte, publicKey ObliviousDoHConfigContents) (ObliviousDNSMessage, QueryContext, error) { 554 | odohQuery := CreateObliviousDNSQuery(dnsQuery, 0) 555 | 556 | odohMessage, queryContext, err := publicKey.EncryptQuery(odohQuery) 557 | if err != nil { 558 | return ObliviousDNSMessage{}, QueryContext{}, err 559 | } 560 | 561 | return odohMessage, queryContext, nil 562 | } 563 | 564 | func (c QueryContext) OpenAnswer(message ObliviousDNSMessage) ([]byte, error) { 565 | if message.MessageType != ResponseType { 566 | return nil, errors.New("message is not a response") 567 | } 568 | 569 | decryptedResponseBytes, err := c.DecryptResponse(message) 570 | if err != nil { 571 | return nil, errors.New("unable to decrypt the obtained response using the symmetric key sent") 572 | } 573 | 574 | decryptedResponse, err := UnmarshalResponseBody(decryptedResponseBytes) 575 | if err != nil { 576 | return nil, err 577 | } 578 | 579 | return decryptedResponse.DnsMessage, nil 580 | } 581 | -------------------------------------------------------------------------------- /odoh_test.go: -------------------------------------------------------------------------------- 1 | // The MIT License 2 | // 3 | // Copyright (c) 2019-2020, Cloudflare, Inc. and Apple, Inc. All rights reserved. 4 | // 5 | // Permission is hereby granted, free of charge, to any person obtaining a copy 6 | // of this software and associated documentation files (the "Software"), to deal 7 | // in the Software without restriction, including without limitation the rights 8 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | // copies of the Software, and to permit persons to whom the Software is 10 | // furnished to do so, subject to the following conditions: 11 | // 12 | // The above copyright notice and this permission notice shall be included in 13 | // all copies or substantial portions of the Software. 14 | // 15 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | // THE SOFTWARE. 22 | 23 | package odoh 24 | 25 | import ( 26 | "bytes" 27 | "crypto/rand" 28 | "encoding/binary" 29 | "encoding/hex" 30 | "encoding/json" 31 | "fmt" 32 | "io" 33 | "os" 34 | "testing" 35 | 36 | "github.com/cisco/go-hpke" 37 | ) 38 | 39 | const ( 40 | outputTestVectorEnvironmentKey = "ODOH_TEST_VECTORS_OUT" 41 | inputTestVectorEnvironmentKey = "ODOH_TEST_VECTORS_IN" 42 | ) 43 | 44 | func TestConfigDerivation(t *testing.T) { 45 | keyPair, err := CreateDefaultKeyPair() 46 | if err != nil { 47 | t.Fatalf("CreateDefaultKeyPair failed") 48 | } 49 | 50 | derivedKeyPair, err := CreateDefaultKeyPairFromSeed(keyPair.Seed) 51 | if err != nil { 52 | t.Fatalf("CreateDefaultKeyPair failed") 53 | } 54 | 55 | if keyPair.Config.Version != derivedKeyPair.Config.Version { 56 | t.Fatalf("Mismatched versions.") 57 | } 58 | if !bytes.Equal(keyPair.Config.Marshal(), derivedKeyPair.Config.Marshal()) { 59 | t.Fatalf("Mismatched configs.") 60 | } 61 | if !bytes.Equal(keyPair.Seed, derivedKeyPair.Seed) { 62 | t.Fatalf("Mismatched seeds.") 63 | } 64 | } 65 | 66 | func TestConfigDeserialization(t *testing.T) { 67 | keyPair, err := CreateDefaultKeyPair() 68 | if err != nil { 69 | t.Fatalf("CreateDefaultKeyPair failed") 70 | } 71 | 72 | serializedConfig := keyPair.Config.Marshal() 73 | 74 | recoveredConfig, err := UnmarshalObliviousDoHConfig(serializedConfig) 75 | if err != nil { 76 | t.Fatalf("Failed deserializing config") 77 | } 78 | if recoveredConfig.Version != keyPair.Config.Version { 79 | t.Fatalf("Mismatched versions.") 80 | } 81 | if !bytes.Equal(keyPair.Config.Marshal(), recoveredConfig.Marshal()) { 82 | t.Fatalf("Mismatched configs.") 83 | } 84 | 85 | serializedConfig = append(serializedConfig, 0x00) // append an extra byte 86 | 87 | recoveredConfig, err = UnmarshalObliviousDoHConfig(serializedConfig) 88 | if err != nil { 89 | t.Fatalf("Failed deserializing config") 90 | } 91 | if recoveredConfig.Version != keyPair.Config.Version { 92 | t.Fatalf("Mismatched versions.") 93 | } 94 | if !bytes.Equal(keyPair.Config.Marshal(), recoveredConfig.Marshal()) { 95 | t.Fatalf("Mismatched configs.") 96 | } 97 | } 98 | 99 | func TestConfigDeserializationFailures(t *testing.T) { 100 | keyPair, err := CreateDefaultKeyPair() 101 | if err != nil { 102 | t.Fatalf("CreateDefaultKeyPair failed") 103 | } 104 | 105 | serializedConfig := keyPair.Config.Marshal() 106 | 107 | // Encoding without full version or length 108 | _, err = UnmarshalObliviousDoHConfig(serializedConfig[0:1]) 109 | if err == nil { 110 | t.Fatalf("Failed to deserialize with insufficient length") 111 | } 112 | 113 | // Encoding with a mismatched version 114 | invalidSerializedConfig := serializedConfig[:] 115 | invalidSerializedConfig[0] = invalidSerializedConfig[0] ^ 0xFF 116 | _, err = UnmarshalObliviousDoHConfig(invalidSerializedConfig) 117 | if err == nil { 118 | t.Fatalf("Failed to deserialize with invalid version") 119 | } 120 | 121 | // Encoding with an invalid length 122 | invalidSerializedConfig = serializedConfig[:] 123 | _, err = UnmarshalObliviousDoHConfig(invalidSerializedConfig[0:4]) 124 | if err == nil { 125 | t.Fatalf("Failed to deserialize with insufficient contents length") 126 | } 127 | } 128 | 129 | func mustCreateDefaultKeyPair(t *testing.T) ObliviousDoHKeyPair { 130 | keyPair, err := CreateDefaultKeyPair() 131 | if err != nil { 132 | t.Fatalf("CreateDefaultKeyPair failed") 133 | } 134 | return keyPair 135 | } 136 | 137 | func copySlice(src []byte) []byte { 138 | copied := make([]byte, len(src)) 139 | copy(copied, src) 140 | return copied 141 | } 142 | 143 | func TestConfigsDeserialization(t *testing.T) { 144 | keyPairA := mustCreateDefaultKeyPair(t) 145 | keyPairB := mustCreateDefaultKeyPair(t) 146 | 147 | configSet := []ObliviousDoHConfig{keyPairA.Config, keyPairB.Config} 148 | configs := CreateObliviousDoHConfigs(configSet) 149 | 150 | serializedConfigA := keyPairA.Config.Marshal() 151 | serializedConfigB := keyPairB.Config.Marshal() 152 | serializedConfigs := configs.Marshal() 153 | 154 | if len(serializedConfigs) != 2+len(serializedConfigA)+len(serializedConfigB) { 155 | t.Fatalf("Invalid serialized length. Expected %v, got %v", 2+len(serializedConfigA)+len(serializedConfigB), len(serializedConfigs)) 156 | } 157 | 158 | _, err := UnmarshalObliviousDoHConfigs(serializedConfigs) 159 | if err != nil { 160 | t.Fatalf("UnmarshalObliviousDoHConfigs failed: %v", err) 161 | } 162 | 163 | longSerializedConfigs := append(serializedConfigs, 0x00) 164 | _, err = UnmarshalObliviousDoHConfigs(longSerializedConfigs) 165 | if err != nil { 166 | t.Fatalf("UnmarshalObliviousDoHConfigs failed: %v", err) 167 | } 168 | 169 | invalidSerializedConfigs := copySlice(serializedConfigs) 170 | deserializedConfigs, err := UnmarshalObliviousDoHConfigs(invalidSerializedConfigs[0 : len(invalidSerializedConfigs)-1]) 171 | if err != nil { 172 | t.Fatalf("UnmarshalObliviousDoHConfigs failed to parse first config") 173 | } 174 | if len(deserializedConfigs.Configs) != 1 { 175 | t.Fatalf("UnmarshalObliviousDoHConfigs parsed more than one ObliviousDoHConfig elements, got %v", len(deserializedConfigs.Configs)) 176 | } 177 | 178 | invalidSerializedConfigs = copySlice(serializedConfigs) 179 | invalidSerializedConfigs[0] = 0xFF // Invalidate the outer vector length 180 | _, err = UnmarshalObliviousDoHConfigs(invalidSerializedConfigs) 181 | if err == nil { 182 | t.Fatalf("UnmarshalObliviousDoHConfigs succeeded without enough bytes") 183 | } 184 | 185 | invalidSerializedConfigs = copySlice(serializedConfigs) 186 | invalidSerializedConfigs[2] ^= 0xFF // Flip the version value 187 | deserializedConfigs, err = UnmarshalObliviousDoHConfigs(invalidSerializedConfigs) 188 | if err != nil { 189 | t.Fatalf("UnmarshalObliviousDoHConfigs failed to parse one of the valid configs: %v", err) 190 | } 191 | if len(deserializedConfigs.Configs) != 1 { 192 | t.Fatalf("UnmarshalObliviousDoHConfigs parsed more than one ObliviousDoHConfig elements, got %v", len(deserializedConfigs.Configs)) 193 | } 194 | 195 | invalidSerializedConfigs = copySlice(serializedConfigs) 196 | invalidSerializedConfigs[4] = 0xFF // Extend the length of the first config 197 | _, err = UnmarshalObliviousDoHConfigs(invalidSerializedConfigs) 198 | if err == nil { 199 | t.Fatalf("UnmarshalObliviousDoHConfigs succeeded without enough bytes") 200 | } 201 | } 202 | 203 | func createDefaultSerializedPublicKey(t *testing.T) []byte { 204 | suite, err := hpke.AssembleCipherSuite(ODOH_DEFAULT_KEMID, ODOH_DEFAULT_KDFID, ODOH_DEFAULT_AEADID) 205 | if err != nil { 206 | t.Fatalf("Failed generating HPKE suite") 207 | } 208 | 209 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 210 | _, _ = rand.Read(ikm) 211 | _, publicKey, err := suite.KEM.DeriveKeyPair(ikm) 212 | if err != nil { 213 | t.Fatalf("Failed generating public key") 214 | } 215 | 216 | return suite.KEM.SerializePublicKey(publicKey) 217 | } 218 | 219 | func validateSerializedContents(t *testing.T, configContents ObliviousDoHConfigContents, serializedContents []byte) { 220 | kemId := binary.BigEndian.Uint16(serializedContents[0:]) 221 | kdfId := binary.BigEndian.Uint16(serializedContents[2:]) 222 | aeadId := binary.BigEndian.Uint16(serializedContents[4:]) 223 | publicKeyLength := int(binary.BigEndian.Uint16(serializedContents[6:])) 224 | 225 | if kemId != uint16(ODOH_DEFAULT_KEMID) { 226 | t.Fatalf("Invalid serialized KEMID. Expected %v, got %v.", ODOH_DEFAULT_KEMID, kemId) 227 | } 228 | if kdfId != uint16(ODOH_DEFAULT_KDFID) { 229 | t.Fatalf("Invalid serialized KDFID. Expected %v, got %v.", ODOH_DEFAULT_KDFID, kdfId) 230 | } 231 | if aeadId != uint16(ODOH_DEFAULT_AEADID) { 232 | t.Fatalf("Invalid serialized AEADID. Expected %v, got %v.", ODOH_DEFAULT_AEADID, aeadId) 233 | } 234 | if publicKeyLength != len(configContents.PublicKeyBytes) { 235 | t.Fatalf("Invalid serialized public key length. Expected %v, got %v.", len(configContents.PublicKeyBytes), publicKeyLength) 236 | } 237 | if !bytes.Equal(configContents.PublicKeyBytes, serializedContents[8:8+publicKeyLength]) { 238 | t.Fatalf("Invalid bytes serialized. Expected %x, got %x", configContents.PublicKeyBytes, serializedContents[8:8+publicKeyLength]) 239 | } 240 | } 241 | 242 | func TestConfigContentsSerialization(t *testing.T) { 243 | publicKeyBytes := createDefaultSerializedPublicKey(t) 244 | configContents, err := CreateObliviousDoHConfigContents(ODOH_DEFAULT_KEMID, ODOH_DEFAULT_KDFID, ODOH_DEFAULT_AEADID, publicKeyBytes) 245 | if err != nil { 246 | t.Fatalf("CreateObliviousDoHConfigContents failed: %v", err) 247 | } 248 | 249 | serializedContents := configContents.Marshal() 250 | if len(serializedContents) != 8+len(publicKeyBytes) { 251 | t.Fatalf("Invalid length of serialized ObliviousDoHConfigContents. Expected %v, got %v.", 8+len(publicKeyBytes), len(serializedContents)) 252 | } 253 | 254 | validateSerializedContents(t, configContents, serializedContents) 255 | 256 | serializedContents = append(serializedContents, 0x00) 257 | validateSerializedContents(t, configContents, serializedContents) 258 | } 259 | 260 | func TestConfigContentsDeserialization(t *testing.T) { 261 | publicKeyBytes := createDefaultSerializedPublicKey(t) 262 | configContents, err := CreateObliviousDoHConfigContents(ODOH_DEFAULT_KEMID, ODOH_DEFAULT_KDFID, ODOH_DEFAULT_AEADID, publicKeyBytes) 263 | if err != nil { 264 | t.Fatalf("CreateObliviousDoHConfigContents failed: %v", err) 265 | } 266 | 267 | serializedContents := configContents.Marshal() 268 | 269 | _, err = UnmarshalObliviousDoHConfigContents(serializedContents[0:7]) 270 | if err == nil { 271 | t.Fatalf("Failed to deserialize with insufficient length") 272 | } 273 | 274 | _, err = UnmarshalObliviousDoHConfigContents(serializedContents[0:8]) 275 | if err == nil { 276 | t.Fatalf("Failed to deserialize with insufficient public key length") 277 | } 278 | 279 | invalidSerializedConfig := serializedContents[:] 280 | invalidSerializedConfig[0] = invalidSerializedConfig[0] ^ 0xFF 281 | _, err = UnmarshalObliviousDoHConfigContents(invalidSerializedConfig) 282 | if err == nil { 283 | t.Fatalf("Failed to deserialize with invalid KEMID") 284 | } 285 | 286 | invalidSerializedConfig = serializedContents[:] 287 | invalidSerializedConfig[2] = invalidSerializedConfig[2] ^ 0xFF 288 | _, err = UnmarshalObliviousDoHConfigContents(invalidSerializedConfig) 289 | if err == nil { 290 | t.Fatalf("Failed to deserialize with invalid KDFID") 291 | } 292 | 293 | invalidSerializedConfig = serializedContents[:] 294 | invalidSerializedConfig[4] = invalidSerializedConfig[4] ^ 0xFF 295 | _, err = UnmarshalObliviousDoHConfigContents(invalidSerializedConfig) 296 | if err == nil { 297 | t.Fatalf("Failed to deserialize with invalid AEADID") 298 | } 299 | 300 | invalidSerializedConfig = serializedContents[:] 301 | invalidSerializedConfig[7] = 0x1F // instead of 0x20, for x25519 public keys 302 | _, err = UnmarshalObliviousDoHConfigContents(invalidSerializedConfig[0 : len(invalidSerializedConfig)-1]) 303 | if err == nil { 304 | t.Fatalf("Failed to deserialize with invalid x25519 public key") 305 | } 306 | } 307 | 308 | func TestQueryBodyMarshal(t *testing.T) { 309 | message := []byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 310 | 311 | queryBody := CreateObliviousDNSQuery(message, 0) 312 | 313 | encoded := queryBody.Marshal() 314 | decoded, err := UnmarshalQueryBody(encoded) 315 | if err != nil { 316 | t.Fatalf("Encode/decode failed") 317 | } 318 | if !bytes.Equal(decoded.DnsMessage, message) { 319 | t.Fatalf("Key mismatch") 320 | } 321 | } 322 | 323 | func TestDNSMessageMarshal(t *testing.T) { 324 | keyID := []byte{0x00, 0x01, 0x02, 0x04} 325 | encryptedMessage := []byte{0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F} 326 | 327 | message := ObliviousDNSMessage{ 328 | MessageType: 0x01, 329 | KeyID: keyID, 330 | EncryptedMessage: encryptedMessage, 331 | } 332 | 333 | encoded := message.Marshal() 334 | decoded, err := UnmarshalDNSMessage(encoded) 335 | if err != nil { 336 | t.Fatalf("Encode/decode failed") 337 | } 338 | if decoded.MessageType != 0x01 { 339 | t.Fatalf("MessageType mismatch") 340 | } 341 | if !bytes.Equal(decoded.KeyID, keyID) { 342 | t.Fatalf("KeyID mismatch") 343 | } 344 | if !bytes.Equal(decoded.EncryptedMessage, encryptedMessage) { 345 | t.Fatalf("EncryptedMessage mismatch") 346 | } 347 | } 348 | 349 | func TestQueryEncryption(t *testing.T) { 350 | kemID := hpke.DHKEM_X25519 351 | kdfID := hpke.KDF_HKDF_SHA256 352 | aeadID := hpke.AEAD_AESGCM128 353 | 354 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 355 | if err != nil { 356 | t.Fatalf("[%x, %x, %x] Error looking up ciphersuite: %s", kemID, kdfID, aeadID, err) 357 | } 358 | 359 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 360 | _, err = rand.Reader.Read(ikm) 361 | if err != nil { 362 | t.Fatalf("Cannot read random ikm: %s", err) 363 | } 364 | skR, pkR, err := suite.KEM.DeriveKeyPair(ikm) 365 | if err != nil { 366 | t.Fatalf("[%x, %x, %x] Error generating DH key pair: %s", kemID, kdfID, aeadID, err) 367 | } 368 | 369 | targetKey := ObliviousDoHConfigContents{ 370 | KemID: kemID, 371 | KdfID: kdfID, 372 | AeadID: aeadID, 373 | PublicKeyBytes: suite.KEM.SerializePublicKey(pkR), 374 | } 375 | 376 | targetConfig := ObliviousDoHConfig{ 377 | Contents: targetKey, 378 | } 379 | 380 | odohKeyPair := ObliviousDoHKeyPair{targetConfig, skR, ikm} 381 | 382 | dnsMessage := []byte{0x01, 0x02} 383 | 384 | message := CreateObliviousDNSQuery(dnsMessage, 0) 385 | 386 | encryptedMessage, _, err := targetKey.EncryptQuery(message) 387 | if err != nil { 388 | t.Fatalf("EncryptQuery failed: %s", err) 389 | } 390 | 391 | result, _, err := odohKeyPair.DecryptQuery(encryptedMessage) 392 | if err != nil { 393 | t.Fatalf("DecryptQuery failed: %s", err) 394 | } 395 | 396 | if !bytes.Equal(result.DnsMessage, dnsMessage) { 397 | t.Fatalf("Incorrect DnsMessage returned") 398 | } 399 | } 400 | 401 | func Test_Sender_ODOHQueryEncryption(t *testing.T) { 402 | kemID := hpke.DHKEM_P256 // 0x0010 403 | kdfID := hpke.KDF_HKDF_SHA256 // 0x0001 404 | aeadID := hpke.AEAD_AESGCM128 // 0x0001 405 | 406 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 407 | if err != nil { 408 | t.Fatalf("[%x, %x, %x] Error looking up ciphersuite: %s", kemID, kdfID, aeadID, err) 409 | } 410 | 411 | responseKey := make([]byte, suite.AEAD.KeySize()) 412 | if _, err := io.ReadFull(rand.Reader, responseKey); err != nil { 413 | t.Fatalf("Failed generating random key: %s", err) 414 | } 415 | 416 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 417 | _, err = rand.Reader.Read(ikm) 418 | if err != nil { 419 | t.Fatalf("Cannot read random ikm: %s", err) 420 | } 421 | 422 | skR, pkR, err := suite.KEM.DeriveKeyPair(ikm) 423 | if err != nil { 424 | t.Fatalf("[%x, %x, %x] Error generating DH key pair: %s", kemID, kdfID, aeadID, err) 425 | } 426 | 427 | targetKey := ObliviousDoHConfigContents{ 428 | KemID: kemID, 429 | KdfID: kdfID, 430 | AeadID: aeadID, 431 | PublicKeyBytes: suite.KEM.SerializePublicKey(pkR), 432 | } 433 | 434 | targetConfig := ObliviousDoHConfig{ 435 | Contents: targetKey, 436 | } 437 | 438 | odohKeyPair := ObliviousDoHKeyPair{targetConfig, skR, ikm} 439 | symmetricKey := make([]byte, suite.AEAD.KeySize()) 440 | _, err = rand.Read(symmetricKey) 441 | if err != nil { 442 | t.Fatalf("Cannot read symmetric key: %s", err) 443 | } 444 | 445 | dnsMessage := []byte{0x01, 0x02, 0x03} 446 | message := CreateObliviousDNSQuery(dnsMessage, 0) 447 | 448 | encryptedMessage, _, err := targetKey.EncryptQuery(message) 449 | if err != nil { 450 | t.Fatalf("Failed to encrypt the message using the public key.") 451 | } 452 | 453 | dnsQuery, _, err := odohKeyPair.DecryptQuery(encryptedMessage) 454 | if err != nil { 455 | t.Fatalf("Failed to decrypt message with error: %s", err) 456 | } 457 | 458 | if !bytes.Equal(dnsQuery.DnsMessage, dnsMessage) { 459 | t.Fatalf("Incorrect dnsMessage returned") 460 | } 461 | } 462 | 463 | func TestEncoding(t *testing.T) { 464 | emptySlice := make([]byte, 0) 465 | if !bytes.Equal([]byte{0x00, 0x00}, encodeLengthPrefixedSlice(emptySlice)) { 466 | t.Fatalf("encodeLengthPrefixedSlice for empty slice failed") 467 | } 468 | } 469 | 470 | func TestOdohPublicKeyMarshalUnmarshal(t *testing.T) { 471 | kemID := hpke.DHKEM_P256 // 0x0010 472 | kdfID := hpke.KDF_HKDF_SHA256 // 0x0001 473 | aeadID := hpke.AEAD_AESGCM128 // 0x0001 474 | 475 | suite, err := hpke.AssembleCipherSuite(kemID, kdfID, aeadID) 476 | if err != nil { 477 | t.Fatalf("[%x, %x, %x] Error looking up ciphersuite: %s", kemID, kdfID, aeadID, err) 478 | } 479 | 480 | responseKey := make([]byte, suite.AEAD.KeySize()) 481 | if _, err := io.ReadFull(rand.Reader, responseKey); err != nil { 482 | t.Fatalf("Failed generating random key: %s", err) 483 | } 484 | 485 | ikm := make([]byte, suite.KEM.PrivateKeySize()) 486 | _, err = rand.Reader.Read(ikm) 487 | if err != nil { 488 | t.Fatalf("Cannot read random ikm: %s", err) 489 | } 490 | 491 | _, pkR, err := suite.KEM.DeriveKeyPair(ikm) 492 | if err != nil { 493 | t.Fatalf("[%x, %x, %x] Error generating DH key pair: %s", kemID, kdfID, aeadID, err) 494 | } 495 | 496 | targetKey := ObliviousDoHConfigContents{ 497 | KemID: kemID, 498 | KdfID: kdfID, 499 | AeadID: aeadID, 500 | PublicKeyBytes: suite.KEM.SerializePublicKey(pkR), 501 | } 502 | 503 | serializedPublicKey := targetKey.Marshal() 504 | deserializedPublicKey, err := UnmarshalObliviousDoHConfigContents(serializedPublicKey) 505 | if err != nil { 506 | t.Fatalf("UnmarshalObliviousDoHConfigContents failed: %v", err) 507 | } 508 | 509 | if !bytes.Equal(deserializedPublicKey.PublicKeyBytes, targetKey.PublicKeyBytes) { 510 | t.Fatalf("The deserialized and serialized bytes do not match.") 511 | } 512 | 513 | if deserializedPublicKey.KemID != targetKey.KemID { 514 | t.Fatalf("The KEM IDs do not match.") 515 | } 516 | 517 | if deserializedPublicKey.KdfID != targetKey.KdfID { 518 | t.Fatalf("The KDF IDs do not match.") 519 | } 520 | 521 | if deserializedPublicKey.AeadID != targetKey.AeadID { 522 | t.Fatalf("The AEAD IDs do not match.") 523 | } 524 | } 525 | 526 | func TestFixedOdohKeyPairCreation(t *testing.T) { 527 | const ( 528 | kemID = hpke.DHKEM_X25519 529 | kdfID = hpke.KDF_HKDF_SHA256 530 | aeadID = hpke.AEAD_AESGCM128 531 | ) 532 | 533 | // Fixed 16 byte seed 534 | seedHex := "f7c664a7959b2aa02ffa7abb0d2022ab" 535 | seed, err := hex.DecodeString(seedHex) 536 | if err != nil { 537 | t.Fatalf("Unable to decode seed to bytes") 538 | } 539 | keyPair, err := CreateKeyPairFromSeed(kemID, kdfID, aeadID, seed) 540 | if err != nil { 541 | t.Fatalf("Unable to derive a ObliviousDoHKeyPair") 542 | } 543 | for i := 0; i < 10; i++ { 544 | keyPairDerived, err := CreateKeyPairFromSeed(kemID, kdfID, aeadID, seed) 545 | if err != nil { 546 | t.Fatalf("Unable to derive a ObliviousDoHKeyPair") 547 | } 548 | if !bytes.Equal(keyPairDerived.Config.Contents.Marshal(), keyPair.Config.Contents.Marshal()) { 549 | t.Fatalf("Public Key Derived does not match") 550 | } 551 | } 552 | } 553 | 554 | func TestSealQueryAndOpenAnswer(t *testing.T) { 555 | kemID := hpke.DHKEM_X25519 556 | kdfID := hpke.KDF_HKDF_SHA256 557 | aeadID := hpke.AEAD_AESGCM128 558 | 559 | kp, err := CreateKeyPair(kemID, kdfID, aeadID) 560 | if err != nil { 561 | t.Fatalf("Unable to create a Key Pair") 562 | } 563 | 564 | dnsQueryData := make([]byte, 40) 565 | _, err = rand.Read(dnsQueryData) 566 | if err != nil { 567 | t.Fatalf("Cannot read dns query data: %s", err) 568 | } 569 | 570 | encryptedData, queryContext, err := SealQuery(dnsQueryData, kp.Config.Contents) 571 | if err != nil { 572 | t.Fatalf("Cannot seal query: %s", err) 573 | } 574 | 575 | mockAnswerData := make([]byte, 100) 576 | _, err = rand.Read(mockAnswerData) 577 | if err != nil { 578 | t.Fatalf("Cannot read mock answer: %s", err) 579 | } 580 | 581 | _, responseContext, err := kp.DecryptQuery(encryptedData) 582 | if err != nil { 583 | t.Fatalf("Cannot decrypt query: %s", err) 584 | } 585 | 586 | mockResponse := CreateObliviousDNSResponse(mockAnswerData, 0) 587 | encryptedAnswer, err := responseContext.EncryptResponse(mockResponse) 588 | if err != nil { 589 | t.Fatalf("Cannot encrypt response: %s", err) 590 | } 591 | 592 | response, err := queryContext.OpenAnswer(encryptedAnswer) 593 | if err != nil { 594 | t.Fatalf("Cannot open answer: %s", err) 595 | } 596 | 597 | if !bytes.Equal(response, mockAnswerData) { 598 | t.Fatalf("Decryption of the result does not match encrypted value") 599 | } 600 | } 601 | 602 | // ///// 603 | // Assertions 604 | func assert(t *testing.T, msg string, test bool) { 605 | if !test { 606 | t.Fatalf("%s", msg) 607 | } 608 | } 609 | 610 | func assertBytesEqual(t *testing.T, msg string, lhs, rhs []byte) { 611 | realMsg := fmt.Sprintf("%s: [%x] != [%x]", msg, lhs, rhs) 612 | assert(t, realMsg, bytes.Equal(lhs, rhs)) 613 | } 614 | 615 | func assertNotError(t *testing.T, msg string, err error) { 616 | realMsg := fmt.Sprintf("%s: %v", msg, err) 617 | assert(t, realMsg, err == nil) 618 | } 619 | 620 | func fatalOnError(t *testing.T, err error, msg string) { 621 | realMsg := fmt.Sprintf("%s: %v", msg, err) 622 | if err != nil { 623 | if t != nil { 624 | t.Fatalf(realMsg) 625 | } 626 | panic(realMsg) 627 | } 628 | } 629 | 630 | func mustUnhex(t *testing.T, h string) []byte { 631 | out, err := hex.DecodeString(h) 632 | fatalOnError(t, err, "Unhex failed") 633 | return out 634 | } 635 | 636 | func mustHex(d []byte) string { 637 | return hex.EncodeToString(d) 638 | } 639 | 640 | /* Unused ... 641 | func mustDeserializePub(t *testing.T, suite hpke.CipherSuite, h string, required bool) hpke.KEMPublicKey { 642 | pkm := mustUnhex(t, h) 643 | pk, err := suite.KEM.DeserializePublicKey(pkm) 644 | if required { 645 | fatalOnError(t, err, "Deserialize failed") 646 | } 647 | return pk 648 | } 649 | 650 | func mustSerializePub(suite hpke.CipherSuite, pub hpke.KEMPublicKey) string { 651 | return mustHex(suite.KEM.SerializePublicKey(pub)) 652 | } 653 | ... Unused */ 654 | 655 | // ///// 656 | // Query/Response transaction test vector structure 657 | type rawTransactionTestVector struct { 658 | Query string `json:"query"` 659 | QueryPaddingLength int `json:"queryPaddingLength"` 660 | Response string `json:"response"` 661 | ResponsePaddingLength int `json:"responsePaddingLength"` 662 | ObliviousQuery string `json:"obliviousQuery"` 663 | ObliviousResponse string `json:"obliviousResponse"` 664 | } 665 | 666 | type transactionTestVector struct { 667 | query []byte 668 | queryPaddingLength uint16 669 | response []byte 670 | responsePaddingLength uint16 671 | obliviousQuery ObliviousDNSMessage 672 | obliviousResponse ObliviousDNSMessage 673 | } 674 | 675 | func (etv transactionTestVector) MarshalJSON() ([]byte, error) { 676 | return json.Marshal(rawTransactionTestVector{ 677 | Query: mustHex(etv.query), 678 | QueryPaddingLength: int(etv.queryPaddingLength), 679 | Response: mustHex(etv.response), 680 | ResponsePaddingLength: int(etv.responsePaddingLength), 681 | ObliviousQuery: mustHex(etv.obliviousQuery.Marshal()), 682 | ObliviousResponse: mustHex(etv.obliviousResponse.Marshal()), 683 | }) 684 | } 685 | 686 | func (etv *transactionTestVector) UnmarshalJSON(data []byte) error { 687 | raw := rawTransactionTestVector{} 688 | err := json.Unmarshal(data, &raw) 689 | if err != nil { 690 | return err 691 | } 692 | 693 | etv.query = mustUnhex(nil, raw.Query) 694 | etv.queryPaddingLength = uint16(raw.QueryPaddingLength) 695 | etv.response = mustUnhex(nil, raw.Response) 696 | etv.responsePaddingLength = uint16(raw.ResponsePaddingLength) 697 | 698 | obliviousQueryBytes := mustUnhex(nil, raw.ObliviousQuery) 699 | obliviousResponseBytes := mustUnhex(nil, raw.ObliviousResponse) 700 | 701 | etv.obliviousQuery, err = UnmarshalDNSMessage(obliviousQueryBytes) 702 | if err != nil { 703 | return err 704 | } 705 | etv.obliviousResponse, err = UnmarshalDNSMessage(obliviousResponseBytes) 706 | if err != nil { 707 | return err 708 | } 709 | 710 | return nil 711 | } 712 | 713 | type rawTestVector struct { 714 | KemID int `json:"kem_id"` 715 | KdfID int `json:"kdf_id"` 716 | AeadID int `json:"aead_id"` 717 | Configs string `json:"odohconfigs"` 718 | PublicKeySeed string `json:"public_key_seed"` 719 | KeyId string `json:"key_id"` 720 | 721 | Transactions []transactionTestVector `json:"transactions"` 722 | } 723 | 724 | type testVector struct { 725 | t *testing.T 726 | kem_id hpke.KEMID 727 | kdf_id hpke.KDFID 728 | aead_id hpke.AEADID 729 | odoh_configs []byte 730 | public_key_seed []byte 731 | key_id []byte 732 | 733 | transactions []transactionTestVector 734 | } 735 | 736 | func (tv testVector) MarshalJSON() ([]byte, error) { 737 | return json.Marshal(rawTestVector{ 738 | KemID: int(tv.kem_id), 739 | KdfID: int(tv.kdf_id), 740 | AeadID: int(tv.aead_id), 741 | Configs: mustHex(tv.odoh_configs), 742 | PublicKeySeed: mustHex(tv.public_key_seed), 743 | KeyId: mustHex(tv.key_id), 744 | Transactions: tv.transactions, 745 | }) 746 | } 747 | 748 | func (tv *testVector) UnmarshalJSON(data []byte) error { 749 | raw := rawTestVector{} 750 | err := json.Unmarshal(data, &raw) 751 | if err != nil { 752 | return err 753 | } 754 | 755 | tv.kem_id = hpke.KEMID(raw.KemID) 756 | tv.kdf_id = hpke.KDFID(raw.KdfID) 757 | tv.aead_id = hpke.AEADID(raw.AeadID) 758 | tv.public_key_seed = mustUnhex(tv.t, raw.PublicKeySeed) 759 | tv.odoh_configs = mustUnhex(tv.t, raw.Configs) 760 | tv.key_id = mustUnhex(tv.t, raw.KeyId) 761 | 762 | tv.transactions = raw.Transactions 763 | return nil 764 | } 765 | 766 | type testVectorArray struct { 767 | t *testing.T 768 | vectors []testVector 769 | } 770 | 771 | func (tva testVectorArray) MarshalJSON() ([]byte, error) { 772 | return json.Marshal(tva.vectors) 773 | } 774 | 775 | func (tva *testVectorArray) UnmarshalJSON(data []byte) error { 776 | err := json.Unmarshal(data, &tva.vectors) 777 | if err != nil { 778 | return err 779 | } 780 | 781 | for i := range tva.vectors { 782 | tva.vectors[i].t = tva.t 783 | } 784 | return nil 785 | } 786 | 787 | func generateRandomData(n int) []byte { 788 | data := make([]byte, n) 789 | _, err := rand.Read(data) 790 | if err != nil { 791 | panic(err) 792 | } 793 | return data 794 | } 795 | 796 | func generateTransaction(t *testing.T, kp ObliviousDoHKeyPair, querySize int, queryPadding, responsePadding uint16) transactionTestVector { 797 | publicKey := kp.Config.Contents 798 | 799 | mockQueryData := generateRandomData(querySize) 800 | mockResponseData := append(mockQueryData, mockQueryData...) // answer = query || query 801 | 802 | mockQuery := CreateObliviousDNSQuery(mockQueryData, queryPadding) 803 | mockResponse := CreateObliviousDNSResponse(mockResponseData, responsePadding) 804 | 805 | // Run the query/response transaction 806 | obliviousQuery, queryContext, err := publicKey.EncryptQuery(mockQuery) 807 | if err != nil { 808 | t.Fatalf("Query encryption failed: %v", err) 809 | } 810 | 811 | recoveredQuery, responseContext, err := kp.DecryptQuery(obliviousQuery) 812 | if !bytes.Equal(recoveredQuery.Marshal(), mockQuery.Marshal()) { 813 | t.Fatalf("Query decryption did not match plaintext value: %v", err) 814 | } 815 | 816 | obliviousResponse, err := responseContext.EncryptResponse(mockResponse) 817 | if err != nil { 818 | t.Fatalf("Response encryption failed: %v", err) 819 | } 820 | 821 | responseData, err := queryContext.OpenAnswer(obliviousResponse) 822 | if err != nil || !bytes.Equal(responseData, mockResponseData) { 823 | t.Fatalf("Decryption of the result does not match encrypted value: %v", err) 824 | } 825 | 826 | return transactionTestVector{ 827 | query: mockQueryData, 828 | queryPaddingLength: queryPadding, 829 | obliviousQuery: obliviousQuery, 830 | response: mockResponseData, 831 | responsePaddingLength: responsePadding, 832 | obliviousResponse: obliviousResponse, 833 | } 834 | } 835 | 836 | func generateTestVector(t *testing.T, kem_id hpke.KEMID, kdf_id hpke.KDFID, aead_id hpke.AEADID) testVector { 837 | kp, err := CreateKeyPair(kem_id, kdf_id, aead_id) 838 | if err != nil { 839 | t.Fatalf("Unable to create a Key Pair") 840 | } 841 | 842 | queryBlockPaddingLengths := []int{0, 32, 64, 128} 843 | responseBlockPaddingLengths := []int{0, 128, 256, 468} 844 | queryLength := 32 845 | 846 | transactions := make([]transactionTestVector, 0) 847 | for _, queryBlockLength := range queryBlockPaddingLengths { 848 | for _, responseBlockLength := range responseBlockPaddingLengths { 849 | queryPadding := 0 850 | if queryBlockLength > 0 { 851 | queryPadding = queryBlockLength - (queryLength % queryBlockLength) 852 | } 853 | responsePadding := 0 854 | if responseBlockLength > 0 { 855 | responsePadding = responseBlockLength - ((queryLength * 2) % responseBlockLength) 856 | } 857 | 858 | transactions = append(transactions, generateTransaction(t, kp, queryLength, uint16(queryPadding), uint16(responsePadding))) 859 | } 860 | } 861 | 862 | configs := []ObliviousDoHConfig{kp.Config} 863 | vector := testVector{ 864 | t: t, 865 | kem_id: kem_id, 866 | kdf_id: kdf_id, 867 | aead_id: aead_id, 868 | odoh_configs: CreateObliviousDoHConfigs(configs).Marshal(), 869 | public_key_seed: kp.Seed, 870 | key_id: kp.Config.Contents.KeyID(), 871 | transactions: transactions, 872 | } 873 | 874 | return vector 875 | } 876 | 877 | func verifyTestVector(t *testing.T, tv testVector) { 878 | configs, err := UnmarshalObliviousDoHConfigs(tv.odoh_configs) 879 | assertNotError(t, "UnmarshalObliviousDoHConfigs failed", err) 880 | config := configs.Configs[0] 881 | 882 | kp, err := CreateKeyPairFromSeed(config.Contents.KemID, config.Contents.KdfID, config.Contents.AeadID, tv.public_key_seed) 883 | assertNotError(t, "CreateKeyPairFromSeed failed", err) 884 | 885 | expectedKeyId := kp.Config.Contents.KeyID() 886 | assertBytesEqual(t, "KeyID mismatch", expectedKeyId, tv.key_id) 887 | 888 | for _, transaction := range tv.transactions { 889 | query, responseContext, err := kp.DecryptQuery(transaction.obliviousQuery) 890 | assertNotError(t, "Query decryption failed", err) 891 | assertBytesEqual(t, "Query decryption mismatch", query.DnsMessage, transaction.query) 892 | 893 | testResponse := CreateObliviousDNSResponse(transaction.response, transaction.responsePaddingLength) 894 | obliviousResponse, err := responseContext.encryptResponseWithNonce(testResponse, transaction.obliviousResponse.KeyID) 895 | assertNotError(t, "Response encryption failed", err) 896 | assertBytesEqual(t, "Response encryption mismatch", obliviousResponse.Marshal(), transaction.obliviousResponse.Marshal()) 897 | 898 | // Rebuild decryption context, since we don't control the client's ephemeral key 899 | queryContext := QueryContext{ 900 | secret: responseContext.secret, 901 | query: query.Marshal(), 902 | suite: responseContext.suite, 903 | } 904 | response, err := queryContext.OpenAnswer(obliviousResponse) 905 | assertNotError(t, "Response decryption failed", err) 906 | assertBytesEqual(t, "Final response encryption mismatch", response, transaction.response) 907 | } 908 | } 909 | 910 | func vectorTest(vector testVector) func(t *testing.T) { 911 | return func(t *testing.T) { 912 | verifyTestVector(t, vector) 913 | } 914 | } 915 | 916 | func verifyTestVectors(t *testing.T, vectorString []byte, subtest bool) { 917 | vectors := testVectorArray{t: t} 918 | err := json.Unmarshal(vectorString, &vectors) 919 | if err != nil { 920 | t.Fatalf("Error decoding test vector string: %v", err) 921 | } 922 | 923 | for _, tv := range vectors.vectors { 924 | test := vectorTest(tv) 925 | if !subtest { 926 | test(t) 927 | } else { 928 | label := fmt.Sprintf("odohconfigs=%x", tv.odoh_configs) 929 | t.Run(label, test) 930 | } 931 | } 932 | } 933 | 934 | func TestVectorGenerate(t *testing.T) { 935 | // This is the mandatory HPKE ciphersuite 936 | supportedKEMs := []hpke.KEMID{hpke.DHKEM_X25519} 937 | supportedKDFs := []hpke.KDFID{hpke.KDF_HKDF_SHA256} 938 | supportedAEADs := []hpke.AEADID{hpke.AEAD_AESGCM128} 939 | 940 | vectors := make([]testVector, 0) 941 | for _, kem_id := range supportedKEMs { 942 | for _, kdf_id := range supportedKDFs { 943 | for _, aead_id := range supportedAEADs { 944 | vectors = append(vectors, generateTestVector(t, kem_id, kdf_id, aead_id)) 945 | } 946 | } 947 | } 948 | 949 | // Encode the test vectors 950 | encoded, err := json.Marshal(vectors) 951 | if err != nil { 952 | t.Fatalf("Error producing test vectors: %v", err) 953 | } 954 | 955 | // Verify that we process them correctly 956 | verifyTestVectors(t, encoded, false) 957 | 958 | // Write them to a file if requested 959 | var outputFile string 960 | if outputFile = os.Getenv(outputTestVectorEnvironmentKey); len(outputFile) > 0 { 961 | err = os.WriteFile(outputFile, encoded, 0644) 962 | if err != nil { 963 | t.Fatalf("Error writing test vectors: %v", err) 964 | } 965 | } 966 | } 967 | 968 | func TestVectorVerify(t *testing.T) { 969 | var inputFile string 970 | if inputFile = os.Getenv(inputTestVectorEnvironmentKey); len(inputFile) == 0 { 971 | t.Skip("Test vectors were not provided") 972 | } 973 | 974 | encoded, err := os.ReadFile(inputFile) 975 | if err != nil { 976 | t.Fatalf("Failed reading test vectors: %v", err) 977 | } 978 | 979 | verifyTestVectors(t, encoded, true) 980 | } 981 | -------------------------------------------------------------------------------- /test-vectors.json: -------------------------------------------------------------------------------- 1 | [{"kem_id":32,"kdf_id":1,"aead_id":1,"odohconfigs":"002c000100280020000100010020c6a793bedbd601c25970b1cc46bea80fdb1a8ec51540d79e4f9f17b8baa9da33","public_key_seed":"c9d84d04e6369fccb8a4d5a264001491221f1b97d9b80dd32c35834bb4462383","key_id":"9265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b9","transactions":[{"query":"9db1072b0ab473d1e4b74b09637d5f8f253ad4047426ab4dfcc58350bb67b60c","queryPaddingLength":0,"response":"9db1072b0ab473d1e4b74b09637d5f8f253ad4047426ab4dfcc58350bb67b60c9db1072b0ab473d1e4b74b09637d5f8f253ad4047426ab4dfcc58350bb67b60c","responsePaddingLength":0,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b90054655d2fa2b2271e40b5a78745e41e6d6c5c181fd1fcffcc30fc451d5ec7fdcc5a6ae0da1cba2bd379da93d02d0e42a3849ec6ba53a54c7c8216f0d3cc2cef30ac54f824f5d8b57657d8c7b95e2c0276580b3851d9","obliviousResponse":"0200100f474d14998a841b15f84388a8af1881005413556bcd8d86194fb47a51982b715b7f253f4f3ea14d89a5dd9d2b67e6c13b0b7eb7ae740c09d915ef77461956ba8acac5c5f1d8965ca0888b1c0aa2a0084b4c2c375b74e1c3d4e51ad3fe8080b7941fd5719df5"},{"query":"44f20987ac22db1994d3bb73826e2a20e24e5ca3e98d13fcf664a96c59fab7a0","queryPaddingLength":0,"response":"44f20987ac22db1994d3bb73826e2a20e24e5ca3e98d13fcf664a96c59fab7a044f20987ac22db1994d3bb73826e2a20e24e5ca3e98d13fcf664a96c59fab7a0","responsePaddingLength":64,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900540af79ff8441b04b98ae2e433879a6aa315eeb9325140fc43f3bbcd1617de271cc08906d35de8c575c61ba3d989e3c1663b6e9a727a97c9326f06d11a9720e89b5f5a7513ad6fbd73ce4d996d6ce2b1c202836691","obliviousResponse":"02001033e1570b05a7a3001041a94bba0c77130094678dfb7e2dbb456ca05a4af5d9f7c2e82564cde42ec37a904d8fb57fb6bdf7661bd9a32df37d2dfe1686ca56544e1b7f435a29aff10ccbf9bc9c996cea7aa69b6a8e123f652b86938d79a7883b756d45f9ca6e0f38ddf8b9e5dac088480f6187a1287b788d3dc4991b532f36736188e1a9e3d7a615cf1b61396652502400bd740e35265357876a9345ea7efe4c7f19a1081dd886"},{"query":"0e8a6efce38ba8b5eb32722ce6ec7a54e8f85774d6e5cb4dadd9b46fe2d0a4e7","queryPaddingLength":0,"response":"0e8a6efce38ba8b5eb32722ce6ec7a54e8f85774d6e5cb4dadd9b46fe2d0a4e70e8a6efce38ba8b5eb32722ce6ec7a54e8f85774d6e5cb4dadd9b46fe2d0a4e7","responsePaddingLength":192,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900546f5d6fdd16247adec53f69d811d92962d107c9307e5596a6284acb0cc177676399ef7402f9f04005d21a2a3f1a92cf5b3d9d3bd11b02eb9c21093cdcc6a9752a34397e7083bde83fc5cc416068c318721b87840c","obliviousResponse":"0200100cc3da3808387c8bac7b6660c017a72d01142de493b3e02d32f513a249e116f0b0b10a839504c19c137d43cb18942526f41952e3b5696c3c06e54bd424c6a2e5530a75ee1432231c461f253e5fb14259d7392dc0eb679cbbca162d82841a90c48275ab63d7e016d5cc88d7b8640d3e2bc3c92dc7ec9110cb9ad72f808d3d44668a0381c1eb094276079a81ed1666bbcff9b89caae5182c086951d53a2021413c9acb9c23666187547e51050cbd768a2f1f46133fbbdbd4ff6d1c25d76a9c31923db2c085d8d4a6fb2ff367bf075fd255a48848bff9fa996abd8ad551402954df99aee06a54406c0d075f5e88e741c20c41dca0de3656df39a5e63e8a8eea0676eab7ecec2e3b3d27a5a2e93837e9ceb33725b7ad16b2b82d36b54d1775b1d46d6a2bb5bb8bd8"},{"query":"03ae8f2dac3c83fcde93b2124da8657092f1172e471514aeddd5507c759171b8","queryPaddingLength":0,"response":"03ae8f2dac3c83fcde93b2124da8657092f1172e471514aeddd5507c759171b803ae8f2dac3c83fcde93b2124da8657092f1172e471514aeddd5507c759171b8","responsePaddingLength":404,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b9005439d4bcd57371463a7fdc7c934619f72766b8179237315588709cff69040ca338b4509a92028ffe59e37e6afa4cc9751a4cb23d206290c950b1e95244fb7ca9e3b9741f7175637e59d40d377dbde8dd3f05354c82","obliviousResponse":"020010a5996cdc5120e2d0e3907a0a2f9ac85201e8f80fb0154d1b66122ca45ab29663b981bb46c20e09d709bd1bcac6c9a0f95c46c308bfbf2b95b690c7f53e26ce6f7b250b8a2580da30e4f0696401d3c37a14ba905be7d38fd3f4bd99c503238b1625e39f92dede0362f25d2b4c8ec6a6417cdc977a0f5ca969f47a5b8ea9b808918e9ac6af255b7e8230e3d690fd21e6e4930254cf3864c91f962bb51daf4628311d1eca84babc91bf8aed503521d91dbe9aa57c88e98a2d3eb194581fdaf4a5c6f79435200d959bd49611d88f91a1925bd170ad7751c11c5e698aef559f8dec1951db1fec4c5eb6c6a840b9d9dfd6894e2f70bd785da6a0ff6dd871ad3f965a47eef53142c98bb3558266c34830fe56bae6b0b0f7051f58c1e52fff09a2d0526f7b31e54accce358d5cebb5381147e342e10fe17a90fc6192354716530f31d8427d656965b0cd9cbce3ad88aa87eae3610da5abef5e1f4897dac8c77a6a4407ea073a7c45f50439b52d6166d8b05e4e5fe9c8360415d85fd131f3d8d12b1d69ee3f22a6c3347ebf92e811dfed2a373bbdc32d8085f0c0b2accd2887b80393d5faf727d2a350cdd9c983107d418a9e0feed96efd1dd6a1e53d03eef3d89bb485c8a67edecbf35f9ee406fa9eb7d7329b8548cb177306c766f964fa08551abec24c75afd5677d16525ee1fcfd6ff18547db4b26398e673953153a82"},{"query":"2491c01a8d9d41d2c346925dbebf34280a490ea45f5d40caab57402ae2e00c5b","queryPaddingLength":32,"response":"2491c01a8d9d41d2c346925dbebf34280a490ea45f5d40caab57402ae2e00c5b2491c01a8d9d41d2c346925dbebf34280a490ea45f5d40caab57402ae2e00c5b","responsePaddingLength":0,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900748384db49bd8f414ff22a525822120c93f1324ee7ca11ab2a942d7a9cd9b1736b4b7fe470508509ed9115d11eeeefab17042be3633c9896fdfcb2325092a57842fe27ce701519dbc0b9bef228ff6fcccacc5245eba80d652f7214f993fb2eaa87348d9d203e97e77f488a3a1f03379bb7b971daaa","obliviousResponse":"0200105db7f417de4b62f692e024dd21a7d8a700549b59c20fb7d2975963837cb103c6e19d8ca2eba8513ab2f976be1f9a3b616ceaef3fc39d32dbbe2346963880079e73e731aa5521cd8196f8372a3df83909e372a61625764bcd55782cdd822900fc88b38812f203"},{"query":"6537c42600c6a3c6db735f8fb9e8e3618acf7508bb315a8862360c4b18dc83b8","queryPaddingLength":32,"response":"6537c42600c6a3c6db735f8fb9e8e3618acf7508bb315a8862360c4b18dc83b86537c42600c6a3c6db735f8fb9e8e3618acf7508bb315a8862360c4b18dc83b8","responsePaddingLength":64,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b90074e743c3dcfadd8b146103a69f59544d25eeb7de64772910b4413c94c5716ae94743655e725a6d5e00e29e05fa812108b03d9913450b08b0ab04a7eeec65e13ab52adcfac71b1ea280cbfd9c5865022835addc74f6f71d5c28358b121fae3150470324d4f4ecd0e49b729b74e525bed627e2668aa0","obliviousResponse":"0200104463598990890a8bf9051685d6694597009499a1d75c78516db57ef3ffcfddc137ac801acf4a8632a1238ca4b21facded26bc60fe132a1d44725f42fff07b11a10d00760592200c8aebd441a16b4902506c529a11e40af23634645e9d1be643274a5ab65cf4fff9346f9a47cc752b885d242b65ebf62b6ace2ae6d8544cbe64310fcfdb85ad0272b142d6a39de262577bdd6c7b573ccb6e9f194c91e1034b40b57e10700a130"},{"query":"bc389e2d2a103b4b016c54480bbc0c730369d5ab49d084650e24de30fd92167a","queryPaddingLength":32,"response":"bc389e2d2a103b4b016c54480bbc0c730369d5ab49d084650e24de30fd92167abc389e2d2a103b4b016c54480bbc0c730369d5ab49d084650e24de30fd92167a","responsePaddingLength":192,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900740a95e5d3623112d9b254b1bb82a547c0164e292b770566d77ea5cba094855701c57f4b9aa09a251360c1845d75a59527b40adf6b90aec8625b14189e3ede90646a8fecf57230e2aca75f8f49a36807d0a7c6dc5b27960cc3d336944ff093a05186908a4361e09a84f5c754732b997d9a80136040","obliviousResponse":"0200108ec1808e931a5ec92aaf363e1eafabae011489b8306746c0780ff2464ab05664329fc03a9d2844ee94664a7f8d468fbb4d43f54730e13558c44416723f18373c10302a042588a5e4fc8fa13f6b81e4cbf0a927e8c16426e81108927dd13cf6c549c1d82d691c8729342fcc6e4e55cd74f238263cba16d2a173f271975953b259b0bf36371a78a465727d1c5c09f67dbe814e956e444af018b46e449f41d6a24b4941a3cb00bda9fe164f9f06d55795a2dafe3e0e51e75582e46f62805ddc384d2321e7d77bcbd23a24306fcc91f6a3dbff3b0141b81a56919660a6a7c326376123a9d4cc34c8b1ac5e0954699d4937db134e930ebb2990cecef6f831163fe4abeaf5eb0cb86e2f64bdcf03eec2cbb5a4b1fc31b50ef365e62766de9952807c161ebdf27dd904"},{"query":"a8566d7387143cea9ceab709bd111db388575e503ddc0df9d82305d8eb70912d","queryPaddingLength":32,"response":"a8566d7387143cea9ceab709bd111db388575e503ddc0df9d82305d8eb70912da8566d7387143cea9ceab709bd111db388575e503ddc0df9d82305d8eb70912d","responsePaddingLength":404,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b9007484d408fdae2844e39f2453fc7230cd465f09756ad93f133350393abf20bada17518d0fb5258dfa417257c591153f940545fdefef666d350b9cd21b2480d7037d7ef00072add3294a6a2b35a50d386549f23178740059c9572a805cb7a3054dfc9506ddb7233e6b77abe8c4b9bfb38ee2c5f6f2c1","obliviousResponse":"02001012027ae831e3828598fb2e1088c975b401e850a72f31aec8c83965f2c05d9eadd46f76e93a898eadd7968a6c8554fc8caf04c70ecd5f7eb2d7bfaeb22d19273b32f47fd1556b5b848b5e683bce2527cec2179a3c859f0ef22bd4d0651edbbd490a54e4a775bd78b1521900af69c4415105ae8a75e659c2ca54d2486435cc6269d1883146e165ac6102478481456c656c326e9a3769d08d1624e02c0feecd707776cec2646947911368404b258e5eb69b9553a84c7ab18b23fde54e39b8b190046db49e58a4004b25149b427c70baef06b62b523942444d5cdddaa5f4b589086a68502bc5ae045594829c8acb78271641d54609de63999dc58cf789ca22804381bdb2a8add3d2040566873ccd109451e0da3a0f724dfaa73fd1d6498161bd8905ad8df83bbd73ed4c2ea30f32db57cf86e2a45827355b1ce3600f3c9ed9211a069a008affc1d1cccd10e31e996ab49a7225d0c37dd2807b97613d332831a38129df04bfc81113e779f1dafae5ca8368aaeb59b08d8269ae16e1de1f1dac80cf5fd9e255cbbe1cf93e1e2ffb4f5dd16e738ea8f0411d2d6741f7c0316f8dc0790c8bba2d717e427f1cea9aafd143fd1a2f899dc6e7a869612d405ae9bb2f8a259e83a83792173e4ef9b582cf6c9792889e082a4b0d0c6c1c5c9a2a8b6c41c793aa0a18774f2041d42882fa950eb0270a3fb92b8c2c8069bb7e6d23"},{"query":"80bdac7b284c4743ea8d665523cd5e538e0edb00df39f4eeadf99564ef34825e","queryPaddingLength":32,"response":"80bdac7b284c4743ea8d665523cd5e538e0edb00df39f4eeadf99564ef34825e80bdac7b284c4743ea8d665523cd5e538e0edb00df39f4eeadf99564ef34825e","responsePaddingLength":0,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900742db0830746e710c6bbc235b08dcb9a2a7c5693b392d911f504f5e1e3e10dd24ef9e91a01ca1ec2b07934a382c7f204afe8ce479d233a7fb8d089e0238ac164d660a11235d7be568d8b4b9f758264b63ab35aa81d4a5f5587fefc30bff990a394e89956a6894e1fe2f8f80c2668de804457eb61b1","obliviousResponse":"0200106cf1ea20eb2346d36117982dd26cbe0b005474c8f2a09c5e94231a605865bdf3bd90b83447a89d050b7f8a903cd3742dd6e8d1e9a18c4c2cc881e7c34599e3d2eb761087844b840514a001785ded23c44e11801464fd38d496996dfb810712b6d3f41384570d"},{"query":"e3ad820972fb3eef6ac0601315738e07d95c8a5c999605fd4c9c17f53783a37b","queryPaddingLength":32,"response":"e3ad820972fb3eef6ac0601315738e07d95c8a5c999605fd4c9c17f53783a37be3ad820972fb3eef6ac0601315738e07d95c8a5c999605fd4c9c17f53783a37b","responsePaddingLength":64,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b90074614b51e9510423fc548f9e395add95867d0b1cc1655b475e2473a762332e361630b0dbb60f7fad5d67be43ec8ffd766199c84905de3df910a54fc6bdf1f819b70984a502ecd1a6affa20d2bb3e8673bcf14c94b51d8e6afb48bbfb5a5714f5f55dd7c13e8926c21003fadcad02e704d594b24c84","obliviousResponse":"020010725e7b46034aaefb468bcf1331fd6e8d0094a90d99eae107c97948be7b10b6f59e6aef1592f76a32b96183c6d2bf14dbf432d0a516ae5e7214fd4a3542b9dcd2a4065a4e42629a7be9e1bbab3ebc355d4460b9462c7f4b8c6213f57adad123de5c4ed065c4c11517b385347ff52455d45e7c92644ce74af5a5b6f2561bb81ea7f9cf02b8f0b68bb44249649c9f725ffcfed41ff242017b86b132b9836ef9fe794896297d470c"},{"query":"6248b8ba8abdd45b4d4f80bcca2a44dbcf1318d0b485c1c09b52e569e0f38490","queryPaddingLength":32,"response":"6248b8ba8abdd45b4d4f80bcca2a44dbcf1318d0b485c1c09b52e569e0f384906248b8ba8abdd45b4d4f80bcca2a44dbcf1318d0b485c1c09b52e569e0f38490","responsePaddingLength":192,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b90074cf7bc59c372e42765bdc1112d1688e06fa0e39b077ab0533b57f34548872df5120db38bfa04adf4c267b278665ef54ddd55d7325b628814f8e1fddaa38ca25832a870cc516016d25001a7e8ca4b32c98d08976df42545b743a6611ac230bedeb9a9c771a79116a2116cde48733b29456bdf1cc7b","obliviousResponse":"02001073fbe6d68b77673566a83b1653c4fa830114e3a281fabc8270f196d0d711ca483df0d44689698a5de66a62a06fe63390ba9f2ff5e35bcc135457d6dca26e32264d4b146aad88568291d9c82775c1f8550bc4e04e9ddede679afd4e2f4862dee5584c8f428bfaeb90363a139145fae371c0135e66db202c27af6b8c86f16aaa15c020be915124cde1115d8d6df8e1de0efe06e7b20d16d345d508f0af1214b15faf54aa9881d256488b445958d96217e0eca99e1f68a34c27c9670910afc6605daeddbdf748a75e4bb7e96487922cf0968542d649906ceba42bc8d31cb192d739552b1658703e0d82f92c4782ff861b6ed1370b72ccea9f5735b3653f88f8e4d363f9952c993ad9d96f8af95800f52f5ac7f6b2c0a107e275b4ab22510e60ed411cb80dfaa03f"},{"query":"fedc4f95704d25ccd1cceda39dddf74c61881b81be055b792ad4b31fc6ca5766","queryPaddingLength":32,"response":"fedc4f95704d25ccd1cceda39dddf74c61881b81be055b792ad4b31fc6ca5766fedc4f95704d25ccd1cceda39dddf74c61881b81be055b792ad4b31fc6ca5766","responsePaddingLength":404,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b9007450b3e91ad6df76e518cf0d14b552a38110bd157d598cecace84bfdacddfce7522ed04ad2751a266371f7d2f82f2cbd747ef4f64ebc0a58437c063a0221a147c5d595cf28707e4a03fe868b0ecc265a98a6288d4e748b1fdea6c2f27c3e8a640bf35ae85ae9d37108c4959994b21bd45f1236cb68","obliviousResponse":"020010ac3b91d15f2854a632b08acfcf9b0a5c01e8a36de291200a38378f3410769606dbadb3d27c992f2c26b3200d59bc293530a8b8046be5a7f9930154130ead268a30f5b69299bf9410d5969ac8959165dd24427ead9ea356aff47fa43598db301c55d0efdcb7d94fdcb6845db1cd162a5f69766c04c3ffe234c94bfb67c61317d2adcd418bd0c002d6b47d83e196cee520fe30a23899e0bde7c304de40d25f1667a90bd12d5f1299ccc0186df85c4214a9d272420fb8e54c6e69ea7754ffa4c142b9a4561f09db8cd0e50bb22963ca0b5ef9beb11e1e3ff21bf82285789df129c8f9a0371057f0638532c13e997cb85e19988424c59833edd54db46e75676fe498fa35b7837b90468123fc1bb21f5627c958ea2f8f44a2a77c15dddda08f489e71915241bc1dc74e1a0cb58ca0a28eedf6e0e74f1b3d37078a7132b12b0e1a34a7cdbb6126236a5205af34ceb3955f4a0233f74e48eca06dc5a7d658c451295922f1c456d21bd409b54fb27ce1392445a688fe81bea8ba422b741bec40a3362a1bf23490405d17d0e7b6035228b0a79ea0240d7ba1231c0ec657abb3c4661cb0c27668552fbe361542f23f0960c514cd56b83e0a37550ae8b579f710079c81f8fa8e39e1fb41c94b4e673524c26a506610822af94aa0c4ea8703d068dd5b0f7c816523fc20806427de2007dcc31e0c0ed6dcdcd42e1a0c70e5651a"},{"query":"3c61a330a885add39b3035b811abc6a950d2f538ace30633485b5a824ed7e91d","queryPaddingLength":96,"response":"3c61a330a885add39b3035b811abc6a950d2f538ace30633485b5a824ed7e91d3c61a330a885add39b3035b811abc6a950d2f538ace30633485b5a824ed7e91d","responsePaddingLength":0,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900b4b731c47830e69f40ab4e0799ea9a09e7686b0a656e5ea3ae7237758f79f1e34ef040ffea243113c4ca3621ae8d8e94717abc02f62db012654f395fe1fa8ee333828f7324e636d78cdb9ef612076fcb19c29f93ff9026f753e2908ac98e1de3dd86b1ecc02ef15ee48cecbcf5edef3f3ee36e0294bd1475d52a4c8d6e02b0bb9f4f173a26148320543760e57abbad20f452c0db3cac59b2273d417f85a40b6dea11fab49ed8f620cce4ff36c9e88d8de68c2a8b9f","obliviousResponse":"020010fff22c9dd6c145f49bcfdb7c9575b57100541ed3d9b2ab6c94e1de9055089bf301cae735dcd116521acffe32e12fb5e8aec084b2f2bda949ccd3b4486f49c470798c38ea69b69a22aae5aa595848d26ce5dba5a68f6b8c07e557751786d81d8d6ddd7a6c4d65"},{"query":"57054db24b335e900b400d13a13c51f5a5686551cba5a2716392e63ff5e3ee83","queryPaddingLength":96,"response":"57054db24b335e900b400d13a13c51f5a5686551cba5a2716392e63ff5e3ee8357054db24b335e900b400d13a13c51f5a5686551cba5a2716392e63ff5e3ee83","responsePaddingLength":64,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900b4828a21fe82b0e7e1a638793d0d48c9bdddf8d437a3228709b329d0d84d912e04bd197e8dee3a5fc928889ff16068b0c971b9527a75bf9ca1edf5fdc705c160d718b328805c47973d0df94921046d83e1d780f1945e287e7d50daf9da242d0aa0b3c95c54b8ad8c482e1d377ab55807db51a17bc609914dfaa1b2681faab814b9fa2424aadc7d48856e30560997b49600c49a92a02dc0fbc3e53464292f0d80f4b7f7101f8f647087f73befb2920810760210b45e","obliviousResponse":"02001055cceebcda0eec6f3b9d5a6506cfcdf70094da984782c222915c312c393394ca5d318cd77c62595e7a9310683b422d4096b249d4f2d40585f79b4919948d3dade740eff71ad85bac1b019be3a6f4ab06fc182c2359390b731b4cca45244978b6473059fb02f2477fbc53a68b23869ff45c9a1467f1dfc96d682bb8a9698703a06f44a7483ff13cb2a3c2b86e0d4c31396d036f28f400688b2ef54f11824799900a7b46609e32"},{"query":"d2f385705b564739e6ece8955af34c3ff4e2e1e9bfafc82867eeac807f6c7c81","queryPaddingLength":96,"response":"d2f385705b564739e6ece8955af34c3ff4e2e1e9bfafc82867eeac807f6c7c81d2f385705b564739e6ece8955af34c3ff4e2e1e9bfafc82867eeac807f6c7c81","responsePaddingLength":192,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900b484100fa15b975f9b747b573dbeaa5e86fb57a7a0f92f180a8efdf9d650ee8126d382e9171757bbf7227bb5149aef5047630bf1a6310b841fc3a6485783a7b610cf34c335fb3f93ce8821d465d755e65a85e04bb438cb8dd7db98f1cdb6a35dd2c26be255a32a485ade757512dd1f023e6aa637c471a3634d4961273e33f52cb736c2e67853974416003b6e099c7bfeaee4fb40dddcd3f8d1ddab2dc8062ae83db35d5e3b61752806f0403390b485aac005ccf610","obliviousResponse":"020010ef6318d8c63d3571d0e2284b6c38f86d0114561dd986901c2c58b9d104a4cd74384dc39841ebb9781263eee117ef3e25ee9f650b23f66695a09601b95f5f1c1e9c171253a185669d40cc5b07c725fa663800cd0ad4fd2c7e50fb6ba53886d963efa90256098da57bf55c0b43cbc16e5caedc65e0de0bc60771820189c4078b26a0ef404669716214646f095f9beb058e5a5e170d2e76063214731132bb97b91ad58e5bc4a3f524998622367d7cdbf75dc8cb785c810feccd4a2ef80e9e4ce45eaa1a623311138f22bcc066b1bbeeb531ddfdcb8d4bd520fca8c7cc4b2fa6ea595105cafc08d59405ac655289d40b151ff1490da36cfb9fb00c1c89eea97e7862402c039c4f4b57174ea22ff678ad22afc53a3bf86339b29906f382030f9952e720062f9ba2f1"},{"query":"2486d426a22fd2d56fa027ea800e676cf15698792e878c8366aa35211f9faceb","queryPaddingLength":96,"response":"2486d426a22fd2d56fa027ea800e676cf15698792e878c8366aa35211f9faceb2486d426a22fd2d56fa027ea800e676cf15698792e878c8366aa35211f9faceb","responsePaddingLength":404,"obliviousQuery":"0100209265d14d640ff991b31892f36326ab601ea84d61964fc7a9c7f981a5313e58b900b49f8ab654e033fcf4b421441f67c0a792690ef3fcafb26e8684c8bd335dc6d561d9355baf8b1365da53b4d39617e51ee7814831b02841b1c618dd93de6205f90adf312079a9b93ac57eaec5ffdd53c2354992f0f3231307e009380f171f2228c639bf4c1af262c0e41552d44f9a5a08cebc13df248cfddea8e77e6ac43cf99bdc18c46fff587141c57dd66b3723df4cfa03501693751ff19ee82aad5bcb3760080d871a83e3690a7a724f49816d2bcc6861a331b9","obliviousResponse":"020010a1a577d296df4c780daecb27de39babc01e858e92949d238e774070c07fe36b9c479176ae8511ee6e5cccd0d653a5c5400bb8411488914db3c40cd808c746c1cf5cc0fde2f4eb966b02c823ec0282c30c9dcd5fc619505d163454f45a189db3c1e7fda97dc55608c2adca39402f643f00f19a29aaa4975f8249f0804cb61231142c4ef93b6a06663db8bd55423f7c29c5a49fe7d510b9c1e2f5c81485c4bc4dd97b17983d45615b16c2f38719b8859a34d553d4e9e989b3dfbd063c89de6139efdf6ceed9a5ed364237a283e6932420700d11da4cac143c49e5095b3772993f915d32c3db880040964b1fc0c4a69e14d40d3d8ee9c2f68dc1d836aae2b02b0cf3bcdd6896890d34c71291cc0e1cb44e60ab03e92713035a84da93d34270077ecc3a735690f94a0ae698663ed53b713ba48c12d3167a46898b70ebfb1e604454d0a77c7ae75cfe7dc7ca7b1ceca00df3ed35b43d75975d8ebc79a8d31bee409101bd3100ff55cfa9eb3e5aee46b39fda19666d2587bd755c69e90beb93effb8214fe34f9551d67b083efdd9e4032fe0b8ba1549e07ea215611fd7a0bdb87393cf28f5175a6e46a6ca65184ad161c79eba089522b5d60e510b85c60b130d9dd6359f0fe984cebf2b8943c1b5b6c96f0c9e8338c7d9d12b9986d2fa40b2d0866521c28bf43c5940d2a91d3da5630a5493ce574121ec504e3eb68d9f"}]}] --------------------------------------------------------------------------------