├── .github └── workflows │ └── semgrep.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── https-server └── main.go ├── opaque.png ├── public ├── go │ ├── client.go │ ├── main-local.go │ └── main-prod.go ├── images │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ └── favicon-32x32.png ├── index.html ├── script.js └── wasm_exec.js └── src ├── common ├── common.go └── errors.go ├── expauth ├── expauth.go ├── expauth_test.go ├── expauth_types.go ├── tls_connection_state.go ├── tls_messages.go └── utils.go ├── integrationtests ├── expauth_integration_test.go ├── helpers.go ├── opaque_ea_integration_test.go └── opaque_integration_test.go ├── ohttp ├── client.go ├── common.go ├── opaque_http_test.go └── server.go ├── opaque ├── core.go ├── core_messages.go ├── core_messages_test.go ├── core_test.go ├── credentials.go ├── credentials_test.go ├── envelope.go ├── envelope_test.go ├── json_encoding.go ├── onetimepad.go ├── onetimepad_test.go ├── oprf.go ├── oprf_test.go ├── registration.go ├── registration_messages.go ├── registration_messages_test.go ├── roles.go ├── test_helpers.go ├── tls_additions.go ├── tls_additions_test.go ├── user_record_table.go ├── user_record_table_test.go └── utils.go ├── opaqueea ├── exported_keys.go ├── json_encoding.go ├── opaque-ea.go ├── opaque-ea_test.go ├── opaque-ea_types.go ├── protocol_messages.go └── registration.go └── testhelp └── test_helpers.go /.github/workflows/semgrep.yml: -------------------------------------------------------------------------------- 1 | 2 | on: 3 | pull_request: {} 4 | workflow_dispatch: {} 5 | push: 6 | branches: 7 | - main 8 | - master 9 | schedule: 10 | - cron: '0 0 * * *' 11 | name: Semgrep config 12 | jobs: 13 | semgrep: 14 | name: semgrep/ci 15 | runs-on: ubuntu-20.04 16 | env: 17 | SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} 18 | SEMGREP_URL: https://cloudflare.semgrep.dev 19 | SEMGREP_APP_URL: https://cloudflare.semgrep.dev 20 | SEMGREP_VERSION_CHECK_URL: https://cloudflare.semgrep.dev/api/check-version 21 | container: 22 | image: returntocorp/semgrep 23 | steps: 24 | - uses: actions/checkout@v3 25 | - run: semgrep ci 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin/ 3 | testing/testing 4 | .vscode/ 5 | .history/ 6 | coverage.txt 7 | profile.cov 8 | .cover/** 9 | integrationtests/test_test.go 10 | expauth-abc.json 11 | opaque-abc.json 12 | opaqueea-abc.json 13 | .golangci.yml 14 | https-server/https-server 15 | make-web.sh 16 | public/main.wasm 17 | https-server/main 18 | setup.sh 19 | make-web-test.sh 20 | jsontest/* 21 | artifacts -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Cloudflare. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 1. Redistributions of source code must retain the above copyright notice, 6 | this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the above copyright notice, 8 | this list of conditions and the following disclaimer in the documentation 9 | and/or other materials provided with the distribution. 10 | 3. Neither the name of the copyright holder nor the names of its contributors 11 | may be used to endorse or promote products derived from this software without 12 | specific prior written permission. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | POSSIBILITY OF SUCH DAMAGE. 25 | 26 | ======================================================================== 27 | 28 | Copyright (c) 2009 The Go Authors. All rights reserved. 29 | 30 | Redistribution and use in source and binary forms, with or without 31 | modification, are permitted provided that the following conditions are 32 | met: 33 | 34 | * Redistributions of source code must retain the above copyright 35 | notice, this list of conditions and the following disclaimer. 36 | * Redistributions in binary form must reproduce the above 37 | copyright notice, this list of conditions and the following disclaimer 38 | in the documentation and/or other materials provided with the 39 | distribution. 40 | * Neither the name of Google Inc. nor the names of its 41 | contributors may be used to endorse or promote products derived from 42 | this software without specific prior written permission. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 46 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 47 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 48 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 49 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 50 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 51 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 52 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 53 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 54 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: help test cover 3 | 4 | BOLD = \033[1m 5 | UNDERLINE = \033[4m 6 | BLUE = \033[36m 7 | RESET = \033[0m 8 | 9 | VERSION := $(shell git describe --tags --always --dirty="-dev") 10 | DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC') 11 | ARCHS ?= amd64 arm64 12 | 13 | Q ?= @ 14 | 15 | ## Show usage information for this Makefile 16 | help: 17 | @printf "$(BOLD)opaque-ea$(RESET)\n\n" 18 | @printf "$(UNDERLINE)Available Tasks$(RESET)\n\n" 19 | @awk -F \ 20 | ':|##' '/^##/ {c=$$2; getline; printf "$(BLUE)%10s$(RESET) %s\n", $$1, c}' \ 21 | $(MAKEFILE_LIST) 22 | @printf "\n" 23 | 24 | ## Run unit tests 25 | test: 26 | cd src && GOCACHE=off && go test -v -race ./... && cd .. 27 | 28 | ## Run linters 29 | lint: 30 | cd src && GOCACHE=off && golint ./... && golangci-lint run && cd .. 31 | 32 | ## Generate cover report 33 | cover: 34 | $Qmkdir -p .cover 35 | $Qrm -f .cover/*.out .cover/all.merged .cover/all.html 36 | $Qfor pkg in $$(go list ./...); do \ 37 | go test -coverprofile=.cover/`echo $$pkg|tr "/" "_"`.out $$pkg; \ 38 | done 39 | $Qecho 'mode: set' > .cover/all.merged 40 | $Qgrep -h -v "mode: set" .cover/*.out >> .cover/all.merged 41 | ifndef CI 42 | $Qgo tool cover -html .cover/all.merged 43 | else 44 | $Qgo tool cover -html .cover/all.merged -o .cover/all.html 45 | endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![OPAQUE logo](opaque.png) 2 | # opaque-ea 3 | 4 | This project is a proof-of-concept implementation 5 | of [OPAQUE with Exported Authenticators](https://tools.ietf.org/html/draft-sullivan-tls-opaque-00), written in Go. 6 | 7 | You can play with the demo at [https://opaque.research.cloudflare.com/](https://opaque.research.cloudflare.com/). 8 | 9 | **DISCLAIMER**: This is a reference implementation only. 10 | *DO NOT* use in production systems. 11 | 12 | ## Getting started 13 | 14 | Get the source code: 15 | 16 | ```sh 17 | go get github.com/cloudflare/opaque-ea 18 | ``` 19 | 20 | ## Running tests 21 | 22 | From the `opaque-ea` folder, run all tests: 23 | 24 | ```sh 25 | make test 26 | ``` 27 | 28 | ## Run a test client and server 29 | 30 | Spin up a local server: 31 | 32 | ```sh 33 | # Set PUBLIC_PATH to path of public folder 34 | export PUBLIC_PATH="public/" 35 | # Build the server 36 | cd https-server && go build main.go && cd ../.. 37 | # Build the client 38 | cd public/go && GOOS=js GOARCH=wasm go build -o ../main.wasm && cd ../.. 39 | # Start local server 40 | ./https-server/main 41 | ``` 42 | 43 | View the result locally at [http://localhost:8080/](http://localhost:8080/). 44 | 45 | ### Making changes to client 46 | 47 | If you make any changes to the client code, you need to re-compile 48 | Go to Wasm to see them in your browser. 49 | 50 | From the `opaque-ea` folder: 51 | 52 | ```sh 53 | # Compile go to wasm 54 | cd public/go && GOOS=js GOARCH=wasm go build -o ../main.wasm && cd ../.. 55 | ``` 56 | 57 | ## Packages 58 | 59 | ### Library packages 60 | 61 | | | | | 62 | |---|---|---| 63 | | expauth | TLS Exported Authenticators | Partially implements https://datatracker.ietf.org/doc/html/draft-ietf-tls-exported-authenticator-13 | 64 | | opaque | OPAQUE core (no key exchange) | Partially implements https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06 | 65 | | opaqueea | OPAQUE with Exported Authenticators | Partially implements https://tools.ietf.org/html/draft-sullivan-tls-opaque-00 | 66 | | ohttp | OPAQUE-EA over HTTPS, client and server | | 67 | 68 | ### Executable packages 69 | 70 | | | | 71 | |---|---| 72 | | public/go | Wrapper for HTTPS client, compiles to Wasm | 73 | | https-server | Wrapper for HTTPS server, compiles to Go executable | 74 | 75 | ## How to Cite 76 | 77 | To cite OPAQUE-EA, use one of the following formats and update with the date 78 | you accessed this project. 79 | 80 | APA Style 81 | 82 | ``` 83 | Bradley, T. and Celi, S. (2020). Introducing OPAQUE-EA: 84 | A Proof of Concept implementation of OPAQUE with Exported Authenticators. Cloudflare. 85 | Available at https://github.com/cloudflare/opaque-ea. Accessed Feb 2021. 86 | ``` 87 | 88 | Bibtex Source 89 | 90 | ```bibtex 91 | @manual{circl, 92 | title = {Introducing OPAQUE-EA: A Proof of Concept implementation of OPAQUE with Exported Authenticators}, 93 | author = {Tatiana Bradley and Sof\'{i}a Celi}, 94 | organization = {Cloudflare}, 95 | note = {Available at \url{https://github.com/cloudflare/opaque-ea}. Accessed Feb 2021}, 96 | month = dec, 97 | year = {2020} 98 | } 99 | ``` 100 | 101 | ## License 102 | 103 | The project is licensed under the [BSD-3-Clause License](LICENSE). 104 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudflare/opaque-ea 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/cloudflare/circl v1.0.1-0.20201125170039-ebd10dd620d1 7 | github.com/fatih/color v1.9.0 8 | github.com/gorilla/mux v1.7.4 9 | github.com/pkg/errors v0.9.1 10 | github.com/rs/cors v1.7.0 11 | github.com/tatianab/mint v0.0.0-20200819182909-0544d841078f 12 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 13 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cloudflare/circl v1.0.1-0.20201125170039-ebd10dd620d1 h1:ABtmeStXuua2KMK/Ho/dJtzLoMtCKwBF0baGECK96yo= 2 | github.com/cloudflare/circl v1.0.1-0.20201125170039-ebd10dd620d1/go.mod h1:gp06x/hyMk6Qy/+Vpjz9hPt1RMHdRYPDKpdsbhffgAw= 3 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 4 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 5 | github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= 6 | github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 7 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 8 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 9 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 10 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 11 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 12 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 13 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 14 | github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= 15 | github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= 16 | github.com/tatianab/mint v0.0.0-20200819182909-0544d841078f h1:GDUcUQktRiCG9dZnKZxdvx41NnQ8724Y/OpMmoqVKC0= 17 | github.com/tatianab/mint v0.0.0-20200819182909-0544d841078f/go.mod h1:Kr3FuEcGaZDrSg3Najix9/Zksb+FTq9Q+w6pGDx609U= 18 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 19 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= 20 | golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 21 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 22 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= 23 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 24 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 25 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 26 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 27 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 28 | golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= 29 | golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 31 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 32 | -------------------------------------------------------------------------------- /https-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/cloudflare/opaque-ea/src/ohttp" 7 | ) 8 | 9 | func main() { 10 | err := ohttp.RunOpaqueServer() 11 | if err != nil { 12 | log.Println("Fatal error occurred while running OPAQUE server.") 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /opaque.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/opaque-ea/9b7c71ccdf2155931ab5aa9aa0a0a94d9c41352b/opaque.png -------------------------------------------------------------------------------- /public/go/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package main 26 | 27 | import ( 28 | "log" 29 | "strings" 30 | "syscall/js" 31 | 32 | "github.com/cloudflare/circl/oprf" 33 | "github.com/cloudflare/opaque-ea/src/ohttp" 34 | ) 35 | 36 | var domain string 37 | 38 | // RunLocalClient runs a local client. 39 | func RunLocalClient() { 40 | run() 41 | } 42 | 43 | func run() { 44 | domain = ohttp.LocalDomain 45 | 46 | js.Global().Set("runOpaqueRegisterClient", js.FuncOf(runOpaqueRegisterClient)) 47 | js.Global().Set("runOpaqueLoginClient", js.FuncOf(runOpaqueLoginClient)) 48 | select {} // run indefinitely 49 | } 50 | 51 | // DOMWriter represents the div to write to. 52 | type DOMWriter struct { 53 | divID string 54 | } 55 | 56 | func (dw *DOMWriter) Write(p []byte) (int, error) { 57 | split := strings.Split(string(p), "===") 58 | js.Global().Call("addBlock", split[0], split[1], dw.divID) 59 | return len(p), nil 60 | } 61 | 62 | func runOpaqueLoginClient(this js.Value, vals []js.Value) interface{} { 63 | username := vals[0].String() 64 | password := vals[1].String() 65 | divID := vals[2].String() 66 | 67 | go func() { 68 | cfg := setup(divID) 69 | 70 | getExportedKey, authHash, err := cfg.RequestExportedKeys() 71 | if err != nil { 72 | log.Println(err) 73 | return 74 | } 75 | 76 | err = cfg.RunOpaqueLoginClient(username, password, getExportedKey, authHash) 77 | if err != nil { 78 | cfg.AddError(err) 79 | log.Println(err) 80 | return 81 | } 82 | 83 | cfg.AddTitle(ohttp.SuccessLogin) 84 | }() 85 | 86 | return nil 87 | } 88 | 89 | func runOpaqueRegisterClient(this js.Value, vals []js.Value) interface{} { 90 | username := vals[0].String() 91 | password := vals[1].String() 92 | divID := vals[2].String() 93 | 94 | go func() { 95 | cfg := setup(divID) 96 | 97 | err := cfg.RunOpaqueRegisterClient(username, password) 98 | if err != nil { 99 | cfg.AddError(err) 100 | log.Println(err) 101 | return 102 | } 103 | 104 | cfg.AddTitle(ohttp.SuccessRegistration) 105 | }() 106 | 107 | return nil 108 | } 109 | 110 | func setup(divID string) *ohttp.ClientConfig { 111 | logger := log.New(&DOMWriter{divID: divID}, "", 0) 112 | 113 | return &ohttp.ClientConfig{ 114 | Domain: "opaque.research.cloudflare.com", 115 | Logger: logger, 116 | Ciphersuite: oprf.OPRFP256, 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /public/go/main-local.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | // +build js,wasm,!prod 25 | 26 | package main 27 | 28 | func main() { 29 | RunLocalClient() 30 | } 31 | -------------------------------------------------------------------------------- /public/go/main-prod.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | // +build js,wasm,prod 25 | 26 | package main 27 | 28 | func main() { 29 | RunProductionClient() 30 | } 31 | -------------------------------------------------------------------------------- /public/images/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/opaque-ea/9b7c71ccdf2155931ab5aa9aa0a0a94d9c41352b/public/images/apple-touch-icon.png -------------------------------------------------------------------------------- /public/images/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/opaque-ea/9b7c71ccdf2155931ab5aa9aa0a0a94d9c41352b/public/images/favicon-16x16.png -------------------------------------------------------------------------------- /public/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudflare/opaque-ea/9b7c71ccdf2155931ab5aa9aa0a0a94d9c41352b/public/images/favicon-32x32.png -------------------------------------------------------------------------------- /public/script.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | window.onload = collapse 25 | 26 | function collapse() { 27 | var coll = document.getElementsByClassName("collapsible"); 28 | var i; 29 | 30 | for (i = 0; i < coll.length; i++) { 31 | coll[i].addEventListener("click", function() { 32 | this.classList.toggle("active"); 33 | var content = this.nextElementSibling; 34 | if (content.style.display === "block") { 35 | content.style.display = "none"; 36 | } else { 37 | content.style.display = "block"; 38 | } 39 | }); 40 | } 41 | } 42 | 43 | 44 | function addBlock(text, message, divId) { 45 | var elem = document.getElementById(divId); 46 | if (message == "" || message == "\n") { 47 | elem.innerHTML += "
" + "" + text + "" + "
"; 48 | } else { 49 | elem.innerHTML += "
" + text + 50 | "
" +
 51 |         message + "
