CSR Service (part 2): cert signing rpc (#950)
* CSR Service: + implement certificate sign rpc method + implement certificate signer client/server + refactor `AuthorizationDB#Create` + refactor `NewTestIdentity` + add `AuthorizationDB#Claim` + add `Token#Equal` + fix `Authorizations#Marshal` when marshaling identities and certificates + tweak `Authorization#String` format + cert debugging improvements (jsondiff) + receive context arg in `NewTestIdentity` + misc. fixes
This commit is contained in:
parent
b712fbcbb0
commit
249244536a
2
Makefile
2
Makefile
@ -57,7 +57,7 @@ goimports-fix: ## Applies goimports to every go file (excluding vendored files)
|
|||||||
|
|
||||||
.PHONY: goimports-st
|
.PHONY: goimports-st
|
||||||
goimports-st: ## Applies goimports to every go file in `git status` (ignores untracked files)
|
goimports-st: ## Applies goimports to every go file in `git status` (ignores untracked files)
|
||||||
git status --porcelain -uno|grep .go|sed -E 's,\w+\s+,,g'|xargs -I {} goimports -w -local storj.io {}
|
git status --porcelain -uno|grep .go|grep -v "^D"|sed -E 's,\w+\s+(.+->\s+)?,,g'|xargs -I {} goimports -w -local storj.io {}
|
||||||
|
|
||||||
.PHONY: proto
|
.PHONY: proto
|
||||||
proto: ## Rebuild protobuf files
|
proto: ## Rebuild protobuf files
|
||||||
|
3
go.mod
3
go.mod
@ -42,7 +42,7 @@ require (
|
|||||||
github.com/go-redis/redis v6.14.1+incompatible
|
github.com/go-redis/redis v6.14.1+incompatible
|
||||||
github.com/gogo/protobuf v1.1.2-0.20181116123445-07eab6a8298c
|
github.com/gogo/protobuf v1.1.2-0.20181116123445-07eab6a8298c
|
||||||
github.com/golang-migrate/migrate/v3 v3.5.2
|
github.com/golang-migrate/migrate/v3 v3.5.2
|
||||||
github.com/golang/mock v1.1.1
|
github.com/golang/mock v1.2.0
|
||||||
github.com/golang/protobuf v1.2.0
|
github.com/golang/protobuf v1.2.0
|
||||||
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
github.com/gomodule/redigo v2.0.0+incompatible // indirect
|
||||||
github.com/google/go-cmp v0.2.0
|
github.com/google/go-cmp v0.2.0
|
||||||
@ -81,6 +81,7 @@ require (
|
|||||||
github.com/nats-io/nats v1.6.0 // indirect
|
github.com/nats-io/nats v1.6.0 // indirect
|
||||||
github.com/nats-io/nats-streaming-server v0.11.0 // indirect
|
github.com/nats-io/nats-streaming-server v0.11.0 // indirect
|
||||||
github.com/nats-io/nuid v1.0.0 // indirect
|
github.com/nats-io/nuid v1.0.0 // indirect
|
||||||
|
github.com/nsf/jsondiff v0.0.0-20160203110537-7de28ed2b6e3
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
|
github.com/pierrec/lz4 v2.0.5+incompatible // indirect
|
||||||
github.com/pkg/profile v1.2.1 // indirect
|
github.com/pkg/profile v1.2.1 // indirect
|
||||||
|
10
go.sum
10
go.sum
@ -105,6 +105,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
|||||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
|
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
|
||||||
|
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
@ -179,8 +181,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|||||||
github.com/kshvakov/clickhouse v1.3.4/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
|
github.com/kshvakov/clickhouse v1.3.4/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE=
|
||||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/loov/hrtime v0.0.0-20180911122900-a9e82bc6c180 h1:kLwg5eA/kaWQ/RwANTH7Gg+VdxmdjbcSWyaS/1VQGkA=
|
|
||||||
github.com/loov/hrtime v0.0.0-20180911122900-a9e82bc6c180/go.mod h1:2871C3urfEJnq/bpTYjFdMOdgxVd8otLLEL6vMNy/Iw=
|
|
||||||
github.com/loov/hrtime v0.0.0-20181214195526-37a208e8344e h1:UC+nLCm+w3WL+ibAW/wsWbQC3KAz7LLawR2hgX0eR9s=
|
github.com/loov/hrtime v0.0.0-20181214195526-37a208e8344e h1:UC+nLCm+w3WL+ibAW/wsWbQC3KAz7LLawR2hgX0eR9s=
|
||||||
github.com/loov/hrtime v0.0.0-20181214195526-37a208e8344e/go.mod h1:2871C3urfEJnq/bpTYjFdMOdgxVd8otLLEL6vMNy/Iw=
|
github.com/loov/hrtime v0.0.0-20181214195526-37a208e8344e/go.mod h1:2871C3urfEJnq/bpTYjFdMOdgxVd8otLLEL6vMNy/Iw=
|
||||||
github.com/loov/plot v0.0.0-20180510142208-e59891ae1271 h1:51ToN6N0TDtCruf681gufYuEhO9qFHQzM3RFTS/n6XE=
|
github.com/loov/plot v0.0.0-20180510142208-e59891ae1271 h1:51ToN6N0TDtCruf681gufYuEhO9qFHQzM3RFTS/n6XE=
|
||||||
@ -189,8 +189,6 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
|
|||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA=
|
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA=
|
||||||
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
|
||||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
|
||||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
@ -241,6 +239,8 @@ github.com/nats-io/nats-streaming-server v0.11.0 h1:6d32ASBeZJQOoams2GJuviQyf5GV
|
|||||||
github.com/nats-io/nats-streaming-server v0.11.0/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
|
github.com/nats-io/nats-streaming-server v0.11.0/go.mod h1:RyqtDJZvMZO66YmyjIYdIvS69zu/wDAkyNWa8PIUa5c=
|
||||||
github.com/nats-io/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs=
|
github.com/nats-io/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs=
|
||||||
github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.0/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/nsf/jsondiff v0.0.0-20160203110537-7de28ed2b6e3 h1:OqFSgO6CJ8heZRAbXLpT+ojX+jnnGij4qZwUz/SJJ9I=
|
||||||
|
github.com/nsf/jsondiff v0.0.0-20160203110537-7de28ed2b6e3/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs=
|
||||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
|
||||||
@ -322,8 +322,6 @@ github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb h1:Jmfk7z2f/+gxVFA
|
|||||||
github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
|
||||||
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c h1:WoYvMZp+keiJz+ZogLAhwsUZvWe81W+mCnpfdgEUOl4=
|
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c h1:WoYvMZp+keiJz+ZogLAhwsUZvWe81W+mCnpfdgEUOl4=
|
||||||
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c/go.mod h1:Aq7yiXoKLFIDzh4eR6EG4owIO9alpttZ0XJ5c/z/QrE=
|
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c/go.mod h1:Aq7yiXoKLFIDzh4eR6EG4owIO9alpttZ0XJ5c/z/QrE=
|
||||||
github.com/zeebo/errs v1.0.0 h1:uPx2/S2dQn9npw2Y9AKTae12AHkHSqk1WiaRiBNguFA=
|
|
||||||
github.com/zeebo/errs v1.0.0/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
|
|
||||||
github.com/zeebo/errs v1.1.0 h1:4dNyQKsWPyBDqLzZUpx+QMP0Qil9STQPdBsKk6+O2qA=
|
github.com/zeebo/errs v1.1.0 h1:4dNyQKsWPyBDqLzZUpx+QMP0Qil9STQPdBsKk6+O2qA=
|
||||||
github.com/zeebo/errs v1.1.0/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
|
github.com/zeebo/errs v1.1.0/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
|
||||||
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
|
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
|
||||||
|
96
internal/debugging/certificates.go
Normal file
96
internal/debugging/certificates.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright (C) 2018 Storj Labs, Inc.
|
||||||
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
|
package debugging
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/nsf/jsondiff"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
diffOpts = jsondiff.DefaultConsoleOptions()
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugCert is a subset of the most relevant fields from an x509.Certificate for debugging
|
||||||
|
type DebugCert struct {
|
||||||
|
Cert *x509.Certificate
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDebugCert converts an *x509.Certificate into a DebugCert
|
||||||
|
func NewDebugCert(cert x509.Certificate) DebugCert {
|
||||||
|
return DebugCert{
|
||||||
|
Cert: &cert,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintJSON uses a json marshaler to pretty-print arbitrary data for debugging
|
||||||
|
// with special considerations for certain, specific types
|
||||||
|
func PrintJSON(data interface{}, label string) {
|
||||||
|
var (
|
||||||
|
jsonBytes []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
switch d := data.(type) {
|
||||||
|
case x509.Certificate:
|
||||||
|
data = NewDebugCert(d)
|
||||||
|
case *x509.Certificate:
|
||||||
|
data = NewDebugCert(*d)
|
||||||
|
case ecdsa.PublicKey:
|
||||||
|
data = struct {
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
}{
|
||||||
|
d.X, d.Y,
|
||||||
|
}
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
data = struct {
|
||||||
|
X *big.Int
|
||||||
|
Y *big.Int
|
||||||
|
D *big.Int
|
||||||
|
}{
|
||||||
|
d.X, d.Y, d.D,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err = json.MarshalIndent(data, "", "\t\t")
|
||||||
|
|
||||||
|
if label != "" {
|
||||||
|
fmt.Println(label + ": ---================================================================---")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("ERROR: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(string(jsonBytes))
|
||||||
|
fmt.Println("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cmp is used to compare 2 DebugCerts against each other and print the diff
|
||||||
|
func (c DebugCert) Cmp(c2 DebugCert, label string) error {
|
||||||
|
fmt.Println("diff " + label + " ---================================================================---")
|
||||||
|
cJSON, err := c.JSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
c2JSON, err := c2.JSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
diffType, diff := jsondiff.Compare(cJSON, c2JSON, &diffOpts)
|
||||||
|
fmt.Printf("Difference type: %s\n======\n%s", diffType, diff)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON serializes the certificate to JSON
|
||||||
|
func (c DebugCert) JSON() ([]byte, error) {
|
||||||
|
return json.Marshal(c.Cert)
|
||||||
|
}
|
@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
// NewTestIdentity is a helper function to generate new node identities with
|
// NewTestIdentity is a helper function to generate new node identities with
|
||||||
// correct difficulty and concurrency
|
// correct difficulty and concurrency
|
||||||
func NewTestIdentity() (*provider.FullIdentity, error) {
|
func NewTestIdentity(ctx context.Context) (*provider.FullIdentity, error) {
|
||||||
ca, err := provider.NewCA(context.Background(), provider.NewCAOptions{
|
ca, err := provider.NewCA(ctx, provider.NewCAOptions{
|
||||||
Difficulty: 12,
|
Difficulty: 12,
|
||||||
Concurrency: 4,
|
Concurrency: 4,
|
||||||
})
|
})
|
@ -4,102 +4,43 @@
|
|||||||
package testpeertls
|
package testpeertls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
|
||||||
"encoding/json"
|
"storj.io/storj/pkg/peertls"
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// DebugCert is a subset of the most relevant fields from an x509.Certificate for debugging
|
// NewCertChain creates a valid peertls certificate chain (and respective keys) of the desired length.
|
||||||
type DebugCert struct {
|
// NB: keys are in the reverse order compared to certs (i.e. first key belongs to last cert)!
|
||||||
Raw []byte
|
func NewCertChain(length int) (keys []crypto.PrivateKey, certs []*x509.Certificate, _ error) {
|
||||||
RawTBSCertificate []byte
|
for i := 0; i < length; i++ {
|
||||||
Signature []byte
|
key, err := peertls.NewKey()
|
||||||
PublicKeyX *big.Int
|
if err != nil {
|
||||||
PublicKeyY *big.Int
|
return nil, nil, err
|
||||||
Extensions []pkix.Extension
|
|
||||||
}
|
}
|
||||||
|
keys = append(keys, key)
|
||||||
|
|
||||||
// NewCertDebug converts an *x509.Certificate into a DebugCert
|
var template *x509.Certificate
|
||||||
func NewCertDebug(cert x509.Certificate) DebugCert {
|
if i == length-1 {
|
||||||
pubKey := cert.PublicKey.(*ecdsa.PublicKey)
|
template, err = peertls.CATemplate()
|
||||||
c := DebugCert{
|
} else {
|
||||||
Raw: make([]byte, len(cert.Raw)),
|
template, err = peertls.LeafTemplate()
|
||||||
RawTBSCertificate: make([]byte, len(cert.RawTBSCertificate)),
|
|
||||||
Signature: make([]byte, len(cert.Signature)),
|
|
||||||
PublicKeyX: pubKey.X,
|
|
||||||
PublicKeyY: pubKey.Y,
|
|
||||||
Extensions: []pkix.Extension{},
|
|
||||||
}
|
|
||||||
|
|
||||||
copy(c.Raw, cert.Raw)
|
|
||||||
copy(c.RawTBSCertificate, cert.RawTBSCertificate)
|
|
||||||
copy(c.Signature, cert.Signature)
|
|
||||||
for _, e := range cert.ExtraExtensions {
|
|
||||||
ext := pkix.Extension{Id: e.Id, Value: make([]byte, len(e.Value))}
|
|
||||||
copy(ext.Value, e.Value)
|
|
||||||
c.Extensions = append(c.Extensions, ext)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cmp is used to compare 2 DebugCerts against each other and print the diff
|
|
||||||
func (c DebugCert) Cmp(c2 DebugCert, label string) {
|
|
||||||
fmt.Println("diff " + label + " ---================================================================---")
|
|
||||||
cmpBytes := func(a, b []byte) {
|
|
||||||
PrintJSON(bytes.Compare(a, b), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmpBytes(c.Raw, c2.Raw)
|
|
||||||
cmpBytes(c.RawTBSCertificate, c2.RawTBSCertificate)
|
|
||||||
cmpBytes(c.Signature, c2.Signature)
|
|
||||||
c.PublicKeyX.Cmp(c2.PublicKeyX)
|
|
||||||
c.PublicKeyY.Cmp(c2.PublicKeyY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrintJSON uses a json marshaler to pretty-print arbitrary data for debugging
|
|
||||||
// with special considerations for certain, specific types
|
|
||||||
func PrintJSON(data interface{}, label string) {
|
|
||||||
var (
|
|
||||||
jsonBytes []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
switch d := data.(type) {
|
|
||||||
case x509.Certificate:
|
|
||||||
data = NewCertDebug(d)
|
|
||||||
case *x509.Certificate:
|
|
||||||
data = NewCertDebug(*d)
|
|
||||||
case ecdsa.PublicKey:
|
|
||||||
data = struct {
|
|
||||||
X *big.Int
|
|
||||||
Y *big.Int
|
|
||||||
}{
|
|
||||||
d.X, d.Y,
|
|
||||||
}
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
data = struct {
|
|
||||||
X *big.Int
|
|
||||||
Y *big.Int
|
|
||||||
D *big.Int
|
|
||||||
}{
|
|
||||||
d.X, d.Y, d.D,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBytes, err = json.MarshalIndent(data, "", "\t\t")
|
|
||||||
|
|
||||||
if label != "" {
|
|
||||||
fmt.Println(label + ": ---================================================================---")
|
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR: %s", err.Error())
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(jsonBytes))
|
var cert *x509.Certificate
|
||||||
fmt.Println("")
|
if i == 0 {
|
||||||
|
cert, err = peertls.NewCert(key, nil, template, nil)
|
||||||
|
} else {
|
||||||
|
cert, err = peertls.NewCert(key, keys[i-1], template, certs[i-1:][0])
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
certs = append([]*x509.Certificate{cert}, certs...)
|
||||||
|
}
|
||||||
|
return keys, certs, nil
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/pkg/bwagreement"
|
"storj.io/storj/pkg/bwagreement"
|
||||||
"storj.io/storj/pkg/bwagreement/test"
|
"storj.io/storj/pkg/bwagreement/test"
|
||||||
"storj.io/storj/pkg/overlay"
|
"storj.io/storj/pkg/overlay"
|
||||||
@ -58,7 +58,7 @@ func TestQueryWithBw(t *testing.T) {
|
|||||||
tally := newTally(zap.NewNop(), db.Accounting(), bwDb, pointerdb, overlayServer, 0, time.Second)
|
tally := newTally(zap.NewNop(), db.Accounting(), bwDb, pointerdb, overlayServer, 0, time.Second)
|
||||||
|
|
||||||
//get a private key
|
//get a private key
|
||||||
fiC, err := testidentity.NewTestIdentity()
|
fiC, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
k, ok := fiC.Key.(*ecdsa.PrivateKey)
|
k, ok := fiC.Key.(*ecdsa.PrivateKey)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
@ -16,7 +16,7 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/internal/teststorj"
|
"storj.io/storj/internal/teststorj"
|
||||||
"storj.io/storj/pkg/auth"
|
"storj.io/storj/pkg/auth"
|
||||||
"storj.io/storj/pkg/overlay"
|
"storj.io/storj/pkg/overlay"
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/gtank/cryptopasta"
|
"github.com/gtank/cryptopasta"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"storj.io/storj/internal/identity"
|
"storj.io/storj/internal/testidentity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenerateSignature(t *testing.T) {
|
func TestGenerateSignature(t *testing.T) {
|
||||||
|
@ -11,8 +11,8 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/pkg/bwagreement"
|
"storj.io/storj/pkg/bwagreement"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
"storj.io/storj/satellite"
|
"storj.io/storj/satellite"
|
||||||
@ -41,7 +41,7 @@ func TestBandwidthAgreements(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func generateKeys(ctx context.Context, t *testing.T) (satellitePubKey *ecdsa.PublicKey, satellitePrivKey *ecdsa.PrivateKey, uplinkPrivKey *ecdsa.PrivateKey) {
|
func generateKeys(ctx context.Context, t *testing.T) (satellitePubKey *ecdsa.PublicKey, satellitePrivKey *ecdsa.PrivateKey, uplinkPrivKey *ecdsa.PrivateKey) {
|
||||||
fiS, err := testidentity.NewTestIdentity()
|
fiS, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
satellitePubKey, ok := fiS.Leaf.PublicKey.(*ecdsa.PublicKey)
|
satellitePubKey, ok := fiS.Leaf.PublicKey.(*ecdsa.PublicKey)
|
||||||
@ -50,7 +50,7 @@ func generateKeys(ctx context.Context, t *testing.T) (satellitePubKey *ecdsa.Pub
|
|||||||
satellitePrivKey, ok = fiS.Key.(*ecdsa.PrivateKey)
|
satellitePrivKey, ok = fiS.Key.(*ecdsa.PrivateKey)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
||||||
fiU, err := testidentity.NewTestIdentity()
|
fiU, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
uplinkPrivKey, ok = fiU.Key.(*ecdsa.PrivateKey)
|
uplinkPrivKey, ok = fiU.Key.(*ecdsa.PrivateKey)
|
||||||
|
@ -6,21 +6,27 @@ package certificates
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil/base58"
|
"github.com/btcsuite/btcutil/base58"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"gopkg.in/spacemonkeygo/monkit.v2"
|
"google.golang.org/grpc/peer"
|
||||||
|
monkit "gopkg.in/spacemonkeygo/monkit.v2"
|
||||||
|
|
||||||
|
"storj.io/storj/pkg/identity"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
"storj.io/storj/pkg/peertls"
|
"storj.io/storj/pkg/peertls"
|
||||||
"storj.io/storj/pkg/provider"
|
"storj.io/storj/pkg/provider"
|
||||||
|
"storj.io/storj/pkg/transport"
|
||||||
"storj.io/storj/pkg/utils"
|
"storj.io/storj/pkg/utils"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
"storj.io/storj/storage/boltdb"
|
"storj.io/storj/storage/boltdb"
|
||||||
@ -30,6 +36,9 @@ import (
|
|||||||
const (
|
const (
|
||||||
// AuthorizationsBucket is the bucket used with a bolt-backed authorizations DB.
|
// AuthorizationsBucket is the bucket used with a bolt-backed authorizations DB.
|
||||||
AuthorizationsBucket = "authorizations"
|
AuthorizationsBucket = "authorizations"
|
||||||
|
// MaxClaimDelaySeconds is the max duration in seconds in the past or
|
||||||
|
// future that a claim timestamp is allowed to have and still be valid.
|
||||||
|
MaxClaimDelaySeconds = 15
|
||||||
tokenDataLength = 64 // 2^(64*8) =~ 1.34E+154
|
tokenDataLength = 64 // 2^(64*8) =~ 1.34E+154
|
||||||
tokenDelimiter = ":"
|
tokenDelimiter = ":"
|
||||||
tokenVersion = 0
|
tokenVersion = 0
|
||||||
@ -47,15 +56,26 @@ var (
|
|||||||
ErrAuthorizationCount = ErrAuthorizationDB.New("cannot add less than one authorizations")
|
ErrAuthorizationCount = ErrAuthorizationDB.New("cannot add less than one authorizations")
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertSignerConfig is a config struct for use with a certificate signing service
|
// CertSigningConfig is a config struct for use with a certificate signing service client
|
||||||
|
type CertSigningConfig struct {
|
||||||
|
AuthToken string `help:"authorization token to use to claim a certificate signing request (only applicable for the alpha network)"`
|
||||||
|
Address string `help:"address of the certificate signing rpc service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CertSignerConfig is a config struct for use with a certificate signing service server
|
||||||
type CertSignerConfig struct {
|
type CertSignerConfig struct {
|
||||||
Overwrite bool `help:"if true, overwrites config AND authorization db is truncated" default:"false"`
|
Overwrite bool `help:"if true, overwrites config AND authorization db is truncated" default:"false"`
|
||||||
AuthorizationDBURL string `help:"url to the certificate signing authorization database" default:"bolt://$CONFDIR/authorizations.db"`
|
AuthorizationDBURL string `help:"url to the certificate signing authorization database" default:"bolt://$CONFDIR/authorizations.db"`
|
||||||
|
MinDifficulty uint `help:"minimum difficulty of the requester's identity required to claim an authorization"`
|
||||||
|
CA provider.FullCAConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// CertificateSigner implements pb.CertificatesServer
|
// CertificateSigner implements pb.CertificatesServer
|
||||||
type CertificateSigner struct {
|
type CertificateSigner struct {
|
||||||
Log *zap.Logger
|
Logger *zap.Logger
|
||||||
|
Signer *provider.FullCertificateAuthority
|
||||||
|
AuthDB *AuthorizationDB
|
||||||
|
MinDifficulty uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthorizationDB stores authorizations which may be claimed in exchange for a
|
// AuthorizationDB stores authorizations which may be claimed in exchange for a
|
||||||
@ -82,22 +102,52 @@ type Token struct {
|
|||||||
Data [tokenDataLength]byte
|
Data [tokenDataLength]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClaimOpts hold parameters for claiming an authorization
|
||||||
|
type ClaimOpts struct {
|
||||||
|
Req *pb.SigningRequest
|
||||||
|
Peer *peer.Peer
|
||||||
|
ChainBytes [][]byte
|
||||||
|
MinDifficulty uint16
|
||||||
|
}
|
||||||
|
|
||||||
// Claim holds information about the circumstances under which an authorization
|
// Claim holds information about the circumstances under which an authorization
|
||||||
// token was claimed.
|
// token was claimed.
|
||||||
type Claim struct {
|
type Claim struct {
|
||||||
IP string
|
Addr string
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
Identity *provider.PeerIdentity
|
Identity *provider.PeerIdentity
|
||||||
SignedCert *x509.Certificate
|
SignedChainBytes [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewServer creates a new certificate signing grpc server
|
// Client implements pb.CertificateClient
|
||||||
func NewServer(log *zap.Logger) pb.CertificatesServer {
|
type Client struct {
|
||||||
srv := CertificateSigner{
|
client pb.CertificatesClient
|
||||||
Log: log,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &srv
|
func init() {
|
||||||
|
gob.Register(&ecdsa.PublicKey{})
|
||||||
|
gob.Register(elliptic.P256())
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new certificate signing grpc client
|
||||||
|
func NewClient(ctx context.Context, ident *provider.FullIdentity, address string) (*Client, error) {
|
||||||
|
tc := transport.NewClient(ident)
|
||||||
|
conn, err := tc.DialAddress(ctx, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
client: pb.NewCertificatesClient(conn),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientFrom creates a new certificate signing grpc client from an existing
|
||||||
|
// grpc cert signing client
|
||||||
|
func NewClientFrom(client pb.CertificatesClient) (*Client, error) {
|
||||||
|
return &Client{
|
||||||
|
client: client,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthorization creates a new, unclaimed authorization with a random token value
|
// NewAuthorization creates a new, unclaimed authorization with a random token value
|
||||||
@ -141,6 +191,20 @@ func ParseToken(tokenString string) (*Token, error) {
|
|||||||
return t, nil
|
return t, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sign claims an authorization using the token string and returns a signed
|
||||||
|
// copy of the client's CA certificate
|
||||||
|
func (c Client) Sign(ctx context.Context, tokenStr string) ([][]byte, error) {
|
||||||
|
res, err := c.client.Sign(ctx, &pb.SigningRequest{
|
||||||
|
AuthToken: tokenStr,
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.Chain, nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewAuthDB creates or opens the authorization database specified by the config
|
// NewAuthDB creates or opens the authorization database specified by the config
|
||||||
func (c CertSignerConfig) NewAuthDB() (*AuthorizationDB, error) {
|
func (c CertSignerConfig) NewAuthDB() (*AuthorizationDB, error) {
|
||||||
// TODO: refactor db selection logic?
|
// TODO: refactor db selection logic?
|
||||||
@ -186,7 +250,25 @@ func (c CertSignerConfig) NewAuthDB() (*AuthorizationDB, error) {
|
|||||||
func (c CertSignerConfig) Run(ctx context.Context, server *provider.Provider) (err error) {
|
func (c CertSignerConfig) Run(ctx context.Context, server *provider.Provider) (err error) {
|
||||||
defer mon.Task()(&ctx)(&err)
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
|
||||||
srv := NewServer(zap.L())
|
authDB, err := c.NewAuthDB()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err = errs.Combine(err, authDB.Close())
|
||||||
|
}()
|
||||||
|
|
||||||
|
signer, err := c.CA.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
srv := &CertificateSigner{
|
||||||
|
Logger: zap.L(),
|
||||||
|
Signer: signer,
|
||||||
|
AuthDB: authDB,
|
||||||
|
MinDifficulty: uint16(c.MinDifficulty),
|
||||||
|
}
|
||||||
pb.RegisterCertificatesServer(server.GRPC(), srv)
|
pb.RegisterCertificatesServer(server.GRPC(), srv)
|
||||||
|
|
||||||
return server.Run(ctx)
|
return server.Run(ctx)
|
||||||
@ -194,10 +276,42 @@ func (c CertSignerConfig) Run(ctx context.Context, server *provider.Provider) (e
|
|||||||
|
|
||||||
// Sign signs a valid certificate signing request's cert.
|
// Sign signs a valid certificate signing request's cert.
|
||||||
func (c CertificateSigner) Sign(ctx context.Context, req *pb.SigningRequest) (*pb.SigningResponse, error) {
|
func (c CertificateSigner) Sign(ctx context.Context, req *pb.SigningRequest) (*pb.SigningResponse, error) {
|
||||||
// lookup authtoken
|
grpcPeer, ok := peer.FromContext(ctx)
|
||||||
// sign cert
|
if !ok {
|
||||||
// send response
|
// TODO: better error
|
||||||
return &pb.SigningResponse{}, nil
|
return nil, errs.New("unable to get peer from context")
|
||||||
|
}
|
||||||
|
|
||||||
|
peerIdent, err := identity.PeerIdentityFromPeer(grpcPeer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedPeerCA, err := c.Signer.Sign(peerIdent.CA)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signedChainBytes := append(
|
||||||
|
[][]byte{
|
||||||
|
signedPeerCA.Raw,
|
||||||
|
c.Signer.Cert.Raw,
|
||||||
|
},
|
||||||
|
c.Signer.RestChainRaw()...,
|
||||||
|
)
|
||||||
|
err = c.AuthDB.Claim(&ClaimOpts{
|
||||||
|
Req: req,
|
||||||
|
Peer: grpcPeer,
|
||||||
|
ChainBytes: signedChainBytes,
|
||||||
|
MinDifficulty: c.MinDifficulty,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.SigningResponse{
|
||||||
|
Chain: signedChainBytes,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the authorization database's underlying store.
|
// Close closes the authorization database's underlying store.
|
||||||
@ -214,11 +328,6 @@ func (a *AuthorizationDB) Create(userID string, count int) (Authorizations, erro
|
|||||||
return nil, ErrAuthorizationCount
|
return nil, ErrAuthorizationCount
|
||||||
}
|
}
|
||||||
|
|
||||||
existingAuths, err := a.Get(userID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
newAuths Authorizations
|
newAuths Authorizations
|
||||||
authErrs utils.ErrorGroup
|
authErrs utils.ErrorGroup
|
||||||
@ -235,22 +344,16 @@ func (a *AuthorizationDB) Create(userID string, count int) (Authorizations, erro
|
|||||||
return nil, ErrAuthorizationDB.Wrap(err)
|
return nil, ErrAuthorizationDB.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
existingAuths = append(existingAuths, newAuths...)
|
if err := a.add(userID, newAuths); err != nil {
|
||||||
authsBytes, err := existingAuths.Marshal()
|
return nil, err
|
||||||
if err != nil {
|
|
||||||
return nil, ErrAuthorizationDB.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := a.DB.Put(storage.Key(userID), authsBytes); err != nil {
|
|
||||||
return nil, ErrAuthorizationDB.Wrap(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return newAuths, nil
|
return newAuths, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get retrieves authorizations by email.
|
// Get retrieves authorizations by user ID.
|
||||||
func (a *AuthorizationDB) Get(email string) (Authorizations, error) {
|
func (a *AuthorizationDB) Get(userID string) (Authorizations, error) {
|
||||||
authsBytes, err := a.DB.Get(storage.Key(email))
|
authsBytes, err := a.DB.Get(storage.Key(userID))
|
||||||
if err != nil && !storage.ErrKeyNotFound.Has(err) {
|
if err != nil && !storage.ErrKeyNotFound.Has(err) {
|
||||||
return nil, ErrAuthorizationDB.Wrap(err)
|
return nil, ErrAuthorizationDB.Wrap(err)
|
||||||
}
|
}
|
||||||
@ -274,6 +377,84 @@ func (a *AuthorizationDB) UserIDs() ([]string, error) {
|
|||||||
return keys.Strings(), nil
|
return keys.Strings(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Claim marks an authorization as claimed and records claim information
|
||||||
|
func (a *AuthorizationDB) Claim(opts *ClaimOpts) error {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
if !(now-MaxClaimDelaySeconds < opts.Req.Timestamp) ||
|
||||||
|
!(opts.Req.Timestamp < now+MaxClaimDelaySeconds) {
|
||||||
|
return ErrAuthorization.New("claim timestamp is outside of max delay window: %d", opts.Req.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident, err := identity.PeerIdentityFromPeer(opts.Peer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
peerDifficulty, err := ident.ID.Difficulty()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if peerDifficulty < opts.MinDifficulty {
|
||||||
|
return ErrAuthorization.New("difficulty must be greater than: %d", opts.MinDifficulty)
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := ParseToken(opts.Req.AuthToken)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
auths, err := a.Get(token.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, auth := range auths {
|
||||||
|
if auth.Token.Equal(token) {
|
||||||
|
if auth.Claim != nil {
|
||||||
|
return ErrAuthorization.New("authorization has already been claimed: %s", auth.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
auths[i] = &Authorization{
|
||||||
|
Token: auth.Token,
|
||||||
|
Claim: &Claim{
|
||||||
|
Timestamp: now,
|
||||||
|
Addr: opts.Peer.Addr.String(),
|
||||||
|
Identity: ident,
|
||||||
|
SignedChainBytes: opts.ChainBytes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := a.put(token.UserID, auths); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthorizationDB) add(userID string, newAuths Authorizations) error {
|
||||||
|
auths, err := a.Get(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
auths = append(auths, newAuths...)
|
||||||
|
return a.put(userID, auths)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthorizationDB) put(userID string, auths Authorizations) error {
|
||||||
|
authsBytes, err := auths.Marshal()
|
||||||
|
if err != nil {
|
||||||
|
return ErrAuthorizationDB.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := a.DB.Put(storage.Key(userID), authsBytes); err != nil {
|
||||||
|
return ErrAuthorizationDB.Wrap(err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Unmarshal deserializes a set of authorizations
|
// Unmarshal deserializes a set of authorizations
|
||||||
func (a *Authorizations) Unmarshal(data []byte) error {
|
func (a *Authorizations) Unmarshal(data []byte) error {
|
||||||
decoder := gob.NewDecoder(bytes.NewBuffer(data))
|
decoder := gob.NewDecoder(bytes.NewBuffer(data))
|
||||||
@ -309,9 +490,15 @@ func (a Authorizations) Group() (claimed, open Authorizations) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String implements the stringer interface and prevents authorization data
|
// String implements the stringer interface and prevents authorization data
|
||||||
// from completely leaking into logs.
|
// from completely leaking into logs and errors.
|
||||||
func (a Authorization) String() string {
|
func (a Authorization) String() string {
|
||||||
return fmt.Sprintf("%.5s..", a.Token.String())
|
fmtLen := strconv.Itoa(len(a.Token.UserID) + 7)
|
||||||
|
return fmt.Sprintf("%."+fmtLen+"s..", a.Token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal checks if two tokens have equal user IDs and data
|
||||||
|
func (t *Token) Equal(cmpToken *Token) bool {
|
||||||
|
return t.UserID == cmpToken.UserID && bytes.Equal(t.Data[:], cmpToken.Data[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements the stringer interface. Base68 w/ version and checksum bytes
|
// String implements the stringer interface. Base68 w/ version and checksum bytes
|
||||||
|
@ -5,22 +5,36 @@ package certificates
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil/base58"
|
"github.com/btcsuite/btcutil/base58"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"google.golang.org/grpc/credentials"
|
||||||
|
"google.golang.org/grpc/peer"
|
||||||
|
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
|
"storj.io/storj/internal/testplanet"
|
||||||
|
"storj.io/storj/pkg/identity"
|
||||||
|
"storj.io/storj/pkg/pb"
|
||||||
|
"storj.io/storj/pkg/provider"
|
||||||
|
"storj.io/storj/pkg/transport"
|
||||||
"storj.io/storj/pkg/utils"
|
"storj.io/storj/pkg/utils"
|
||||||
"storj.io/storj/storage"
|
"storj.io/storj/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
idents = testplanet.NewPregeneratedIdentities()
|
||||||
t1 = Token{
|
t1 = Token{
|
||||||
UserID: "user@example.com",
|
UserID: "user@example.com",
|
||||||
Data: [tokenDataLength]byte{1, 2, 3},
|
Data: [tokenDataLength]byte{1, 2, 3},
|
||||||
@ -33,14 +47,12 @@ var (
|
|||||||
|
|
||||||
func TestCertSignerConfig_NewAuthDB(t *testing.T) {
|
func TestCertSignerConfig_NewAuthDB(t *testing.T) {
|
||||||
ctx := testcontext.New(t)
|
ctx := testcontext.New(t)
|
||||||
|
defer ctx.Cleanup()
|
||||||
authDB, err := newTestAuthDB(ctx)
|
authDB, err := newTestAuthDB(ctx)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer ctx.Check(authDB.Close)
|
||||||
_ = authDB.Close()
|
|
||||||
ctx.Cleanup()
|
|
||||||
}()
|
|
||||||
|
|
||||||
assert.NotNil(t, authDB)
|
assert.NotNil(t, authDB)
|
||||||
assert.NotNil(t, authDB.DB)
|
assert.NotNil(t, authDB.DB)
|
||||||
@ -48,14 +60,12 @@ func TestCertSignerConfig_NewAuthDB(t *testing.T) {
|
|||||||
|
|
||||||
func TestAuthorizationDB_Create(t *testing.T) {
|
func TestAuthorizationDB_Create(t *testing.T) {
|
||||||
ctx := testcontext.New(t)
|
ctx := testcontext.New(t)
|
||||||
|
defer ctx.Cleanup()
|
||||||
authDB, err := newTestAuthDB(ctx)
|
authDB, err := newTestAuthDB(ctx)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer ctx.Check(authDB.Close)
|
||||||
_ = authDB.Close()
|
|
||||||
ctx.Cleanup()
|
|
||||||
}()
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
testID,
|
testID,
|
||||||
@ -144,7 +154,8 @@ func TestAuthorizationDB_Get(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = authDB.Close()
|
err := authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
ctx.Cleanup()
|
ctx.Cleanup()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -194,6 +205,225 @@ func TestAuthorizationDB_Get(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationDB_Claim_Valid(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
userID := "user@example.com"
|
||||||
|
authDB, err := newTestAuthDB(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, authDB) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
ctx.Cleanup()
|
||||||
|
}()
|
||||||
|
|
||||||
|
auths, err := authDB.Create(userID, 1)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, auths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident, err := testidentity.NewTestIdentity(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ident) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &net.TCPAddr{
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Port: 5,
|
||||||
|
}
|
||||||
|
grpcPeer := &peer.Peer{
|
||||||
|
Addr: addr,
|
||||||
|
AuthInfo: credentials.TLSInfo{
|
||||||
|
State: tls.ConnectionState{
|
||||||
|
PeerCertificates: []*x509.Certificate{ident.Leaf, ident.CA},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
req := &pb.SigningRequest{
|
||||||
|
AuthToken: auths[0].Token.String(),
|
||||||
|
Timestamp: now,
|
||||||
|
}
|
||||||
|
difficulty, err := ident.ID.Difficulty()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = authDB.Claim(&ClaimOpts{
|
||||||
|
Req: req,
|
||||||
|
Peer: grpcPeer,
|
||||||
|
ChainBytes: [][]byte{ident.CA.Raw},
|
||||||
|
MinDifficulty: difficulty,
|
||||||
|
})
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, updatedAuths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, auths[0].Token, updatedAuths[0].Token)
|
||||||
|
|
||||||
|
if !assert.NotNil(t, updatedAuths[0].Claim) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
claim := updatedAuths[0].Claim
|
||||||
|
assert.Equal(t, grpcPeer.Addr.String(), claim.Addr)
|
||||||
|
assert.Equal(t, [][]byte{ident.CA.Raw}, claim.SignedChainBytes)
|
||||||
|
assert.Condition(t, func() bool {
|
||||||
|
return now-MaxClaimDelaySeconds < claim.Timestamp &&
|
||||||
|
claim.Timestamp < now+MaxClaimDelaySeconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationDB_Claim_Invalid(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
userID := "user@example.com"
|
||||||
|
claimedTime := int64(1000000)
|
||||||
|
claimedAddr := "6.7.8.9:0"
|
||||||
|
ident1, err := testidentity.NewTestIdentity(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ident1) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
claimedIdent := &provider.PeerIdentity{
|
||||||
|
CA: ident1.CA,
|
||||||
|
Leaf: ident1.Leaf,
|
||||||
|
}
|
||||||
|
|
||||||
|
authDB, err := newTestAuthDB(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, authDB) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
ctx.Cleanup()
|
||||||
|
}()
|
||||||
|
|
||||||
|
auths, err := authDB.Create(userID, 2)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, auths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
claimedIndex, unclaimedIndex := 0, 1
|
||||||
|
|
||||||
|
auths[claimedIndex].Claim = &Claim{
|
||||||
|
Timestamp: claimedTime,
|
||||||
|
Addr: claimedAddr,
|
||||||
|
Identity: claimedIdent,
|
||||||
|
SignedChainBytes: [][]byte{claimedIdent.CA.Raw},
|
||||||
|
}
|
||||||
|
err = authDB.put(userID, auths)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ident2, err := testidentity.NewTestIdentity(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ident2) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := &net.TCPAddr{
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Port: 5,
|
||||||
|
}
|
||||||
|
grpcPeer := &peer.Peer{
|
||||||
|
Addr: addr,
|
||||||
|
AuthInfo: credentials.TLSInfo{
|
||||||
|
State: tls.ConnectionState{
|
||||||
|
PeerCertificates: []*x509.Certificate{ident2.Leaf, ident2.CA},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
difficulty2, err := ident2.ID.Difficulty()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("double claim", func(t *testing.T) {
|
||||||
|
err = authDB.Claim(&ClaimOpts{
|
||||||
|
Req: &pb.SigningRequest{
|
||||||
|
AuthToken: auths[claimedIndex].Token.String(),
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
Peer: grpcPeer,
|
||||||
|
ChainBytes: [][]byte{ident2.CA.Raw},
|
||||||
|
MinDifficulty: difficulty2,
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.True(t, ErrAuthorization.Has(err))
|
||||||
|
// NB: token string shouldn't leak into error message
|
||||||
|
assert.NotContains(t, err.Error(), auths[claimedIndex].Token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, updatedAuths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, auths[claimedIndex].Token, updatedAuths[claimedIndex].Token)
|
||||||
|
|
||||||
|
claim := updatedAuths[claimedIndex].Claim
|
||||||
|
assert.Equal(t, claimedAddr, claim.Addr)
|
||||||
|
assert.Equal(t, [][]byte{ident1.CA.Raw}, claim.SignedChainBytes)
|
||||||
|
assert.Equal(t, claimedTime, claim.Timestamp)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid timestamp", func(t *testing.T) {
|
||||||
|
err = authDB.Claim(&ClaimOpts{
|
||||||
|
Req: &pb.SigningRequest{
|
||||||
|
AuthToken: auths[unclaimedIndex].Token.String(),
|
||||||
|
// NB: 1 day ago
|
||||||
|
Timestamp: time.Now().Unix() - 86400,
|
||||||
|
},
|
||||||
|
Peer: grpcPeer,
|
||||||
|
ChainBytes: [][]byte{ident2.CA.Raw},
|
||||||
|
MinDifficulty: difficulty2,
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.True(t, ErrAuthorization.Has(err))
|
||||||
|
// NB: token string shouldn't leak into error message
|
||||||
|
assert.NotContains(t, err.Error(), auths[unclaimedIndex].Token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, updatedAuths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, auths[unclaimedIndex].Token, updatedAuths[unclaimedIndex].Token)
|
||||||
|
assert.Nil(t, updatedAuths[unclaimedIndex].Claim)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid difficulty", func(t *testing.T) {
|
||||||
|
err = authDB.Claim(&ClaimOpts{
|
||||||
|
Req: &pb.SigningRequest{
|
||||||
|
AuthToken: auths[unclaimedIndex].Token.String(),
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
},
|
||||||
|
Peer: grpcPeer,
|
||||||
|
ChainBytes: [][]byte{ident2.CA.Raw},
|
||||||
|
MinDifficulty: difficulty2 + 1,
|
||||||
|
})
|
||||||
|
if assert.Error(t, err) {
|
||||||
|
assert.True(t, ErrAuthorization.Has(err))
|
||||||
|
// NB: token string shouldn't leak into error message
|
||||||
|
assert.NotContains(t, err.Error(), auths[unclaimedIndex].Token.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, updatedAuths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, auths[unclaimedIndex].Token, updatedAuths[unclaimedIndex].Token)
|
||||||
|
assert.Nil(t, updatedAuths[unclaimedIndex].Claim)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewAuthorization(t *testing.T) {
|
func TestNewAuthorization(t *testing.T) {
|
||||||
userID := "user@example.com"
|
userID := "user@example.com"
|
||||||
auth, err := NewAuthorization(userID)
|
auth, err := NewAuthorization(userID)
|
||||||
@ -274,7 +504,8 @@ func TestAuthorizationDB_Emails(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
_ = authDB.Close()
|
err = authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
ctx.Cleanup()
|
ctx.Cleanup()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -368,6 +599,322 @@ func TestParseToken_Invalid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToken_Equal(t *testing.T) {
|
||||||
|
assert.True(t, t1.Equal(&t1))
|
||||||
|
assert.False(t, t1.Equal(&t2))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: test sad path
|
||||||
|
func TestCertificateSigner_Sign_E2E(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
tmp := ctx.Dir()
|
||||||
|
defer ctx.Cleanup()
|
||||||
|
caCert := filepath.Join(tmp, "ca.cert")
|
||||||
|
caKey := filepath.Join(tmp, "ca.key")
|
||||||
|
userID := "user@example.com"
|
||||||
|
caSetupConfig := provider.CASetupConfig{
|
||||||
|
CertPath: caCert,
|
||||||
|
KeyPath: caKey,
|
||||||
|
}
|
||||||
|
caConfig := provider.FullCAConfig{
|
||||||
|
CertPath: caCert,
|
||||||
|
KeyPath: caKey,
|
||||||
|
}
|
||||||
|
config := CertSignerConfig{
|
||||||
|
AuthorizationDBURL: "bolt://" + filepath.Join(tmp, "authorizations.db"),
|
||||||
|
CA: caConfig,
|
||||||
|
}
|
||||||
|
signingCA, err := caSetupConfig.Create(ctx)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authDB, err := config.NewAuthDB()
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, authDB) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
auths, err := authDB.Create("user@example.com", 1)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, auths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = authDB.Close()
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bryanchriswhite): figure out why pregenerated
|
||||||
|
// identities change issuers when signed
|
||||||
|
//
|
||||||
|
// Issuer: {
|
||||||
|
// Names: null => [],
|
||||||
|
// Organization: null => [],
|
||||||
|
// RawIssue": "MAA=" => "MBAxDjAMBgNVBAoTBVN0b3Jq",
|
||||||
|
//------
|
||||||
|
//serverIdent, err := idents.NewIdentity()
|
||||||
|
//------
|
||||||
|
serverCA, err := testidentity.NewTestCA(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, serverCA) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
serverIdent, err := serverCA.NewIdentity()
|
||||||
|
//------
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, serverIdent) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, listener) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig := provider.ServerConfig{Address: listener.Addr().String()}
|
||||||
|
opts, err := provider.NewServerOptions(serverIdent, serverConfig)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, opts) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service, err := provider.NewProvider(opts, listener, nil, config)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, service) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Go(func() error {
|
||||||
|
err := service.Run(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
defer func() {
|
||||||
|
err := service.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// TODO(bryanchriswhite): figure out why pregenerated
|
||||||
|
// identities change issuers when signed
|
||||||
|
//
|
||||||
|
// Issuer: {
|
||||||
|
// Names: null => [],
|
||||||
|
// Organization: null => [],
|
||||||
|
// RawIssue": "MAA=" => "MBAxDjAMBgNVBAoTBVN0b3Jq",
|
||||||
|
//------
|
||||||
|
//clientIdent, err := idents.NewIdentity()
|
||||||
|
//------
|
||||||
|
clientCA, err := testidentity.NewTestCA(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, clientCA) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
clientIdent, err := clientCA.NewIdentity()
|
||||||
|
//------
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, clientIdent) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(ctx, clientIdent, listener.Addr().String())
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, client) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedChainBytes, err := client.Sign(ctx, auths[0].Token.String())
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, signedChainBytes) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedChain, err := identity.ParseCertChain(signedChainBytes)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, clientIdent.CA.RawTBSCertificate, signedChain[0].RawTBSCertificate)
|
||||||
|
assert.Equal(t, signingCA.Cert.Raw, signedChainBytes[1])
|
||||||
|
// TODO: test scenario with rest chain
|
||||||
|
//assert.Equal(t, signingCA.RestChainRaw(), signedChainBytes[1:])
|
||||||
|
|
||||||
|
err = signedChain[0].CheckSignatureFrom(signingCA.Cert)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
err = service.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// NB: re-open after closing for server
|
||||||
|
authDB, err = config.NewAuthDB()
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, authDB) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) ||
|
||||||
|
!assert.NotEmpty(t, updatedAuths) ||
|
||||||
|
!assert.NotNil(t, updatedAuths[0].Claim) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
claim := updatedAuths[0].Claim
|
||||||
|
assert.Equal(t,
|
||||||
|
strings.Split(listener.Addr().String(), ":")[0],
|
||||||
|
strings.Split(claim.Addr, ":")[0])
|
||||||
|
assert.Equal(t, signedChainBytes, claim.SignedChainBytes)
|
||||||
|
assert.Condition(t, func() bool {
|
||||||
|
return now-10 < claim.Timestamp &&
|
||||||
|
claim.Timestamp < now+10
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClient(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
ident, err := idents.NewIdentity()
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ident) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(ctx, ident, "")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewClientFrom(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
ident, err := idents.NewIdentity()
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ident) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, listener) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tc := transport.NewClient(ident)
|
||||||
|
conn, err := tc.DialAddress(ctx, listener.Addr().String())
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, conn) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pbClient := pb.NewCertificatesClient(conn)
|
||||||
|
if !assert.NotNil(t, pbClient) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClientFrom(pbClient)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, client)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCertificateSigner_Sign(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
tmp := ctx.Dir()
|
||||||
|
defer ctx.Cleanup()
|
||||||
|
caCert := filepath.Join(tmp, "ca.cert")
|
||||||
|
caKey := filepath.Join(tmp, "ca.key")
|
||||||
|
userID := "user@example.com"
|
||||||
|
caSetupConfig := provider.CASetupConfig{
|
||||||
|
CertPath: caCert,
|
||||||
|
KeyPath: caKey,
|
||||||
|
}
|
||||||
|
config := CertSignerConfig{
|
||||||
|
AuthorizationDBURL: "bolt://" + filepath.Join(tmp, "authorizations.db"),
|
||||||
|
}
|
||||||
|
signingCA, err := caSetupConfig.Create(ctx)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
authDB, err := config.NewAuthDB()
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, authDB) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := authDB.Close()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
auths, err := authDB.Create(userID, 1)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotEmpty(t, auths) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bryanchriswhite): figure out why pregenerated
|
||||||
|
// identities change issuers when signed
|
||||||
|
//
|
||||||
|
// Issuer: {
|
||||||
|
// Names: null => [],
|
||||||
|
// Organization: null => [],
|
||||||
|
// RawIssue": "MAA=" => "MBAxDjAMBgNVBAoTBVN0b3Jq",
|
||||||
|
//------
|
||||||
|
//clientIdent, err := idents.NewIdentity()
|
||||||
|
//------
|
||||||
|
clientCA, err := testidentity.NewTestCA(ctx)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, clientCA) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
clientIdent, err := clientCA.NewIdentity()
|
||||||
|
//------
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, clientIdent) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedAddr := &net.TCPAddr{
|
||||||
|
IP: net.ParseIP("1.2.3.4"),
|
||||||
|
Port: 5,
|
||||||
|
}
|
||||||
|
grpcPeer := &peer.Peer{
|
||||||
|
Addr: expectedAddr,
|
||||||
|
AuthInfo: credentials.TLSInfo{
|
||||||
|
State: tls.ConnectionState{
|
||||||
|
PeerCertificates: []*x509.Certificate{clientIdent.Leaf, clientIdent.CA},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
peerCtx := peer.NewContext(ctx, grpcPeer)
|
||||||
|
|
||||||
|
certSigner := &CertificateSigner{
|
||||||
|
Logger: zap.L(),
|
||||||
|
Signer: signingCA,
|
||||||
|
AuthDB: authDB,
|
||||||
|
}
|
||||||
|
req := pb.SigningRequest{
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
|
AuthToken: auths[0].Token.String(),
|
||||||
|
}
|
||||||
|
res, err := certSigner.Sign(peerCtx, &req)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, res) || !assert.NotEmpty(t, res.Chain) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedChain, err := identity.ParseCertChain(res.Chain)
|
||||||
|
if !assert.NoError(t, err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, clientIdent.CA.RawTBSCertificate, signedChain[0].RawTBSCertificate)
|
||||||
|
assert.Equal(t, signingCA.Cert.Raw, signedChain[1].Raw)
|
||||||
|
// TODO: test scenario with rest chain
|
||||||
|
//assert.Equal(t, signingCA.RestChainRaw(), res.Chain[1:])
|
||||||
|
|
||||||
|
err = signedChain[0].CheckSignatureFrom(signingCA.Cert)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
updatedAuths, err := authDB.Get(userID)
|
||||||
|
if !assert.NoError(t, err) ||
|
||||||
|
!assert.NotEmpty(t, updatedAuths) ||
|
||||||
|
!assert.NotNil(t, updatedAuths[0].Claim) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now().Unix()
|
||||||
|
claim := updatedAuths[0].Claim
|
||||||
|
assert.Equal(t, expectedAddr.String(), claim.Addr)
|
||||||
|
assert.Equal(t, res.Chain, claim.SignedChainBytes)
|
||||||
|
assert.Condition(t, func() bool {
|
||||||
|
return now-MaxClaimDelaySeconds < claim.Timestamp &&
|
||||||
|
claim.Timestamp < now+MaxClaimDelaySeconds
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func newTestAuthDB(ctx *testcontext.Context) (*AuthorizationDB, error) {
|
func newTestAuthDB(ctx *testcontext.Context) (*AuthorizationDB, error) {
|
||||||
dbPath := "bolt://" + filepath.Join(ctx.Dir(), "authorizations.db")
|
dbPath := "bolt://" + filepath.Join(ctx.Dir(), "authorizations.db")
|
||||||
config := CertSignerConfig{
|
config := CertSignerConfig{
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"storj.io/storj/internal/testcontext"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewCA(t *testing.T) {
|
func TestNewCA(t *testing.T) {
|
||||||
@ -27,21 +29,19 @@ func TestNewCA(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
|
func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
|
||||||
ca, err := NewCA(context.Background(), NewCAOptions{
|
ctx := testcontext.New(t)
|
||||||
|
ca, err := NewCA(ctx, NewCAOptions{
|
||||||
Difficulty: 12,
|
Difficulty: 12,
|
||||||
Concurrency: 4,
|
Concurrency: 4,
|
||||||
})
|
})
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ca) {
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.NotEmpty(t, ca)
|
|
||||||
|
|
||||||
fi, err := ca.NewIdentity()
|
fi, err := ca.NewIdentity()
|
||||||
if err != nil {
|
if !assert.NoError(t, err) || !assert.NotNil(t, fi) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.NotEmpty(t, fi)
|
|
||||||
|
|
||||||
assert.Equal(t, ca.Cert, fi.CA)
|
assert.Equal(t, ca.Cert, fi.CA)
|
||||||
assert.Equal(t, ca.ID, fi.ID)
|
assert.Equal(t, ca.ID, fi.ID)
|
||||||
@ -52,6 +52,36 @@ func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFullCertificateAuthority_Sign(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
|
caOpts := NewCAOptions{
|
||||||
|
Difficulty: 12,
|
||||||
|
Concurrency: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
ca, err := NewCA(ctx, caOpts)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, ca) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
toSign, err := NewCA(ctx, caOpts)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, toSign) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signed, err := ca.Sign(toSign.Cert)
|
||||||
|
if !assert.NoError(t, err) || !assert.NotNil(t, signed) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, toSign.Cert.RawTBSCertificate, signed.RawTBSCertificate)
|
||||||
|
assert.NotEqual(t, toSign.Cert.Signature, signed.Signature)
|
||||||
|
assert.NotEqual(t, toSign.Cert.Raw, signed.Raw)
|
||||||
|
|
||||||
|
err = signed.CheckSignatureFrom(ca.Cert)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
func TestFullCAConfig_Save(t *testing.T) {
|
func TestFullCAConfig_Save(t *testing.T) {
|
||||||
// TODO(bryanchriswhite): test with both
|
// TODO(bryanchriswhite): test with both
|
||||||
// TODO(bryanchriswhite): test with only cert path
|
// TODO(bryanchriswhite): test with only cert path
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -75,6 +76,38 @@ type FullCAConfig struct {
|
|||||||
KeyPath string `help:"path to the private key for this identity" default:"$CONFDIR/ca.key"`
|
KeyPath string `help:"path to the private key for this identity" default:"$CONFDIR/ca.key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewCA creates a new full identity with the given difficulty
|
||||||
|
func NewCA(ctx context.Context, opts NewCAOptions) (_ *FullCertificateAuthority, err error) {
|
||||||
|
defer mon.Task()(&ctx)(&err)
|
||||||
|
var (
|
||||||
|
highscore uint32
|
||||||
|
)
|
||||||
|
|
||||||
|
if opts.Concurrency < 1 {
|
||||||
|
opts.Concurrency = 1
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
log.Printf("Generating a certificate matching a difficulty of %d\n", opts.Difficulty)
|
||||||
|
eC := make(chan error)
|
||||||
|
caC := make(chan FullCertificateAuthority, 1)
|
||||||
|
for i := 0; i < int(opts.Concurrency); i++ {
|
||||||
|
go newCAWorker(ctx, i, &highscore, opts.Difficulty, opts.ParentCert, opts.ParentKey, caC, eC)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case ca := <-caC:
|
||||||
|
cancel()
|
||||||
|
return &ca, nil
|
||||||
|
case err := <-eC:
|
||||||
|
cancel()
|
||||||
|
return nil, err
|
||||||
|
case <-ctx.Done():
|
||||||
|
cancel()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Status returns the status of the CA cert/key files for the config
|
// Status returns the status of the CA cert/key files for the config
|
||||||
func (caS CASetupConfig) Status() TLSFilesStatus {
|
func (caS CASetupConfig) Status() TLSFilesStatus {
|
||||||
return statTLSFiles(caS.CertPath, caS.KeyPath)
|
return statTLSFiles(caS.CertPath, caS.KeyPath)
|
||||||
@ -175,39 +208,6 @@ func (pc PeerCAConfig) Load() (*PeerCertificateAuthority, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCA creates a new full identity with the given difficulty
|
|
||||||
func NewCA(ctx context.Context, opts NewCAOptions) (
|
|
||||||
rv *FullCertificateAuthority, err error) {
|
|
||||||
defer mon.Task()(&ctx)(&err)
|
|
||||||
var (
|
|
||||||
highscore uint32
|
|
||||||
)
|
|
||||||
|
|
||||||
if opts.Concurrency < 1 {
|
|
||||||
opts.Concurrency = 1
|
|
||||||
}
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
|
||||||
|
|
||||||
log.Printf("Generating a certificate matching a difficulty of %d\n", opts.Difficulty)
|
|
||||||
eC := make(chan error)
|
|
||||||
caC := make(chan FullCertificateAuthority, 1)
|
|
||||||
for i := 0; i < int(opts.Concurrency); i++ {
|
|
||||||
go newCAWorker(ctx, i, &highscore, opts.Difficulty, opts.ParentCert, opts.ParentKey, caC, eC)
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case ca := <-caC:
|
|
||||||
cancel()
|
|
||||||
return &ca, nil
|
|
||||||
case err := <-eC:
|
|
||||||
cancel()
|
|
||||||
return nil, err
|
|
||||||
case <-ctx.Done():
|
|
||||||
cancel()
|
|
||||||
return nil, ctx.Err()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save saves a CA with the given configuration
|
// Save saves a CA with the given configuration
|
||||||
func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
|
func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
|
||||||
var (
|
var (
|
||||||
@ -246,7 +246,7 @@ func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
|
|||||||
// NewIdentity generates a new `FullIdentity` based on the CA. The CA
|
// NewIdentity generates a new `FullIdentity` based on the CA. The CA
|
||||||
// cert is included in the identity's cert chain and the identity's leaf cert
|
// cert is included in the identity's cert chain and the identity's leaf cert
|
||||||
// is signed by the CA.
|
// is signed by the CA.
|
||||||
func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
|
func (ca *FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
|
||||||
leafTemplate, err := peertls.LeafTemplate()
|
leafTemplate, err := peertls.LeafTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -278,4 +278,29 @@ func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
|
|||||||
Key: leafKey,
|
Key: leafKey,
|
||||||
ID: ca.ID,
|
ID: ca.ID,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestChainRaw returns the rest (excluding leaf and CA) of the certificate chain as a 2d byte slice
|
||||||
|
func (ca *FullCertificateAuthority) RestChainRaw() [][]byte {
|
||||||
|
var chain [][]byte
|
||||||
|
for _, cert := range ca.RestChain {
|
||||||
|
chain = append(chain, cert.Raw)
|
||||||
|
}
|
||||||
|
return chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign signs the passed certificate with ca certificate
|
||||||
|
func (ca *FullCertificateAuthority) Sign(cert *x509.Certificate) (*x509.Certificate, error) {
|
||||||
|
signedCertBytes, err := x509.CreateCertificate(rand.Reader, cert, ca.Cert, cert.PublicKey, ca.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedCert, err := x509.ParseCertificate(signedCertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return signedCert, nil
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ func PeerIdentityFromPeer(peer *peer.Peer) (*PeerIdentity, error) {
|
|||||||
if len(c) < 2 {
|
if len(c) < 2 {
|
||||||
return nil, Error.New("invalid certificate chain")
|
return nil, Error.New("invalid certificate chain")
|
||||||
}
|
}
|
||||||
pi, err := PeerIdentityFromCerts(c[0], c[1], c[2:])
|
pi, err := PeerIdentityFromCerts(c[peertls.LeafIndex], c[peertls.CAIndex], c[2:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -244,7 +244,7 @@ func (ic Config) Save(fi *FullIdentity) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RestChainRaw returns the rest (excluding leaf and CA) of the certficate chain as a 2d byte slice
|
// RestChainRaw returns the rest (excluding leaf and CA) of the certificate chain as a 2d byte slice
|
||||||
func (fi *FullIdentity) RestChainRaw() [][]byte {
|
func (fi *FullIdentity) RestChainRaw() [][]byte {
|
||||||
var chain [][]byte
|
var chain [][]byte
|
||||||
for _, cert := range fi.RestChain {
|
for _, cert := range fi.RestChain {
|
||||||
|
@ -21,9 +21,10 @@ import (
|
|||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/internal/teststorj"
|
"storj.io/storj/internal/teststorj"
|
||||||
|
"storj.io/storj/pkg/identity"
|
||||||
"storj.io/storj/pkg/node"
|
"storj.io/storj/pkg/node"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
"storj.io/storj/pkg/provider"
|
"storj.io/storj/pkg/provider"
|
||||||
@ -41,14 +42,14 @@ func TestNewKademlia(t *testing.T) {
|
|||||||
rootdir, cleanup := mktempdir(t, "kademlia")
|
rootdir, cleanup := mktempdir(t, "kademlia")
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
id *provider.FullIdentity
|
id *identity.FullIdentity
|
||||||
bn []pb.Node
|
bn []pb.Node
|
||||||
addr string
|
addr string
|
||||||
expectedErr error
|
expectedErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
id: func() *provider.FullIdentity {
|
id: func() *identity.FullIdentity {
|
||||||
id, err := testidentity.NewTestIdentity()
|
id, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return id
|
return id
|
||||||
}(),
|
}(),
|
||||||
@ -57,7 +58,7 @@ func TestNewKademlia(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: func() *provider.FullIdentity {
|
id: func() *provider.FullIdentity {
|
||||||
id, err := testidentity.NewTestIdentity()
|
id, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return id
|
return id
|
||||||
}(),
|
}(),
|
||||||
@ -163,12 +164,13 @@ func TestBootstrap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testNode(t *testing.T, bn []pb.Node) (*Kademlia, *grpc.Server, func()) {
|
func testNode(t *testing.T, bn []pb.Node) (*Kademlia, *grpc.Server, func()) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
// new address
|
// new address
|
||||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// new config
|
// new config
|
||||||
// new identity
|
// new identity
|
||||||
fid, err := testidentity.NewTestIdentity()
|
fid, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
// new kademlia
|
// new kademlia
|
||||||
dir, cleanup := mktempdir(t, "kademlia")
|
dir, cleanup := mktempdir(t, "kademlia")
|
||||||
@ -238,9 +240,9 @@ func TestGetNodes(t *testing.T) {
|
|||||||
defer srv.Stop()
|
defer srv.Stop()
|
||||||
|
|
||||||
// make new identity
|
// make new identity
|
||||||
fid, err := testidentity.NewTestIdentity()
|
fid, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
fid2, err := testidentity.NewTestIdentity()
|
fid2, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
fid.ID = nodeIDA
|
fid.ID = nodeIDA
|
||||||
fid2.ID = nodeIDB
|
fid2.ID = nodeIDB
|
||||||
|
@ -17,9 +17,9 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"go.uber.org/zap/zaptest"
|
"go.uber.org/zap/zaptest"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
|
||||||
"storj.io/storj/internal/s3client"
|
"storj.io/storj/internal/s3client"
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/internal/testplanet"
|
"storj.io/storj/internal/testplanet"
|
||||||
"storj.io/storj/pkg/cfgstruct"
|
"storj.io/storj/pkg/cfgstruct"
|
||||||
"storj.io/storj/pkg/miniogw"
|
"storj.io/storj/pkg/miniogw"
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
testidentity "storj.io/storj/internal/identity"
|
|
||||||
"storj.io/storj/internal/testcontext"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/internal/testplanet"
|
"storj.io/storj/internal/testplanet"
|
||||||
"storj.io/storj/pkg/overlay"
|
"storj.io/storj/pkg/overlay"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
|
@ -36,7 +36,7 @@ func (m *SigningRequest) Reset() { *m = SigningRequest{} }
|
|||||||
func (m *SigningRequest) String() string { return proto.CompactTextString(m) }
|
func (m *SigningRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SigningRequest) ProtoMessage() {}
|
func (*SigningRequest) ProtoMessage() {}
|
||||||
func (*SigningRequest) Descriptor() ([]byte, []int) {
|
func (*SigningRequest) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_certificate_80970fe789528feb, []int{0}
|
return fileDescriptor_certificate_c55bd879e75eb964, []int{0}
|
||||||
}
|
}
|
||||||
func (m *SigningRequest) XXX_Unmarshal(b []byte) error {
|
func (m *SigningRequest) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SigningRequest.Unmarshal(m, b)
|
return xxx_messageInfo_SigningRequest.Unmarshal(m, b)
|
||||||
@ -71,7 +71,7 @@ func (m *SigningRequest) GetTimestamp() int64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SigningResponse struct {
|
type SigningResponse struct {
|
||||||
Cert []byte `protobuf:"bytes,1,opt,name=cert,proto3" json:"cert,omitempty"`
|
Chain [][]byte `protobuf:"bytes,1,rep,name=chain" json:"chain,omitempty"`
|
||||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_unrecognized []byte `json:"-"`
|
||||||
XXX_sizecache int32 `json:"-"`
|
XXX_sizecache int32 `json:"-"`
|
||||||
@ -81,7 +81,7 @@ func (m *SigningResponse) Reset() { *m = SigningResponse{} }
|
|||||||
func (m *SigningResponse) String() string { return proto.CompactTextString(m) }
|
func (m *SigningResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*SigningResponse) ProtoMessage() {}
|
func (*SigningResponse) ProtoMessage() {}
|
||||||
func (*SigningResponse) Descriptor() ([]byte, []int) {
|
func (*SigningResponse) Descriptor() ([]byte, []int) {
|
||||||
return fileDescriptor_certificate_80970fe789528feb, []int{1}
|
return fileDescriptor_certificate_c55bd879e75eb964, []int{1}
|
||||||
}
|
}
|
||||||
func (m *SigningResponse) XXX_Unmarshal(b []byte) error {
|
func (m *SigningResponse) XXX_Unmarshal(b []byte) error {
|
||||||
return xxx_messageInfo_SigningResponse.Unmarshal(m, b)
|
return xxx_messageInfo_SigningResponse.Unmarshal(m, b)
|
||||||
@ -101,9 +101,9 @@ func (m *SigningResponse) XXX_DiscardUnknown() {
|
|||||||
|
|
||||||
var xxx_messageInfo_SigningResponse proto.InternalMessageInfo
|
var xxx_messageInfo_SigningResponse proto.InternalMessageInfo
|
||||||
|
|
||||||
func (m *SigningResponse) GetCert() []byte {
|
func (m *SigningResponse) GetChain() [][]byte {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
return m.Cert
|
return m.Chain
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -185,20 +185,20 @@ var _Certificates_serviceDesc = grpc.ServiceDesc{
|
|||||||
Metadata: "certificate.proto",
|
Metadata: "certificate.proto",
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { proto.RegisterFile("certificate.proto", fileDescriptor_certificate_80970fe789528feb) }
|
func init() { proto.RegisterFile("certificate.proto", fileDescriptor_certificate_c55bd879e75eb964) }
|
||||||
|
|
||||||
var fileDescriptor_certificate_80970fe789528feb = []byte{
|
var fileDescriptor_certificate_c55bd879e75eb964 = []byte{
|
||||||
// 188 bytes of a gzipped FileDescriptorProto
|
// 192 bytes of a gzipped FileDescriptorProto
|
||||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x4e, 0x2d, 0x2a,
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x4e, 0x2d, 0x2a,
|
||||||
0xc9, 0x4c, 0xcb, 0x4c, 0x4e, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9,
|
0xc9, 0x4c, 0xcb, 0x4c, 0x4e, 0x2c, 0x49, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9,
|
||||||
0xcb, 0x4f, 0x49, 0x95, 0xe2, 0x4a, 0xcf, 0x4f, 0xcf, 0x87, 0x88, 0x28, 0xf9, 0x72, 0xf1, 0x05,
|
0xcb, 0x4f, 0x49, 0x95, 0xe2, 0x4a, 0xcf, 0x4f, 0xcf, 0x87, 0x88, 0x28, 0xf9, 0x72, 0xf1, 0x05,
|
||||||
0x67, 0xa6, 0xe7, 0x65, 0xe6, 0xa5, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0xc9, 0x72,
|
0x67, 0xa6, 0xe7, 0x65, 0xe6, 0xa5, 0x07, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97, 0x08, 0xc9, 0x72,
|
||||||
0x71, 0x25, 0x96, 0x96, 0x64, 0xc4, 0x97, 0xe4, 0x67, 0xa7, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a,
|
0x71, 0x25, 0x96, 0x96, 0x64, 0xc4, 0x97, 0xe4, 0x67, 0xa7, 0xe6, 0x49, 0x30, 0x2a, 0x30, 0x6a,
|
||||||
0x70, 0x06, 0x71, 0x82, 0x44, 0x42, 0x40, 0x02, 0x42, 0x32, 0x5c, 0x9c, 0x25, 0x99, 0xb9, 0xa9,
|
0x70, 0x06, 0x71, 0x82, 0x44, 0x42, 0x40, 0x02, 0x42, 0x32, 0x5c, 0x9c, 0x25, 0x99, 0xb9, 0xa9,
|
||||||
0xc5, 0x25, 0x89, 0xb9, 0x05, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x08, 0x01, 0x25, 0x55,
|
0xc5, 0x25, 0x89, 0xb9, 0x05, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x08, 0x01, 0x25, 0x75,
|
||||||
0x2e, 0x7e, 0xb8, 0x71, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x42, 0x5c, 0x2c, 0x20, 0x87,
|
0x2e, 0x7e, 0xb8, 0x71, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x22, 0x5c, 0xac, 0xc9, 0x19,
|
||||||
0x80, 0x4d, 0xe2, 0x09, 0x02, 0xb3, 0x8d, 0x9c, 0xb9, 0x78, 0x9c, 0x11, 0x8e, 0x2b, 0x16, 0x32,
|
0x89, 0x99, 0x20, 0xa3, 0x98, 0x35, 0x78, 0x82, 0x20, 0x1c, 0x23, 0x67, 0x2e, 0x1e, 0x67, 0x84,
|
||||||
0xe6, 0x62, 0x01, 0x69, 0x13, 0x12, 0xd1, 0x03, 0x39, 0x50, 0x0f, 0xd5, 0x45, 0x52, 0xa2, 0x68,
|
0xf3, 0x8a, 0x85, 0x8c, 0xb9, 0x58, 0x40, 0x1a, 0x85, 0x44, 0xf4, 0x40, 0x4e, 0xd4, 0x43, 0x75,
|
||||||
0xa2, 0x10, 0x83, 0x9d, 0x58, 0xa2, 0x98, 0x0a, 0x92, 0x92, 0xd8, 0xc0, 0xfe, 0x30, 0x06, 0x04,
|
0x93, 0x94, 0x28, 0x9a, 0x28, 0xc4, 0x68, 0x27, 0x96, 0x28, 0xa6, 0x82, 0xa4, 0x24, 0x36, 0xb0,
|
||||||
0x00, 0x00, 0xff, 0xff, 0x4b, 0x1b, 0x87, 0x66, 0xee, 0x00, 0x00, 0x00,
|
0x4f, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x3a, 0xd8, 0xc7, 0x87, 0xf0, 0x00, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
@ -18,5 +18,5 @@ message SigningRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message SigningResponse {
|
message SigningResponse {
|
||||||
bytes cert = 1;
|
repeated bytes chain = 1;
|
||||||
}
|
}
|
@ -72,13 +72,15 @@ type TLSExtConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ExtensionHandlers is a collection of `extensionHandler`s for convenience (see `VerifyFunc`)
|
// ExtensionHandlers is a collection of `extensionHandler`s for convenience (see `VerifyFunc`)
|
||||||
type ExtensionHandlers []extensionHandler
|
type ExtensionHandlers []ExtensionHandler
|
||||||
|
|
||||||
type extensionVerificationFunc func(pkix.Extension, [][]*x509.Certificate) error
|
type extensionVerificationFunc func(pkix.Extension, [][]*x509.Certificate) error
|
||||||
|
|
||||||
type extensionHandler struct {
|
// ExtensionHandler represents a verify function for handling an extension
|
||||||
id asn1.ObjectIdentifier
|
// with the given ID
|
||||||
verify extensionVerificationFunc
|
type ExtensionHandler struct {
|
||||||
|
ID asn1.ObjectIdentifier
|
||||||
|
Verify extensionVerificationFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseExtOptions holds options for calling `ParseExtensions`
|
// ParseExtOptions holds options for calling `ParseExtensions`
|
||||||
@ -107,16 +109,16 @@ type RevocationDB struct {
|
|||||||
// to be used in the context of peer certificate verification.
|
// to be used in the context of peer certificate verification.
|
||||||
func ParseExtensions(c TLSExtConfig, opts ParseExtOptions) (handlers ExtensionHandlers) {
|
func ParseExtensions(c TLSExtConfig, opts ParseExtOptions) (handlers ExtensionHandlers) {
|
||||||
if c.WhitelistSignedLeaf {
|
if c.WhitelistSignedLeaf {
|
||||||
handlers = append(handlers, extensionHandler{
|
handlers = append(handlers, ExtensionHandler{
|
||||||
id: ExtensionIDs[SignedCertExtID],
|
ID: ExtensionIDs[SignedCertExtID],
|
||||||
verify: verifyCAWhitelistSignedLeafFunc(opts.CAWhitelist),
|
Verify: verifyCAWhitelistSignedLeafFunc(opts.CAWhitelist),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Revocation {
|
if c.Revocation {
|
||||||
handlers = append(handlers, extensionHandler{
|
handlers = append(handlers, ExtensionHandler{
|
||||||
id: ExtensionIDs[RevocationExtID],
|
ID: ExtensionIDs[RevocationExtID],
|
||||||
verify: func(certExt pkix.Extension, chains [][]*x509.Certificate) error {
|
Verify: func(certExt pkix.Extension, chains [][]*x509.Certificate) error {
|
||||||
if err := opts.RevDB.Put(chains[0], certExt); err != nil {
|
if err := opts.RevDB.Put(chains[0], certExt); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -245,8 +247,8 @@ func (e ExtensionHandlers) VerifyFunc() PeerCertVerificationFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, handler := range e {
|
for _, handler := range e {
|
||||||
if ext, ok := leafExts[handler.id.String()]; ok {
|
if ext, ok := leafExts[handler.ID.String()]; ok {
|
||||||
err := handler.verify(ext, parsedChains)
|
err := handler.Verify(ext, parsedChains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrExtension.Wrap(err)
|
return ErrExtension.Wrap(err)
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (C) 2018 Storj Labs, Inc.
|
// Copyright (C) 2018 Storj Labs, Inc.
|
||||||
// See LICENSE for copying information.
|
// See LICENSE for copying information.
|
||||||
|
|
||||||
package peertls
|
package peertls_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@ -21,16 +21,19 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/zeebo/errs"
|
"github.com/zeebo/errs"
|
||||||
|
|
||||||
|
"storj.io/storj/internal/testpeertls"
|
||||||
|
"storj.io/storj/pkg/peertls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewCert_CA(t *testing.T) {
|
func TestNewCert_CA(t *testing.T) {
|
||||||
caKey, err := NewKey()
|
caKey, err := peertls.NewKey()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
caTemplate, err := CATemplate()
|
caTemplate, err := peertls.CATemplate()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
caCert, err := NewCert(caKey, nil, caTemplate, nil)
|
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
|
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
|
||||||
@ -42,22 +45,22 @@ func TestNewCert_CA(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewCert_Leaf(t *testing.T) {
|
func TestNewCert_Leaf(t *testing.T) {
|
||||||
caKey, err := NewKey()
|
caKey, err := peertls.NewKey()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
caTemplate, err := CATemplate()
|
caTemplate, err := peertls.CATemplate()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
caCert, err := NewCert(caKey, nil, caTemplate, nil)
|
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
leafKey, err := NewKey()
|
leafKey, err := peertls.NewKey()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
leafTemplate, err := LeafTemplate()
|
leafTemplate, err := peertls.LeafTemplate()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
|
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caCert)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
|
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
|
||||||
@ -71,7 +74,7 @@ func TestNewCert_Leaf(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyPeerFunc(t *testing.T) {
|
func TestVerifyPeerFunc(t *testing.T) {
|
||||||
_, chain, err := newCertChain(2)
|
_, chain, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -97,88 +100,88 @@ func TestVerifyPeerFunc(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = VerifyPeerFunc(testFunc)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(testFunc)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyPeerCertChains(t *testing.T) {
|
func TestVerifyPeerCertChains(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
leafKey, leafCert, caCert := keys[1], chain[0], chain[1]
|
leafKey, leafCert, caCert := keys[1], chain[0], chain[1]
|
||||||
|
|
||||||
err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
wrongKey, err := NewKey()
|
wrongKey, err := peertls.NewKey()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
leafCert, err = NewCert(leafKey, wrongKey, leafCert, caCert)
|
leafCert, err = peertls.NewCert(leafKey, wrongKey, leafCert, caCert)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.True(t, ErrVerifyPeerCert.Has(err))
|
assert.True(t, peertls.ErrVerifyPeerCert.Has(err))
|
||||||
assert.True(t, ErrVerifyCertificateChain.Has(err))
|
assert.True(t, peertls.ErrVerifyCertificateChain.Has(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVerifyCAWhitelist(t *testing.T) {
|
func TestVerifyCAWhitelist(t *testing.T) {
|
||||||
_, chain2, err := newCertChain(2)
|
_, chain2, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
leafCert, caCert := chain2[0], chain2[1]
|
leafCert, caCert := chain2[0], chain2[1]
|
||||||
|
|
||||||
t.Run("empty whitelist", func(t *testing.T) {
|
t.Run("empty whitelist", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist(nil))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist(nil))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("whitelist contains ca", func(t *testing.T) {
|
t.Run("whitelist contains ca", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
_, unrelatedChain, err := newCertChain(1)
|
_, unrelatedChain, err := testpeertls.NewCertChain(1)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
unrelatedCert := unrelatedChain[0]
|
unrelatedCert := unrelatedChain[0]
|
||||||
|
|
||||||
t.Run("no valid signed extension, non-empty whitelist", func(t *testing.T) {
|
t.Run("no valid signed extension, non-empty whitelist", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{unrelatedCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{unrelatedCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.True(t, ErrVerifyCAWhitelist.Has(err))
|
assert.True(t, peertls.ErrVerifyCAWhitelist.Has(err))
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("last cert in whitelist is signer", func(t *testing.T) {
|
t.Run("last cert in whitelist is signer", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{unrelatedCert, caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{unrelatedCert, caCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("first cert in whitelist is signer", func(t *testing.T) {
|
t.Run("first cert in whitelist is signer", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{caCert, unrelatedCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{caCert, unrelatedCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
_, chain3, err := newCertChain(3)
|
_, chain3, err := testpeertls.NewCertChain(3)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
leaf2Cert, ca2Cert, rootCert := chain3[0], chain3[1], chain3[2]
|
leaf2Cert, ca2Cert, rootCert := chain3[0], chain3[1], chain3[2]
|
||||||
|
|
||||||
t.Run("length 3 chain - first cert in whitelist is signer", func(t *testing.T) {
|
t.Run("length 3 chain - first cert in whitelist is signer", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{rootCert, unrelatedCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, unrelatedCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{rootCert, unrelatedCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, unrelatedCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("length 3 chain - last cert in whitelist is signer", func(t *testing.T) {
|
t.Run("length 3 chain - last cert in whitelist is signer", func(t *testing.T) {
|
||||||
err = VerifyPeerFunc(VerifyCAWhitelist([]*x509.Certificate{unrelatedCert, rootCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, unrelatedCert.Raw}, nil)
|
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{unrelatedCert, rootCert}))([][]byte{leaf2Cert.Raw, ca2Cert.Raw, unrelatedCert.Raw}, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddExtension(t *testing.T) {
|
func TestAddExtension(t *testing.T) {
|
||||||
_, chain, err := newCertChain(1)
|
_, chain, err := testpeertls.NewCertChain(1)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -197,30 +200,30 @@ func TestAddExtension(t *testing.T) {
|
|||||||
Value: randBytes,
|
Value: randBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = AddExtension(chain[0], ext)
|
err = peertls.AddExtension(chain[0], ext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, chain[0].ExtraExtensions, 1)
|
assert.Len(t, chain[0].ExtraExtensions, 1)
|
||||||
assert.Equal(t, ext, chain[0].ExtraExtensions[0])
|
assert.Equal(t, ext, chain[0].ExtraExtensions[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddSignedCertExt(t *testing.T) {
|
func TestAddSignedCertExt(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(1)
|
keys, chain, err := testpeertls.NewCertChain(1)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = AddSignedCertExt(keys[0], chain[0])
|
err = peertls.AddSignedCertExt(keys[0], chain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
assert.Len(t, chain[0].ExtraExtensions, 1)
|
assert.Len(t, chain[0].ExtraExtensions, 1)
|
||||||
assert.Equal(t, ExtensionIDs[SignedCertExtID], chain[0].ExtraExtensions[0].Id)
|
assert.Equal(t, peertls.ExtensionIDs[peertls.SignedCertExtID], chain[0].ExtraExtensions[0].Id)
|
||||||
|
|
||||||
ecKey, ok := keys[0].(*ecdsa.PrivateKey)
|
ecKey, ok := keys[0].(*ecdsa.PrivateKey)
|
||||||
if !assert.True(t, ok) {
|
if !assert.True(t, ok) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = VerifySignature(
|
err = peertls.VerifySignature(
|
||||||
chain[0].ExtraExtensions[0].Value,
|
chain[0].ExtraExtensions[0].Value,
|
||||||
chain[0].RawTBSCertificate,
|
chain[0].RawTBSCertificate,
|
||||||
&ecKey.PublicKey,
|
&ecKey.PublicKey,
|
||||||
@ -229,35 +232,35 @@ func TestAddSignedCertExt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSignLeafExt(t *testing.T) {
|
func TestSignLeafExt(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
caKey, leafCert := keys[0], chain[0]
|
caKey, leafCert := keys[0], chain[0]
|
||||||
|
|
||||||
err = AddSignedCertExt(caKey, leafCert)
|
err = peertls.AddSignedCertExt(caKey, leafCert)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 1, len(leafCert.ExtraExtensions))
|
assert.Equal(t, 1, len(leafCert.ExtraExtensions))
|
||||||
assert.True(t, ExtensionIDs[SignedCertExtID].Equal(leafCert.ExtraExtensions[0].Id))
|
assert.True(t, peertls.ExtensionIDs[peertls.SignedCertExtID].Equal(leafCert.ExtraExtensions[0].Id))
|
||||||
|
|
||||||
caECKey, ok := caKey.(*ecdsa.PrivateKey)
|
caECKey, ok := caKey.(*ecdsa.PrivateKey)
|
||||||
if !assert.True(t, ok) {
|
if !assert.True(t, ok) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
err = VerifySignature(leafCert.ExtraExtensions[0].Value, leafCert.RawTBSCertificate, &caECKey.PublicKey)
|
err = peertls.VerifySignature(leafCert.ExtraExtensions[0].Value, leafCert.RawTBSCertificate, &caECKey.PublicKey)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRevocation_Sign(t *testing.T) {
|
func TestRevocation_Sign(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
leafCert, caKey := chain[0], keys[0]
|
leafCert, caKey := chain[0], keys[0]
|
||||||
|
|
||||||
leafHash, err := SHA256Hash(leafCert.Raw)
|
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rev := Revocation{
|
rev := peertls.Revocation{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
CertHash: make([]byte, len(leafHash)),
|
CertHash: make([]byte, len(leafHash)),
|
||||||
}
|
}
|
||||||
@ -268,14 +271,14 @@ func TestRevocation_Sign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRevocation_Verify(t *testing.T) {
|
func TestRevocation_Verify(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
leafCert, caCert, caKey := chain[0], chain[1], keys[0]
|
leafCert, caCert, caKey := chain[0], chain[1], keys[0]
|
||||||
|
|
||||||
leafHash, err := SHA256Hash(leafCert.Raw)
|
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rev := Revocation{
|
rev := peertls.Revocation{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
CertHash: make([]byte, len(leafHash)),
|
CertHash: make([]byte, len(leafHash)),
|
||||||
}
|
}
|
||||||
@ -289,14 +292,14 @@ func TestRevocation_Verify(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRevocation_Marshal(t *testing.T) {
|
func TestRevocation_Marshal(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
leafCert, caKey := chain[0], keys[0]
|
leafCert, caKey := chain[0], keys[0]
|
||||||
|
|
||||||
leafHash, err := SHA256Hash(leafCert.Raw)
|
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rev := Revocation{
|
rev := peertls.Revocation{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
CertHash: make([]byte, len(leafHash)),
|
CertHash: make([]byte, len(leafHash)),
|
||||||
}
|
}
|
||||||
@ -309,7 +312,7 @@ func TestRevocation_Marshal(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotEmpty(t, revBytes)
|
assert.NotEmpty(t, revBytes)
|
||||||
|
|
||||||
decodedRev := new(Revocation)
|
decodedRev := new(peertls.Revocation)
|
||||||
decoder := gob.NewDecoder(bytes.NewBuffer(revBytes))
|
decoder := gob.NewDecoder(bytes.NewBuffer(revBytes))
|
||||||
err = decoder.Decode(decodedRev)
|
err = decoder.Decode(decodedRev)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -317,14 +320,14 @@ func TestRevocation_Marshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestRevocation_Unmarshal(t *testing.T) {
|
func TestRevocation_Unmarshal(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
leafCert, caKey := chain[0], keys[0]
|
leafCert, caKey := chain[0], keys[0]
|
||||||
|
|
||||||
leafHash, err := SHA256Hash(leafCert.Raw)
|
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rev := Revocation{
|
rev := peertls.Revocation{
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
CertHash: make([]byte, len(leafHash)),
|
CertHash: make([]byte, len(leafHash)),
|
||||||
}
|
}
|
||||||
@ -338,7 +341,7 @@ func TestRevocation_Unmarshal(t *testing.T) {
|
|||||||
err = encoder.Encode(rev)
|
err = encoder.Encode(rev)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
unmarshaledRev := new(Revocation)
|
unmarshaledRev := new(peertls.Revocation)
|
||||||
err = unmarshaledRev.Unmarshal(encodedRev.Bytes())
|
err = unmarshaledRev.Unmarshal(encodedRev.Bytes())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, rev)
|
assert.NotNil(t, rev)
|
||||||
@ -346,13 +349,13 @@ func TestRevocation_Unmarshal(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNewRevocationExt(t *testing.T) {
|
func TestNewRevocationExt(t *testing.T) {
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
ext, err := NewRevocationExt(keys[0], chain[0])
|
ext, err := peertls.NewRevocationExt(keys[0], chain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
var rev Revocation
|
var rev peertls.Revocation
|
||||||
err = rev.Unmarshal(ext.Value)
|
err = rev.Unmarshal(ext.Value)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -364,29 +367,29 @@ func TestRevocationDB_Get(t *testing.T) {
|
|||||||
tmp, err := ioutil.TempDir("", "TestRevocationDB_Get")
|
tmp, err := ioutil.TempDir("", "TestRevocationDB_Get")
|
||||||
defer func() { _ = os.RemoveAll(tmp) }()
|
defer func() { _ = os.RemoveAll(tmp) }()
|
||||||
|
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
ext, err := NewRevocationExt(keys[0], chain[0])
|
ext, err := peertls.NewRevocationExt(keys[0], chain[0])
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
revDB, err := NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
revDB, err := peertls.NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
var rev *Revocation
|
var rev *peertls.Revocation
|
||||||
t.Run("missing key", func(t *testing.T) {
|
t.Run("missing key", func(t *testing.T) {
|
||||||
rev, err = revDB.Get(chain)
|
rev, err = revDB.Get(chain)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Nil(t, rev)
|
assert.Nil(t, rev)
|
||||||
})
|
})
|
||||||
|
|
||||||
caHash, err := SHA256Hash(chain[1].Raw)
|
caHash, err := peertls.SHA256Hash(chain[1].Raw)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -410,25 +413,25 @@ func TestRevocationDB_Put(t *testing.T) {
|
|||||||
tmp, err := ioutil.TempDir("", "TestRevocationDB_Put")
|
tmp, err := ioutil.TempDir("", "TestRevocationDB_Put")
|
||||||
defer func() { _ = os.RemoveAll(tmp) }()
|
defer func() { _ = os.RemoveAll(tmp) }()
|
||||||
|
|
||||||
keys, chain, err := newCertChain(2)
|
keys, chain, err := testpeertls.NewCertChain(2)
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
olderExt, err := NewRevocationExt(keys[0], chain[0])
|
olderExt, err := peertls.NewRevocationExt(keys[0], chain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
ext, err := NewRevocationExt(keys[0], chain[0])
|
ext, err := peertls.NewRevocationExt(keys[0], chain[0])
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
newerExt, err := NewRevocationExt(keys[0], chain[0])
|
newerExt, err := peertls.NewRevocationExt(keys[0], chain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
revDB, err := NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
revDB, err := peertls.NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -448,8 +451,8 @@ func TestRevocationDB_Put(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"existing key - older timestamp",
|
"existing key - older timestamp",
|
||||||
olderExt,
|
olderExt,
|
||||||
&ErrExtension,
|
&peertls.ErrExtension,
|
||||||
ErrRevocationTimestamp,
|
peertls.ErrRevocationTimestamp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"existing key - newer timestamp",
|
"existing key - newer timestamp",
|
||||||
@ -480,7 +483,7 @@ func TestRevocationDB_Put(t *testing.T) {
|
|||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
func(t2 *testing.T, ext pkix.Extension) {
|
func(t2 *testing.T, ext pkix.Extension) {
|
||||||
caHash, err := SHA256Hash(chain[1].Raw)
|
caHash, err := peertls.SHA256Hash(chain[1].Raw)
|
||||||
if !assert.NoError(t2, err) {
|
if !assert.NoError(t2, err) {
|
||||||
t2.FailNow()
|
t2.FailNow()
|
||||||
}
|
}
|
||||||
@ -490,7 +493,7 @@ func TestRevocationDB_Put(t *testing.T) {
|
|||||||
t2.FailNow()
|
t2.FailNow()
|
||||||
}
|
}
|
||||||
|
|
||||||
rev := new(Revocation)
|
rev := new(peertls.Revocation)
|
||||||
err = rev.Unmarshal(revBytes)
|
err = rev.Unmarshal(revBytes)
|
||||||
assert.NoError(t2, err)
|
assert.NoError(t2, err)
|
||||||
assert.True(t2, bytes.Equal(ext.Value, revBytes))
|
assert.True(t2, bytes.Equal(ext.Value, revBytes))
|
||||||
@ -515,7 +518,7 @@ func TestExtensionHandlers_VerifyFunc(t *testing.T) {
|
|||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
err = AddSignedCertExt(keys[0], chain[0])
|
err = peertls.AddSignedCertExt(keys[0], chain[0])
|
||||||
if !assert.NoError(t, err) {
|
if !assert.NoError(t, err) {
|
||||||
t.FailNow()
|
t.FailNow()
|
||||||
}
|
}
|
||||||
@ -525,24 +528,24 @@ func TestExtensionHandlers_VerifyFunc(t *testing.T) {
|
|||||||
return extMock.verify(ext, chain)
|
return extMock.verify(ext, chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers := ExtensionHandlers{
|
handlers := peertls.ExtensionHandlers{
|
||||||
{
|
{
|
||||||
id: ExtensionIDs[RevocationExtID],
|
ID: peertls.ExtensionIDs[peertls.RevocationExtID],
|
||||||
verify: verify,
|
Verify: verify,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: ExtensionIDs[SignedCertExtID],
|
ID: peertls.ExtensionIDs[peertls.SignedCertExtID],
|
||||||
verify: verify,
|
Verify: verify,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
extMock.On("verify", chains[0][LeafIndex].ExtraExtensions[0], chains).Return(nil)
|
extMock.On("verify", chains[0][peertls.LeafIndex].ExtraExtensions[0], chains).Return(nil)
|
||||||
extMock.On("verify", chains[0][LeafIndex].ExtraExtensions[1], chains).Return(nil)
|
extMock.On("verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains).Return(nil)
|
||||||
|
|
||||||
err = handlers.VerifyFunc()(nil, chains)
|
err = handlers.VerifyFunc()(nil, chains)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
extMock.AssertCalled(t, "verify", chains[0][LeafIndex].ExtraExtensions[0], chains)
|
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[0], chains)
|
||||||
extMock.AssertCalled(t, "verify", chains[0][LeafIndex].ExtraExtensions[1], chains)
|
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains)
|
||||||
extMock.AssertExpectations(t)
|
extMock.AssertExpectations(t)
|
||||||
|
|
||||||
// TODO: test error scenario(s)
|
// TODO: test error scenario(s)
|
||||||
@ -552,13 +555,13 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
revokedLeafKeys, revokedLeafChain, err := newRevokedLeafChain()
|
revokedLeafKeys, revokedLeafChain, err := newRevokedLeafChain()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
whitelistSignedKeys, whitelistSignedChain, err := newCertChain(3)
|
whitelistSignedKeys, whitelistSignedChain, err := testpeertls.NewCertChain(3)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = AddSignedCertExt(whitelistSignedKeys[0], whitelistSignedChain[0])
|
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], whitelistSignedChain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
_, unrelatedChain, err := newCertChain(1)
|
_, unrelatedChain, err := testpeertls.NewCertChain(1)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
tmp, err := ioutil.TempDir("", "TestParseExtensions")
|
tmp, err := ioutil.TempDir("", "TestParseExtensions")
|
||||||
@ -567,12 +570,12 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
defer func() { _ = os.RemoveAll(tmp) }()
|
defer func() { _ = os.RemoveAll(tmp) }()
|
||||||
revDB, err := NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
revDB, err := peertls.NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
testID string
|
testID string
|
||||||
config TLSExtConfig
|
config peertls.TLSExtConfig
|
||||||
extLen int
|
extLen int
|
||||||
certChain []*x509.Certificate
|
certChain []*x509.Certificate
|
||||||
whitelist []*x509.Certificate
|
whitelist []*x509.Certificate
|
||||||
@ -581,7 +584,7 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"leaf whitelist signature - success",
|
"leaf whitelist signature - success",
|
||||||
TLSExtConfig{WhitelistSignedLeaf: true},
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
||||||
1,
|
1,
|
||||||
whitelistSignedChain,
|
whitelistSignedChain,
|
||||||
[]*x509.Certificate{whitelistSignedChain[2]},
|
[]*x509.Certificate{whitelistSignedChain[2]},
|
||||||
@ -590,25 +593,25 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"leaf whitelist signature - failure (empty whitelist)",
|
"leaf whitelist signature - failure (empty whitelist)",
|
||||||
TLSExtConfig{WhitelistSignedLeaf: true},
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
||||||
1,
|
1,
|
||||||
whitelistSignedChain,
|
whitelistSignedChain,
|
||||||
nil,
|
nil,
|
||||||
&ErrVerifyCAWhitelist,
|
&peertls.ErrVerifyCAWhitelist,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"leaf whitelist signature - failure",
|
"leaf whitelist signature - failure",
|
||||||
TLSExtConfig{WhitelistSignedLeaf: true},
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
||||||
1,
|
1,
|
||||||
whitelistSignedChain,
|
whitelistSignedChain,
|
||||||
unrelatedChain,
|
unrelatedChain,
|
||||||
&ErrVerifyCAWhitelist,
|
&peertls.ErrVerifyCAWhitelist,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"certificate revocation - single revocation ",
|
"certificate revocation - single revocation ",
|
||||||
TLSExtConfig{Revocation: true},
|
peertls.TLSExtConfig{Revocation: true},
|
||||||
1,
|
1,
|
||||||
revokedLeafChain,
|
revokedLeafChain,
|
||||||
nil,
|
nil,
|
||||||
@ -617,10 +620,10 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"certificate revocation - serial revocations",
|
"certificate revocation - serial revocations",
|
||||||
TLSExtConfig{Revocation: true},
|
peertls.TLSExtConfig{Revocation: true},
|
||||||
1,
|
1,
|
||||||
func() []*x509.Certificate {
|
func() []*x509.Certificate {
|
||||||
rev := new(Revocation)
|
rev := new(peertls.Revocation)
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
_, chain, err := revokeLeaf(revokedLeafKeys, revokedLeafChain)
|
_, chain, err := revokeLeaf(revokedLeafKeys, revokedLeafChain)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -636,13 +639,13 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"certificate revocation - serial revocations error (older timestamp)",
|
"certificate revocation - serial revocations error (older timestamp)",
|
||||||
TLSExtConfig{Revocation: true},
|
peertls.TLSExtConfig{Revocation: true},
|
||||||
1,
|
1,
|
||||||
func() []*x509.Certificate {
|
func() []*x509.Certificate {
|
||||||
keys, chain, err := newRevokedLeafChain()
|
keys, chain, err := newRevokedLeafChain()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
rev := new(Revocation)
|
rev := new(peertls.Revocation)
|
||||||
err = rev.Unmarshal(chain[0].ExtraExtensions[0].Value)
|
err = rev.Unmarshal(chain[0].ExtraExtensions[0].Value)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
@ -654,25 +657,25 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = revDB.Put(chain, pkix.Extension{
|
err = revDB.Put(chain, pkix.Extension{
|
||||||
Id: ExtensionIDs[RevocationExtID],
|
Id: peertls.ExtensionIDs[peertls.RevocationExtID],
|
||||||
Value: revBytes,
|
Value: revBytes,
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
return chain
|
return chain
|
||||||
}(),
|
}(),
|
||||||
nil,
|
nil,
|
||||||
&ErrExtension,
|
&peertls.ErrExtension,
|
||||||
ErrRevocationTimestamp,
|
peertls.ErrRevocationTimestamp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"certificate revocation and leaf whitelist signature",
|
"certificate revocation and leaf whitelist signature",
|
||||||
TLSExtConfig{Revocation: true, WhitelistSignedLeaf: true},
|
peertls.TLSExtConfig{Revocation: true, WhitelistSignedLeaf: true},
|
||||||
2,
|
2,
|
||||||
func() []*x509.Certificate {
|
func() []*x509.Certificate {
|
||||||
_, chain, err := newRevokedLeafChain()
|
_, chain, err := newRevokedLeafChain()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
err = AddSignedCertExt(whitelistSignedKeys[0], chain[0])
|
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], chain[0])
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
return chain
|
return chain
|
||||||
@ -685,12 +688,12 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
t.Run(c.testID, func(t *testing.T) {
|
t.Run(c.testID, func(t *testing.T) {
|
||||||
opts := ParseExtOptions{
|
opts := peertls.ParseExtOptions{
|
||||||
CAWhitelist: c.whitelist,
|
CAWhitelist: c.whitelist,
|
||||||
RevDB: revDB,
|
RevDB: revDB,
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers := ParseExtensions(c.config, opts)
|
handlers := peertls.ParseExtensions(c.config, opts)
|
||||||
assert.Equal(t, c.extLen, len(handlers))
|
assert.Equal(t, c.extLen, len(handlers))
|
||||||
err := handlers.VerifyFunc()(nil, [][]*x509.Certificate{c.certChain})
|
err := handlers.VerifyFunc()(nil, [][]*x509.Certificate{c.certChain})
|
||||||
if c.errClass != nil {
|
if c.errClass != nil {
|
||||||
@ -706,57 +709,23 @@ func TestParseExtensions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NB: keys are in the reverse order compared to certs (i.e. first key belongs to last cert)!
|
|
||||||
func newCertChain(length int) (keys []crypto.PrivateKey, certs []*x509.Certificate, _ error) {
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
key, err := NewKey()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
keys = append(keys, key)
|
|
||||||
|
|
||||||
var template *x509.Certificate
|
|
||||||
if i == length-1 {
|
|
||||||
template, err = CATemplate()
|
|
||||||
} else {
|
|
||||||
template, err = LeafTemplate()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cert *x509.Certificate
|
|
||||||
if i == 0 {
|
|
||||||
cert, err = NewCert(key, nil, template, nil)
|
|
||||||
} else {
|
|
||||||
cert, err = NewCert(key, keys[i-1], template, certs[i-1:][0])
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
certs = append([]*x509.Certificate{cert}, certs...)
|
|
||||||
}
|
|
||||||
return keys, certs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func revokeLeaf(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
func revokeLeaf(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
||||||
revokingKey, err := NewKey()
|
revokingKey, err := peertls.NewKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
revokingTemplate, err := LeafTemplate()
|
revokingTemplate, err := peertls.LeafTemplate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
revokingCert, err := NewCert(revokingKey, keys[0], revokingTemplate, chain[1])
|
revokingCert, err := peertls.NewCert(revokingKey, keys[0], revokingTemplate, chain[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = AddRevocationExt(keys[0], chain[0], revokingCert)
|
err = peertls.AddRevocationExt(keys[0], chain[0], revokingCert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -765,7 +734,7 @@ func revokeLeaf(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]crypto.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newRevokedLeafChain() ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
func newRevokedLeafChain() ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
||||||
keys2, certs2, err := newCertChain(2)
|
keys2, certs2, err := testpeertls.NewCertChain(2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,8 @@ import (
|
|||||||
"github.com/mr-tron/base58/base58"
|
"github.com/mr-tron/base58/base58"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"storj.io/storj/internal/identity"
|
"storj.io/storj/internal/testcontext"
|
||||||
|
"storj.io/storj/internal/testidentity"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewPieceID(t *testing.T) {
|
func TestNewPieceID(t *testing.T) {
|
||||||
@ -26,8 +27,9 @@ func TestNewPieceID(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDerivePieceID(t *testing.T) {
|
func TestDerivePieceID(t *testing.T) {
|
||||||
|
ctx := testcontext.New(t)
|
||||||
pid := NewPieceID()
|
pid := NewPieceID()
|
||||||
fid, err := testidentity.NewTestIdentity()
|
fid, err := testidentity.NewTestIdentity(ctx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
did, err := pid.Derive(fid.ID.Bytes())
|
did, err := pid.Derive(fid.ID.Bytes())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
@ -28,9 +28,9 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"storj.io/storj/internal/identity"
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
"storj.io/storj/pkg/piecestore"
|
pstore "storj.io/storj/pkg/piecestore"
|
||||||
"storj.io/storj/pkg/piecestore/psserver/psdb"
|
"storj.io/storj/pkg/piecestore/psserver/psdb"
|
||||||
"storj.io/storj/pkg/storj"
|
"storj.io/storj/pkg/storj"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ import (
|
|||||||
"google.golang.org/grpc/peer"
|
"google.golang.org/grpc/peer"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
"storj.io/storj/internal/identity"
|
"storj.io/storj/internal/testidentity"
|
||||||
"storj.io/storj/pkg/auth"
|
"storj.io/storj/pkg/auth"
|
||||||
"storj.io/storj/pkg/pb"
|
"storj.io/storj/pkg/pb"
|
||||||
"storj.io/storj/pkg/storage/meta"
|
"storj.io/storj/pkg/storage/meta"
|
||||||
|
Loading…
Reference in New Issue
Block a user