Skip to content

Commit 2a5cbbc

Browse files
committed
initial public release
0 parents  commit 2a5cbbc

File tree

164 files changed

+42890
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+42890
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/.GOPATH
2+
/bin

LICENSE

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Copyright (c) 2017, 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+
6+
1. Redistributions of source code must retain the above copyright notice, this
7+
list of conditions and the following disclaimer.
8+
9+
2. Redistributions in binary form must reproduce the above copyright notice,
10+
this list of conditions and the following disclaimer in the documentation
11+
and/or other materials provided with the distribution.
12+
13+
3. Neither the name of the copyright holder nor the names of its contributors
14+
may be used to endorse or promote products derived from this software without
15+
specific prior written permission.
16+
17+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
21+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
24+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Makefile

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# The import path is where your repository can be found.
2+
# To import subpackages, always prepend the full import path.
3+
# If you change this, run `make clean`. Read more: https://git.io/vM7zV
4+
IMPORT_PATH := github.com/cloudflare/btd
5+
6+
# V := 1 # When V is set, print commands and build progress.
7+
8+
# Space separated patterns of packages to skip in list, test, format.
9+
IGNORED_PACKAGES := /vendor/
10+
11+
.PHONY: all
12+
all: build
13+
14+
.PHONY: build btd
15+
build: btd
16+
17+
btd: .GOPATH/.ok
18+
$Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/server
19+
$Q mv bin/server bin/btd
20+
21+
### Code not in the repository root? Another binary? Add to the path like this.
22+
# .PHONY: otherbin
23+
# otherbin: .GOPATH/.ok
24+
# $Q go install $(if $V,-v) $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/otherbin
25+
26+
##### ^^^^^^ EDIT ABOVE ^^^^^^ #####
27+
28+
##### =====> Utility targets <===== #####
29+
30+
.PHONY: clean test list cover format
31+
32+
clean:
33+
$Q rm -rf bin .GOPATH
34+
35+
test: .GOPATH/.ok
36+
$Q go test $(if $V,-v) -i -race $(allpackages) # install -race libs to speed up next run
37+
ifndef CI
38+
$Q go vet $(allpackages)
39+
$Q GODEBUG=cgocheck=2 go test -race $(allpackages)
40+
else
41+
$Q ( go vet $(allpackages); echo $$? ) | \
42+
tee .GOPATH/test/vet.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/vet.txt)
43+
$Q ( GODEBUG=cgocheck=2 go test -v -race $(allpackages); echo $$? ) | \
44+
tee .GOPATH/test/output.txt | sed '$$ d'; exit $$(tail -1 .GOPATH/test/output.txt)
45+
endif
46+
47+
list: .GOPATH/.ok
48+
@echo $(allpackages)
49+
50+
cover: bin/gocovmerge .GOPATH/.ok
51+
@echo "NOTE: make cover does not exit 1 on failure, don't use it to check for tests success!"
52+
$Q rm -f .GOPATH/cover/*.out .GOPATH/cover/all.merged
53+
$(if $V,@echo "-- go test -coverpkg=./... -coverprofile=.GOPATH/cover/... ./...")
54+
@for MOD in $(allpackages); do \
55+
go test -coverpkg=`echo $(allpackages)|tr " " ","` \
56+
-coverprofile=.GOPATH/cover/unit-`echo $$MOD|tr "/" "_"`.out \
57+
$$MOD 2>&1 | grep -v "no packages being tested depend on"; \
58+
done
59+
$Q ./bin/gocovmerge .GOPATH/cover/*.out > .GOPATH/cover/all.merged
60+
ifndef CI
61+
$Q go tool cover -html .GOPATH/cover/all.merged
62+
else
63+
$Q go tool cover -html .GOPATH/cover/all.merged -o .GOPATH/cover/all.html
64+
endif
65+
@echo ""
66+
@echo "=====> Total test coverage: <====="
67+
@echo ""
68+
$Q go tool cover -func .GOPATH/cover/all.merged
69+
70+
format: bin/goimports .GOPATH/.ok
71+
$Q find .GOPATH/src/$(IMPORT_PATH)/ -iname \*.go | grep -v \
72+
-e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) | xargs ./bin/goimports -w
73+
74+
##### =====> Internals <===== #####
75+
76+
.PHONY: setup
77+
setup: clean .GOPATH/.ok
78+
@if ! grep "/.GOPATH" .gitignore > /dev/null 2>&1; then \
79+
echo "/.GOPATH" >> .gitignore; \
80+
echo "/bin" >> .gitignore; \
81+
fi
82+
go get -u github.com/FiloSottile/gvt
83+
- ./bin/gvt fetch golang.org/x/tools/cmd/goimports
84+
- ./bin/gvt fetch github.com/wadey/gocovmerge
85+
86+
VERSION := $(shell git describe --tags --always --dirty="-dev")
87+
DATE := $(shell date -u '+%Y-%m-%d-%H%M UTC')
88+
VERSION_FLAGS := -ldflags='-X "main.Version=$(VERSION)" -X "main.BuildTime=$(DATE)"'
89+
90+
# cd into the GOPATH to workaround ./... not following symlinks
91+
_allpackages = $(shell ( cd $(CURDIR)/.GOPATH/src/$(IMPORT_PATH) && \
92+
GOPATH=$(CURDIR)/.GOPATH go list ./... 2>&1 1>&3 | \
93+
grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)) 1>&2 ) 3>&1 | \
94+
grep -v -e "^$$" $(addprefix -e ,$(IGNORED_PACKAGES)))
95+
96+
# memoize allpackages, so that it's executed only once and only if used
97+
allpackages = $(if $(__allpackages),,$(eval __allpackages := $$(_allpackages)))$(__allpackages)
98+
99+
export GOPATH := $(CURDIR)/.GOPATH
100+
unexport GOBIN
101+
102+
Q := $(if $V,,@)
103+
104+
.GOPATH/.ok:
105+
$Q mkdir -p "$(dir .GOPATH/src/$(IMPORT_PATH))"
106+
$Q ln -s ../../../.. ".GOPATH/src/$(IMPORT_PATH)"
107+
$Q mkdir -p .GOPATH/test .GOPATH/cover
108+
$Q mkdir -p bin
109+
$Q ln -s ../bin .GOPATH/bin
110+
$Q touch $@
111+
112+
.PHONY: bin/gocovmerge bin/goimports
113+
bin/gocovmerge: .GOPATH/.ok
114+
@test -d ./vendor/github.com/wadey/gocovmerge || \
115+
{ echo "Vendored gocovmerge not found, try running 'make setup'..."; exit 1; }
116+
$Q go install $(IMPORT_PATH)/vendor/github.com/wadey/gocovmerge
117+
bin/goimports: .GOPATH/.ok
118+
@test -d ./vendor/golang.org/x/tools/cmd/goimports || \
119+
{ echo "Vendored goimports not found, try running 'make setup'..."; exit 1; }
120+
$Q go install $(IMPORT_PATH)/vendor/golang.org/x/tools/cmd/goimports
121+
122+
# Based on https://github.com/cloudflare/hellogopher - v1.1 - MIT License
123+
#
124+
# Copyright (c) 2017 Cloudflare
125+
#
126+
# Permission is hereby granted, free of charge, to any person obtaining a copy
127+
# of this software and associated documentation files (the "Software"), to deal
128+
# in the Software without restriction, including without limitation the rights
129+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
130+
# copies of the Software, and to permit persons to whom the Software is
131+
# furnished to do so, subject to the following conditions:
132+
#
133+
# The above copyright notice and this permission notice shall be included in all
134+
# copies or substantial portions of the Software.
135+
#
136+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
137+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
138+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
139+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
140+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
141+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
142+
# SOFTWARE.

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
## Blind Token Daemon
2+
3+
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).
4+
5+
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.
6+
7+
## Quickstart
8+
9+
To run the server:
10+
11+
`go run server/main.go --key testdata/p256-key.pem`
12+
13+
To demo token issuance:
14+
15+
`cat testdata/bl_sig_req | nc localhost 2416`
16+
17+
For a full client implementation, see the [browser extension](https://github.com/cloudflare/challenge-bypass-extension).
18+
19+
### Definitions
20+
21+
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.
22+
23+
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.
24+
25+
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.
26+
27+
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.
28+
29+
### Protocol sketch
30+
31+
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.
32+
33+
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.
34+
35+
1. Client generates random token `x` and a blinding factor `r`
36+
2. Client calculates `a = H_1(x)^r` and sends `a` to the edge along with a CAPTCHA solution
37+
3. Edge validates the solution and computes `b = a^k = H_1(x)^(rk)`, returns `b` to client
38+
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.
39+
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.
40+
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.
41+
7. We know that a matching commitment value is valid because generating it requires access to `k`.
42+
43+
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).
44+
45+
### Benefits vs blind-RSA protocol
46+
47+
- 10x savings in token size (~256 bits instead of ~2048)
48+
- Simpler & faster primitives (also: available in-browser via SJCL)
49+
- 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.
50+
- The only secret to be managed is a 32-byte scalar.
51+
- 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.

crypto/batch.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// This implements a non-interactive version of the common-exponent batch
2+
// Schnorr proof from Ryan Henry's thesis. This specifically applies to the
3+
// case where a set of group elements shares a common discrete log with regard
4+
// to a set of generators. Slightly more formally, the case:
5+
//
6+
// (G, q, g_1,...,g_n) and (h_1,...,h_n) ∈ (G)^n with h_i = (g_i)^x for i = 1,...,n
7+
//
8+
// Inspired by an observation of Ian Goldberg's that this "common-exponent" case
9+
// is drastically simpler than a general batch proof. The general idea is that
10+
// we can produce a linear combination of the elements and perform a
11+
// Chaum-Pedersen proof on the resulting composite elements.
12+
//
13+
// See Section 3.2.3.3 for the interactive protocol:
14+
// https://uwspace.uwaterloo.ca/bitstream/handle/10012/8621/Henry_Ryan.pdf
15+
package crypto
16+
17+
import (
18+
"crypto"
19+
"errors"
20+
"math/big"
21+
22+
"golang.org/x/crypto/sha3"
23+
)
24+
25+
var (
26+
ErrUnequalPointCounts = errors.New("batch proof had unequal numbers of points")
27+
)
28+
29+
type BatchProof struct {
30+
P *Proof
31+
G, H *Point
32+
M, Z []*Point
33+
C [][]byte
34+
}
35+
36+
func NewBatchProof(hash crypto.Hash, g, h *Point, m []*Point, z []*Point, x *big.Int) (*BatchProof, error) {
37+
if len(m) != len(z) {
38+
return nil, ErrUnequalPointCounts
39+
}
40+
41+
// The underlying proof and validation steps will do consistency checks.
42+
curve := g.Curve
43+
44+
// seed = H(g, h, [m], [z])
45+
H := hash.New()
46+
H.Write(g.Marshal())
47+
H.Write(h.Marshal())
48+
for i := 0; i < len(m); i++ {
49+
H.Write(m[i].Marshal())
50+
H.Write(z[i].Marshal())
51+
}
52+
seed := H.Sum(nil)
53+
54+
prng := sha3.NewShake256()
55+
prng.Write(seed)
56+
57+
// Non-interactively generate random c_1, c_2, ... , c_n in Z/qZ
58+
// to combine m and z elements. Here's how this works:
59+
// For (m_1, m_2), (z_1, z_2), and (c_1, c_2) we have
60+
// z_1 = (m_1)^x
61+
// z_2 = (m_2)^x
62+
// (z_1^c_1) = (m_1^c_1)^x
63+
// (z_2^c_2) = (m_2^c_2)^x
64+
// (z_1^c_1)(z_2^c_2) = [(m_1^c_1)(m_2^c_2)]^x
65+
// This generalizes to produce composite elements for the entire batch that
66+
// can be compared to the public key in the standard two-point DLEQ proof.
67+
68+
Mx, My, Zx, Zy := new(big.Int), new(big.Int), new(big.Int), new(big.Int)
69+
C := make([][]byte, len(m))
70+
for i := 0; i < len(m); i++ {
71+
ci, _, err := randScalar(curve, prng)
72+
if err != nil {
73+
return nil, err
74+
}
75+
// cM = c[i]M[i]
76+
cMx, cMy := curve.ScalarMult(m[i].X, m[i].Y, ci)
77+
// cZ = c[i]Z[i]
78+
cZx, cZy := curve.ScalarMult(z[i].X, z[i].Y, ci)
79+
// Accumulate
80+
Mx, My = curve.Add(cMx, cMy, Mx, My)
81+
Zx, Zy = curve.Add(cZx, cZy, Zx, Zy)
82+
C[i] = ci
83+
}
84+
compositeM := &Point{Curve: curve, X: Mx, Y: My}
85+
compositeZ := &Point{Curve: curve, X: Zx, Y: Zy}
86+
87+
proof, err := NewProof(hash, g, h, compositeM, compositeZ, x)
88+
if err != nil {
89+
return nil, err
90+
}
91+
return &BatchProof{
92+
P: proof,
93+
G: g, H: h,
94+
M: m, Z: z,
95+
C: C,
96+
}, nil
97+
}
98+
99+
func (b *BatchProof) IsComplete() bool {
100+
hasPublicKey := b.G != nil && b.H != nil
101+
hasPointSets := b.M != nil && b.Z != nil && len(b.M) == len(b.Z)
102+
return hasPublicKey && hasPointSets && b.C != nil
103+
}
104+
105+
func (b *BatchProof) IsSane() bool {
106+
if len(b.M) != len(b.Z) {
107+
return false
108+
}
109+
if b.G.Curve != b.H.Curve {
110+
return false
111+
}
112+
for i := 0; i < len(b.M); i++ {
113+
if b.G.Curve != b.M[i].Curve || b.G.Curve != b.Z[i].Curve {
114+
return false
115+
}
116+
if !b.M[i].IsOnCurve() || !b.Z[i].IsOnCurve() {
117+
return false
118+
}
119+
}
120+
return true
121+
}
122+
123+
func (b *BatchProof) Verify() bool {
124+
if !b.IsComplete() || !b.IsSane() {
125+
return false
126+
}
127+
return b.P.Verify()
128+
}

0 commit comments

Comments
 (0)