"; 52 | } 53 | 54 | var coll = elem.getElementsByClassName("collapsible"); 55 | var i; 56 | 57 | for (i = 0; i < coll.length; i++) { 58 | coll[i].addEventListener("click", function() { 59 | this.classList.toggle("active"); 60 | var content = this.nextElementSibling; 61 | if (content.style.display === "block") { 62 | content.style.display = "none"; 63 | } else { 64 | content.style.display = "block"; 65 | } 66 | }); 67 | } 68 | } 69 | 70 | function clearResults(divId) { 71 | document.getElementById(divId).innerHTML = ""; 72 | } 73 | 74 | function register(e) { 75 | e.preventDefault(); 76 | 77 | var formId = "register"; 78 | var divId = "result"; 79 | 80 | clearResults(divId); 81 | 82 | var username = document.forms[formId]["reg-username"].value; 83 | var password = document.forms[formId]["reg-password"].value; 84 | 85 | addBlock(`Registering username \"${username}\" with the provided password...`, "", divId); 86 | 87 | runOpaqueRegisterClient(username, password, divId); 88 | 89 | return false; 90 | } 91 | 92 | function login(e) { 93 | e.preventDefault(); 94 | 95 | var formId = "login"; 96 | var divId = "result"; 97 | 98 | clearResults(divId); 99 | 100 | var username = document.forms[formId]["login-username"].value; 101 | var password = document.forms[formId]["login-password"].value; 102 | 103 | addBlock(`Logging in with username \"${username}\" with the provided password...`, "", divId); 104 | 105 | runOpaqueLoginClient(username, password, divId); 106 | 107 | return false; 108 | } -------------------------------------------------------------------------------- /src/common/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package common 27 | 28 | import ( 29 | "crypto" 30 | "crypto/rand" 31 | "crypto/x509" 32 | 33 | "github.com/pkg/errors" 34 | 35 | "github.com/tatianab/mint" 36 | "golang.org/x/crypto/cryptobyte" 37 | ) 38 | 39 | // GetRandomBytes returns n random bytes. 40 | func GetRandomBytes(n int) []byte { 41 | var key []byte 42 | for success := false; !success; { 43 | key = make([]byte, n) 44 | if _, err := rand.Read(key); err != nil { 45 | continue 46 | } 47 | 48 | success = true 49 | } 50 | 51 | return key 52 | } 53 | 54 | // Marshaler is the interface implemented by types that can be marshaled 55 | // (converted to raw bytes). 56 | type Marshaler interface { 57 | Marshal() ([]byte, error) 58 | } 59 | 60 | // MarshalList marshals a list of values that are marshal-able. 61 | func MarshalList(toMarshal []Marshaler) ([]byte, error) { 62 | var b cryptobyte.Builder 63 | 64 | for _, tm := range toMarshal { 65 | if tm != nil { 66 | raw, err := tm.Marshal() 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | b.AddBytes(raw) 72 | } 73 | } 74 | 75 | return b.BytesOrPanic(), nil 76 | } 77 | 78 | // Unmarshaler is the interface implemented by types that can be unmarshaled 79 | // (converted from raw bytes to a struct). 80 | type Unmarshaler interface { 81 | Unmarshal([]byte) (int, error) 82 | } 83 | 84 | // UnmarshalList unmarshals a list of values that are unmarshal-able. 85 | func UnmarshalList(toUnmarshal []Unmarshaler, data []byte) (int, error) { 86 | totalBytes := 0 87 | 88 | for _, tu := range toUnmarshal { 89 | bytesRead, err := tu.Unmarshal(data[totalBytes:]) 90 | if err != nil { 91 | return 0, err 92 | } 93 | 94 | totalBytes += bytesRead 95 | } 96 | 97 | return totalBytes, nil 98 | } 99 | 100 | // MarshalUnmarshaler is a type that can marshal and unmarshal itself. 101 | type MarshalUnmarshaler interface { 102 | Marshaler 103 | Unmarshaler 104 | } 105 | 106 | // SelfSignedCerts returns a list of self-signed certificates, each signed with 107 | // a different signature scheme supported by mint. 108 | func SelfSignedCerts(domain string, roots *x509.CertPool) ([]*mint.Certificate, error) { 109 | certs := make([]*mint.Certificate, len(MintSupportedSignatureSchemes)) 110 | 111 | for i, s := range MintSupportedSignatureSchemes { 112 | cert, err := SelfSignedCert(domain, roots, s) 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | certs[i] = cert 118 | } 119 | 120 | return certs, nil 121 | } 122 | 123 | // SelfSignedCert creates a new self-signed cert for the given domain and 124 | // adds it to the roots pool (if provided). 125 | func SelfSignedCert(domain string, roots *x509.CertPool, scheme mint.SignatureScheme) (*mint.Certificate, error) { 126 | // Make self signed certificate for testing 127 | privKey, x509cert, err := mint.MakeNewSelfSignedCert(domain, scheme) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | cert := &mint.Certificate{ 133 | Chain: []*x509.Certificate{x509cert}, 134 | PrivateKey: privKey, 135 | } 136 | 137 | // set cert as roots 138 | if roots != nil { 139 | roots.AddCert(x509cert) 140 | } 141 | 142 | return cert, nil 143 | } 144 | 145 | // GetExtensionListFromSignatureScheme returns an ExtensionList containing the 146 | // given signature scheme. 147 | func GetExtensionListFromSignatureScheme(sigScheme mint.SignatureScheme) mint.ExtensionList { 148 | supportedSchemes := &mint.SignatureAlgorithmsExtension{Algorithms: []mint.SignatureScheme{sigScheme}} 149 | 150 | var extensions mint.ExtensionList 151 | 152 | err := extensions.Add(supportedSchemes) 153 | if err != nil { 154 | panic(err) 155 | } 156 | 157 | return extensions 158 | } 159 | 160 | // GetExtensionListFromSignatureSchemes returns an ExtensionList containing the 161 | // given signature schemes. 162 | func GetExtensionListFromSignatureSchemes(sigSchemes []mint.SignatureScheme) mint.ExtensionList { 163 | var extensions mint.ExtensionList 164 | 165 | supportedSchemes := &mint.SignatureAlgorithmsExtension{Algorithms: sigSchemes} 166 | 167 | err := extensions.Add(supportedSchemes) 168 | if err != nil { 169 | panic(err) 170 | } 171 | 172 | return extensions 173 | } 174 | 175 | // MintSupportedSignatureSchemes is a list of all signature schemes supported 176 | // by the mint package. 177 | var MintSupportedSignatureSchemes = []mint.SignatureScheme{ 178 | mint.ECDSA_P256_SHA256, mint.ECDSA_P384_SHA384, mint.ECDSA_P521_SHA512, 179 | mint.RSA_PKCS1_SHA256, mint.RSA_PKCS1_SHA384, mint.RSA_PKCS1_SHA512, 180 | } 181 | 182 | // HashMap is a map from signature schemes to the hash that they use. 183 | var HashMap = map[mint.SignatureScheme]crypto.Hash{ 184 | mint.RSA_PKCS1_SHA1: crypto.SHA1, 185 | mint.RSA_PKCS1_SHA256: crypto.SHA256, 186 | mint.RSA_PKCS1_SHA384: crypto.SHA384, 187 | mint.RSA_PKCS1_SHA512: crypto.SHA512, 188 | mint.ECDSA_P256_SHA256: crypto.SHA256, 189 | mint.ECDSA_P384_SHA384: crypto.SHA384, 190 | mint.ECDSA_P521_SHA512: crypto.SHA512, 191 | mint.RSA_PSS_SHA256: crypto.SHA256, 192 | mint.RSA_PSS_SHA384: crypto.SHA384, 193 | mint.RSA_PSS_SHA512: crypto.SHA512, 194 | } 195 | 196 | // XORBytes xors b into a. Byte slices a and b must be of equal length. 197 | func XORBytes(a, b []byte) ([]byte, error) { 198 | if len(a) != len(b) { 199 | return nil, errors.New("XORBytes: byte slices must have equal length") 200 | } 201 | 202 | for i := 0; i < len(a); i++ { 203 | a[i] ^= b[i] 204 | } 205 | 206 | return a, nil 207 | } 208 | -------------------------------------------------------------------------------- /src/common/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package common 27 | 28 | import ( 29 | "encoding/json" 30 | "fmt" 31 | 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | // Error represents an error for the library. 36 | type Error uint8 37 | 38 | const ( 39 | // ErrorNoError represents an empty error. 40 | ErrorNoError Error = 0 + iota 41 | 42 | // ErrorSpontaneousAuthForbidden represents error when an spontaneous session is forbidden. 43 | ErrorSpontaneousAuthForbidden 44 | // ErrorSessionNotFound represents error when the session is not found. 45 | ErrorSessionNotFound 46 | // ErrorIncorrectState represents error it reaches an incorrect state. 47 | ErrorIncorrectState 48 | // ErrorIncorrectRole represents error when it reaches an incorrect role. 49 | ErrorIncorrectRole 50 | // ErrorUnrecognizedMessage represents error when the message is not recognized. 51 | ErrorUnrecognizedMessage 52 | // ErrorNoCertificates represents error when there are no certificates. 53 | ErrorNoCertificates 54 | // ErrorInconsistentContext represents error when the context is inconsistent. 55 | ErrorInconsistentContext 56 | // ErrorReusedContext represents error when context is reused. 57 | ErrorReusedContext 58 | // ErrorInvalidContext represents error when the context is invalid. 59 | ErrorInvalidContext 60 | // ErrorUnrecognizedLabel represents error when the label is not recognized. 61 | ErrorUnrecognizedLabel 62 | // ErrorNoPasswordTable represents error when there is no password table. 63 | ErrorNoPasswordTable 64 | // ErrorUserNotRegistered represents error when the user is not yet registered. 65 | ErrorUserNotRegistered 66 | // ErrorUserAlreadyRegistered represents error when the user is already registered. 67 | ErrorUserAlreadyRegistered 68 | // ErrorUnexpectedMessage represents error when an unexpected message arrives. 69 | ErrorUnexpectedMessage 70 | // ErrorHmacTagInvalid represents error when HMAC tag is invalid. 71 | ErrorHmacTagInvalid 72 | // ErrorForbiddenPolicy represents error when there is a forbidden credential encoding policy. 73 | ErrorForbiddenPolicy 74 | // ErrorUnexpectedData represents error when unexpected data arrived. 75 | ErrorUnexpectedData 76 | // ErrorInvalidFinishedMac represents error when the finished MAC is invalid. 77 | ErrorInvalidFinishedMac 78 | // ErrorBadEnvelope represents error when decription of the envelope fails. 79 | ErrorBadEnvelope 80 | // ErrorInvalidAuthenticator represents error when the authenticator is invalid. 81 | ErrorInvalidAuthenticator 82 | // ErrorUnsupportedCiphersuite represents error when the ciphersuite is not supported. 83 | ErrorUnsupportedCiphersuite 84 | // ErrorNotFound represents error when a field is not found. 85 | ErrorNotFound 86 | 87 | // ErrorOtherError represents other kinds of errors not previously covered. 88 | ErrorOtherError 89 | ) 90 | 91 | // Error returns the corresponding string to the error. 92 | func (e Error) Error() string { 93 | return alertToString[e] 94 | } 95 | 96 | // Is compares two errors. 97 | func (e Error) Is(target error) bool { 98 | return e == target.(Error) 99 | } 100 | 101 | // Wrap coverts an stdlib error to a library one. 102 | func (e Error) Wrap(err error) error { 103 | if err == nil { 104 | return nil 105 | } 106 | 107 | err = &withError{ 108 | cause: err, 109 | err: e, 110 | } 111 | 112 | return errors.WithStack(err) 113 | } 114 | 115 | type withError struct { 116 | cause error 117 | err error 118 | } 119 | 120 | // Error returs the string associated with the error with cause. 121 | func (we *withError) Error() string { 122 | return fmt.Sprintf("%s: %s", we.err, we.cause) 123 | } 124 | 125 | func (we *withError) Is(target error) bool { 126 | return errors.Is(we.cause, target) || errors.Is(we.err, target) 127 | } 128 | 129 | func (we *withError) Cause() error { 130 | return we.cause 131 | } 132 | 133 | func (we *withError) Unwrap() error { 134 | return we.cause 135 | } 136 | 137 | // Get error code from latest Error 138 | func (we *withError) MarshalJSON() ([]byte, error) { 139 | if latestErr, ok := we.err.(Error); ok { 140 | return json.Marshal(latestErr) 141 | } 142 | 143 | if err, ok := errors.Cause(we).(Error); ok { 144 | return json.Marshal(err) 145 | } 146 | 147 | return json.Marshal(ErrorOtherError) 148 | } 149 | 150 | // Causer is an interface for the cause of an error. 151 | type Causer interface { 152 | Cause() error 153 | } 154 | 155 | // MarshalErrorAsJSON marshals an error as json. 156 | func MarshalErrorAsJSON(target error) ([]byte, error) { 157 | switch t := target.(type) { 158 | case Error, *withError: 159 | return json.Marshal(t) 160 | case Causer: 161 | return MarshalErrorAsJSON(t.Cause()) 162 | } 163 | 164 | return json.Marshal(ErrorOtherError) 165 | } 166 | 167 | var alertToString = map[Error]string{ 168 | ErrorSpontaneousAuthForbidden: "client cannot authenticate spontaneously", 169 | ErrorSessionNotFound: "session not found", 170 | ErrorIncorrectState: "session in incorrect state", 171 | ErrorIncorrectRole: "incorrect role for action", 172 | ErrorUnrecognizedMessage: "unrecognized message", 173 | ErrorNoCertificates: "no certificates", 174 | ErrorInconsistentContext: "contexts do not match", 175 | ErrorInvalidContext: "invalid context", 176 | ErrorUnrecognizedLabel: "unrecognized mode/label", 177 | ErrorNoPasswordTable: "no record table", 178 | ErrorUserNotRegistered: "user is not registered", 179 | ErrorUserAlreadyRegistered: "user already registered", 180 | ErrorUnexpectedMessage: "unrecognized protocol message", 181 | ErrorHmacTagInvalid: "hmac verification failed, tags not equal", 182 | ErrorForbiddenPolicy: "forbidden credential encoding policy", 183 | ErrorUnexpectedData: "unexpected data", 184 | ErrorInvalidFinishedMac: "finished MACs not equal", 185 | ErrorBadEnvelope: "decrypt envelope failed", 186 | ErrorInvalidAuthenticator: "invalid client exported authenticator", 187 | ErrorUnsupportedCiphersuite: "unsupported ciphersuite", 188 | ErrorNotFound: "not found", 189 | } 190 | 191 | // Test strings 192 | const ( 193 | ErrMarshalUnmarshalFailed = "marshal unmarshal %v failed: \nexpected %#v,\ngot %#v" 194 | ) 195 | -------------------------------------------------------------------------------- /src/expauth/expauth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package expauth 27 | 28 | import ( 29 | "errors" 30 | "reflect" 31 | "testing" 32 | 33 | "github.com/cloudflare/opaque-ea/src/common" 34 | "github.com/cloudflare/opaque-ea/src/testhelp" 35 | "github.com/tatianab/mint" 36 | ) 37 | 38 | func getDummyEA(sigScheme mint.SignatureScheme) *ExportedAuthenticator { 39 | authHash := common.HashMap[sigScheme] 40 | 41 | return &ExportedAuthenticator{ 42 | CertMsg: &mint.CertificateBody{ 43 | CertificateRequestContext: common.GetRandomBytes(32), 44 | CertificateList: []mint.CertificateEntry{}, 45 | }, 46 | CertVerify: &mint.CertificateVerifyBody{ 47 | Algorithm: sigScheme, 48 | Signature: []byte{}, 49 | }, 50 | Finished: &mint.FinishedBody{ 51 | VerifyDataLen: authHash.Size(), 52 | VerifyData: make([]byte, authHash.Size()), 53 | }, 54 | } 55 | } 56 | 57 | func getDummyEmptyEA(sigScheme mint.SignatureScheme) *ExportedAuthenticator { 58 | authHash := common.HashMap[sigScheme] 59 | 60 | return &ExportedAuthenticator{ 61 | Finished: &mint.FinishedBody{ 62 | VerifyDataLen: authHash.Size(), 63 | VerifyData: make([]byte, authHash.Size()), 64 | }, 65 | } 66 | } 67 | 68 | func TestMarshalUnmarshalExportedAuthenticator(t *testing.T) { 69 | ea1 := getDummyEA(ExpAuthTestSignatureScheme) 70 | 71 | ea2 := &ExportedAuthenticator{} 72 | if err := testhelp.TestMarshalUnmarshal(ea1, ea2); err != nil { 73 | t.Error(err) 74 | } 75 | } 76 | 77 | func TestMarshalUnmarshalEmptyExportedAuthenticator(t *testing.T) { 78 | ea1 := getDummyEmptyEA(ExpAuthTestSignatureScheme) 79 | 80 | ea2 := &ExportedAuthenticator{} 81 | if err := testhelp.TestMarshalUnmarshal(ea1, ea2); err != nil { 82 | t.Error(err) 83 | } 84 | } 85 | 86 | func TestMarshalUnmarshalExportedAuthenticatorWithTailData(t *testing.T) { 87 | ea1 := getDummyEA(ExpAuthTestSignatureScheme) 88 | ea2 := &ExportedAuthenticator{} 89 | 90 | mea1, err := ea1.Marshal() 91 | if err != nil { 92 | t.Errorf("FAIL: Marshal failed: %v", err) 93 | return 94 | } 95 | 96 | if _, err := ea2.Unmarshal(append(mea1, make([]byte, 10)...)); err != nil { 97 | t.Errorf("FAIL: Unmarshal failed: %v", err) 98 | return 99 | } 100 | 101 | if !reflect.DeepEqual(ea1, ea2) { 102 | t.Errorf("FAIL: Unmarshal returned different values: \n original %v \n unmarshaled %v", 103 | ea1, ea2) 104 | } 105 | } 106 | 107 | func TestExportedAuthenticatorFlow(t *testing.T) { 108 | getExportedKey, authHash := GetTestGetterAndHash() 109 | client := ClientFromGetter(getExportedKey, authHash) 110 | server := ServerFromGetter(getExportedKey, authHash) 111 | 112 | // Client requests an EA from Server 113 | request, err := client.Request(common.GetExtensionListFromSignatureSchemes(common.MintSupportedSignatureSchemes)) 114 | if err != nil { 115 | t.Errorf("could not create EA request: %v", err) 116 | } 117 | 118 | // Server replies with an EA 119 | certs, err := common.SelfSignedCerts("example.com", nil) 120 | if err != nil { 121 | t.Errorf("could not get cert chain: %v", err) 122 | } 123 | 124 | ea, err := server.Authenticate(certs, nil, request) 125 | if err != nil { 126 | t.Errorf("could not create EA: %v", err) 127 | } 128 | 129 | // Client validates server's EA 130 | _, _, err = client.Validate(ea, request) 131 | if err != nil { 132 | t.Errorf("EA invalid: %v", err) 133 | } 134 | } 135 | 136 | func TestEAFlowServerRequest(t *testing.T) { 137 | client, server := getTestClientServer() 138 | 139 | request, err := server.Request(common.GetExtensionListFromSignatureScheme(ExpAuthTestSignatureScheme)) 140 | if err != nil { 141 | t.Errorf("could not create EA request: %v", err) 142 | } 143 | 144 | cert, err := common.SelfSignedCert("example.com", nil, ExpAuthTestSignatureScheme) 145 | if err != nil { 146 | t.Errorf("could not get cert chain: %v", err) 147 | } 148 | 149 | ea, err := client.Authenticate([]*mint.Certificate{cert}, nil, request) 150 | if err != nil { 151 | t.Errorf("could not create EA: %v", err) 152 | } 153 | 154 | // validate EA 155 | _, _, err = server.Validate(ea, request) 156 | 157 | if err != nil { 158 | t.Errorf("EA invalid: %v", err) 159 | } 160 | } 161 | 162 | func TestEAFlowServerSpontaneous(t *testing.T) { 163 | client, server := getTestClientServer() 164 | 165 | cert, err := common.SelfSignedCert("example.com", nil, ExpAuthTestSignatureScheme) 166 | if err != nil { 167 | t.Errorf("could not get cert chain: %v", err) 168 | } 169 | 170 | ea, err := server.AuthenticateSpontaneously([]*mint.Certificate{cert}, nil) 171 | if err != nil { 172 | t.Errorf("could not create EA: %v", err) 173 | } 174 | 175 | // validate EA 176 | _, _, err = client.Validate(ea, nil) 177 | 178 | if err != nil { 179 | t.Errorf("EA invalid: %v", err) 180 | } 181 | } 182 | 183 | func TestEAFlowClientRequestServerReject(t *testing.T) { 184 | client, server := getTestClientServer() 185 | 186 | // Client requests an EA from Server 187 | request, err := client.Request(common.GetExtensionListFromSignatureScheme(ExpAuthTestSignatureScheme)) 188 | if err != nil { 189 | t.Errorf("could not create EA request: %v", err) 190 | } 191 | 192 | // Server replies 193 | ea, err := server.RefuseAuthentication(request) 194 | if err != nil { 195 | t.Errorf("could not create EA: %v", err) 196 | } 197 | 198 | // Client checks EA 199 | certs, _, err := client.Validate(ea, request) 200 | 201 | if len(certs) > 0 { 202 | t.Errorf("certs should be empty") 203 | } 204 | if err == nil { 205 | t.Errorf("EA should be invalid") 206 | } 207 | } 208 | 209 | func TestRejectReusedContext(t *testing.T) { 210 | client, server := getTestClientServer() 211 | 212 | request, err := EAFlowClientRequest(client, server) 213 | if err != nil { 214 | t.Errorf("First EA flow failed: %v", err) 215 | } 216 | 217 | _, err = client.newAuthenticatorSession(request) 218 | if err == nil || !errors.Is(err, common.ErrorInvalidContext) { 219 | t.Errorf("expected error to contain '%v', got %v", common.ErrorInvalidContext, err) 220 | } 221 | 222 | _, err = server.newAuthenticatorSession(request) 223 | if err == nil || !errors.Is(err, common.ErrorInvalidContext) { 224 | t.Errorf("expected error to contain '%v', got %v", common.ErrorInvalidContext, err) 225 | } 226 | } 227 | 228 | func EAFlowClientRequest(client, server *Party) (ExportedAuthenticatorRequest, error) { 229 | // Client requests an EA from Server 230 | request, err := client.Request(common.GetExtensionListFromSignatureScheme(ExpAuthTestSignatureScheme)) 231 | if err != nil { 232 | return nil, err 233 | } 234 | 235 | // Server replies 236 | cert, err := common.SelfSignedCert("example.com", nil, ExpAuthTestSignatureScheme) 237 | if err != nil { 238 | return nil, err 239 | } 240 | 241 | ea, err := server.Authenticate([]*mint.Certificate{cert}, nil, request) 242 | if err != nil { 243 | return nil, err 244 | } 245 | 246 | // Client checks EA 247 | _, _, err = client.Validate(ea, request) 248 | 249 | return request, err 250 | } 251 | 252 | func TestSupportedSignatureSchemes(t *testing.T) { 253 | getExportedKey, authHash := GetTestGetterAndHash() 254 | client := ClientFromGetter(getExportedKey, authHash) 255 | 256 | // Client requests an EA from Server 257 | request, err := client.Request(common.GetExtensionListFromSignatureSchemes(common.MintSupportedSignatureSchemes)) 258 | if err != nil { 259 | t.Errorf("could not create EA request: %v", err) 260 | } 261 | 262 | gotSupportedSchemes, err := request.SupportedSignatureSchemes() 263 | if err != nil { 264 | t.Errorf("could not get supported schemes: %v", err) 265 | } 266 | 267 | if !reflect.DeepEqual(common.MintSupportedSignatureSchemes, gotSupportedSchemes) { 268 | t.Errorf("supported schemes do not match") 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/expauth/tls_connection_state.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package expauth 26 | 27 | import ( 28 | "crypto" 29 | "crypto/tls" 30 | "fmt" 31 | "log" 32 | 33 | "github.com/tatianab/mint" 34 | ) 35 | 36 | // ServerFromTLSConnState returns a server from a TLS connection state. 37 | func ServerFromTLSConnState(connState *tls.ConnectionState) *Party { 38 | party := newPartyFromTLSConnState(connState) 39 | party.isClient = false 40 | 41 | return party 42 | } 43 | 44 | func newPartyFromTLSConnState(connState *tls.ConnectionState) *Party { 45 | return &Party{ 46 | sessions: make(map[string]*session), 47 | getExportedKey: NewExportedKeyGetterFromTLSConnState(connState), 48 | authHash: AuthHashFromTLSConnState(connState), 49 | } 50 | } 51 | 52 | // NewExportedKeyGetterFromTLSConnState returns an Exported KeyGetter from a TLS connection state. 53 | func NewExportedKeyGetterFromTLSConnState(connState *tls.ConnectionState) ExportedKeyGetter { 54 | return func(mode, label string) ([]byte, error) { 55 | return getExportedKeyFromTLSConnState(connState, mode, label) 56 | } 57 | } 58 | 59 | // AuthHashFromTLSConnState returs a crypto.Hash from a TLS connection state. 60 | func AuthHashFromTLSConnState(connState *tls.ConnectionState) crypto.Hash { 61 | log.Printf("TLS Suite %v", connState.CipherSuite) 62 | return mint.CipherSuiteMap[mint.CipherSuite(connState.CipherSuite)].Hash 63 | } 64 | 65 | func getExportedKeyFromTLSConnState(connState *tls.ConnectionState, mode, label string) ([]byte, error) { 66 | keyLength := mint.CipherSuiteMap[mint.CipherSuite(connState.CipherSuite)].Hash.Size() 67 | fullLabel := fmt.Sprintf("EXPORTER-%s authenticator %s", mode, label) 68 | 69 | key, err := connState.ExportKeyingMaterial(fullLabel, nil, keyLength) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return key, nil 75 | } 76 | -------------------------------------------------------------------------------- /src/expauth/tls_messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package expauth 26 | 27 | import ( 28 | "github.com/pkg/errors" 29 | "github.com/tatianab/mint" 30 | "github.com/tatianab/mint/syntax" 31 | ) 32 | 33 | // TLSMessage is a wrapper for mint HandshakeMessageBody 34 | // Similar to mint's HandshakeMessage but without DTLS info, 35 | // and allows us to use mint/syntax marshal/unmarshal functionality. 36 | type TLSMessage struct { 37 | MessageType mint.HandshakeType 38 | MessageBodyRaw []byte `tls:"head=3"` 39 | } 40 | 41 | // Marshal marshals a TLS Message. 42 | func (msg *TLSMessage) Marshal() ([]byte, error) { 43 | return syntax.Marshal(msg) 44 | } 45 | 46 | // Unmarshal unmarshals a TLS Message. 47 | func (msg *TLSMessage) Unmarshal(data []byte) (int, error) { 48 | return syntax.Unmarshal(data, msg) 49 | } 50 | 51 | // ToBody returns the body of a TLS Message. 52 | func (msg *TLSMessage) ToBody() (mint.HandshakeMessageBody, error) { 53 | var body mint.HandshakeMessageBody 54 | 55 | switch msg.MessageType { 56 | case mint.HandshakeTypeCertificate: 57 | body = new(mint.CertificateBody) 58 | case mint.HandshakeTypeCertificateVerify: 59 | body = new(mint.CertificateVerifyBody) 60 | case mint.HandshakeTypeFinished: 61 | body = &mint.FinishedBody{VerifyDataLen: len(msg.MessageBodyRaw)} 62 | default: 63 | return body, errors.New("unrecognized TLS message") 64 | } 65 | 66 | _, err := body.Unmarshal(msg.MessageBodyRaw) 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | return body, nil 72 | } 73 | 74 | // TLSMessageFromBody returns a TLS Message from a body. 75 | func TLSMessageFromBody(body mint.HandshakeMessageBody) (*TLSMessage, error) { 76 | data, err := body.Marshal() 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | m := &TLSMessage{ 82 | MessageType: body.Type(), 83 | MessageBodyRaw: data, 84 | } 85 | 86 | return m, nil 87 | } 88 | -------------------------------------------------------------------------------- /src/expauth/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package expauth 27 | 28 | import ( 29 | "crypto" 30 | 31 | "github.com/cloudflare/opaque-ea/src/common" 32 | "github.com/tatianab/mint" 33 | ) 34 | 35 | const ( 36 | //ExpAuthTestSignatureScheme represents the signature scheme for exported authenticators. 37 | ExpAuthTestSignatureScheme mint.SignatureScheme = mint.ECDSA_P521_SHA512 38 | ) 39 | 40 | const keyLen = 32 41 | 42 | var key1 []byte = common.GetRandomBytes(keyLen) 43 | var key2 []byte = common.GetRandomBytes(keyLen) 44 | var key3 []byte = common.GetRandomBytes(keyLen) 45 | var key4 []byte = common.GetRandomBytes(keyLen) 46 | 47 | // GetTestGetterAndHash gets the getter and hasher. 48 | func GetTestGetterAndHash() (ExportedKeyGetter, crypto.Hash) { 49 | return ExportedKeyGetterFromKeys(key1, key2, key3, key4), common.HashMap[ExpAuthTestSignatureScheme] 50 | } 51 | 52 | func getTestClientServer() (client, server *Party) { 53 | ekg, h := GetTestGetterAndHash() 54 | return ClientFromGetter(ekg, h), ServerFromGetter(ekg, h) 55 | } 56 | -------------------------------------------------------------------------------- /src/integrationtests/expauth_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package integrationtests 27 | 28 | import ( 29 | "io/ioutil" 30 | "log" 31 | "os" 32 | "strings" 33 | "testing" 34 | 35 | "github.com/cloudflare/opaque-ea/src/common" 36 | "github.com/cloudflare/opaque-ea/src/expauth" 37 | "github.com/pkg/errors" 38 | "github.com/tatianab/mint" 39 | ) 40 | 41 | func TestMain(m *testing.M) { 42 | // Turn off logging 43 | log.SetOutput(ioutil.Discard) 44 | os.Exit(m.Run()) 45 | } 46 | 47 | func TestInvalidEAIntegration(t *testing.T) { 48 | err := IntegrationTest(runEAClientResponderBad, runEAServerRequestor, expauth.ExpAuthTestSignatureScheme) 49 | if err == nil || !errors.Is(err, common.ErrorInvalidFinishedMac) { 50 | t.Errorf("expected error to contain %s, got %v", common.ErrorInvalidFinishedMac, err) 51 | } 52 | } 53 | 54 | func TestEmptyEAIntegration(t *testing.T) { 55 | err := IntegrationTest(runEAClientResponderEmpty, runEAServerRequestor, expauth.ExpAuthTestSignatureScheme) 56 | if err == nil { 57 | t.Errorf("Exported Authenticator should be invalid") 58 | } 59 | } 60 | 61 | func TestEAIntegrationServerAuth(t *testing.T) { 62 | err := IntegrationTest(runEAClientRequestor, runEAServerResponderGood, expauth.ExpAuthTestSignatureScheme) 63 | if err != nil { 64 | t.Errorf("%v", err) 65 | } 66 | } 67 | 68 | func TestEAIntegrationServerSpontaneousAuth(t *testing.T) { 69 | err := IntegrationTest(runEAClientRequestorNoRequest, runEAServerResponderNoRequest, expauth.ExpAuthTestSignatureScheme) 70 | if err != nil { 71 | t.Errorf("%v", err) 72 | } 73 | } 74 | func TestEAIntegrationClientAuth(t *testing.T) { 75 | err := IntegrationTest(runEAClientResponderGood, runEAServerRequestor, expauth.ExpAuthTestSignatureScheme) 76 | if err != nil { 77 | t.Errorf("%v", err) 78 | } 79 | } 80 | 81 | func runEAServerRequestor(server *mint.Conn, cert []*mint.Certificate) error { 82 | return runEARequestor(server, false, nonEmptyAuth) 83 | } 84 | 85 | func runEAClientRequestor(client *mint.Conn, cert []*mint.Certificate) error { 86 | return runEARequestor(client, true, nonEmptyAuth) 87 | } 88 | 89 | func runEAClientRequestorNoRequest(client *mint.Conn, cert []*mint.Certificate) error { 90 | return runEARequestor(client, true, noRequest) 91 | } 92 | 93 | func runEAServerResponderGood(server *mint.Conn, cert []*mint.Certificate) error { 94 | return runEAResponder(server, cert, false, nonEmptyAuth) 95 | } 96 | 97 | func runEAServerResponderNoRequest(server *mint.Conn, cert []*mint.Certificate) error { 98 | return runEAResponder(server, cert, false, noRequest) 99 | } 100 | 101 | func runEAClientResponderGood(client *mint.Conn, cert []*mint.Certificate) error { 102 | return runEAResponder(client, cert, true, nonEmptyAuth) 103 | } 104 | 105 | func runEAClientResponderBad(client *mint.Conn, cert []*mint.Certificate) error { 106 | return runEAResponder(client, cert, true, badAuth) 107 | } 108 | 109 | func runEAClientResponderEmpty(client *mint.Conn, cert []*mint.Certificate) error { 110 | return runEAResponder(client, cert, true, emptyAuth) 111 | } 112 | 113 | const ( 114 | emptyAuth int = 1 + iota 115 | nonEmptyAuth 116 | badAuth 117 | noRequest 118 | ) 119 | 120 | func runEARequestor(conn *mint.Conn, isClient bool, option int) error { 121 | var request expauth.ExportedAuthenticatorRequest 122 | var err error 123 | 124 | party := ServerLogContext 125 | newEAParty := expauth.Server 126 | 127 | if isClient { 128 | party = ClientLogContext 129 | newEAParty = expauth.Client 130 | } 131 | 132 | log.Printf("Running EA %v...\n", party) 133 | 134 | // Party starts an EA instance 135 | eaParty := newEAParty(conn) 136 | 137 | if option != noRequest { 138 | // Party creates request for authenticator and sends over TLS channel 139 | request, err = eaParty.Request(common.GetExtensionListFromSignatureScheme(expauth.ExpAuthTestSignatureScheme)) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | log.Printf("%v sending EA request...\n", strings.Title(party)) 145 | 146 | rawRequest, err := request.Marshal() 147 | if err != nil { 148 | return errors.Wrapf(err, "%v", party) 149 | } 150 | 151 | _, err = conn.Write(rawRequest) 152 | if err != nil { 153 | return errors.Wrapf(err, "%v", party) 154 | } 155 | } 156 | 157 | // Party waits for an Exported Authenticator 158 | buf := make([]byte, 1500) 159 | 160 | _, err = conn.Read(buf) 161 | if err != nil { 162 | return errors.Wrapf(err, "%v", party) 163 | } 164 | 165 | ea := &expauth.ExportedAuthenticator{} 166 | 167 | _, err = ea.Unmarshal(buf) 168 | if err != nil { 169 | return errors.Wrapf(err, "%v", party) 170 | } 171 | 172 | // Party validates EA 173 | log.Printf("%v validating EA...\n", strings.Title(party)) 174 | 175 | if option != noRequest { 176 | _, _, err = eaParty.Validate(ea, request) 177 | } else { 178 | _, _, err = eaParty.Validate(ea, nil) 179 | } 180 | 181 | return err 182 | } 183 | 184 | func runEAResponder(conn *mint.Conn, cert []*mint.Certificate, isClient bool, option int) error { 185 | var request expauth.ExportedAuthenticatorRequest 186 | 187 | party := ServerLogContext 188 | request = &expauth.ClientExportedAuthenticatorRequest{} 189 | newEAParty := expauth.Server 190 | 191 | if isClient { 192 | party = ClientLogContext 193 | request = &expauth.ServerExportedAuthenticatorRequest{} 194 | newEAParty = expauth.Client 195 | } 196 | 197 | log.Printf("Running EA %v as a Responder...", party) 198 | 199 | // Party waits for authenticator request 200 | if option != noRequest { 201 | buf := make([]byte, 1500) 202 | 203 | _, err := conn.Read(buf) 204 | if err != nil { 205 | return errors.Wrapf(err, "%v", party) 206 | } 207 | 208 | _, err = request.Unmarshal(buf) 209 | if err != nil { 210 | return errors.Wrapf(err, "%v", party) 211 | } 212 | } 213 | 214 | // Party starts an EA instance 215 | eaParty := newEAParty(conn) 216 | 217 | // Party responds with exported authenticator 218 | log.Printf("%v sending exported authenticator...", strings.Title(party)) 219 | 220 | var rawEA []byte 221 | var ea *expauth.ExportedAuthenticator 222 | var err error 223 | 224 | switch option { 225 | case noRequest: 226 | ea, err = eaParty.AuthenticateSpontaneously(cert, nil) 227 | case emptyAuth: 228 | ea, err = eaParty.RefuseAuthentication(request) 229 | case nonEmptyAuth: 230 | ea, err = eaParty.Authenticate(cert, nil, request) 231 | case badAuth: 232 | ea, err = eaParty.Authenticate(cert, nil, request) 233 | if err != nil { 234 | return errors.Wrapf(err, "%v", party) 235 | } 236 | 237 | ea.Finished.VerifyData[1] = byte(2) // corrupt a byte 238 | } 239 | 240 | if err != nil { 241 | return errors.Wrapf(err, "%v", party) 242 | } 243 | 244 | rawEA, err = ea.Marshal() 245 | if err != nil { 246 | return errors.Wrapf(err, "%v", party) 247 | } 248 | 249 | _, err = conn.Write(rawEA) 250 | if err != nil { 251 | return errors.Wrapf(err, "%v", party) 252 | } 253 | 254 | return nil 255 | } 256 | -------------------------------------------------------------------------------- /src/integrationtests/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package integrationtests 27 | 28 | import ( 29 | "crypto/x509" 30 | "log" 31 | "net" 32 | "time" 33 | 34 | "github.com/cloudflare/opaque-ea/src/common" 35 | "github.com/fatih/color" 36 | "github.com/pkg/errors" 37 | "github.com/tatianab/mint" 38 | ) 39 | 40 | const ( 41 | // ServerLogContext represents the log context of a server. 42 | ServerLogContext = "server" 43 | // ClientLogContext represents the log context of a client. 44 | ClientLogContext = "client" 45 | ) 46 | 47 | // IntegrationTest starts a TLS client and server and runs the provided test code. 48 | func IntegrationTest(runClient, runServer func(*mint.Conn, []*mint.Certificate) error, scheme mint.SignatureScheme) error { 49 | serverError := make(chan error, 1) 50 | done := make(chan bool, 1) 51 | exampleDomain := "example.com" 52 | exampleDomain2 := "example2.com" 53 | roots := x509.NewCertPool() 54 | 55 | certs, err := common.SelfSignedCerts(exampleDomain, roots) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | eaCert, err := common.SelfSignedCert(exampleDomain2, roots, scheme) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | eaCerts := []*mint.Certificate{eaCert} 66 | serverCfg := &mint.Config{Certificates: certs, RootCAs: roots} 67 | 68 | // Local TCP+TLS connection (Server) 69 | go func() { 70 | listener, err := net.Listen("tcp", "localhost:8000") 71 | if err != nil { 72 | serverError <- err 73 | return 74 | } 75 | 76 | serverConn, err := listener.Accept() 77 | if err != nil { 78 | serverError <- err 79 | return 80 | } 81 | 82 | server := mint.Server(serverConn, serverCfg) 83 | 84 | defer func() { 85 | // Server closes 86 | listener.Close() 87 | server.Close() 88 | log.Println("Server connection closed.") 89 | done <- true 90 | }() 91 | 92 | if alert := server.Handshake(); alert != mint.AlertNoAlert { 93 | serverError <- errors.Wrap(alert, "server handshake") 94 | return 95 | } 96 | 97 | // Run post-hanshake server code 98 | err = runServer(server, eaCerts) 99 | if err == nil { 100 | log.Println(color.GreenString("SERVER SUCCEEDED")) 101 | serverError <- nil 102 | } else { 103 | log.Println(color.RedString("SERVER FAILED: %v", err)) 104 | serverError <- err 105 | } 106 | }() 107 | 108 | time.Sleep(1 * time.Second) 109 | 110 | // Start local client 111 | clientCfg := &mint.Config{ 112 | ServerName: exampleDomain, 113 | RootCAs: roots, 114 | } 115 | 116 | client, err := mint.Dial("tcp", "localhost:8000", clientCfg) 117 | if err != nil { 118 | return errors.Wrap(err, "client dial") 119 | } 120 | 121 | defer func() { 122 | client.Close() 123 | log.Println("Client connection closed.") 124 | // Wait for server to finish 125 | <-done 126 | }() 127 | 128 | log.Println("Successful TLS handshake complete.") 129 | 130 | // Run post-handshake client code 131 | err = runClient(client, eaCerts) 132 | if err == nil { 133 | log.Println(color.GreenString("CLIENT SUCCEEDED")) 134 | 135 | sError := <-serverError 136 | 137 | return sError 138 | } 139 | 140 | log.Println(color.RedString("CLIENT FAILED: %v", err)) 141 | 142 | return err 143 | } 144 | -------------------------------------------------------------------------------- /src/integrationtests/opaque_ea_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package integrationtests 27 | 28 | import ( 29 | "log" 30 | "testing" 31 | 32 | "github.com/cloudflare/circl/oprf" 33 | "github.com/cloudflare/opaque-ea/src/expauth" 34 | "github.com/cloudflare/opaque-ea/src/opaque" 35 | "github.com/cloudflare/opaque-ea/src/opaqueea" 36 | "github.com/cloudflare/opaque-ea/src/testhelp" 37 | "github.com/pkg/errors" 38 | "github.com/tatianab/mint" 39 | ) 40 | 41 | const TestDomain string = "example.com" 42 | 43 | func TestOpaqueEAIntegration(t *testing.T) { 44 | err := IntegrationTest(runOpaqueEAClient, runOpaqueEAServer, expauth.ExpAuthTestSignatureScheme) 45 | if err != nil { 46 | t.Errorf("%v", err) 47 | } 48 | } 49 | 50 | func runOpaqueEAServer(server *mint.Conn, cert []*mint.Certificate) error { 51 | log.Println("Running OPAQUE-EA Server...") 52 | 53 | // Set up EA server 54 | party := ServerLogContext 55 | eaServer := expauth.Server(server) 56 | 57 | // Set up OPAQUE-EA server 58 | domain := TestDomain 59 | suite := oprf.OPRFP256 60 | 61 | cfg, err := opaque.NewTestServerConfig(domain, suite) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | s, err := opaqueea.NewServer(eaServer, &opaqueea.ServerConfig{OpaqueCfg: cfg}) 67 | if err != nil { 68 | return errors.Wrapf(err, "%v", party) 69 | } 70 | 71 | // Server waits for OPAQUE exp-auth msg from Client 72 | log.Println("Server waiting for OPAQUE msg 1") 73 | 74 | buf := make([]byte, 1500) 75 | 76 | _, err = server.Read(buf) 77 | if err != nil { 78 | return errors.Wrapf(err, "%v", party) 79 | } 80 | 81 | clientInitMsg := &opaqueea.ProtocolMessage{} 82 | 83 | _, err = clientInitMsg.Unmarshal(buf) 84 | if err != nil { 85 | return errors.Wrapf(err, "%v", party) 86 | } 87 | 88 | // Server runs OPAQUE-EA Response protocol and sends messages over TLS Channel 89 | log.Println("Server running OPAQUE Response") 90 | 91 | // Get server response 92 | serverResponse, err := s.Respond(clientInitMsg) 93 | if err != nil { 94 | return errors.Wrapf(err, "%v", party) 95 | } 96 | 97 | rawResponse, err := serverResponse.Marshal() 98 | if err != nil { 99 | return errors.Wrapf(err, "%v", party) 100 | } 101 | 102 | _, err = server.Write(rawResponse) 103 | if err != nil { 104 | return errors.Wrapf(err, "%v", party) 105 | } 106 | 107 | // Server waits for response 108 | log.Println("Server waiting for OPAQUE msg 2") 109 | 110 | buf = make([]byte, 1500) 111 | 112 | _, err = server.Read(buf) 113 | if err != nil { 114 | return errors.Wrapf(err, "%v", party) 115 | } 116 | 117 | clientResponse := &opaqueea.ProtocolMessage{} 118 | 119 | _, err = clientResponse.Unmarshal(buf) 120 | if err != nil { 121 | return errors.Wrapf(err, "%v", party) 122 | } 123 | 124 | // Server validates response with ServerVerify 125 | log.Println("Server verifying response") 126 | 127 | err = s.Verify(clientResponse) 128 | 129 | return err 130 | } 131 | 132 | func runOpaqueEAClient(client *mint.Conn, cert []*mint.Certificate) error { 133 | log.Println("Running OPAQUE-EA Client...") 134 | 135 | party := ClientLogContext 136 | suite := oprf.OPRFP256 137 | 138 | // Set up EA client 139 | eaClient := expauth.Client(client) 140 | 141 | // Client initiates OPAQUE-EA 142 | log.Println("Client initiating OPAQUE-EA.") 143 | 144 | // Set up client OPAQUE session 145 | c, err := opaqueea.NewClient(eaClient, testhelp.TestUser, TestDomain, suite) 146 | if err != nil { 147 | return err 148 | } 149 | 150 | initMsg, err := c.Request(testhelp.TestPassword) 151 | if err != nil { 152 | return errors.Wrapf(err, "%v", party) 153 | } 154 | 155 | rawInitMsg, err := initMsg.Marshal() 156 | if err != nil { 157 | return errors.Wrapf(err, "%v", party) 158 | } 159 | 160 | _, err = client.Write(rawInitMsg) 161 | if err != nil { 162 | return errors.Wrapf(err, "%v", party) 163 | } 164 | 165 | log.Println("Client waiting for response.") 166 | 167 | buf := make([]byte, 1500) 168 | 169 | _, err = client.Read(buf) 170 | if err != nil { 171 | return errors.Wrapf(err, "%v", party) 172 | } 173 | 174 | serverResponse := &opaqueea.ProtocolMessage{} 175 | 176 | _, err = serverResponse.Unmarshal(buf) 177 | if err != nil { 178 | return errors.Wrapf(err, "%v", party) 179 | } 180 | 181 | log.Println("Client verifying, and sending response.") 182 | 183 | response, err := c.VerifyAndRespond(serverResponse) 184 | if err != nil { 185 | return errors.Wrapf(err, "%v", party) 186 | } 187 | 188 | rawResponse, err := response.Marshal() 189 | if err != nil { 190 | return errors.Wrapf(err, "%v", party) 191 | } 192 | 193 | _, err = client.Write(rawResponse) 194 | if err != nil { 195 | return errors.Wrapf(err, "%v", party) 196 | } 197 | 198 | return nil 199 | } 200 | -------------------------------------------------------------------------------- /src/integrationtests/opaque_integration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package integrationtests 27 | 28 | // TODO: test full OPAQUE flow (with AKE) 29 | -------------------------------------------------------------------------------- /src/ohttp/common.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package ohttp 26 | 27 | const ( 28 | // SessionIDLength represents the lenght of the session ID. 29 | SessionIDLength = 32 30 | 31 | httpsPrefix = "https://" 32 | 33 | // error messages to expose to user 34 | 35 | // ErrorBadPasswordLikely represents an when when the password is wrong. 36 | ErrorBadPasswordLikely = "Server provided a bad envelope. This likely indicates a mistyped password." 37 | // ErrorUsernameTaken represents an error when the username is already taken. 38 | ErrorUsernameTaken = "Username already registered. Try again with a new username." 39 | // ErrorUsernameNotRegistered represents and error when the username is not yet registered. 40 | ErrorUsernameNotRegistered = "Username not registered. Register your username before logging in." 41 | // ErrorGeneric represents a generic error. 42 | ErrorGeneric = "Something went wrong. Try again." 43 | 44 | // success messages to expose to the user 45 | 46 | // SuccessLogin represents a message for when the login is successful. 47 | SuccessLogin = "Login successful!" 48 | // SuccessRegistration represents a message for when the registration is successful. 49 | SuccessRegistration = "Registration successful!" 50 | 51 | // LocalDomain represents the domain for local view. 52 | LocalDomain = "127.0.0.1:8080" 53 | 54 | exporterKeyTestEndpoint = "/auth/exporter-keys-test" // TEST ONLY 55 | 56 | // API endpoints 57 | configEndpoint = "/config" 58 | exporterKeyEndpoint = "/auth/exporter-keys" 59 | registerRequestEndpoint = "/auth/register" 60 | registerFinalizeEndpoint = "/auth/register-finalize" 61 | loginRequestEndpoint = "/auth/login" 62 | loginFinalizeEndpoint = "/auth/login-finalize" 63 | ) 64 | 65 | // HTTPMessage represents an HTTP Message. 66 | type HTTPMessage struct { 67 | RequestID []byte 68 | RequestBody []byte 69 | } 70 | -------------------------------------------------------------------------------- /src/ohttp/opaque_http_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package ohttp 26 | 27 | import ( 28 | "io/ioutil" 29 | "log" 30 | "os" 31 | 32 | "testing" 33 | "time" 34 | 35 | "github.com/cloudflare/circl/oprf" 36 | "github.com/cloudflare/opaque-ea/src/common" 37 | "github.com/pkg/errors" 38 | ) 39 | 40 | func TestMain(m *testing.M) { 41 | // Turn off logging 42 | log.SetOutput(ioutil.Discard) 43 | os.Exit(m.Run()) 44 | } 45 | 46 | func TestRunOpaqueOverHTTP(t *testing.T) { 47 | t.Skip("Does not work with proxies") 48 | go func() { 49 | err := RunOpaqueServer() 50 | if err != nil { 51 | t.Error(err) 52 | } 53 | }() 54 | 55 | cfg := setup() 56 | 57 | t.Run("Happy path", func(t *testing.T) { 58 | username := "new_user" 59 | password := "new_password" 60 | 61 | err := cfg.RunOpaqueRegisterClient(username, password) 62 | if err != nil { 63 | t.Error(err) 64 | return 65 | } 66 | 67 | getExportedKey, authHash, err := cfg.RequestExportedKeys() 68 | if err != nil { 69 | t.Error(err) 70 | return 71 | } 72 | 73 | err = cfg.RunOpaqueLoginClient(username, password, getExportedKey, authHash) 74 | if err != nil { 75 | t.Error(err) 76 | return 77 | } 78 | }) 79 | 80 | t.Run("User not registered", func(t *testing.T) { 81 | username := "not_registered" 82 | password := "password" 83 | 84 | // skip registration 85 | 86 | getExportedKey, authHash, err := cfg.RequestExportedKeys() 87 | if err != nil { 88 | t.Error(err) 89 | return 90 | } 91 | 92 | err = cfg.RunOpaqueLoginClient(username, password, getExportedKey, authHash) 93 | if err == nil || !errors.Is(err, common.ErrorUserNotRegistered) { 94 | t.Errorf("expected error %s, got %s", common.ErrorUserNotRegistered, err) 95 | return 96 | } 97 | }) 98 | 99 | t.Run("User already registered", func(t *testing.T) { 100 | username := "user1" 101 | password := "new_password" 102 | 103 | err := cfg.RunOpaqueRegisterClient(username, password) 104 | if err != nil { 105 | t.Error(err) 106 | return 107 | } 108 | 109 | err = cfg.RunOpaqueRegisterClient(username, password) 110 | if err == nil || !errors.Is(err, common.ErrorUserAlreadyRegistered) { 111 | t.Errorf("expected error %s, got %s", common.ErrorUserAlreadyRegistered, err) 112 | return 113 | } 114 | }) 115 | 116 | t.Run("Bad password", func(t *testing.T) { 117 | username := "user1" 118 | password := "wrong" 119 | 120 | getExportedKey, authHash, err := cfg.RequestExportedKeys() 121 | if err != nil { 122 | t.Error(err) 123 | return 124 | } 125 | 126 | err = cfg.RunOpaqueLoginClient(username, password, getExportedKey, authHash) 127 | if err == nil || !errors.Is(err, common.ErrorBadEnvelope) { 128 | t.Errorf("expected error %s, got %s", common.ErrorBadEnvelope, err) 129 | return 130 | } 131 | }) 132 | 133 | t.Run("Bad ciphersuite", func(t *testing.T) { 134 | username := "user42" 135 | password := "password" 136 | 137 | cfg.Ciphersuite = 0x00 138 | 139 | err := cfg.RunOpaqueRegisterClient(username, password) 140 | if err == nil || !errors.Is(err, common.ErrorUnsupportedCiphersuite) { 141 | t.Errorf("expected error %s, got %s", common.ErrorUnsupportedCiphersuite, err) 142 | return 143 | } 144 | }) 145 | } 146 | 147 | func setup() *ClientConfig { 148 | time.Sleep(3 * time.Second) // wait for server to start 149 | 150 | return &ClientConfig{ 151 | Domain: "127.0.0.1:8080", 152 | Logger: log.New(log.Writer(), "", 0), 153 | Ciphersuite: oprf.OPRFP256, 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/opaque/core.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "github.com/cloudflare/opaque-ea/src/common" 30 | ) 31 | 32 | // CreateCredentialRequest is called by the client on a password to initiate the 33 | // online OPAQUE protocol. 34 | // Returns a credential request, which will be sent to the server. 35 | func (c *Client) CreateCredentialRequest(password []byte) (*CredentialRequest, error) { 36 | blinded, err := c.blind(string(password)) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | return &CredentialRequest{ 42 | UserID: c.UserID, 43 | OprfData: blinded, 44 | }, nil 45 | } 46 | 47 | // CreateCredentialResponse is called by the server on receiving a request from 48 | // the client. 49 | // Returns a credential response, which will be sent to the server. 50 | func (s *Server) CreateCredentialResponse(request *CredentialRequest) (*CredentialResponse, error) { 51 | record, err := s.GetUserRecordFromUsername(request.UserID) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | s.UserRecord = record 57 | eval, err := s.evaluate(request.OprfData) 58 | if err != nil { 59 | s.UserRecord = nil 60 | return nil, err 61 | } 62 | 63 | return &CredentialResponse{ 64 | OprfData: eval, 65 | Envelope: record.Envelope, 66 | serverPublicKey: s.Config.Signer.Public(), 67 | }, nil 68 | } 69 | 70 | // RecoverCredentials is called by the client on receiving an OPAQUE response from 71 | // the server. 72 | // Returns the credentials that the client uploaded during the registration phase. 73 | func (c *Client) RecoverCredentials(response *CredentialResponse) (*Credentials, error) { 74 | rwd, err := c.finalizeHarden(response.OprfData) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | creds, err := DecryptCredentials(rwd, response.Envelope) 80 | if err != nil { 81 | return nil, common.ErrorBadEnvelope.Wrap(err) 82 | } 83 | 84 | return creds, nil 85 | } 86 | -------------------------------------------------------------------------------- /src/opaque/core_messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "crypto/x509" 31 | "math/big" 32 | 33 | "github.com/cloudflare/opaque-ea/src/common" 34 | "github.com/pkg/errors" 35 | "github.com/tatianab/mint/syntax" 36 | ) 37 | 38 | // ProtocolMessageType indicates the OPAQUE protocol message type 39 | // 40 | // enum { 41 | // registration_request(1), 42 | // registration_response(2), 43 | // registration_upload(3), 44 | // credential_request(4), 45 | // credential_response(5), 46 | // (255) 47 | // } ProtocolMessageType;. 48 | type ProtocolMessageType byte 49 | 50 | // OPAQUE protocol message types. 51 | const ( 52 | ProtocolMessageTypeRegistrationRequest ProtocolMessageType = 1 + iota 53 | ProtocolMessageTypeRegistrationResponse 54 | ProtocolMessageTypeRegistrationUpload 55 | ProtocolMessageTypeCredentialRequest 56 | ProtocolMessageTypeCredentialResponse 57 | ) 58 | 59 | // A ProtocolMessage is a bundle containing all OPAQUE data sent in a flow 60 | // between parties (during registration or login). 61 | // 62 | // struct { 63 | // ProtocolMessageType msg_type; /* protocol message type */ 64 | // uint24 length; /* remaining bytes in message */ 65 | // select (ProtocolMessage.msg_type) { 66 | // case registration_request: RegistrationRequest; 67 | // case registration_response: RegistrationResponse; 68 | // case registration_upload: RegistrationUpload; 69 | // case credential_request: CredentialRequest; 70 | // case credential_response: CredentialResponse; 71 | // }; 72 | // } ProtocolMessage; 73 | // 74 | // 1 3 75 | // | messageType | messageBodyLen | messageBody | 76 | type ProtocolMessage struct { 77 | MessageType ProtocolMessageType 78 | MessageBodyRaw []byte `tls:"head=3"` 79 | } 80 | 81 | // Marshal encodes a ProtocolMessage. 82 | func (msg *ProtocolMessage) Marshal() ([]byte, error) { 83 | return syntax.Marshal(msg) 84 | } 85 | 86 | // Unmarshal decodes a ProtocolMessage. 87 | func (msg *ProtocolMessage) Unmarshal(data []byte) (int, error) { 88 | return syntax.Unmarshal(data, msg) 89 | } 90 | 91 | // ToBody assigns the message type. 92 | func (msg *ProtocolMessage) ToBody() (ProtocolMessageBody, error) { 93 | var body ProtocolMessageBody 94 | 95 | switch msg.MessageType { 96 | case ProtocolMessageTypeRegistrationRequest: 97 | body = new(RegistrationRequest) 98 | case ProtocolMessageTypeRegistrationResponse: 99 | body = new(RegistrationResponse) 100 | case ProtocolMessageTypeRegistrationUpload: 101 | body = new(RegistrationUpload) 102 | case ProtocolMessageTypeCredentialRequest: 103 | body = new(CredentialRequest) 104 | case ProtocolMessageTypeCredentialResponse: 105 | body = new(CredentialResponse) 106 | default: 107 | return body, errors.Wrapf(common.ErrorUnrecognizedMessage, "message type %s", msg.MessageType) 108 | } 109 | 110 | return body, nil 111 | } 112 | 113 | // ProtocolMessageFromBody reconstructs a ProtocolMessage from its body. 114 | func ProtocolMessageFromBody(body ProtocolMessageBody) (*ProtocolMessage, error) { 115 | bodyRaw, err := body.Marshal() 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | return &ProtocolMessage{ 121 | MessageType: body.Type(), 122 | MessageBodyRaw: bodyRaw, 123 | }, nil 124 | } 125 | 126 | // ProtocolMessageBody is an interface implemented by all protocol messages. 127 | // Represents the "inner" part of the message, not including metadata. 128 | type ProtocolMessageBody interface { 129 | Marshal() ([]byte, error) 130 | Unmarshal([]byte) (int, error) 131 | Type() ProtocolMessageType 132 | } 133 | 134 | // A CredentialRequest is the first message sent by the client to initiate 135 | // OPAQUE. 136 | // Implements ProtocolMessageBody. 137 | // 138 | // struct { 139 | // opaque id<0..2^16-1>; 140 | // opaque data<1..2^16-1>; 141 | // } CredentialRequest; 142 | // 143 | // 2 2 144 | // | userIDLen | userID | oprfDataLen | oprfData | 145 | type CredentialRequest struct { 146 | UserID []byte `tls:"head=2"` // client account info, if present 147 | OprfData []byte `tls:"head=2,min=1"` // an encoded element in the OPRF group 148 | } 149 | 150 | var _ ProtocolMessageBody = (*CredentialRequest)(nil) 151 | 152 | // Marshal returns the raw form of the struct. 153 | func (cr *CredentialRequest) Marshal() ([]byte, error) { 154 | return syntax.Marshal(cr) 155 | } 156 | 157 | // Unmarshal puts raw data into fields of a struct. 158 | func (cr *CredentialRequest) Unmarshal(data []byte) (int, error) { 159 | return syntax.Unmarshal(data, cr) 160 | } 161 | 162 | // Type returns the type of this struct. 163 | func (*CredentialRequest) Type() ProtocolMessageType { 164 | return ProtocolMessageTypeCredentialRequest 165 | } 166 | 167 | // A CredentialResponse is the message sent by the server in response to 168 | // the Client's initial OPAQUE message. 169 | // Implements ProtocolMessageBody. 170 | // 171 | // struct { 172 | // opaque data<1..2^16-1>; 173 | // opaque envelope<1..2^16-1>; 174 | // opaque pkS<0..2^16-1; 175 | // } CredentialResponse; 176 | // 177 | // 2 2 178 | // | oprfDataLen | oprfData | envelope | pkSLen | pkS | 179 | type CredentialResponse struct { 180 | OprfData []byte // an encoded element in the OPRF group 181 | Envelope *Envelope // an authenticated encoding of a Credentials structure 182 | serverPublicKey crypto.PublicKey // OPTIONAL: an encoded public key that will be used for the online authenticated key exchange stage. 183 | } 184 | 185 | // Type returns the type of this struct. 186 | func (*CredentialResponse) Type() ProtocolMessageType { 187 | return ProtocolMessageTypeCredentialResponse 188 | } 189 | 190 | type credentialResponseInner struct { 191 | OprfData []byte `tls:"head=2,min=1"` 192 | Envelope *Envelope 193 | ServerPublicKey []byte `tls:"head=2"` 194 | } 195 | 196 | // Marshal encodes a Credential Response. 197 | func (cr *CredentialResponse) Marshal() ([]byte, error) { 198 | rawPublicKey, err := x509.MarshalPKIXPublicKey(cr.serverPublicKey) 199 | if err != nil { 200 | return nil, err 201 | } 202 | 203 | toMarshal := &credentialResponseInner{ 204 | cr.OprfData, 205 | cr.Envelope, 206 | rawPublicKey, 207 | } 208 | 209 | return syntax.Marshal(toMarshal) 210 | } 211 | 212 | // Unmarshal decodes a Credential Response. 213 | func (cr *CredentialResponse) Unmarshal(data []byte) (int, error) { 214 | cri := new(credentialResponseInner) 215 | 216 | bytesRead, err := syntax.Unmarshal(data, cri) 217 | if err != nil { 218 | return 0, err 219 | } 220 | 221 | publicKey, err := x509.ParsePKIXPublicKey(cri.ServerPublicKey) 222 | if err != nil { 223 | return 0, err 224 | } 225 | 226 | *cr = CredentialResponse{ 227 | cri.OprfData, cri.Envelope, publicKey, 228 | } 229 | 230 | return bytesRead, nil 231 | } 232 | 233 | // RequestMetadata is the secret state generated and held by the client 234 | // during the OPAQUE protocol. Should be deleted after the protocol completes. 235 | // 236 | // struct { 237 | // opaque data_blind<1..2^16-1>; 238 | // } RequestMetadata;. 239 | type RequestMetadata struct { 240 | Blind *big.Int // an oprf scalar element (randomness used to blind OPRF1) 241 | } 242 | -------------------------------------------------------------------------------- /src/opaque/core_messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto/rand" 30 | "testing" 31 | 32 | "github.com/tatianab/mint" 33 | ) 34 | 35 | func TestMarshalUnmarshalProtocolMessage(t *testing.T) { 36 | oprfData := make([]byte, 32) 37 | _, _ = rand.Read(oprfData) 38 | 39 | cr1 := &CredentialRequest{ 40 | UserID: []byte("username"), 41 | OprfData: oprfData, 42 | } 43 | 44 | msg1, err := ProtocolMessageFromBody(cr1) 45 | if err != nil { 46 | t.Error(err) 47 | return 48 | } 49 | 50 | msg2 := &ProtocolMessage{} 51 | 52 | if err := TestMarshalUnmarshal(msg1, msg2); err != nil { 53 | t.Error(err) 54 | return 55 | } 56 | } 57 | 58 | func TestMarshalUnmarshalCredentialRequest(t *testing.T) { 59 | oprfData := make([]byte, 32) 60 | _, _ = rand.Read(oprfData) 61 | 62 | cr1 := &CredentialRequest{ 63 | UserID: []byte("username"), 64 | OprfData: oprfData, 65 | } 66 | 67 | cr2 := &CredentialRequest{} 68 | if err := TestMarshalUnmarshal(cr1, cr2); err != nil { 69 | t.Error(err) 70 | return 71 | } 72 | } 73 | 74 | func TestMarshalUnmarshalCredentialResponse(t *testing.T) { 75 | oprfData := make([]byte, 32) 76 | _, _ = rand.Read(oprfData) 77 | 78 | signer, err := mint.NewSigningKey(mint.ECDSA_P256_SHA256) 79 | if err != nil { 80 | t.Error(err) 81 | return 82 | } 83 | 84 | cr1 := &CredentialResponse{ 85 | OprfData: oprfData, 86 | Envelope: getDummyEnvelope(), 87 | serverPublicKey: signer.Public(), 88 | } 89 | 90 | cr2 := &CredentialResponse{} 91 | if err := TestMarshalUnmarshal(cr1, cr2); err != nil { 92 | t.Error(err) 93 | return 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/opaque/core_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "testing" 30 | 31 | "github.com/cloudflare/circl/oprf" 32 | "github.com/pkg/errors" 33 | "github.com/tatianab/mint" 34 | ) 35 | 36 | func TestOPAQUECore(t *testing.T) { 37 | err := RegisterAndRunOPAQUE(oprf.OPRFP256) 38 | if err != nil { 39 | t.Errorf("%v", err) 40 | } 41 | } 42 | 43 | func RegisterAndRunOPAQUE(suite oprf.SuiteID) error { 44 | domain := "example.com" 45 | 46 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 47 | if err != nil { 48 | return err 49 | } 50 | 51 | cfg := &ServerConfig{ 52 | ServerID: domain, 53 | Signer: signer, 54 | RecordTable: make(InMemoryUserRecordTable), 55 | Suite: suite, 56 | } 57 | s, err := NewServer(cfg) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | username := "user" 63 | password := []byte("password") 64 | c, err := NewClient(username, domain, suite) 65 | if err != nil { 66 | return errors.Wrap(err, "new client") 67 | } 68 | 69 | // REGISTRATION FLOW 70 | regRequest, err := c.CreateRegistrationRequest(string(password), signer) 71 | if err != nil { 72 | return errors.Wrap(err, "create reg request") 73 | } 74 | 75 | regResponse, err := s.CreateRegistrationResponse(regRequest) 76 | if err != nil { 77 | return errors.Wrap(err, "create reg response") 78 | } 79 | 80 | regUpload, _, err := c.FinalizeRegistrationRequest(regResponse) 81 | if err != nil { 82 | return errors.Wrap(err, "finalize request") 83 | } 84 | 85 | err = s.StoreUserRecord(regUpload) 86 | if err != nil { 87 | return errors.Wrap(err, "store user record") 88 | } 89 | 90 | // LOGIN FLOW 91 | // C -- (username, OPRF_1) --> S 92 | loginRequest, err := c.CreateCredentialRequest(password) 93 | if err != nil { 94 | return errors.Wrap(err, "create cred request") 95 | } 96 | // S -- (Envelope, OPRF_2) --> C 97 | loginResponse, err := s.CreateCredentialResponse(loginRequest) 98 | if err != nil { 99 | return errors.Wrap(err, "create cred response") 100 | } 101 | 102 | // C - finish OPRF and decrypt 103 | _, err = c.RecoverCredentials(loginResponse) 104 | if err != nil { 105 | return errors.Wrap(err, "recover creds") 106 | } 107 | 108 | // TODO: validation 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /src/opaque/credentials.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "crypto/x509" 31 | 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | "github.com/pkg/errors" 34 | "github.com/tatianab/mint/syntax" 35 | ) 36 | 37 | // CredentialType indicates the type of an OPAQUE credential extension struct. 38 | // enum { 39 | // skU(1), 40 | // pkU(2), 41 | // pkS(3), 42 | // idU(4), 43 | // idS(5), 44 | // (255) 45 | // } CredentialType; 46 | type CredentialType byte 47 | 48 | // Credential types. 49 | const ( 50 | CredentialTypeUserPrivateKey CredentialType = 1 + iota 51 | CredentialTypeUserPublicKey 52 | CredentialTypeServerPublicKey 53 | CredentialTypeUserIdentity 54 | CredentialTypeServerIdentity 55 | ) 56 | 57 | // A CredentialExtension is a piece of data that may be included in client Credentials. 58 | // 59 | // struct { 60 | // CredentialType type; 61 | // CredentialData data<0..2^16-1>; 62 | // } CredentialExtension; 63 | // 64 | // 1 2 65 | // | credType | credDataLen | credData |. 66 | type CredentialExtension struct { 67 | CredentialType CredentialType 68 | CredentialData []byte `tls:"head=2"` 69 | } 70 | 71 | // Marshal returns the raw form of the struct. 72 | func (ce *CredentialExtension) Marshal() ([]byte, error) { 73 | return syntax.Marshal(ce) 74 | } 75 | 76 | // Unmarshal puts raw data into fields of a struct. 77 | func (ce *CredentialExtension) Unmarshal(data []byte) (int, error) { 78 | return syntax.Unmarshal(data, ce) 79 | } 80 | 81 | func newCredentialExtension(t CredentialType, val interface{}) (*CredentialExtension, error) { 82 | var data []byte 83 | var err error 84 | var ok bool 85 | 86 | switch t { 87 | case CredentialTypeServerIdentity, CredentialTypeUserIdentity: 88 | data, ok = val.([]byte) 89 | if !ok { 90 | return nil, errors.New("expected array of bytes") 91 | } 92 | case CredentialTypeServerPublicKey, CredentialTypeUserPublicKey: 93 | data, err = x509.MarshalPKIXPublicKey(val) 94 | if err != nil { 95 | return nil, err 96 | } 97 | case CredentialTypeUserPrivateKey: 98 | data, err = x509.MarshalPKCS8PrivateKey(val) 99 | if err != nil { 100 | return nil, err 101 | } 102 | } 103 | 104 | return &CredentialExtension{ 105 | CredentialType: t, 106 | CredentialData: data, 107 | }, nil 108 | } 109 | 110 | func (ce *CredentialExtension) parseToValue(t CredentialType) (interface{}, error) { 111 | if t != ce.CredentialType { 112 | return nil, errors.New("incorrect credential type") 113 | } 114 | 115 | switch t { 116 | case CredentialTypeServerIdentity, CredentialTypeUserIdentity: 117 | return string(ce.CredentialData), nil 118 | case CredentialTypeServerPublicKey, CredentialTypeUserPublicKey: 119 | val, err := x509.ParsePKIXPublicKey(ce.CredentialData) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | return val, nil 125 | case CredentialTypeUserPrivateKey: 126 | val, err := x509.ParsePKCS8PrivateKey(ce.CredentialData) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | signer, ok := val.(crypto.Signer) 132 | if !ok { 133 | return nil, errors.New("credential data is not a Signer") 134 | } 135 | 136 | return signer, nil 137 | } 138 | 139 | return nil, errors.New("unrecognized credential type") 140 | } 141 | 142 | // Credentials holds the decrypted user-specific envelope contents. 143 | // 144 | // struct { 145 | // CredentialExtension secret_credentials<1..2^16-1>; 146 | // CredentialExtension cleartext_credentials<0..2^16-1>; 147 | // } Credentials; 148 | // 149 | // 2 2 150 | // | secretCredsLen | secretCreds | cleartextCredsLen | cleartextCreds | 151 | // SecretCredentials MUST contain the skU. It can contain the pkS. 152 | // CleartextCredentials MUST contain the pkS 153 | type Credentials struct { 154 | SecretCredentials CredentialExtensionList `tls:"head=2,min=1"` 155 | CleartextCredentials CredentialExtensionList `tls:"head=2"` 156 | } 157 | 158 | // Find returns the value of credential type t in this Credentials struct, or 159 | // false if not present. 160 | func (creds *Credentials) Find(t CredentialType) (interface{}, bool) { 161 | if val, ok := creds.SecretCredentials.Find(t); ok { 162 | return val, true 163 | } 164 | 165 | if val, ok := creds.CleartextCredentials.Find(t); ok { 166 | return val, true 167 | } 168 | 169 | return nil, false 170 | } 171 | 172 | // Marshal returns the raw form of the struct. 173 | func (creds *Credentials) Marshal() ([]byte, error) { 174 | return syntax.Marshal(creds) 175 | } 176 | 177 | // MarshalSplit encodes the Credential into its secret and clear parts. 178 | func (creds *Credentials) MarshalSplit() (secret, cleartext []byte, err error) { 179 | secret, err = creds.SecretCredentials.Marshal() 180 | if err != nil { 181 | return nil, nil, err 182 | } 183 | 184 | cleartext, err = creds.CleartextCredentials.Marshal() 185 | 186 | return secret, cleartext, err 187 | } 188 | 189 | // Unmarshal puts raw data into fields of a struct. 190 | func (creds *Credentials) Unmarshal(data []byte) (int, error) { 191 | return syntax.Unmarshal(data, creds) 192 | } 193 | 194 | // UnmarshalSplit decodes the Credential into its secret and clear parts. 195 | func (creds *Credentials) UnmarshalSplit(secret, cleartext []byte) (int, error) { 196 | return creds.Unmarshal(append(secret, cleartext...)) 197 | } 198 | 199 | // CredentialExtensionList is a list of credential extensions. 200 | // Used to package secret and cleartext credentials separately. 201 | type CredentialExtensionList []*CredentialExtension 202 | 203 | type credentialExtensionListInner struct { 204 | List []*CredentialExtension `tls:"head=2"` 205 | } 206 | 207 | // Find returns the value of credential type t in this CredentialExtensionList, or 208 | // false if not present. 209 | func (cel CredentialExtensionList) Find(t CredentialType) (interface{}, bool) { 210 | for _, ext := range cel { 211 | if val, err := ext.parseToValue(t); err == nil { 212 | return val, true 213 | } 214 | } 215 | 216 | return nil, false 217 | } 218 | 219 | // Marshal encodes the Credential Extension List. 220 | func (cel CredentialExtensionList) Marshal() ([]byte, error) { 221 | return syntax.Marshal(credentialExtensionListInner{List: cel}) 222 | } 223 | 224 | // EncryptCredentials encrypts the given Credentials 225 | // under a key derived from rwd, the randomized password. 226 | func EncryptCredentials(rwd []byte, creds *Credentials, nonceLength int) (*Envelope, []byte, error) { 227 | nonce := common.GetRandomBytes(nonceLength) 228 | 229 | plaintext, authData, err := creds.MarshalSplit() 230 | if err != nil { 231 | return nil, nil, err 232 | } 233 | 234 | otp, err := NewAuthenticatedOneTimePad(rwd, nonce, len(plaintext)) 235 | if err != nil { 236 | return nil, nil, err 237 | } 238 | 239 | ciphertext, tag, err := otp.Seal(plaintext, authData) 240 | if err != nil { 241 | return nil, nil, err 242 | } 243 | 244 | return &Envelope{ 245 | Nonce: nonce, 246 | EncryptedCreds: ciphertext, 247 | AuthenticatedCreds: authData, 248 | AuthTag: tag, 249 | }, otp.exporterKey, nil 250 | } 251 | 252 | // DecryptCredentials decrypts the encrypted envelope. 253 | // Returns the decrypted Credentials struct, or an error if decryption fails. 254 | func DecryptCredentials(rwd []byte, envelope *Envelope) (*Credentials, error) { 255 | otp, err := NewAuthenticatedOneTimePad(rwd, envelope.Nonce, len(envelope.EncryptedCreds)) 256 | if err != nil { 257 | return nil, err 258 | } 259 | 260 | plaintext, err := otp.Open(envelope.EncryptedCreds, envelope.AuthenticatedCreds, envelope.AuthTag) 261 | if err != nil { 262 | return nil, err 263 | } 264 | 265 | // Make credentials 266 | creds := &Credentials{} 267 | _, err = creds.UnmarshalSplit(plaintext, envelope.AuthenticatedCreds) 268 | if err != nil { 269 | return nil, err 270 | } 271 | 272 | return creds, nil 273 | } 274 | -------------------------------------------------------------------------------- /src/opaque/credentials_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "reflect" 31 | "testing" 32 | 33 | "github.com/cloudflare/circl/oprf" 34 | "github.com/cloudflare/opaque-ea/src/common" 35 | "github.com/pkg/errors" 36 | "github.com/tatianab/mint" 37 | ) 38 | 39 | func getDummyCredentials() (*Credentials, error) { 40 | userSigner, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | serverSigner, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | creds, err := newTestCredentials(userSigner, serverSigner.Public(), "server.com") 51 | if err != nil { 52 | return nil, err 53 | } 54 | return creds, nil 55 | } 56 | 57 | func TestMarshalUnmarshalCredentials(t *testing.T) { 58 | cred1, err := getDummyCredentials() 59 | if err != nil { 60 | t.Errorf("FAIL: get dummy creds failed: %v", err) 61 | return 62 | } 63 | 64 | cred2 := &Credentials{} 65 | if err := TestMarshalUnmarshal(cred1, cred2); err != nil { 66 | t.Error(err) 67 | return 68 | } 69 | } 70 | 71 | func TestMarshalUnmarshalCredentialExtension(t *testing.T) { 72 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 73 | if err != nil { 74 | t.Error(err) 75 | return 76 | } 77 | 78 | credExt1, err := newCredentialExtension(CredentialTypeUserPublicKey, signer.Public()) 79 | if err != nil { 80 | t.Error(err) 81 | return 82 | } 83 | 84 | credExt2 := &CredentialExtension{} 85 | if err := TestMarshalUnmarshal(credExt1, credExt2); err != nil { 86 | t.Error(err) 87 | return 88 | } 89 | } 90 | 91 | func TestEncryptDecryptCredentials(t *testing.T) { 92 | key := common.GetRandomBytes(32) 93 | nonceLen := 32 94 | 95 | creds1, err := getDummyCredentials() // user record does not matter for this test 96 | if err != nil { 97 | t.Errorf("FAIL: get dummy creds failed: %v", err) 98 | return 99 | } 100 | 101 | envelope, exportedKey, err := EncryptCredentials(key, creds1, nonceLen) 102 | if err != nil { 103 | t.Errorf("encryption error: %v", err) 104 | return 105 | } 106 | 107 | if len(exportedKey) == 0 { 108 | t.Errorf("exportedKey not set") 109 | return 110 | } 111 | 112 | creds2, err := DecryptCredentials(key, envelope) 113 | if err != nil { 114 | t.Errorf("decryption error: %v", err) 115 | return 116 | } 117 | 118 | if !reflect.DeepEqual(creds1, creds2) { 119 | t.Errorf("original/decrypted creds are different %v, %v ", creds1, creds2) 120 | return 121 | } 122 | } 123 | 124 | func TestCredentialEncryptionPolicy(t *testing.T) { 125 | c, err := NewClient("user", "server", oprf.OPRFP256) 126 | if err != nil { 127 | t.Error(err) 128 | return 129 | } 130 | 131 | clientSigner, err := mint.NewSigningKey(mint.ECDSA_P256_SHA256) 132 | if err != nil { 133 | t.Error(err) 134 | return 135 | } 136 | 137 | c.signer = clientSigner 138 | 139 | serverSigner, err := mint.NewSigningKey(mint.ECDSA_P256_SHA256) 140 | if err != nil { 141 | t.Error(err) 142 | return 143 | } 144 | 145 | policy1 := &CredentialEncodingPolicy{ 146 | SecretTypes: []CredentialType{CredentialTypeUserPrivateKey}, 147 | CleartextTypes: []CredentialType{CredentialTypeServerIdentity, CredentialTypeServerPublicKey}, 148 | } 149 | 150 | err = checkPolicy(c, serverSigner.Public(), policy1) 151 | if err != nil { 152 | t.Error(errors.Wrap(err, "default policy")) 153 | return 154 | } 155 | 156 | policy2 := &CredentialEncodingPolicy{ 157 | SecretTypes: []CredentialType{CredentialTypeUserPrivateKey, CredentialTypeServerIdentity, CredentialTypeServerPublicKey}, 158 | } 159 | 160 | err = checkPolicy(c, serverSigner.Public(), policy2) 161 | if err != nil { 162 | t.Error(errors.Wrap(err, "all secret policy")) 163 | return 164 | } 165 | 166 | badPolicy := &CredentialEncodingPolicy{ 167 | CleartextTypes: []CredentialType{CredentialTypeUserPrivateKey, CredentialTypeServerIdentity, CredentialTypeServerPublicKey}, 168 | } 169 | 170 | err = checkPolicy(c, serverSigner.Public(), badPolicy) 171 | if !errors.Is(err, common.ErrorForbiddenPolicy) { 172 | t.Error(errors.Wrap(err, "bad policy")) 173 | return 174 | } 175 | } 176 | 177 | func checkPolicy(client *Client, serverPublicKey crypto.PublicKey, policy *CredentialEncodingPolicy) error { 178 | creds, err := client.credentialsFromPolicy(policy, serverPublicKey) 179 | if err != nil { 180 | return errors.Wrap(err, "get creds") 181 | } 182 | 183 | err = checkCreds(creds.SecretCredentials, policy.SecretTypes) 184 | if err != nil { 185 | return errors.Wrap(err, "check secret creds") 186 | } 187 | 188 | err = checkCreds(creds.CleartextCredentials, policy.CleartextTypes) 189 | if err != nil { 190 | return errors.Wrap(err, "check cleartext creds") 191 | } 192 | 193 | key := common.GetRandomBytes(32) 194 | nonceLen := 32 195 | 196 | encrypted, _, err := EncryptCredentials(key, creds, nonceLen) 197 | if err != nil { 198 | return errors.Wrap(err, "encrypt creds") 199 | } 200 | 201 | if len(encrypted.EncryptedCreds) == 0 { 202 | return errors.New("encrypted creds should never be empty") 203 | } 204 | 205 | if len(encrypted.AuthenticatedCreds) == 0 && len(policy.CleartextTypes) != 0 { 206 | return errors.New("policy says authenticated creds should not be empty") 207 | } 208 | 209 | if len(encrypted.AuthTag) == 0 { 210 | return errors.New("auth tag must not be empty") 211 | } 212 | 213 | decrypted, err := DecryptCredentials(key, encrypted) 214 | if err != nil { 215 | return errors.Wrap(err, "decrypt creds") 216 | } 217 | 218 | if !reflect.DeepEqual(creds, decrypted) { 219 | return errors.Errorf("original/decrypted creds are different %v, %v ", creds, decrypted) 220 | } 221 | 222 | return nil 223 | } 224 | 225 | func checkCreds(credList CredentialExtensionList, credTypes []CredentialType) error { 226 | if len(credList) != len(credTypes) { 227 | return errors.New("credentials not encoded according to policy: lengths are different") 228 | } 229 | 230 | for _, credType := range credTypes { 231 | if _, ok := credList.Find(credType); !ok { 232 | return errors.Errorf("credentials not encoded according to policy: %v not found", credType) 233 | } 234 | } 235 | 236 | return nil 237 | } 238 | 239 | func TestFindCredentialExtensions(t *testing.T) { 240 | domain := "server.com" 241 | 242 | userSigner, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 243 | if err != nil { 244 | t.Error(err) 245 | return 246 | } 247 | 248 | serverSigner, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 249 | if err != nil { 250 | t.Error(err) 251 | return 252 | } 253 | 254 | creds, err := newTestCredentials(userSigner, serverSigner.Public(), domain) 255 | if err != nil { 256 | t.Error(err) 257 | return 258 | } 259 | 260 | userSigner2, ok := creds.Find(CredentialTypeUserPrivateKey) 261 | if !ok { 262 | t.Errorf("user private key not in list") 263 | } 264 | 265 | if !reflect.DeepEqual(userSigner, userSigner2) { 266 | t.Errorf("user private keys do not match") 267 | } 268 | 269 | serverPublicKey2, ok := creds.Find(CredentialTypeServerPublicKey) 270 | if !ok { 271 | t.Errorf("server public key not in list") 272 | } 273 | 274 | if !reflect.DeepEqual(serverSigner.Public(), serverPublicKey2) { 275 | t.Errorf("server public keys do not match") 276 | } 277 | 278 | domain2, ok := creds.Find(CredentialTypeServerIdentity) 279 | if !ok { 280 | t.Errorf("server id not in list") 281 | } 282 | 283 | if !reflect.DeepEqual(domain, domain2) { 284 | t.Errorf("server ids do not match") 285 | } 286 | } 287 | -------------------------------------------------------------------------------- /src/opaque/envelope.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "github.com/tatianab/mint/syntax" 30 | ) 31 | 32 | // Envelope is the data encrypted under the randomized password 33 | // which is stored encrypted and sent to to the user. 34 | // 35 | // struct { 36 | // opaque nonce[Nn]; 37 | // opaque ct<1..2^16-1>; 38 | // opaque auth_data<0..2^16-1>; 39 | // opaque auth_tag<1..2^16-1>; 40 | // } Envelope; 41 | // 42 | // 1 2 2 2 43 | // | nonceLen | nonce | encCredsLen | encCreds | authCredsLen | authCreds | authTagLen | authTag |. 44 | type Envelope struct { 45 | Nonce []byte `tls:"head=1"` // unique value, , which must be 32 byte long. 46 | EncryptedCreds []byte `tls:"head=2,min=1"` // raw encrypted and authenticated credential extensions list. 47 | AuthenticatedCreds []byte `tls:"head=2"` // raw authenticated credential extensions list. 48 | AuthTag []byte `tls:"head=2,min=1"` // tag authenticating the envelope contents. 49 | } 50 | 51 | // Marshal returns the raw form of the struct. 52 | func (e *Envelope) Marshal() ([]byte, error) { 53 | return syntax.Marshal(e) 54 | } 55 | 56 | // Unmarshal puts raw data into fields of a struct. 57 | func (e *Envelope) Unmarshal(data []byte) (int, error) { 58 | return syntax.Unmarshal(data, e) 59 | } 60 | -------------------------------------------------------------------------------- /src/opaque/envelope_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "testing" 30 | 31 | "github.com/cloudflare/opaque-ea/src/common" 32 | ) 33 | 34 | func getDummyEnvelope() *Envelope { 35 | return &Envelope{ 36 | Nonce: common.GetRandomBytes(32), 37 | EncryptedCreds: common.GetRandomBytes(32), 38 | AuthenticatedCreds: common.GetRandomBytes(32), 39 | AuthTag: common.GetRandomBytes(32), 40 | } 41 | } 42 | 43 | func TestMarshalUnmarshalEnvelope(t *testing.T) { 44 | envelope1 := getDummyEnvelope() 45 | envelope2 := &Envelope{} 46 | 47 | if err := TestMarshalUnmarshal(envelope1, envelope2); err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/opaque/json_encoding.go: -------------------------------------------------------------------------------- 1 | package opaque 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | func (pmt ProtocolMessageType) String() string { 8 | return ProtocolMessageTypeToStringMap[pmt] 9 | } 10 | 11 | // ProtocolMessageTypeToStringMap maps the Protocol Message Type to its string equivalent. 12 | var ProtocolMessageTypeToStringMap = map[ProtocolMessageType]string{ 13 | ProtocolMessageTypeRegistrationRequest: "OPAQUE Registration Request", 14 | ProtocolMessageTypeRegistrationResponse: "OPAQUE Registration Response", 15 | ProtocolMessageTypeRegistrationUpload: "OPAQUE Registration Upload", 16 | ProtocolMessageTypeCredentialRequest: "OPAQUE Credential Request", 17 | ProtocolMessageTypeCredentialResponse: "OPAQUE Credential Response", 18 | } 19 | 20 | type registrationRequestJSON struct { 21 | UserID string 22 | OprfData []byte 23 | } 24 | 25 | // MarshalJSON encodes the RegistrationRequest. 26 | func (rr *RegistrationRequest) MarshalJSON() ([]byte, error) { 27 | rrJSON := ®istrationRequestJSON{ 28 | UserID: string(rr.UserID), 29 | OprfData: rr.OprfData, 30 | } 31 | 32 | return json.Marshal(rrJSON) 33 | } 34 | 35 | // String returns the string equivalent of the Credential Type. 36 | func (ct CredentialType) String() string { 37 | switch ct { 38 | case CredentialTypeUserPrivateKey: 39 | return "User Private Key" 40 | case CredentialTypeUserPublicKey: 41 | return "User Public Key" 42 | case CredentialTypeServerPublicKey: 43 | return "Server Public Key" 44 | case CredentialTypeUserIdentity: 45 | return "User Identity" 46 | case CredentialTypeServerIdentity: 47 | return "Server Identity" 48 | } 49 | 50 | return "Unrecognized Credential Type" 51 | } 52 | 53 | // MarshalText encodes the Credential Type. 54 | func (ct CredentialType) MarshalText() ([]byte, error) { 55 | return []byte(ct.String()), nil 56 | } 57 | -------------------------------------------------------------------------------- /src/opaque/onetimepad.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto/hmac" 30 | "crypto/sha256" 31 | "hash" 32 | 33 | "github.com/cloudflare/opaque-ea/src/common" 34 | "golang.org/x/crypto/hkdf" 35 | ) 36 | 37 | // AuthenticatedOneTimePad is a cipher for encrypting/decrypting OPAQUE 38 | // credentials. It is specialized and should likely not be used elsewhere. 39 | type AuthenticatedOneTimePad struct { 40 | nonce []byte 41 | pad []byte 42 | exporterKey []byte 43 | mac hash.Hash 44 | } 45 | 46 | // NewAuthenticatedOneTimePad returns a new AOTP cipher initialized with 47 | // the given key and nonce. 48 | // It calculates: 49 | // pseudorandom_pad = HKDF-Expand(rwdU, concat(nonce, "Pad"), len(pt)) 50 | // auth_key = HKDF-Expand(rwdU, concat(nonce, "AuthKey"), Nh) 51 | // export_key = HKDF-Expand(rwdU, concat(nonce, "ExportKey"), Nh) 52 | func NewAuthenticatedOneTimePad(key, nonce []byte, l int) (*AuthenticatedOneTimePad, error) { 53 | hash := sha256.New // should be the same as the OPRF suite 54 | 55 | pad := make([]byte, l) 56 | _, err := hkdf.Expand(hash, key, []byte("Pad")).Read(pad) 57 | if err != nil { 58 | panic(err) 59 | } 60 | 61 | authKey := make([]byte, 32) 62 | _, err = hkdf.Expand(hash, key, []byte("AuthKey")).Read(authKey) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | exporterKey := make([]byte, 32) 68 | _, err = hkdf.Expand(hash, key, []byte("ExportKey")).Read(exporterKey) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | return &AuthenticatedOneTimePad{ 74 | nonce: nonce, 75 | pad: pad, 76 | exporterKey: exporterKey, 77 | mac: hmac.New(hash, authKey), 78 | }, nil 79 | } 80 | 81 | // Seal encrypts and HMACs the given plaintext. 82 | // MUST only be called once (it is a one-time pad after all). 83 | // It calculates: 84 | // Set Ct = SecEnv XOR Pad 85 | // Set E = Nonce | Ct | authData 86 | // Set T = HMAC(HmacKey, E) 87 | func (otp *AuthenticatedOneTimePad) Seal(plaintext, authData []byte) (ciphertext, tag []byte, err error) { 88 | ciphertext, err = common.XORBytes(otp.pad, plaintext) 89 | if err != nil { 90 | return nil, nil, err 91 | } 92 | 93 | _, err = otp.mac.Write(otp.nonce) 94 | if err != nil { 95 | return nil, nil, err 96 | } 97 | 98 | _, err = otp.mac.Write(ciphertext) 99 | if err != nil { 100 | otp.mac.Reset() 101 | return nil, nil, err 102 | } 103 | 104 | _, err = otp.mac.Write(authData) 105 | if err != nil { 106 | otp.mac.Reset() 107 | return nil, nil, err 108 | } 109 | 110 | tag = otp.mac.Sum(nil) 111 | 112 | otp.mac.Reset() 113 | 114 | return ciphertext, tag, nil 115 | } 116 | 117 | // Open decrypts the ciphertext and verifies the HMAC 118 | // Errors if decryption or verification fails. 119 | // It uses HmacKey to validate the received value 'authData', and errors if 120 | // verification fails. 121 | func (otp *AuthenticatedOneTimePad) Open(ciphertext, authData, tag []byte) ([]byte, error) { 122 | // decrypt 123 | plaintext, err := common.XORBytes(otp.pad, ciphertext) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | _, err = otp.mac.Write(otp.nonce) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | _, err = otp.mac.Write(ciphertext) 134 | if err != nil { 135 | otp.mac.Reset() 136 | return nil, err 137 | } 138 | 139 | _, err = otp.mac.Write(authData) 140 | if err != nil { 141 | otp.mac.Reset() 142 | return nil, err 143 | } 144 | 145 | expTag := otp.mac.Sum(nil) 146 | otp.mac.Reset() 147 | 148 | if !hmac.Equal(expTag, tag) { 149 | return nil, common.ErrorHmacTagInvalid 150 | } 151 | 152 | return plaintext, nil 153 | } 154 | -------------------------------------------------------------------------------- /src/opaque/onetimepad_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "bytes" 30 | "testing" 31 | 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | ) 34 | 35 | func TestOTPEncryptDecrypt(t *testing.T) { 36 | key := common.GetRandomBytes(32) 37 | nonce := common.GetRandomBytes(32) 38 | 39 | plaintext := []byte("plaintext") 40 | authData := []byte("authdata") 41 | 42 | otp, err := NewAuthenticatedOneTimePad(key, nonce, len(plaintext)) 43 | if err != nil { 44 | t.Errorf("otp init: %v", err) 45 | } 46 | 47 | ciphertext, tag, err := otp.Seal(plaintext, authData) 48 | if err != nil { 49 | t.Errorf("encryption error: %v", err) 50 | } 51 | 52 | otp, err = NewAuthenticatedOneTimePad(key, nonce, len(ciphertext)) 53 | if err != nil { 54 | t.Errorf("otp init: %v", err) 55 | } 56 | 57 | plaintext2, err := otp.Open(ciphertext, authData, tag) 58 | if err != nil { 59 | t.Errorf("decryption error: %v", err) 60 | } 61 | 62 | if !bytes.Equal(plaintext, plaintext2) { 63 | t.Errorf("incorrect decrypted plaintext") 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/opaque/oprf.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto/sha256" 30 | 31 | "github.com/cloudflare/circl/oprf" 32 | "golang.org/x/crypto/hkdf" 33 | "golang.org/x/crypto/pbkdf2" 34 | ) 35 | 36 | // blind returns OPRF_1 (client OPRF msg) and remembers randomness used to generate it. 37 | func (c *Client) blind(password string) ([]byte, error) { 38 | blind := [][]byte{} 39 | blind = append(blind, []byte(password)) 40 | cRequest, err := c.oprfState.Request(blind) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | c.oprf1 = cRequest 46 | 47 | return cRequest.BlindedElements[0], nil 48 | } 49 | 50 | // evaluate returns OPRF_2 (server OPRF msg). 51 | func (s *Server) evaluate(clientMessage []byte) ([]byte, error) { 52 | var blinded [][]byte 53 | blinded = append(blinded, []byte(clientMessage)) 54 | 55 | evaluation, err := s.UserRecord.OprfState.Evaluate(blinded) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return evaluation.Elements[0], nil 61 | } 62 | 63 | // finalizeHarden returns RwdPass (randomized password). 64 | func (c *Client) finalizeHarden(serverMessage []byte) ([]byte, error) { 65 | var element [][]byte 66 | element = append(element, []byte(serverMessage)) 67 | 68 | eval := &oprf.Evaluation{Elements: element} 69 | rwd, err := c.oprfState.Finalize(c.oprf1, eval, []byte("OPAQUE00")) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | // Harden the rwd. 75 | // "We note that the salt value typically input into the KDF can be set to a 76 | // constant, e.g., all zeros." 77 | OPAQUEPBKDFOutLength := int(32) 78 | OPAQUEPBKDFIters := int(4096) 79 | salt := []byte{0, 0, 0, 0} 80 | hardenedRwd := pbkdf2.Key(rwd[0], salt, OPAQUEPBKDFIters, OPAQUEPBKDFOutLength, sha256.New) 81 | 82 | rwdU := hkdf.Extract(sha256.New, hardenedRwd, []byte("rwdU")) 83 | 84 | return rwdU, nil 85 | } 86 | -------------------------------------------------------------------------------- /src/opaque/oprf_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "bytes" 30 | "testing" 31 | 32 | "github.com/cloudflare/circl/oprf" 33 | "github.com/tatianab/mint" 34 | ) 35 | 36 | func TestOPRFFlow(t *testing.T) { 37 | username := "alice" 38 | password := "wonderland" 39 | domain := "bob.com" 40 | 41 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 42 | if err != nil { 43 | t.Error(err) 44 | } 45 | 46 | s, err := NewServer(&ServerConfig{Signer: signer, ServerID: domain, Suite: oprf.OPRFP256}) 47 | if err != nil { 48 | t.Errorf("new server error: %v", err) 49 | } 50 | 51 | expectedRwd, err := RunLocalOPRF(s, username, password) 52 | if err != nil { 53 | t.Errorf("could not get expected rwd: %v", err) 54 | } 55 | 56 | _, err = s.InsertNewUserRecord(nil, nil) // some contents don't matter for this test 57 | if err != nil { 58 | t.Error(err) 59 | } 60 | 61 | client, err := NewClient(username, domain, oprf.OPRFP256) 62 | if err != nil { 63 | t.Errorf("new client error: %v", err) 64 | } 65 | 66 | oprf1, err := client.blind(password) 67 | if err != nil { 68 | t.Errorf("client OPRF init error: %v", err) 69 | } 70 | 71 | oprf2, err := s.evaluate(oprf1) 72 | if err != nil { 73 | t.Errorf("server OPRF init error: %v", err) 74 | } 75 | 76 | rwd, err := client.finalizeHarden(oprf2) 77 | if err != nil { 78 | t.Errorf("client OPRF finish error: %v", err) 79 | } 80 | 81 | if !bytes.Equal(rwd, expectedRwd) { 82 | t.Errorf("incorrect rwd: expected %v, got %v", expectedRwd, rwd) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/opaque/registration.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | 31 | "github.com/cloudflare/circl/oprf" 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | ) 34 | 35 | // CreateRegistrationRequest is called by the client to create the first OPAQUE registration message 36 | // Errors if the OPRF message cannot be created. e.g, if this client instance has already 37 | // been used to run an OPRF. 38 | func (c *Client) CreateRegistrationRequest(password string, key crypto.Signer) (*RegistrationRequest, error) { 39 | c.signer = key 40 | 41 | blinded, err := c.blind(password) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return &RegistrationRequest{ 47 | UserID: c.UserID, 48 | OprfData: blinded, 49 | }, nil 50 | } 51 | 52 | // CreateRegistrationResponse is called by the server to respond to a OPAQUE registration request 53 | // from a client. 54 | // It fails is an OPRF message cannot be created. 55 | func (s *Server) CreateRegistrationResponse(msg *RegistrationRequest) (*RegistrationResponse, error) { 56 | oprfServer, err := oprf.NewServer(s.Config.Suite, nil) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | s.UserRecord.OprfState = oprfServer 62 | eval, err := s.evaluate(msg.OprfData) 63 | if err != nil { 64 | s.UserRecord.OprfState = nil 65 | return nil, err 66 | } 67 | 68 | if err := s.SetUserID(msg.UserID); err != nil { 69 | s.UserRecord.OprfState = nil 70 | return nil, err 71 | } 72 | 73 | return &RegistrationResponse{ 74 | OprfData: eval, 75 | ServerPublicKey: s.Config.Signer.Public(), 76 | CredentialEncodingPolicy: s.Config.CredentialEncodingPolicy, 77 | }, nil 78 | } 79 | 80 | // FinalizeRegistrationRequest is called by the client to respond to the server's response 81 | // to its registration request (registration response). 82 | // Returns a registration upload message and an exporter key. 83 | // Errors if the OPRF cannot be completed or there is a problem encrypting the 84 | // envelope. 85 | func (c *Client) FinalizeRegistrationRequest(msg *RegistrationResponse) (*RegistrationUpload, []byte, error) { 86 | rwd, err := c.finalizeHarden(msg.OprfData) 87 | if err != nil { 88 | return nil, nil, err 89 | } 90 | 91 | creds, err := c.credentialsFromPolicy(msg.CredentialEncodingPolicy, msg.ServerPublicKey) 92 | if err != nil { 93 | return nil, nil, err 94 | } 95 | 96 | // a fresh random nonce Nonce of length LH, where LH is the 97 | // output length in bytes of the hash function underlying HKDF. 98 | // OPRFP256 hash function is SHA256. 99 | nonceLen := int(32) 100 | envelope, exporterKey, err := EncryptCredentials(rwd, creds, nonceLen) 101 | if err != nil { 102 | return nil, nil, err 103 | } 104 | 105 | return &RegistrationUpload{ 106 | Envelope: envelope, 107 | UserPublicKey: c.signer.Public(), 108 | }, exporterKey, nil 109 | } 110 | 111 | func (c *Client) credentialsFromPolicy(policy *CredentialEncodingPolicy, 112 | serverPublicKey crypto.PublicKey) (*Credentials, error) { 113 | secretCreds := make(CredentialExtensionList, len(policy.SecretTypes)) 114 | cleartextCreds := make(CredentialExtensionList, len(policy.CleartextTypes)) 115 | 116 | for i, credType := range policy.SecretTypes { 117 | cred, err := c.getCredentialFromType(credType, serverPublicKey) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | secretCreds[i] = cred 123 | } 124 | 125 | for i, credType := range policy.CleartextTypes { 126 | if credType == CredentialTypeUserPrivateKey { 127 | return nil, common.ErrorForbiddenPolicy 128 | } 129 | 130 | cred, err := c.getCredentialFromType(credType, serverPublicKey) 131 | if err != nil { 132 | return nil, err 133 | } 134 | 135 | cleartextCreds[i] = cred 136 | } 137 | 138 | return &Credentials{ 139 | SecretCredentials: secretCreds, 140 | CleartextCredentials: cleartextCreds, 141 | }, nil 142 | } 143 | 144 | func (c *Client) getCredentialFromType(t CredentialType, serverPublicKey crypto.PublicKey) (*CredentialExtension, error) { 145 | var val interface{} 146 | 147 | switch t { 148 | case CredentialTypeServerIdentity: 149 | val = c.ServerID 150 | case CredentialTypeUserIdentity: 151 | val = c.UserID 152 | case CredentialTypeServerPublicKey: 153 | val = serverPublicKey 154 | case CredentialTypeUserPublicKey: 155 | val = c.signer.Public() 156 | case CredentialTypeUserPrivateKey: 157 | val = c.signer 158 | } 159 | 160 | cred, err := newCredentialExtension(t, val) 161 | if err != nil { 162 | return nil, err 163 | } 164 | 165 | return cred, nil 166 | } 167 | 168 | // StoreUserRecord is called by the Server to add the new client identity 169 | // to it's records, ending the registration step. 170 | // Errors if the record cannot be added, e.g. because the username has already 171 | // been registered. 172 | func (s *Server) StoreUserRecord(msg *RegistrationUpload) error { 173 | record, err := s.InsertNewUserRecord(msg.UserPublicKey, msg.Envelope) 174 | 175 | s.UserRecord = record 176 | 177 | return err 178 | } 179 | -------------------------------------------------------------------------------- /src/opaque/registration_messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "crypto/x509" 31 | 32 | "github.com/tatianab/mint/syntax" 33 | ) 34 | 35 | // RegistrationRequest is the first message sent by the client to 36 | // register a new OPAQUE identity with that server 37 | // 38 | // struct { 39 | // opaque id<0..2^16-1>; 40 | // opaque data<1..2^16-1>; 41 | // } RegistrationRequest; 42 | // 43 | // 2 2 44 | // | userIDLen | userID | oprfDataLen | oprfData |. 45 | type RegistrationRequest struct { 46 | UserID []byte `tls:"head=2"` 47 | OprfData []byte `tls:"head=2, min=1"` 48 | } 49 | 50 | // Marshal returns the raw form of this struct. 51 | func (rr *RegistrationRequest) Marshal() ([]byte, error) { 52 | return syntax.Marshal(rr) 53 | } 54 | 55 | // Unmarshal converts the raw data into a struct and returns the number of 56 | // bytes read. 57 | func (rr *RegistrationRequest) Unmarshal(data []byte) (int, error) { 58 | return syntax.Unmarshal(data, rr) 59 | } 60 | 61 | // Type returns the type of this struct. 62 | func (*RegistrationRequest) Type() ProtocolMessageType { 63 | return ProtocolMessageTypeRegistrationRequest 64 | } 65 | 66 | // RegistrationResponse is the first message sent by the Server in response 67 | // to the client's registration request. 68 | // 69 | // struct { 70 | // opaque data<0..2^16-1>; 71 | // opaque pkS<0..2^16-1>; 72 | // CredentialType secret_types<1..254>; 73 | // CredentialType cleartext_types<0..254>; 74 | // } RegistrationResponse; 75 | // 76 | // 2 2 1 1 77 | // | oprfDataLen | oprfData | pkSLen | pkS | secretTypesLen | secretTypes | cleartextTypesLen | cleartextTypes |. 78 | type RegistrationResponse struct { 79 | OprfData []byte 80 | ServerPublicKey crypto.PublicKey 81 | CredentialEncodingPolicy *CredentialEncodingPolicy 82 | } 83 | 84 | type registrationResponseInner struct { 85 | OprfData []byte `tls:"head=2"` 86 | ServerPublicKey []byte `tls:"head=2"` 87 | SecretTypes []CredentialType `tls:"head=1, min=1"` 88 | CleartextTypes []CredentialType `tls:"head=1"` 89 | } 90 | 91 | // Marshal returns the raw form of this struct. 92 | func (rr *RegistrationResponse) Marshal() ([]byte, error) { 93 | rawServerPublicKey, err := x509.MarshalPKIXPublicKey(rr.ServerPublicKey) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | inner := ®istrationResponseInner{ 99 | OprfData: rr.OprfData, 100 | ServerPublicKey: rawServerPublicKey, 101 | SecretTypes: rr.CredentialEncodingPolicy.SecretTypes, 102 | CleartextTypes: rr.CredentialEncodingPolicy.CleartextTypes, 103 | } 104 | 105 | return syntax.Marshal(inner) 106 | } 107 | 108 | // Unmarshal converts the raw data into a struct and returns the number of 109 | // bytes read. 110 | func (rr *RegistrationResponse) Unmarshal(data []byte) (int, error) { 111 | inner := ®istrationResponseInner{} 112 | 113 | bytesRead, err := syntax.Unmarshal(data, inner) 114 | if err != nil { 115 | return 0, err 116 | } 117 | 118 | serverPublicKey, err := x509.ParsePKIXPublicKey(inner.ServerPublicKey) 119 | if err != nil { 120 | return 0, err 121 | } 122 | 123 | *rr = RegistrationResponse{ 124 | OprfData: inner.OprfData, 125 | ServerPublicKey: serverPublicKey, 126 | CredentialEncodingPolicy: &CredentialEncodingPolicy{ 127 | SecretTypes: inner.SecretTypes, 128 | CleartextTypes: inner.CleartextTypes, 129 | }, 130 | } 131 | 132 | return bytesRead, nil 133 | } 134 | 135 | // Type returns the type of this struct. 136 | func (*RegistrationResponse) Type() ProtocolMessageType { 137 | return ProtocolMessageTypeRegistrationResponse 138 | } 139 | 140 | // RegistrationUpload is the second and final message sent by the 141 | // client to register a new identity with a server. 142 | // 143 | // struct { 144 | // Envelope envelope; 145 | // opaque pkU<0..2^16-1>; 146 | // } RegistrationUpload; 147 | // 148 | // 2 149 | // | envelope | pubKeyLen | pubKey. 150 | type RegistrationUpload struct { 151 | Envelope *Envelope 152 | UserPublicKey crypto.PublicKey 153 | } 154 | 155 | type registrationUploadInner struct { 156 | Envelope *Envelope 157 | UserPublicKey []byte `tls:"head=2"` 158 | } 159 | 160 | // Marshal returns the raw form of this struct. 161 | func (ru *RegistrationUpload) Marshal() ([]byte, error) { 162 | rawPublicKey, err := x509.MarshalPKIXPublicKey(ru.UserPublicKey) 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | inner := ®istrationUploadInner{ 168 | Envelope: ru.Envelope, 169 | UserPublicKey: rawPublicKey, 170 | } 171 | 172 | return syntax.Marshal(inner) 173 | } 174 | 175 | // Unmarshal converts the raw data into a struct and returns the number of 176 | // bytes read. 177 | func (ru *RegistrationUpload) Unmarshal(data []byte) (int, error) { 178 | inner := ®istrationUploadInner{} 179 | 180 | bytesRead, err := syntax.Unmarshal(data, inner) 181 | if err != nil { 182 | return 0, err 183 | } 184 | 185 | userPublicKey, err := x509.ParsePKIXPublicKey(inner.UserPublicKey) 186 | if err != nil { 187 | return 0, err 188 | } 189 | 190 | *ru = RegistrationUpload{ 191 | Envelope: inner.Envelope, 192 | UserPublicKey: userPublicKey, 193 | } 194 | 195 | return bytesRead, nil 196 | } 197 | 198 | // Type returns the type of this struct. 199 | func (*RegistrationUpload) Type() ProtocolMessageType { 200 | return ProtocolMessageTypeRegistrationUpload 201 | } 202 | -------------------------------------------------------------------------------- /src/opaque/registration_messages_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto/rand" 30 | "testing" 31 | 32 | "github.com/tatianab/mint" 33 | ) 34 | 35 | func TestMarshalUnmarshalRegistrationRequest(t *testing.T) { 36 | oprfData := make([]byte, 32) 37 | _, _ = rand.Read(oprfData) 38 | 39 | regReq1 := &RegistrationRequest{ 40 | UserID: []byte("username"), 41 | OprfData: oprfData, 42 | } 43 | 44 | regReq2 := &RegistrationRequest{} 45 | if err := TestMarshalUnmarshal(regReq1, regReq2); err != nil { 46 | t.Error(err) 47 | return 48 | } 49 | } 50 | 51 | func TestMarshalUnmarshalRegistrationResponse(t *testing.T) { 52 | oprfData := make([]byte, 32) 53 | _, _ = rand.Read(oprfData) 54 | 55 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 56 | if err != nil { 57 | t.Error(err) 58 | } 59 | 60 | regResp1 := &RegistrationResponse{ 61 | OprfData: oprfData, 62 | ServerPublicKey: signer.Public(), 63 | CredentialEncodingPolicy: &CredentialEncodingPolicy{ 64 | SecretTypes: []CredentialType{CredentialTypeUserPrivateKey}, 65 | CleartextTypes: []CredentialType{CredentialTypeServerIdentity, CredentialTypeServerPublicKey}, 66 | }, 67 | } 68 | regResp2 := &RegistrationResponse{} 69 | 70 | if err := TestMarshalUnmarshal(regResp1, regResp2); err != nil { 71 | t.Error(err) 72 | return 73 | } 74 | } 75 | 76 | func TestMarshalUnmarshalRegistrationUpload(t *testing.T) { 77 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 78 | if err != nil { 79 | t.Error(err) 80 | return 81 | } 82 | 83 | regUpload1 := &RegistrationUpload{ 84 | Envelope: getDummyEnvelope(), 85 | UserPublicKey: signer.Public(), 86 | } 87 | 88 | regUpload2 := &RegistrationUpload{} 89 | if err := TestMarshalUnmarshal(regUpload1, regUpload2); err != nil { 90 | t.Error(err) 91 | return 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/opaque/roles.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | 31 | "github.com/cloudflare/circl/oprf" 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | ) 34 | 35 | // Server holds state for an instance of the server role in OPAQUE. 36 | type Server struct { 37 | Config *ServerConfig 38 | UserRecord *UserRecord 39 | } 40 | 41 | // ServerConfig holds long term state for the server. 42 | type ServerConfig struct { 43 | ServerID string 44 | Signer crypto.Signer 45 | RecordTable UserRecordTable 46 | Suite oprf.SuiteID 47 | CredentialEncodingPolicy *CredentialEncodingPolicy 48 | } 49 | 50 | // CredentialEncodingPolicy indicates which user credentials are stored, 51 | // and whether they are held encrypted or in cleartext. 52 | type CredentialEncodingPolicy struct { 53 | SecretTypes []CredentialType 54 | CleartextTypes []CredentialType 55 | } 56 | 57 | // Client holds state for the client role in OPAQUE. 58 | type Client struct { 59 | UserID []byte 60 | ServerID []byte 61 | oprf1 *oprf.ClientRequest 62 | oprfState *oprf.Client 63 | signer crypto.Signer // this value will be assigned during registration 64 | suite oprf.SuiteID 65 | } 66 | 67 | // NewServer returns a new OPAQUE server with the RECOMMENDED credential 68 | // encoding policy. 69 | func NewServer(cfg *ServerConfig) (*Server, error) { 70 | if cfg.CredentialEncodingPolicy == nil { 71 | cfg.CredentialEncodingPolicy = &CredentialEncodingPolicy{ 72 | SecretTypes: []CredentialType{ 73 | CredentialTypeUserPrivateKey, 74 | }, 75 | CleartextTypes: []CredentialType{ 76 | CredentialTypeServerPublicKey, 77 | CredentialTypeServerIdentity, 78 | }, 79 | } 80 | } 81 | 82 | return &Server{Config: cfg, UserRecord: &UserRecord{}}, nil 83 | } 84 | 85 | // SetUserID sets the User ID for the Server's User Record. 86 | func (s *Server) SetUserID(userID []byte) error { 87 | record, err := s.Config.RecordTable.LookupUserRecord(string(userID)) 88 | if record != nil && err == nil { 89 | return common.ErrorUserAlreadyRegistered 90 | } 91 | 92 | s.UserRecord.UserID = userID 93 | 94 | return nil 95 | } 96 | 97 | // GetUserRecordFromUsername looks up the user record associated with the 98 | // given username, and uses it to set the server's user record. 99 | // Errors if no user record can be found, or there is no LookupUserRecord set. 100 | func (s *Server) GetUserRecordFromUsername(username []byte) (*UserRecord, error) { 101 | if s.Config.RecordTable == nil { 102 | return nil, common.ErrorNoPasswordTable 103 | } 104 | 105 | userRecord, err := s.Config.RecordTable.LookupUserRecord(string(username)) 106 | if err != nil { 107 | return nil, err 108 | } 109 | s.UserRecord = userRecord 110 | 111 | return userRecord, nil 112 | } 113 | 114 | // InsertNewUserRecord updates the server's user record struct with the given data, 115 | // registers the record using the InsertUserRecord, and returns the created record. 116 | func (s *Server) InsertNewUserRecord(userPublicKey crypto.PublicKey, envelope *Envelope) (*UserRecord, error) { 117 | record := s.UserRecord 118 | record.Envelope = envelope 119 | record.UserPublicKey = userPublicKey 120 | 121 | if s.Config.RecordTable != nil { 122 | if err := s.Config.RecordTable.InsertUserRecord(string(record.UserID), record); err != nil { 123 | return nil, err 124 | } 125 | } 126 | 127 | return record, nil 128 | } 129 | 130 | // NewClient returns a new OPAQUE client. 131 | func NewClient(userID, serverID string, suite oprf.SuiteID) (*Client, error) { 132 | oprfClient, err := oprf.NewClient(suite) 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | return &Client{ 138 | UserID: []byte(userID), 139 | ServerID: []byte(serverID), 140 | oprfState: oprfClient, 141 | suite: suite, 142 | }, nil 143 | } 144 | -------------------------------------------------------------------------------- /src/opaque/test_helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "reflect" 30 | 31 | "github.com/cloudflare/opaque-ea/src/common" 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | // TestMarshalUnmarshal is a test helper that errors if Marshal/Unmarshal 36 | // for the type of data and empty is not working. 37 | // data should be filled in and empty should be empty. 38 | func TestMarshalUnmarshal(data, empty common.MarshalUnmarshaler) error { 39 | raw, err := data.Marshal() 40 | if err != nil { 41 | return errors.Wrap(err, "marshal") 42 | } 43 | 44 | bytesRead, err := empty.Unmarshal(raw) 45 | if err != nil { 46 | return errors.Wrap(err, "unmarshal") 47 | } 48 | 49 | if bytesRead != len(raw) { 50 | return errors.Errorf("incorrect unmarshal bytesRead: got %v, expected %v", bytesRead, len(raw)) 51 | } 52 | 53 | if !reflect.DeepEqual(data, empty) { 54 | return errors.Errorf(common.ErrMarshalUnmarshalFailed, reflect.TypeOf(data), data, empty) 55 | } 56 | 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /src/opaque/tls_additions.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "crypto/x509" 31 | 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | "github.com/pkg/errors" 34 | "github.com/tatianab/mint" 35 | "github.com/tatianab/mint/syntax" 36 | ) 37 | 38 | // ProtocolVariant indicates which variant of OPAQUE we are using. 39 | type ProtocolVariant byte 40 | 41 | // We only implement OPAQUE-Sign. 42 | const ( 43 | OPAQUESign ProtocolVariant = 1 + iota 44 | ) 45 | 46 | // These are chosen arbitrarily for now. 47 | // enum { 48 | // opaque_client_auth(TBD), 49 | // opaque_server_auth(TBD), 50 | // (65535) 51 | // } ExtensionType; 52 | // FUTURE: update once standardized. 53 | const ( 54 | ExtensionTypeOpaqueServerAuth mint.ExtensionType = 48 55 | ExtensionTypeOpaqueClientAuth mint.ExtensionType = 50 56 | 57 | OPAQUESIGNSignatureScheme mint.SignatureScheme = mint.ECDSA_P521_SHA512 58 | ) 59 | 60 | // CertificateOPAQUESign is an alias for a mint Certificate. 61 | // It is given a distinct name for the possibility of later 62 | // being a separate type. 63 | type CertificateOPAQUESign = mint.Certificate 64 | 65 | // PAKEShareType indicates the type of PAKEShare. 66 | type PAKEShareType byte 67 | 68 | // Server/client indicates who created this PAKEShare. 69 | const ( 70 | PAKEShareTypeServer PAKEShareType = iota + 1 71 | PAKEShareTypeClient 72 | ) 73 | 74 | // A PAKEShare is a collection of OPAQUE data. 75 | // Should be included in a PAKEServerAuthExtension. 76 | type PAKEShare interface { 77 | Marshal() ([]byte, error) 78 | Unmarshal([]byte) (int, error) 79 | Type() PAKEShareType 80 | } 81 | 82 | // A PAKEShareServer is a PAKEShare from the server containing OPRF data 83 | // and the encrypted client credentials. 84 | // 85 | // struct { 86 | // opaque identity<0..2^16-1>; 87 | // opaque OPRF_2<1..2^16-1>; 88 | // opaque vU<1..2^16-1>; // omitted - not needed for OPAQUE-Sign 89 | // opaque EnvU<1..2^16-1>; // should be: Envelope envelope; 90 | // } PAKEShareServer; 91 | // 92 | // 2 2 93 | // | serverIDLen | serverID | oprfMsgLen | oprfMsg | envelope |. 94 | type PAKEShareServer struct { 95 | // NOTE: the OPAQUE-TLS document says this should be UserID, 96 | // but we don't see why it should be echoed. 97 | ServerID []byte `tls:"head=2"` 98 | OprfMsg []byte `tls:"head=2,min=1"` 99 | Envelope *Envelope 100 | } 101 | 102 | // Marshal returns the raw form of the PAKEShareServer struct. 103 | func (pss *PAKEShareServer) Marshal() ([]byte, error) { 104 | return syntax.Marshal(pss) 105 | } 106 | 107 | // Unmarshal puts raw data into fields of a PAKEShareServer struct. 108 | func (pss *PAKEShareServer) Unmarshal(data []byte) (int, error) { 109 | return syntax.Unmarshal(data, pss) 110 | } 111 | 112 | // Type returns the type of this struct: PAKEShareServer. 113 | func (*PAKEShareServer) Type() PAKEShareType { 114 | return PAKEShareTypeServer 115 | } 116 | 117 | // PAKEShareClient is the core OPAQUE data sent by the client to request 118 | // authentication from the server. 119 | // Should be wrapped in a PAKEServerAuthExtension. 120 | // 121 | // struct { 122 | // opaque identity<0..2^16-1>; 123 | // opaque OPRF_1<1..2^16-1>; 124 | // } PAKEShareClient; 125 | // 126 | // 2 2 127 | // | userIDLen | userID | oprfMsgLen | oprfMsg |. 128 | type PAKEShareClient struct { 129 | UserID []byte `tls:"head=2"` 130 | OprfMsg []byte `tls:"head=2,min=1"` 131 | } 132 | 133 | // Marshal returns the raw form of the PAKEShareClient struct. 134 | func (psc *PAKEShareClient) Marshal() ([]byte, error) { 135 | return syntax.Marshal(psc) 136 | } 137 | 138 | // Unmarshal puts raw data into fields of a PAKEShareClient struct. 139 | func (psc *PAKEShareClient) Unmarshal(data []byte) (int, error) { 140 | return syntax.Unmarshal(data, psc) 141 | } 142 | 143 | // Type returns the type of this struct: PAKEShareClient. 144 | func (*PAKEShareClient) Type() PAKEShareType { 145 | return PAKEShareTypeClient 146 | } 147 | 148 | // PAKEServerAuthExtension is an extension that allows OPAQUE data to be 149 | // attached to Exported Authenticator Requests and Exported Authenticators. 150 | // It is for requests TO the server and EAs FROM the server. 151 | // Implements mint.ExtensionBody. 152 | // 153 | // struct { 154 | // select (Handshake.msg_type) { 155 | // ClientHello: // not used in OPAQUE-EA 156 | // PAKEShareClient client_shares<0..2^16-1>; 157 | // OPAQUEType types<0..2^16-1>; 158 | // EncryptedExtensions, Certificate: 159 | // PAKEShareServer server_share; // this can also be PAKEShareClient 160 | // OPAQUEType type; 161 | // } PAKEServerAuthExtension; 162 | // 163 | // 1 164 | // | pakeShare | opaqueType |. 165 | type PAKEServerAuthExtension struct { 166 | PAKEShare PAKEShare 167 | OPAQUEType ProtocolVariant 168 | } 169 | 170 | var _ mint.ExtensionBody = (*PAKEServerAuthExtension)(nil) 171 | 172 | // SetFromList finds a PSAE in the given list of extensions and populates the given 173 | // PSAE with the found data. Errors if list does not contain a PSAE extension. 174 | func (psae *PAKEServerAuthExtension) SetFromList(el mint.ExtensionList) error { 175 | ok, err := el.Find(psae) 176 | if !ok || err != nil { 177 | return errors.Wrapf(common.ErrorNotFound, "pake server auth extension") 178 | } 179 | 180 | return nil 181 | } 182 | 183 | // Type returns the extension type. 184 | func (psae *PAKEServerAuthExtension) Type() mint.ExtensionType { 185 | return ExtensionTypeOpaqueServerAuth 186 | } 187 | 188 | type pakeServerAuthExtensionInner struct { 189 | ServerShare *PAKEShareServer `tls:"optional"` 190 | ClientShare *PAKEShareClient `tls:"optional"` 191 | OPAQUEType ProtocolVariant 192 | } 193 | 194 | // Marshal returns the raw form of the struct. 195 | func (psae *PAKEServerAuthExtension) Marshal() ([]byte, error) { 196 | var clientShare *PAKEShareClient 197 | var serverShare *PAKEShareServer 198 | 199 | switch psae.PAKEShare.Type() { 200 | case PAKEShareTypeClient: 201 | clientShare = psae.PAKEShare.(*PAKEShareClient) 202 | case PAKEShareTypeServer: 203 | serverShare = psae.PAKEShare.(*PAKEShareServer) 204 | default: 205 | return nil, errors.New("unrecognized pake share type") 206 | } 207 | 208 | inner := &pakeServerAuthExtensionInner{ 209 | ServerShare: serverShare, 210 | ClientShare: clientShare, 211 | OPAQUEType: psae.OPAQUEType, 212 | } 213 | 214 | return syntax.Marshal(inner) 215 | } 216 | 217 | // Unmarshal puts raw data into fields of a struct. 218 | func (psae *PAKEServerAuthExtension) Unmarshal(data []byte) (int, error) { 219 | inner := &pakeServerAuthExtensionInner{} 220 | 221 | bytesRead, err := syntax.Unmarshal(data, inner) 222 | if err != nil { 223 | return 0, err 224 | } 225 | 226 | var share PAKEShare 227 | if inner.ClientShare != nil && inner.ServerShare == nil { 228 | share = inner.ClientShare 229 | } else if inner.ServerShare != nil && inner.ClientShare == nil { 230 | share = inner.ServerShare 231 | } 232 | 233 | *psae = PAKEServerAuthExtension{ 234 | PAKEShare: share, 235 | OPAQUEType: inner.OPAQUEType, 236 | } 237 | 238 | return bytesRead, nil 239 | } 240 | 241 | // PAKEClientAuthExtension is an extension that allows OPAQUE data to be 242 | // attached to Exported Authenticator. 243 | // It is for requests TO the client. 244 | // Implements mint.ExtensionBody. 245 | // 246 | // struct { 247 | // opaque identity<0..2^16-1>; 248 | // } PAKEClientAuthExtension; 249 | // 250 | // 2 251 | // | userIDLen | userID |. 252 | type PAKEClientAuthExtension struct { 253 | UserID []byte `tls:"head=2"` 254 | } 255 | 256 | var _ mint.ExtensionBody = (*PAKEClientAuthExtension)(nil) 257 | 258 | // Type returns the extension type: PAKEClientAuthExtension. 259 | func (pcae *PAKEClientAuthExtension) Type() mint.ExtensionType { 260 | return ExtensionTypeOpaqueClientAuth 261 | } 262 | 263 | // Marshal returns the raw form of the PAKEClientAuthExtension struct. 264 | func (pcae *PAKEClientAuthExtension) Marshal() ([]byte, error) { 265 | return syntax.Marshal(pcae) 266 | } 267 | 268 | // Unmarshal puts raw data into fields of a PAKEClientAuthExtension struct and 269 | // returns the number of bytes read. 270 | func (pcae *PAKEClientAuthExtension) Unmarshal(data []byte) (int, error) { 271 | return syntax.Unmarshal(data, pcae) 272 | } 273 | 274 | // SetFromList finds a PCAE in the given list of extensions and populates the given 275 | // PCAE with the found data. Errors if list does not contain a PCAE extension. 276 | func (pcae *PAKEClientAuthExtension) SetFromList(el mint.ExtensionList) error { 277 | ok, err := el.Find(pcae) 278 | if !ok || err != nil { 279 | return errors.Wrapf(common.ErrorNotFound, "pake client auth extension") 280 | } 281 | 282 | return nil 283 | } 284 | 285 | // NewCertificateOPAQUESign returns a new OPAQUE-Sign certificate. 286 | func NewCertificateOPAQUESign(privKey crypto.Signer) *CertificateOPAQUESign { 287 | _, x509cert, _ := mint.MakeNewSelfSignedCert("dummy", OPAQUESIGNSignatureScheme) // make the cert msg non-empty 288 | return &CertificateOPAQUESign{ 289 | Chain: []*x509.Certificate{x509cert}, 290 | PrivateKey: privKey, 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/opaque/tls_additions_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto/rand" 30 | "testing" 31 | 32 | "github.com/cloudflare/opaque-ea/src/common" 33 | ) 34 | 35 | func TestMarshalUnmarshalPSAEServerShare(t *testing.T) { 36 | psae1 := &PAKEServerAuthExtension{ 37 | PAKEShare: &PAKEShareServer{ 38 | ServerID: []byte("example.com"), 39 | OprfMsg: common.GetRandomBytes(32), 40 | Envelope: getDummyEnvelope(), 41 | }, 42 | } 43 | psae2 := &PAKEServerAuthExtension{} 44 | 45 | if err := TestMarshalUnmarshal(psae1, psae2); err != nil { 46 | t.Error(err) 47 | return 48 | } 49 | } 50 | 51 | func TestMarshalUnmarshalPSAEClientShare(t *testing.T) { 52 | oprfMsg := make([]byte, 32) 53 | _, _ = rand.Read(oprfMsg) 54 | 55 | psae1 := &PAKEServerAuthExtension{ 56 | PAKEShare: &PAKEShareClient{ 57 | UserID: []byte("username"), 58 | OprfMsg: oprfMsg, 59 | }, 60 | } 61 | psae2 := &PAKEServerAuthExtension{} 62 | 63 | if err := TestMarshalUnmarshal(psae1, psae2); err != nil { 64 | t.Error(err) 65 | return 66 | } 67 | } 68 | 69 | func TestMarshalUnmarshalPCAE(t *testing.T) { 70 | pcae1 := &PAKEClientAuthExtension{ 71 | UserID: []byte("username"), 72 | } 73 | pcae2 := &PAKEClientAuthExtension{} 74 | 75 | if err := TestMarshalUnmarshal(pcae1, pcae2); err != nil { 76 | t.Error(err) 77 | return 78 | } 79 | } 80 | 81 | func TestMarshalUnmarshalPAKEShareServer(t *testing.T) { 82 | pss1 := &PAKEShareServer{ 83 | ServerID: []byte("example.com"), 84 | OprfMsg: common.GetRandomBytes(32), 85 | Envelope: getDummyEnvelope(), 86 | } 87 | pss2 := &PAKEShareServer{} 88 | 89 | if err := TestMarshalUnmarshal(pss1, pss2); err != nil { 90 | t.Error(err) 91 | return 92 | } 93 | } 94 | 95 | func TestMarshalUnmarshalPAKEShareClient(t *testing.T) { 96 | oprfMsg := make([]byte, 32) 97 | _, _ = rand.Read(oprfMsg) 98 | 99 | psc1 := &PAKEShareClient{ 100 | UserID: []byte("username"), 101 | OprfMsg: oprfMsg, 102 | } 103 | psc2 := &PAKEShareClient{} 104 | 105 | if err := TestMarshalUnmarshal(psc1, psc2); err != nil { 106 | t.Error(err) 107 | return 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/opaque/user_record_table.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "crypto" 30 | "strings" 31 | 32 | "github.com/cloudflare/circl/oprf" 33 | "github.com/cloudflare/opaque-ea/src/common" 34 | "github.com/pkg/errors" 35 | "github.com/tatianab/mint" 36 | ) 37 | 38 | // UserRecord holds the data stored by the server about the user. 39 | // The values UserPublicKey and OprfState should be kept secret. 40 | type UserRecord struct { 41 | UserID []byte 42 | UserPublicKey crypto.PublicKey 43 | OprfState *oprf.Server 44 | Envelope *Envelope 45 | } 46 | 47 | // UserRecordTable is an interface for password storage and lookup. 48 | type UserRecordTable interface { 49 | InsertUserRecord(string, *UserRecord) error 50 | LookupUserRecord(string) (*UserRecord, error) 51 | } 52 | 53 | // InMemoryUserRecordTable is a map from usernames to user records to mimic a 54 | // database. Implements UserRecordTable. 55 | type InMemoryUserRecordTable map[string]*UserRecord 56 | 57 | // NewServerConfig returns a ServerConfig struct containing 58 | // a fresh signing key and an empty lookup table 59 | func NewServerConfig(domain string, suite oprf.SuiteID) (cfg *ServerConfig, err error) { 60 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | t := NewInMemoryUserRecordTable() 66 | 67 | return &ServerConfig{ 68 | ServerID: domain, 69 | Signer: signer, 70 | RecordTable: t, 71 | Suite: suite, 72 | }, nil 73 | } 74 | 75 | // NewInMemoryUserRecordTable returns a new empty in-memory user record table. 76 | func NewInMemoryUserRecordTable() *InMemoryUserRecordTable { 77 | t := make(map[string]*UserRecord) 78 | return (*InMemoryUserRecordTable)(&t) 79 | } 80 | 81 | // LookupUserRecord returns the user record associated with the given username in the 82 | // user record, or an error if the username is not registered. 83 | func (t InMemoryUserRecordTable) LookupUserRecord(username string) (*UserRecord, error) { 84 | record, ok := map[string]*UserRecord(t)[username] 85 | if !ok { 86 | return nil, errors.Wrapf(common.ErrorUserNotRegistered, username) 87 | } 88 | 89 | return record, nil 90 | } 91 | 92 | // InsertUserRecord adds a record to the in-memory user record table. 93 | // Username must be unique. The UserID in the record must be nil or match the 94 | // given username. 95 | func (t InMemoryUserRecordTable) InsertUserRecord(username string, record *UserRecord) error { 96 | // validate username 97 | if len(record.UserID) == 0 { 98 | record.UserID = []byte(username) 99 | } else if strings.Compare(string(record.UserID), username) != 0 { 100 | return errors.Wrapf(common.ErrorUnexpectedData, string(record.UserID), username) 101 | } 102 | 103 | if _, in := map[string]*UserRecord(t)[username]; in { 104 | return errors.Wrapf(common.ErrorUserAlreadyRegistered, string(record.UserID)) 105 | } 106 | 107 | // insert user 108 | map[string]*UserRecord(t)[username] = record 109 | 110 | return nil 111 | } 112 | 113 | // BulkAdd adds the given records to the in-memory user record table. 114 | func (t InMemoryUserRecordTable) BulkAdd(records []*UserRecord) error { 115 | for _, record := range records { 116 | err := t.InsertUserRecord(string(record.UserID), record) 117 | if err != nil { 118 | return err 119 | } 120 | } 121 | 122 | return nil 123 | } 124 | 125 | // newTestCredentials returns Credentials for testing packaged as 126 | // secret: userPrivateKey | cleartext: serverPublicKey, domain 127 | func newTestCredentials(userPrivateKey crypto.Signer, serverPublicKey crypto.PublicKey, domain string) (*Credentials, error) { 128 | upk, err := newCredentialExtension(CredentialTypeUserPrivateKey, userPrivateKey) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | spk, err := newCredentialExtension(CredentialTypeServerPublicKey, serverPublicKey) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | sid, err := newCredentialExtension(CredentialTypeServerIdentity, []byte(domain)) 139 | if err != nil { 140 | return nil, err 141 | } 142 | 143 | // use default policy 144 | return &Credentials{ 145 | SecretCredentials: []*CredentialExtension{upk}, 146 | CleartextCredentials: []*CredentialExtension{spk, sid}, 147 | }, nil 148 | } 149 | -------------------------------------------------------------------------------- /src/opaque/user_record_table_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaque 27 | 28 | import ( 29 | "testing" 30 | 31 | "github.com/cloudflare/opaque-ea/src/common" 32 | 33 | "github.com/cloudflare/circl/oprf" 34 | "github.com/pkg/errors" 35 | ) 36 | 37 | func TestInMemoryUserRecordTable(t *testing.T) { 38 | testUser := "user1" 39 | 40 | cfg, err := NewTestServerConfig("hello.com", oprf.OPRFP256) 41 | if err != nil { 42 | t.Error(err) 43 | return 44 | } 45 | 46 | // Lookup a user 47 | _, err = cfg.RecordTable.LookupUserRecord(testUser) 48 | if err != nil { 49 | t.Error(err) 50 | return 51 | } 52 | 53 | // Add a new user and look up 54 | err = cfg.RecordTable.InsertUserRecord("im_a_new_user", &UserRecord{}) 55 | if err != nil { 56 | t.Error(err) 57 | return 58 | } 59 | 60 | _, err = cfg.RecordTable.LookupUserRecord("im_a_new_user") 61 | if err != nil { 62 | t.Error(err) 63 | return 64 | } 65 | 66 | // Try to add a user who is already there 67 | err = cfg.RecordTable.InsertUserRecord(testUser, &UserRecord{}) 68 | 69 | if !errors.Is(err, common.ErrorUserAlreadyRegistered) { 70 | t.Errorf("expected err %v to contain %v", err, common.ErrorUserAlreadyRegistered) 71 | return 72 | } 73 | 74 | // Try to lookup a user who is not there 75 | _, err = cfg.RecordTable.LookupUserRecord("not a user") 76 | 77 | if !errors.Is(err, common.ErrorUserNotRegistered) { 78 | t.Errorf("expected err %v to contain %v", err, common.ErrorUserNotRegistered) 79 | return 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/opaque/utils.go: -------------------------------------------------------------------------------- 1 | package opaque 2 | 3 | import ( 4 | "crypto" 5 | "fmt" 6 | 7 | "github.com/cloudflare/circl/oprf" 8 | "github.com/pkg/errors" 9 | "github.com/tatianab/mint" 10 | ) 11 | 12 | // RunLocalOPRF returns the randomized password for the given server, username and 13 | // password. 14 | // It runs the OPRF protocol locally with a dummy client. 15 | // Only for testing - in a real setting the server does not know the password. 16 | func RunLocalOPRF(s *Server, username, password string) ([]byte, error) { 17 | client, err := NewClient(username, s.Config.ServerID, s.Config.Suite) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "new client") 20 | } 21 | 22 | oprf1, err := client.blind(password) 23 | if err != nil { 24 | return nil, errors.Wrap(err, "blind") 25 | } 26 | 27 | oprfServer, err := oprf.NewServer(s.Config.Suite, nil) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | s.UserRecord.OprfState = oprfServer 33 | oprf2, err := s.evaluate(oprf1) 34 | if err != nil { 35 | return nil, errors.Wrap(err, "evaluate") 36 | } 37 | 38 | rwd, err := client.finalizeHarden(oprf2) 39 | if err != nil { 40 | return nil, errors.Wrap(err, "unblind finalize harden") 41 | } 42 | 43 | s.UserRecord.UserID = []byte(username) 44 | s.UserRecord.OprfState = oprfServer 45 | 46 | return rwd, nil 47 | } 48 | 49 | // GetTestUserRecord returns a new test user record for the given domain, 50 | // username and password. 51 | // It should be used for testing. 52 | func GetTestUserRecord(s *Server, username, password string) (*UserRecord, error) { 53 | rwd, err := RunLocalOPRF(s, username, password) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "run local oprf") 56 | } 57 | 58 | userPrivKey, _, _ := mint.MakeNewSelfSignedCert(username, OPAQUESIGNSignatureScheme) 59 | 60 | creds, err := newTestCredentials(userPrivKey, s.Config.Signer.Public(), s.Config.ServerID) 61 | if err != nil { 62 | return nil, errors.Wrap(err, "new test creds") 63 | } 64 | 65 | nonceLen := int(32) 66 | envelope, exportedKey, err := EncryptCredentials(rwd, creds, nonceLen) 67 | if err != nil { 68 | return nil, errors.Wrap(err, "encrypt credentials") 69 | } 70 | 71 | if len(exportedKey) == 0 { 72 | return nil, errors.New("exportedKey not set") 73 | } 74 | 75 | return s.InsertNewUserRecord(userPrivKey.Public(), envelope) 76 | } 77 | 78 | // GetTestUserRecords returns numRecords dummy user records with unique usernames 79 | // and passwords: (user1, password1),...,(userN,...,passwordN). 80 | // It should be used for testing. 81 | func GetTestUserRecords(serverSigner crypto.Signer, numRecords int, domain string, suite oprf.SuiteID) (records []*UserRecord, err error) { 82 | records = make([]*UserRecord, numRecords) 83 | 84 | for i := 0; i < numRecords; i++ { 85 | username := fmt.Sprintf("user%v", i) 86 | password := fmt.Sprintf("password%v", i) 87 | 88 | s, err := NewServer( 89 | &ServerConfig{ 90 | Signer: serverSigner, 91 | ServerID: domain, 92 | Suite: suite, 93 | }) 94 | if err != nil { 95 | return nil, errors.Wrap(err, "new server") 96 | } 97 | 98 | record, err := GetTestUserRecord(s, username, password) 99 | if err != nil { 100 | return nil, errors.Wrap(err, "test user record") 101 | } 102 | 103 | records[i] = record 104 | } 105 | 106 | return records, nil 107 | } 108 | 109 | // NewTestServerConfig returns a ServerConfig struct containing a test record 110 | // table of the desired size, with dummy usernames and user records, and a 111 | // function to get a user record from a username. Credentials 112 | // are (user1, password1)...(userN,passwordN). 113 | // It should be used for testing. 114 | func NewTestServerConfig(domain string, suite oprf.SuiteID) (cfg *ServerConfig, err error) { 115 | signer, err := mint.NewSigningKey(OPAQUESIGNSignatureScheme) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | t := NewInMemoryUserRecordTable() 121 | 122 | n := 3 123 | records, err := GetTestUserRecords(signer, n, domain, suite) 124 | if err != nil { 125 | return nil, errors.Wrap(err, "test user records") 126 | } 127 | 128 | err = t.BulkAdd(records) 129 | if err != nil { 130 | return nil, errors.Wrap(err, "bulk add") 131 | } 132 | 133 | return &ServerConfig{ 134 | ServerID: domain, 135 | Signer: signer, 136 | RecordTable: t, 137 | Suite: suite, 138 | }, nil 139 | } 140 | -------------------------------------------------------------------------------- /src/opaqueea/exported_keys.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaqueea 27 | 28 | import ( 29 | "crypto" 30 | "log" 31 | "net/http" 32 | 33 | "github.com/cloudflare/circl/oprf" 34 | "github.com/cloudflare/opaque-ea/src/expauth" 35 | ) 36 | 37 | // ExportedKeyMaterial represents an exported key material struct. 38 | type ExportedKeyMaterial struct { 39 | ClientHandshakeContext []byte 40 | ServerHandshakeContext []byte 41 | ClientFinishedKey []byte 42 | ServerFinishedKey []byte 43 | AuthHash crypto.Hash 44 | } 45 | 46 | // ToGetterAndHash casts an ExportedKeyMaterial into an ExportedKeyGetter. 47 | func (ekm *ExportedKeyMaterial) ToGetterAndHash() (expauth.ExportedKeyGetter, crypto.Hash) { 48 | return expauth.ExportedKeyGetterFromKeys(ekm.ClientHandshakeContext, ekm.ClientFinishedKey, 49 | ekm.ServerHandshakeContext, ekm.ServerFinishedKey), ekm.AuthHash 50 | } 51 | 52 | // GetExportedKeyMaterial gets an ExportedKeyMaterial from a request. 53 | func GetExportedKeyMaterial(request *http.Request) (*ExportedKeyMaterial, error) { 54 | if request.TLS != nil { 55 | getter := expauth.NewExportedKeyGetterFromTLSConnState(request.TLS) 56 | log.Printf("TLS connection: %v", request.TLS) 57 | authHash := expauth.AuthHashFromTLSConnState(request.TLS) 58 | 59 | return getExportedKeyMaterialInner(getter, authHash) 60 | } 61 | 62 | log.Printf("Not a TLS connection") 63 | return nil, nil 64 | } 65 | 66 | // GetTestExportedKeyMaterial gets an ExportedKeyMaterial for testing. 67 | func GetTestExportedKeyMaterial() (*ExportedKeyMaterial, error) { 68 | getter, authHash := expauth.GetTestGetterAndHash() 69 | return getExportedKeyMaterialInner(getter, authHash) 70 | } 71 | 72 | func getExportedKeyMaterialInner(getter expauth.ExportedKeyGetter, authHash crypto.Hash) (*ExportedKeyMaterial, error) { 73 | chc, err := getter(expauth.ClientAuthMode, expauth.HandshakeLabel) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | cfk, err := getter(expauth.ClientAuthMode, expauth.FinishedLabel) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | shc, err := getter(expauth.ServerAuthMode, expauth.HandshakeLabel) 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | sfk, err := getter(expauth.ServerAuthMode, expauth.FinishedLabel) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | return &ExportedKeyMaterial{ 94 | ClientHandshakeContext: chc, 95 | ClientFinishedKey: cfk, 96 | ServerHandshakeContext: shc, 97 | ServerFinishedKey: sfk, 98 | AuthHash: authHash, 99 | }, nil 100 | } 101 | 102 | // ConfigMaterial handles the material for a Config. 103 | type ConfigMaterial struct { 104 | Suite oprf.SuiteID 105 | } 106 | -------------------------------------------------------------------------------- /src/opaqueea/json_encoding.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package opaqueea 26 | 27 | import ( 28 | "encoding/json" 29 | 30 | "github.com/cloudflare/opaque-ea/src/opaque" 31 | ) 32 | 33 | // ProtocolMessageTypeToStringMap is a map representing a protocol message type 34 | // mapped to the appropriate string. 35 | var ProtocolMessageTypeToStringMap = map[opaque.ProtocolMessageType]string{ 36 | ProtocolMessageTypeClientRequest: "OPAQUE-EA Client Login Request", 37 | ProtocolMessageTypeServerResponse: "OPAQUE-EA Server Authenticator and Envelope", 38 | ProtocolMessageTypeClientResponse: "OPAQUE-EA Client Authenticator", 39 | } 40 | 41 | // ToString maps a protocol message type to a string. 42 | func ToString(pmt opaque.ProtocolMessageType) string { 43 | if str, ok := opaque.ProtocolMessageTypeToStringMap[pmt]; ok { 44 | return str 45 | } 46 | 47 | if str, ok := ProtocolMessageTypeToStringMap[pmt]; ok { 48 | return str 49 | } 50 | 51 | panic("invalid Protocol Message Type") 52 | } 53 | 54 | type protocolMessageJSON struct { 55 | ProtocolMessageType string 56 | Inner interface{} `json:"MessageBody"` 57 | } 58 | 59 | // MarshalJSON marshals a protocol message. 60 | func (pm *ProtocolMessage) MarshalJSON() ([]byte, error) { 61 | pmb, err := pm.ToBody() 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | pmJSON := &protocolMessageJSON{ 67 | ProtocolMessageType: ToString(pmb.Type()), 68 | Inner: pmb, 69 | } 70 | 71 | return json.Marshal(pmJSON) 72 | } 73 | -------------------------------------------------------------------------------- /src/opaqueea/opaque-ea_types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaqueea 27 | 28 | import ( 29 | "crypto" 30 | 31 | "github.com/cloudflare/circl/oprf" 32 | "github.com/cloudflare/opaque-ea/src/expauth" 33 | "github.com/cloudflare/opaque-ea/src/opaque" 34 | ) 35 | 36 | // Client is an instance of an OPAQUE-EA client. 37 | type Client struct { 38 | eaState *expauth.Party 39 | opaqueState *opaque.Client 40 | request expauth.ExportedAuthenticatorRequest 41 | validatePublicKey func(crypto.PublicKey) bool 42 | } 43 | 44 | // Server is an instance of an OPAQUE-EA server. 45 | type Server struct { 46 | cfg *ServerConfig 47 | connState *ConnectionState 48 | } 49 | 50 | // ServerConfig represents a configuration for a server, with an OPAQUE configuration 51 | // and a handle. 52 | type ServerConfig struct { 53 | OpaqueCfg *opaque.ServerConfig 54 | HandleMissingUser func(error) (*ServerResponseMsg, error) 55 | } 56 | 57 | // ConnectionState represents the state of a connection. 58 | type ConnectionState struct { 59 | opaqueServer *opaque.Server 60 | eaState *expauth.Party 61 | request expauth.ExportedAuthenticatorRequest 62 | } 63 | 64 | // NewServer takes in a server exported authenticator state and a signing key 65 | // and returns a new OPAQUE-EA server instance. 66 | func NewServer(state *expauth.Party, cfg *ServerConfig) (*Server, error) { 67 | opaqueState, err := opaque.NewServer(cfg.OpaqueCfg) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | if cfg.HandleMissingUser == nil { 73 | cfg.HandleMissingUser = errorOnMissingUser 74 | } 75 | 76 | return &Server{ 77 | cfg: cfg, 78 | connState: &ConnectionState{ 79 | opaqueServer: opaqueState, 80 | eaState: state, 81 | }, 82 | }, nil 83 | } 84 | 85 | // GetUserID gets the user id. 86 | func (s *Server) GetUserID() string { 87 | return string(s.connState.opaqueServer.UserRecord.UserID) 88 | } 89 | 90 | // NewClient takes an existing client exported authenticator state 91 | // and a user ID and returns a new OPAQUE-EA client instance. 92 | func NewClient(state *expauth.Party, userID, domain string, suite oprf.SuiteID) (*Client, error) { 93 | opaqueState, err := opaque.NewClient(userID, domain, suite) 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | return &Client{ 99 | eaState: state, 100 | opaqueState: opaqueState, 101 | validatePublicKey: func(crypto.PublicKey) bool { return true }, // TODO: make this a param 102 | }, nil 103 | } 104 | 105 | func errorOnMissingUser(err error) (*ServerResponseMsg, error) { 106 | return nil, err 107 | } 108 | -------------------------------------------------------------------------------- /src/opaqueea/protocol_messages.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // Redistribution and use in source and binary forms, with or without 3 | // modification, are permitted provided that the following conditions are met: 4 | // 1. Redistributions of source code must retain the above copyright notice, 5 | // this list of conditions and the following disclaimer. 6 | // 2. Redistributions in binary form must reproduce the above copyright notice, 7 | // this list of conditions and the following disclaimer in the documentation 8 | // and/or other materials provided with the distribution. 9 | // 3. Neither the name of the copyright holder nor the names of its contributors 10 | // may be used to endorse or promote products derived from this software without 11 | // specific prior written permission. 12 | 13 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 14 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 16 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 17 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 23 | // POSSIBILITY OF SUCH DAMAGE. 24 | 25 | package opaqueea 26 | 27 | import ( 28 | "github.com/cloudflare/opaque-ea/src/common" 29 | "github.com/cloudflare/opaque-ea/src/expauth" 30 | "github.com/cloudflare/opaque-ea/src/opaque" 31 | ) 32 | 33 | // ProtocolMessage is a wrap around an OPAQUE protocol message. 34 | type ProtocolMessage opaque.ProtocolMessage // renaming so we can define new methods 35 | 36 | // OPAQUE-EA protocol message types. 37 | const ( 38 | ProtocolMessageTypeClientRequest opaque.ProtocolMessageType = 6 + iota // start where OPAQUE left off 39 | ProtocolMessageTypeClientResponse 40 | ProtocolMessageTypeServerResponse 41 | ) 42 | 43 | // ClientInitMsg is the first message sent by a client in the OPAQUE-EA flow. 44 | // Contains an exported authenticator request with the PAKEServerAuth 45 | // extension. 46 | type ClientInitMsg struct { 47 | Request expauth.ExportedAuthenticatorRequest 48 | } 49 | 50 | var _ opaque.ProtocolMessageBody = (*ClientInitMsg)(nil) 51 | 52 | // Marshal returns the raw form of the struct. 53 | func (cim *ClientInitMsg) Marshal() ([]byte, error) { 54 | return cim.Request.Marshal() 55 | } 56 | 57 | // Unmarshal puts raw data into fields of a struct and returns the number of 58 | // bytes read. 59 | func (cim *ClientInitMsg) Unmarshal(data []byte) (int, error) { 60 | request := &expauth.ClientExportedAuthenticatorRequest{} 61 | 62 | bytesRead, err := request.Unmarshal(data) 63 | if err != nil { 64 | return 0, err 65 | } 66 | 67 | cim.Request = request 68 | 69 | return bytesRead, nil 70 | } 71 | 72 | // Type returns the type of this ProtocolMessageBody. 73 | func (cim *ClientInitMsg) Type() opaque.ProtocolMessageType { 74 | return ProtocolMessageTypeClientRequest 75 | } 76 | 77 | // ClientResponseMsg is the second message sent by the client in the OPAQUE-EA 78 | // flow. It is only sent if the Server requests mutual authentication. 79 | // It contains a single Exported Authenticator from the Client. 80 | type ClientResponseMsg struct { 81 | ExpAuth *expauth.ExportedAuthenticator 82 | } 83 | 84 | var _ opaque.ProtocolMessageBody = (*ClientResponseMsg)(nil) 85 | 86 | // Marshal returns the raw form of the struct. 87 | func (cr *ClientResponseMsg) Marshal() ([]byte, error) { 88 | return cr.ExpAuth.Marshal() 89 | } 90 | 91 | // Unmarshal puts raw data into fields of a struct and returns the number of 92 | // bytes read. 93 | func (cr *ClientResponseMsg) Unmarshal(data []byte) (int, error) { 94 | ea := &expauth.ExportedAuthenticator{} 95 | 96 | bytesRead, err := ea.Unmarshal(data) 97 | if err != nil { 98 | return 0, err 99 | } 100 | 101 | cr.ExpAuth = ea 102 | 103 | return bytesRead, nil 104 | } 105 | 106 | // Type returns the type of this ProtocolMessageBody. 107 | func (cr *ClientResponseMsg) Type() opaque.ProtocolMessageType { 108 | return ProtocolMessageTypeClientResponse 109 | } 110 | 111 | // ServerResponseMsg is the first message sent by the server in response to the 112 | // client's initial message in the OPAQUE-EA flow. 113 | // It contains an Exported Authenticator from the Server containing a 114 | // PAKEServerAuth extension. 115 | // This message also contains an EA request from the Server to the Client with a 116 | // PAKEClientAuth extension. 117 | // TODO: make mutual auth optional. 118 | type ServerResponseMsg struct { 119 | ExpAuth *expauth.ExportedAuthenticator // exp auth from server to client 120 | Request expauth.ExportedAuthenticatorRequest // request from server to client (optional mutual auth) 121 | } 122 | 123 | var _ opaque.ProtocolMessageBody = (*ServerResponseMsg)(nil) 124 | 125 | // Marshal returns the raw form of the struct. 126 | func (srm *ServerResponseMsg) Marshal() ([]byte, error) { 127 | toMarshal := []common.Marshaler{srm.ExpAuth, srm.Request} 128 | return common.MarshalList(toMarshal) 129 | } 130 | 131 | // Unmarshal puts raw data into fields of a struct and returns the number of 132 | // bytes read. 133 | func (srm *ServerResponseMsg) Unmarshal(data []byte) (int, error) { 134 | ea := &expauth.ExportedAuthenticator{} 135 | req := &expauth.ServerExportedAuthenticatorRequest{} 136 | toUnmarshal := []common.Unmarshaler{ea, req} 137 | 138 | bytesRead, err := common.UnmarshalList(toUnmarshal, data) 139 | if err != nil { 140 | return 0, err 141 | } 142 | 143 | srm.ExpAuth = ea 144 | srm.Request = req 145 | 146 | return bytesRead, nil 147 | } 148 | 149 | // Type returns the type of this ProtocolMessageBody. 150 | func (srm *ServerResponseMsg) Type() opaque.ProtocolMessageType { 151 | return ProtocolMessageTypeServerResponse 152 | } 153 | 154 | // Marshal marshals a protocol message. 155 | func (pm *ProtocolMessage) Marshal() ([]byte, error) { 156 | return (*opaque.ProtocolMessage)(pm).Marshal() 157 | } 158 | 159 | // Unmarshal unmarshals a protocol message. 160 | func (pm *ProtocolMessage) Unmarshal(data []byte) (int, error) { 161 | return (*opaque.ProtocolMessage)(pm).Unmarshal(data) 162 | } 163 | 164 | // ToBody gets the body of a protocol message. 165 | func (pm *ProtocolMessage) ToBody() (opaque.ProtocolMessageBody, error) { 166 | body, err := (*opaque.ProtocolMessage)(pm).ToBody() 167 | if err != nil { // if type not recognized by opaque package 168 | switch pm.MessageType { 169 | case ProtocolMessageTypeClientRequest: 170 | body = new(ClientInitMsg) 171 | case ProtocolMessageTypeClientResponse: 172 | body = new(ClientResponseMsg) 173 | case ProtocolMessageTypeServerResponse: 174 | body = new(ServerResponseMsg) 175 | default: 176 | return body, err 177 | } 178 | } 179 | 180 | _, err = body.Unmarshal(pm.MessageBodyRaw) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | return body, nil 186 | } 187 | 188 | // ProtocolMessageFromBody creates a protocol message from its body. 189 | func ProtocolMessageFromBody(body opaque.ProtocolMessageBody) (*ProtocolMessage, error) { 190 | pm, err := opaque.ProtocolMessageFromBody(body) 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | return (*ProtocolMessage)(pm), nil 196 | } 197 | -------------------------------------------------------------------------------- /src/opaqueea/registration.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package opaqueea 27 | 28 | import ( 29 | "crypto" 30 | 31 | "github.com/cloudflare/opaque-ea/src/opaque" 32 | ) 33 | 34 | // RegistrationRequest creates a registration request. 35 | func (c *Client) RegistrationRequest(password string, key crypto.Signer) (*ProtocolMessage, error) { 36 | requestBody, err := c.opaqueState.CreateRegistrationRequest(password, key) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | request, err := ProtocolMessageFromBody(requestBody) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | return request, nil 47 | } 48 | 49 | // RegistrationResponse creates a registration response. 50 | func (s *Server) RegistrationResponse(registrationRequest *ProtocolMessage) (*ProtocolMessage, error) { 51 | requestBody, err := registrationRequest.ToBody() 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | responseBody, err := s.connState.opaqueServer.CreateRegistrationResponse(requestBody.(*opaque.RegistrationRequest)) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | response, err := ProtocolMessageFromBody(responseBody) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | return response, nil 67 | } 68 | 69 | // FinalizeRegistration creates a finalize registration. 70 | func (c *Client) FinalizeRegistration(response *ProtocolMessage) (*ProtocolMessage, error) { 71 | responseBody, err := response.ToBody() 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | uploadBody, _, err := c.opaqueState.FinalizeRegistrationRequest(responseBody.(*opaque.RegistrationResponse)) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | upload, err := ProtocolMessageFromBody(uploadBody) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return upload, nil 87 | } 88 | 89 | // UploadCredentials uploads the credentials. 90 | func (s *Server) UploadCredentials(registrationUpload *ProtocolMessage) error { 91 | uploadBody, err := registrationUpload.ToBody() 92 | if err != nil { 93 | return err 94 | } 95 | 96 | return s.connState.opaqueServer.StoreUserRecord(uploadBody.(*opaque.RegistrationUpload)) 97 | } 98 | -------------------------------------------------------------------------------- /src/testhelp/test_helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, Cloudflare. All rights reserved. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 1. Redistributions of source code must retain the above copyright notice, 6 | // this list of conditions and the following disclaimer. 7 | // 2. Redistributions in binary form must reproduce the above copyright notice, 8 | // this list of conditions and the following disclaimer in the documentation 9 | // and/or other materials provided with the distribution. 10 | // 3. Neither the name of the copyright holder nor the names of its contributors 11 | // may be used to endorse or promote products derived from this software without 12 | // specific prior written permission. 13 | // 14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 18 | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 19 | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 20 | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 21 | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 22 | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 23 | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 24 | // POSSIBILITY OF SUCH DAMAGE. 25 | 26 | package testhelp 27 | 28 | import ( 29 | "reflect" 30 | 31 | "github.com/cloudflare/opaque-ea/src/common" 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | // TestMarshalUnmarshal is a test helper that errors if Marshal/Unmarshal 36 | // for the type of data and empty is not working. 37 | // data should be filled in and empty should be empty. 38 | func TestMarshalUnmarshal(data, empty common.MarshalUnmarshaler) error { 39 | raw, err := data.Marshal() 40 | if err != nil { 41 | return errors.Wrap(err, "marshal") 42 | } 43 | 44 | bytesRead, err := empty.Unmarshal(raw) 45 | if err != nil { 46 | return errors.Wrap(err, "unmarshal") 47 | } 48 | 49 | if bytesRead != len(raw) { 50 | return errors.Errorf("incorrect unmarshal bytesRead: got %v, expected %v", bytesRead, len(raw)) 51 | } 52 | 53 | if !reflect.DeepEqual(data, empty) { 54 | return errors.Errorf(common.ErrMarshalUnmarshalFailed, reflect.TypeOf(data), data, empty) 55 | } 56 | 57 | return nil 58 | } 59 | 60 | const ( 61 | // TestUser represents a username for testing. 62 | TestUser = "user1" 63 | // TestPassword represents a password for testing. 64 | TestPassword = "password1" 65 | ) 66 | --------------------------------------------------------------------------------