├── .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 | 
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 |
--------------------------------------------------------------------------------