forked from privacypass/challenge-bypass-server
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2a5cbbc
Showing
164 changed files
with
42,890 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/.GOPATH | ||
/bin |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
Copyright (c) 2017, Cloudflare. All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
|
||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
3. Neither the name of the copyright holder nor the names of its contributors | ||
may be used to endorse or promote products derived from this software without | ||
specific prior written permission. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
# The import path is where your repository can be found. | ||
# To import subpackages, always prepend the full import path. | ||
# If you change this, run `make clean`. Read more: https://git.io/vM7zV | ||
IMPORT_PATH := github.com/cloudflare/btd | ||
|
||
# V := 1 # When V is set, print commands and build progress. | ||
|
||
# Space separated patterns of packages to skip in list, test, format. | ||
IGNORED_PACKAGES := /vendor/ | ||
|
||
.PHONY: all | ||
all: build | ||
|
||
.PHONY: build btd | ||
build: btd | ||
|
||
btd: .GOPATH/.ok | ||
$Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/server | ||
$Q mv bin/server bin/btd | ||
|
||
### Code not in the repository root? Another binary? Add to the path like this. | ||
# .PHONY: otherbin | ||
# otherbin: .GOPATH/.ok | ||
# $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/otherbin | ||
|
||
##### ^^^^^^ EDIT ABOVE ^^^^^^ ##### | ||
|
||
##### =====> Utility targets <===== ##### | ||
|
||
.PHONY: clean test list cover format | ||
|
||
clean: | ||
$Q rm -rf bin .GOPATH | ||
|
||
test: .GOPATH/.ok | ||
$Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run | ||
ifndef CI | ||
$Q go vet $(allpackages) | ||
$Q GODEBUG=cgocheck=2 go test -race $(allpackages) | ||
else | ||
$Q ( go vet $(allpackages); echo $$? ) | \ | ||
tee .GOPATH/test/vet.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/vet.txt) | ||
$Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \ | ||
tee .GOPATH/test/output.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/output.txt) | ||
endif | ||
|
||
list: .GOPATH/.ok | ||
@echo $(allpackages) | ||
|
||
cover: bin/gocovmerge .GOPATH/.ok | ||
@echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!" | ||
$Q rm -f .GOPATH/cover/*.out .GOPATH/cover/all.merged | ||
$(if $V,@echo "-- go test -coverpkg=./... -coverprofile=.GOPATH/cover/... ./...") | ||
@for MOD in $(allpackages); do \ | ||
go test -coverpkg=`echo $(allpackages)|tr " " ","` \ | ||
-coverprofile=.GOPATH/cover/unit-`echo $$MOD|tr "/" "_"`.out \ | ||
$$MOD 2>&1 | grep -v "no packages being tested depend on"; \ | ||
done | ||
$Q ./bin/gocovmerge .GOPATH/cover/*.out > .GOPATH/cover/all.merged | ||
ifndef CI | ||
$Q go tool cover -html .GOPATH/cover/all.merged | ||
else | ||
$Q go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html | ||
endif | ||
@echo "" | ||
@echo "=====> Total test coverage: <=====" | ||
@echo "" | ||
$Q go tool cover -func .GOPATH/cover/all.merged | ||
|
||
format: bin/goimports .GOPATH/.ok | ||
$Q find .GOPATH/src/$(IMPORT_PATH)/ -iname \*.go | grep -v \ | ||
-e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) | xargs ./bin/goimports -w | ||
|
||
##### =====> Internals <===== ##### | ||
|
||
.PHONY: setup | ||
setup: clean .GOPATH/.ok | ||
@if ! grep "/.GOPATH" .gitignore > /dev/null 2>&1; then \ | ||
echo "/.GOPATH" >> .gitignore; \ | ||
echo "/bin" >> .gitignore; \ | ||
fi | ||
go get -u github.com/FiloSottile/gvt | ||
- ./bin/gvt fetch golang.org/x/tools/cmd/goimports | ||
- ./bin/gvt fetch github.com/wadey/gocovmerge | ||
|
||
VERSION := $(shell git describe --tags --always --dirty="-dev") | ||
DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC') | ||
VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"' | ||
|
||
# cd into the GOPATH to workaround ./... not following symlinks | ||
_allpackages = $(shell ( cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && \ | ||
GOPATH=$(CURDIR)/.GOPATH go list ./... 2>&1 1>&3 | \ | ||
grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) 1>&2 ) 3>&1 | \ | ||
grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES))) | ||
|
||
# memoize allpackages, so that it's executed only once and only if used | ||
allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages) | ||
|
||
export GOPATH := $(CURDIR)/.GOPATH | ||
unexport GOBIN | ||
|
||
Q := $(if $V,,@) | ||
|
||
.GOPATH/.ok: | ||
$Q mkdir -p "$(dir .GOPATH/src/$(IMPORT_PATH))" | ||
$Q ln -s ../../../.. ".GOPATH/src/$(IMPORT_PATH)" | ||
$Q mkdir -p .GOPATH/test .GOPATH/cover | ||
$Q mkdir -p bin | ||
$Q ln -s ../bin .GOPATH/bin | ||
$Q touch $@ | ||
|
||
.PHONY: bin/gocovmerge bin/goimports | ||
bin/gocovmerge: .GOPATH/.ok | ||
@test -d ./vendor/github.com/wadey/gocovmerge || \ | ||
{ echo "Vendored gocovmerge not found, try running 'make setup'..."; exit 1; } | ||
$Q go install $(IMPORT_PATH)/vendor/github.com/wadey/gocovmerge | ||
bin/goimports: .GOPATH/.ok | ||
@test -d ./vendor/golang.org/x/tools/cmd/goimports || \ | ||
{ echo "Vendored goimports not found, try running 'make setup'..."; exit 1; } | ||
$Q go install $(IMPORT_PATH)/vendor/golang.org/x/tools/cmd/goimports | ||
|
||
# Based on https://github.com/cloudflare/hellogopher - v1.1 - MIT License | ||
# | ||
# Copyright (c) 2017 Cloudflare | ||
# | ||
# Permission is hereby granted, free of charge, to any person obtaining a copy | ||
# of this software and associated documentation files (the "Software"), to deal | ||
# in the Software without restriction, including without limitation the rights | ||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
# copies of the Software, and to permit persons to whom the Software is | ||
# furnished to do so, subject to the following conditions: | ||
# | ||
# The above copyright notice and this permission notice shall be included in all | ||
# copies or substantial portions of the Software. | ||
# | ||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
# SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
## Blind Token Daemon | ||
|
||
This is the server implementing the second revision of the Cloudflare blinded tokens protocol. For a description of the original protocol and motivations, see the [challenge bypass specification](https://github.com/cloudflare/challenge-bypass-specification) or our talk at [Real World Crypto 2017](https://speakerdeck.com/gtank/solving-the-cloudflare-captcha-rwc2017). | ||
|
||
The protocol is based on a variant of an OPRF [password management scheme](https://eprint.iacr.org/2016/144) by Jarecki, Kiayias, Krawczyk and Xu. When adapted to our needs, this scheme allows us to achieve the same goals using faster primitives, less bandwidth, and simpler secret-key operational logistics compared to the earlier RSA-based protocol. | ||
|
||
## Quickstart | ||
|
||
To run the server: | ||
|
||
`go run server/main.go --key testdata/p256-key.pem` | ||
|
||
To demo token issuance: | ||
|
||
`cat testdata/bl_sig_req | nc localhost 2416` | ||
|
||
For a full client implementation, see the [browser extension](https://github.com/cloudflare/challenge-bypass-extension). | ||
|
||
### Definitions | ||
|
||
A **message authentication code (MAC)** on a message is a keyed authentication tag that can be only be created and verified by the holder of the key. | ||
|
||
A **pseudorandom function** is a function whose output cannot be efficiently distinguished from random output. This is a general class of functions; concrete examples include hashes and encryption algorithms. | ||
|
||
An **oblivious pseudorandom function (OPRF)** is a two-party protocol between sender *S* and receiver *R* for securely computing a pseudorandom function *f_k(·)* on key *k* contributed by *S* and input *x* contributed by *R*, in such a way that receiver *R* learns only the value *f_k(x)* while sender *S* learns nothing from the interaction. | ||
|
||
In this protocol, the Cloudflare edge is the "sender" holding k and the inputs x are the tokens. So the clients don't learn our key and we don't learn the token values. | ||
|
||
### Protocol sketch | ||
|
||
The core difference is that where we previously relied on asymmetric cryptography for signatures, we can instead construct a symmetric exchange in which a secret key known only to the edge is used to create per-token MAC keys for each redemption request in a way that prevents the server from learning the token values and the client from learning the secret key. | ||
|
||
Given a group setting and two hashes H_1, H_2, we build a commitment to a random token per request using a secret key k held by the edge servers. H_1 and H_2 are hash functions onto, respectively, the group and {0, 1}^λ where λ is a security parameter. | ||
|
||
1. Client generates random token `x` and a blinding factor `r` | ||
2. Client calculates `a = H_1(x)^r` and sends `a` to the edge along with a CAPTCHA solution | ||
3. Edge validates the solution and computes `b = a^k = H_1(x)^(rk)`, returns `b` to client | ||
4. Client unblinds b to retrieve `n = b^(1/r) = H_1(x)^k`. Now both the server and the client can calculate `H_2(x, n)` as a shared key for the MAC. | ||
5. When the client wants to redeem a token it presents `(x, MAC(request-binding-data))` where `request-binding-data` is made of information observable by the edge that is unique(ish) to that particular request. | ||
6. The server uses `x` as a double-spend index and recalculates `n` using its secret key. Then it can validate the MAC using the shared key. | ||
7. We know that a matching commitment value is valid because generating it requires access to `k`. | ||
|
||
We prevent the edge from tracking users by tagging with unique keys using batch discrete-logarithm equality proofs (which are implemented but not yet deployed). | ||
|
||
### Benefits vs blind-RSA protocol | ||
|
||
- 10x savings in token size (~256 bits instead of ~2048) | ||
- Simpler & faster primitives (also: available in-browser via SJCL) | ||
- No need for public-key encryption at all, since the derived shared key used to calculate each MAC is never transmitted and cannot be calculated without knowledge of the edge key or the client's blinding factor. | ||
- The only secret to be managed is a 32-byte scalar. | ||
- Easier key rotation. Instead of managing RSA certificates with pinning or transparency, we can publish/pin the commitment component of a DLEQ proof to allow clients to positively verify they're in the same anonymity set with regard to k as everyone else. Alternatively or additionally, if we publish historical k values then auditors who save their b results can check our honesty. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// This implements a non-interactive version of the common-exponent batch | ||
// Schnorr proof from Ryan Henry's thesis. This specifically applies to the | ||
// case where a set of group elements shares a common discrete log with regard | ||
// to a set of generators. Slightly more formally, the case: | ||
// | ||
// (G, q, g_1,...,g_n) and (h_1,...,h_n) ∈ (G)^n with h_i = (g_i)^x for i = 1,...,n | ||
// | ||
// Inspired by an observation of Ian Goldberg's that this "common-exponent" case | ||
// is drastically simpler than a general batch proof. The general idea is that | ||
// we can produce a linear combination of the elements and perform a | ||
// Chaum-Pedersen proof on the resulting composite elements. | ||
// | ||
// See Section 3.2.3.3 for the interactive protocol: | ||
// https://uwspace.uwaterloo.ca/bitstream/handle/10012/8621/Henry_Ryan.pdf | ||
package crypto | ||
|
||
import ( | ||
"crypto" | ||
"errors" | ||
"math/big" | ||
|
||
"golang.org/x/crypto/sha3" | ||
) | ||
|
||
var ( | ||
ErrUnequalPointCounts = errors.New("batch proof had unequal numbers of points") | ||
) | ||
|
||
type BatchProof struct { | ||
P *Proof | ||
G, H *Point | ||
M, Z []*Point | ||
C [][]byte | ||
} | ||
|
||
func NewBatchProof(hash crypto.Hash, g, h *Point, m []*Point, z []*Point, x *big.Int) (*BatchProof, error) { | ||
if len(m) != len(z) { | ||
return nil, ErrUnequalPointCounts | ||
} | ||
|
||
// The underlying proof and validation steps will do consistency checks. | ||
curve := g.Curve | ||
|
||
// seed = H(g, h, [m], [z]) | ||
H := hash.New() | ||
H.Write(g.Marshal()) | ||
H.Write(h.Marshal()) | ||
for i := 0; i < len(m); i++ { | ||
H.Write(m[i].Marshal()) | ||
H.Write(z[i].Marshal()) | ||
} | ||
seed := H.Sum(nil) | ||
|
||
prng := sha3.NewShake256() | ||
prng.Write(seed) | ||
|
||
// Non-interactively generate random c_1, c_2, ... , c_n in Z/qZ | ||
// to combine m and z elements. Here's how this works: | ||
// For (m_1, m_2), (z_1, z_2), and (c_1, c_2) we have | ||
// z_1 = (m_1)^x | ||
// z_2 = (m_2)^x | ||
// (z_1^c_1) = (m_1^c_1)^x | ||
// (z_2^c_2) = (m_2^c_2)^x | ||
// (z_1^c_1)(z_2^c_2) = [(m_1^c_1)(m_2^c_2)]^x | ||
// This generalizes to produce composite elements for the entire batch that | ||
// can be compared to the public key in the standard two-point DLEQ proof. | ||
|
||
Mx, My, Zx, Zy := new(big.Int), new(big.Int), new(big.Int), new(big.Int) | ||
C := make([][]byte, len(m)) | ||
for i := 0; i < len(m); i++ { | ||
ci, _, err := randScalar(curve, prng) | ||
if err != nil { | ||
return nil, err | ||
} | ||
// cM = c[i]M[i] | ||
cMx, cMy := curve.ScalarMult(m[i].X, m[i].Y, ci) | ||
// cZ = c[i]Z[i] | ||
cZx, cZy := curve.ScalarMult(z[i].X, z[i].Y, ci) | ||
// Accumulate | ||
Mx, My = curve.Add(cMx, cMy, Mx, My) | ||
Zx, Zy = curve.Add(cZx, cZy, Zx, Zy) | ||
C[i] = ci | ||
} | ||
compositeM := &Point{Curve: curve, X: Mx, Y: My} | ||
compositeZ := &Point{Curve: curve, X: Zx, Y: Zy} | ||
|
||
proof, err := NewProof(hash, g, h, compositeM, compositeZ, x) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &BatchProof{ | ||
P: proof, | ||
G: g, H: h, | ||
M: m, Z: z, | ||
C: C, | ||
}, nil | ||
} | ||
|
||
func (b *BatchProof) IsComplete() bool { | ||
hasPublicKey := b.G != nil && b.H != nil | ||
hasPointSets := b.M != nil && b.Z != nil && len(b.M) == len(b.Z) | ||
return hasPublicKey && hasPointSets && b.C != nil | ||
} | ||
|
||
func (b *BatchProof) IsSane() bool { | ||
if len(b.M) != len(b.Z) { | ||
return false | ||
} | ||
if b.G.Curve != b.H.Curve { | ||
return false | ||
} | ||
for i := 0; i < len(b.M); i++ { | ||
if b.G.Curve != b.M[i].Curve || b.G.Curve != b.Z[i].Curve { | ||
return false | ||
} | ||
if !b.M[i].IsOnCurve() || !b.Z[i].IsOnCurve() { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func (b *BatchProof) Verify() bool { | ||
if !b.IsComplete() || !b.IsSane() { | ||
return false | ||
} | ||
return b.P.Verify() | ||
} |
Oops, something went wrong.