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:
Bryan White 2019-01-02 12:39:17 -05:00 committed by GitHub
parent b712fbcbb0
commit 249244536a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 1180 additions and 380 deletions

View File

@ -57,7 +57,7 @@ goimports-fix: ## Applies goimports to every go file (excluding vendored files)
.PHONY: goimports-st
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
proto: ## Rebuild protobuf files

3
go.mod
View File

@ -42,7 +42,7 @@ require (
github.com/go-redis/redis v6.14.1+incompatible
github.com/gogo/protobuf v1.1.2-0.20181116123445-07eab6a8298c
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/gomodule/redigo v2.0.0+incompatible // indirect
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-streaming-server v0.11.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/pierrec/lz4 v2.0.5+incompatible // indirect
github.com/pkg/profile v1.2.1 // indirect

10
go.sum
View File

@ -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/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
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/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
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/go.mod h1:2871C3urfEJnq/bpTYjFdMOdgxVd8otLLEL6vMNy/Iw=
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/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-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/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
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/nuid v1.0.0 h1:44QGdhbiANq8ZCbUkdn6W5bqtg+mHuDE4wOUuxxndFs=
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/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
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/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/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/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=

View 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)
}

View File

@ -11,8 +11,8 @@ import (
// NewTestIdentity is a helper function to generate new node identities with
// correct difficulty and concurrency
func NewTestIdentity() (*provider.FullIdentity, error) {
ca, err := provider.NewCA(context.Background(), provider.NewCAOptions{
func NewTestIdentity(ctx context.Context) (*provider.FullIdentity, error) {
ca, err := provider.NewCA(ctx, provider.NewCAOptions{
Difficulty: 12,
Concurrency: 4,
})

View File

@ -4,102 +4,43 @@
package testpeertls
import (
"bytes"
"crypto/ecdsa"
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/json"
"fmt"
"math/big"
"storj.io/storj/pkg/peertls"
)
// DebugCert is a subset of the most relevant fields from an x509.Certificate for debugging
type DebugCert struct {
Raw []byte
RawTBSCertificate []byte
Signature []byte
PublicKeyX *big.Int
PublicKeyY *big.Int
Extensions []pkix.Extension
// NewCertChain creates a valid peertls certificate chain (and respective keys) of the desired length.
// 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 := peertls.NewKey()
if err != nil {
return nil, nil, err
}
keys = append(keys, key)
// NewCertDebug converts an *x509.Certificate into a DebugCert
func NewCertDebug(cert x509.Certificate) DebugCert {
pubKey := cert.PublicKey.(*ecdsa.PublicKey)
c := DebugCert{
Raw: make([]byte, len(cert.Raw)),
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 + ": ---================================================================---")
var template *x509.Certificate
if i == length-1 {
template, err = peertls.CATemplate()
} else {
template, err = peertls.LeafTemplate()
}
if err != nil {
fmt.Printf("ERROR: %s", err.Error())
return nil, nil, err
}
fmt.Println(string(jsonBytes))
fmt.Println("")
var cert *x509.Certificate
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
}

View File

@ -11,8 +11,8 @@ import (
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/testcontext"
"storj.io/storj/internal/testidentity"
"storj.io/storj/pkg/bwagreement"
"storj.io/storj/pkg/bwagreement/test"
"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)
//get a private key
fiC, err := testidentity.NewTestIdentity()
fiC, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
k, ok := fiC.Key.(*ecdsa.PrivateKey)
assert.True(t, ok)

View File

@ -16,7 +16,7 @@ import (
"go.uber.org/zap"
"google.golang.org/grpc"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/testidentity"
"storj.io/storj/internal/teststorj"
"storj.io/storj/pkg/auth"
"storj.io/storj/pkg/overlay"

View File

@ -11,7 +11,7 @@ import (
"github.com/gtank/cryptopasta"
"github.com/stretchr/testify/assert"
"storj.io/storj/internal/identity"
"storj.io/storj/internal/testidentity"
)
func TestGenerateSignature(t *testing.T) {

View File

@ -11,8 +11,8 @@ import (
"github.com/stretchr/testify/assert"
"go.uber.org/zap"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/testcontext"
"storj.io/storj/internal/testidentity"
"storj.io/storj/pkg/bwagreement"
"storj.io/storj/pkg/pb"
"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) {
fiS, err := testidentity.NewTestIdentity()
fiS, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
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)
assert.True(t, ok)
fiU, err := testidentity.NewTestIdentity()
fiU, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
uplinkPrivKey, ok = fiU.Key.(*ecdsa.PrivateKey)

View File

@ -6,21 +6,27 @@ package certificates
import (
"bytes"
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/gob"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/btcsuite/btcutil/base58"
"github.com/zeebo/errs"
"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/peertls"
"storj.io/storj/pkg/provider"
"storj.io/storj/pkg/transport"
"storj.io/storj/pkg/utils"
"storj.io/storj/storage"
"storj.io/storj/storage/boltdb"
@ -30,6 +36,9 @@ import (
const (
// AuthorizationsBucket is the bucket used with a bolt-backed authorizations DB.
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
tokenDelimiter = ":"
tokenVersion = 0
@ -47,15 +56,26 @@ var (
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 {
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"`
MinDifficulty uint `help:"minimum difficulty of the requester's identity required to claim an authorization"`
CA provider.FullCAConfig
}
// CertificateSigner implements pb.CertificatesServer
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
@ -82,22 +102,52 @@ type Token struct {
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
// token was claimed.
type Claim struct {
IP string
Addr string
Timestamp int64
Identity *provider.PeerIdentity
SignedCert *x509.Certificate
SignedChainBytes [][]byte
}
// NewServer creates a new certificate signing grpc server
func NewServer(log *zap.Logger) pb.CertificatesServer {
srv := CertificateSigner{
Log: log,
// Client implements pb.CertificateClient
type Client struct {
client pb.CertificatesClient
}
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
@ -141,6 +191,20 @@ func ParseToken(tokenString string) (*Token, error) {
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
func (c CertSignerConfig) NewAuthDB() (*AuthorizationDB, error) {
// 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) {
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)
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.
func (c CertificateSigner) Sign(ctx context.Context, req *pb.SigningRequest) (*pb.SigningResponse, error) {
// lookup authtoken
// sign cert
// send response
return &pb.SigningResponse{}, nil
grpcPeer, ok := peer.FromContext(ctx)
if !ok {
// TODO: better error
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.
@ -214,11 +328,6 @@ func (a *AuthorizationDB) Create(userID string, count int) (Authorizations, erro
return nil, ErrAuthorizationCount
}
existingAuths, err := a.Get(userID)
if err != nil {
return nil, err
}
var (
newAuths Authorizations
authErrs utils.ErrorGroup
@ -235,22 +344,16 @@ func (a *AuthorizationDB) Create(userID string, count int) (Authorizations, erro
return nil, ErrAuthorizationDB.Wrap(err)
}
existingAuths = append(existingAuths, newAuths...)
authsBytes, err := existingAuths.Marshal()
if err != nil {
return nil, ErrAuthorizationDB.Wrap(err)
}
if err := a.DB.Put(storage.Key(userID), authsBytes); err != nil {
return nil, ErrAuthorizationDB.Wrap(err)
if err := a.add(userID, newAuths); err != nil {
return nil, err
}
return newAuths, nil
}
// Get retrieves authorizations by email.
func (a *AuthorizationDB) Get(email string) (Authorizations, error) {
authsBytes, err := a.DB.Get(storage.Key(email))
// Get retrieves authorizations by user ID.
func (a *AuthorizationDB) Get(userID string) (Authorizations, error) {
authsBytes, err := a.DB.Get(storage.Key(userID))
if err != nil && !storage.ErrKeyNotFound.Has(err) {
return nil, ErrAuthorizationDB.Wrap(err)
}
@ -274,6 +377,84 @@ func (a *AuthorizationDB) UserIDs() ([]string, error) {
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
func (a *Authorizations) Unmarshal(data []byte) error {
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
// from completely leaking into logs.
// from completely leaking into logs and errors.
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

View File

@ -5,22 +5,36 @@ package certificates
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/gob"
"fmt"
"net"
"path/filepath"
"strings"
"testing"
"time"
"github.com/btcsuite/btcutil/base58"
"github.com/stretchr/testify/assert"
"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/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/storage"
)
var (
idents = testplanet.NewPregeneratedIdentities()
t1 = Token{
UserID: "user@example.com",
Data: [tokenDataLength]byte{1, 2, 3},
@ -33,14 +47,12 @@ var (
func TestCertSignerConfig_NewAuthDB(t *testing.T) {
ctx := testcontext.New(t)
defer ctx.Cleanup()
authDB, err := newTestAuthDB(ctx)
if !assert.NoError(t, err) {
t.Fatal(err)
}
defer func() {
_ = authDB.Close()
ctx.Cleanup()
}()
defer ctx.Check(authDB.Close)
assert.NotNil(t, authDB)
assert.NotNil(t, authDB.DB)
@ -48,14 +60,12 @@ func TestCertSignerConfig_NewAuthDB(t *testing.T) {
func TestAuthorizationDB_Create(t *testing.T) {
ctx := testcontext.New(t)
defer ctx.Cleanup()
authDB, err := newTestAuthDB(ctx)
if !assert.NoError(t, err) {
t.Fatal(err)
}
defer func() {
_ = authDB.Close()
ctx.Cleanup()
}()
defer ctx.Check(authDB.Close)
cases := []struct {
testID,
@ -144,7 +154,8 @@ func TestAuthorizationDB_Get(t *testing.T) {
t.Fatal(err)
}
defer func() {
_ = authDB.Close()
err := authDB.Close()
assert.NoError(t, err)
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) {
userID := "user@example.com"
auth, err := NewAuthorization(userID)
@ -274,7 +504,8 @@ func TestAuthorizationDB_Emails(t *testing.T) {
t.Fatal(err)
}
defer func() {
_ = authDB.Close()
err = authDB.Close()
assert.NoError(t, err)
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) {
dbPath := "bolt://" + filepath.Join(ctx.Dir(), "authorizations.db")
config := CertSignerConfig{

View File

@ -9,6 +9,8 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"storj.io/storj/internal/testcontext"
)
func TestNewCA(t *testing.T) {
@ -27,21 +29,19 @@ func TestNewCA(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,
Concurrency: 4,
})
if err != nil {
if !assert.NoError(t, err) || !assert.NotNil(t, ca) {
t.Fatal(err)
}
assert.NotEmpty(t, ca)
fi, err := ca.NewIdentity()
if err != nil {
if !assert.NoError(t, err) || !assert.NotNil(t, fi) {
t.Fatal(err)
}
assert.NotEmpty(t, fi)
assert.Equal(t, ca.Cert, fi.CA)
assert.Equal(t, ca.ID, fi.ID)
@ -52,6 +52,36 @@ func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
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) {
// TODO(bryanchriswhite): test with both
// TODO(bryanchriswhite): test with only cert path

View File

@ -8,6 +8,7 @@ import (
"context"
"crypto"
"crypto/ecdsa"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"io/ioutil"
@ -75,6 +76,38 @@ type FullCAConfig struct {
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
func (caS CASetupConfig) Status() TLSFilesStatus {
return statTLSFiles(caS.CertPath, caS.KeyPath)
@ -175,39 +208,6 @@ func (pc PeerCAConfig) Load() (*PeerCertificateAuthority, error) {
}, 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
func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
var (
@ -246,7 +246,7 @@ func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
// 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
// is signed by the CA.
func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
func (ca *FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
leafTemplate, err := peertls.LeafTemplate()
if err != nil {
return nil, err
@ -278,4 +278,29 @@ func (ca FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
Key: leafKey,
ID: ca.ID,
}, 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
}

View File

@ -131,7 +131,7 @@ func PeerIdentityFromPeer(peer *peer.Peer) (*PeerIdentity, error) {
if len(c) < 2 {
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 {
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 {
var chain [][]byte
for _, cert := range fi.RestChain {

View File

@ -21,9 +21,10 @@ import (
"go.uber.org/zap/zaptest"
"google.golang.org/grpc"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/testcontext"
"storj.io/storj/internal/testidentity"
"storj.io/storj/internal/teststorj"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/node"
"storj.io/storj/pkg/pb"
"storj.io/storj/pkg/provider"
@ -41,14 +42,14 @@ func TestNewKademlia(t *testing.T) {
rootdir, cleanup := mktempdir(t, "kademlia")
defer cleanup()
cases := []struct {
id *provider.FullIdentity
id *identity.FullIdentity
bn []pb.Node
addr string
expectedErr error
}{
{
id: func() *provider.FullIdentity {
id, err := testidentity.NewTestIdentity()
id: func() *identity.FullIdentity {
id, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
return id
}(),
@ -57,7 +58,7 @@ func TestNewKademlia(t *testing.T) {
},
{
id: func() *provider.FullIdentity {
id, err := testidentity.NewTestIdentity()
id, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
return id
}(),
@ -163,12 +164,13 @@ func TestBootstrap(t *testing.T) {
}
func testNode(t *testing.T, bn []pb.Node) (*Kademlia, *grpc.Server, func()) {
ctx := testcontext.New(t)
// new address
lis, err := net.Listen("tcp", "127.0.0.1:0")
assert.NoError(t, err)
// new config
// new identity
fid, err := testidentity.NewTestIdentity()
fid, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
// new kademlia
dir, cleanup := mktempdir(t, "kademlia")
@ -238,9 +240,9 @@ func TestGetNodes(t *testing.T) {
defer srv.Stop()
// make new identity
fid, err := testidentity.NewTestIdentity()
fid, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
fid2, err := testidentity.NewTestIdentity()
fid2, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
fid.ID = nodeIDA
fid2.ID = nodeIDB

View File

@ -17,9 +17,9 @@ import (
"go.uber.org/zap"
"go.uber.org/zap/zaptest"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/s3client"
"storj.io/storj/internal/testcontext"
"storj.io/storj/internal/testidentity"
"storj.io/storj/internal/testplanet"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/miniogw"

View File

@ -9,8 +9,8 @@ import (
"github.com/stretchr/testify/assert"
testidentity "storj.io/storj/internal/identity"
"storj.io/storj/internal/testcontext"
"storj.io/storj/internal/testidentity"
"storj.io/storj/internal/testplanet"
"storj.io/storj/pkg/overlay"
"storj.io/storj/pkg/pb"

View File

@ -36,7 +36,7 @@ func (m *SigningRequest) Reset() { *m = SigningRequest{} }
func (m *SigningRequest) String() string { return proto.CompactTextString(m) }
func (*SigningRequest) ProtoMessage() {}
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 {
return xxx_messageInfo_SigningRequest.Unmarshal(m, b)
@ -71,7 +71,7 @@ func (m *SigningRequest) GetTimestamp() int64 {
}
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_unrecognized []byte `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 (*SigningResponse) ProtoMessage() {}
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 {
return xxx_messageInfo_SigningResponse.Unmarshal(m, b)
@ -101,9 +101,9 @@ func (m *SigningResponse) XXX_DiscardUnknown() {
var xxx_messageInfo_SigningResponse proto.InternalMessageInfo
func (m *SigningResponse) GetCert() []byte {
func (m *SigningResponse) GetChain() [][]byte {
if m != nil {
return m.Cert
return m.Chain
}
return nil
}
@ -185,20 +185,20 @@ var _Certificates_serviceDesc = grpc.ServiceDesc{
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{
// 188 bytes of a gzipped FileDescriptorProto
var fileDescriptor_certificate_c55bd879e75eb964 = []byte{
// 192 bytes of a gzipped FileDescriptorProto
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,
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,
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,
0xc5, 0x25, 0x89, 0xb9, 0x05, 0x12, 0x4c, 0x0a, 0x8c, 0x1a, 0xcc, 0x41, 0x08, 0x01, 0x25, 0x55,
0x2e, 0x7e, 0xb8, 0x71, 0xc5, 0x05, 0xf9, 0x79, 0xc5, 0xa9, 0x42, 0x42, 0x5c, 0x2c, 0x20, 0x87,
0x80, 0x4d, 0xe2, 0x09, 0x02, 0xb3, 0x8d, 0x9c, 0xb9, 0x78, 0x9c, 0x11, 0x8e, 0x2b, 0x16, 0x32,
0xe6, 0x62, 0x01, 0x69, 0x13, 0x12, 0xd1, 0x03, 0x39, 0x50, 0x0f, 0xd5, 0x45, 0x52, 0xa2, 0x68,
0xa2, 0x10, 0x83, 0x9d, 0x58, 0xa2, 0x98, 0x0a, 0x92, 0x92, 0xd8, 0xc0, 0xfe, 0x30, 0x06, 0x04,
0x00, 0x00, 0xff, 0xff, 0x4b, 0x1b, 0x87, 0x66, 0xee, 0x00, 0x00, 0x00,
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, 0x22, 0x5c, 0xac, 0xc9, 0x19,
0x89, 0x99, 0x20, 0xa3, 0x98, 0x35, 0x78, 0x82, 0x20, 0x1c, 0x23, 0x67, 0x2e, 0x1e, 0x67, 0x84,
0xf3, 0x8a, 0x85, 0x8c, 0xb9, 0x58, 0x40, 0x1a, 0x85, 0x44, 0xf4, 0x40, 0x4e, 0xd4, 0x43, 0x75,
0x93, 0x94, 0x28, 0x9a, 0x28, 0xc4, 0x68, 0x27, 0x96, 0x28, 0xa6, 0x82, 0xa4, 0x24, 0x36, 0xb0,
0x4f, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0x3a, 0xd8, 0xc7, 0x87, 0xf0, 0x00, 0x00, 0x00,
}

View File

@ -18,5 +18,5 @@ message SigningRequest {
}
message SigningResponse {
bytes cert = 1;
repeated bytes chain = 1;
}

View File

@ -72,13 +72,15 @@ type TLSExtConfig struct {
}
// 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 extensionHandler struct {
id asn1.ObjectIdentifier
verify extensionVerificationFunc
// ExtensionHandler represents a verify function for handling an extension
// with the given ID
type ExtensionHandler struct {
ID asn1.ObjectIdentifier
Verify extensionVerificationFunc
}
// ParseExtOptions holds options for calling `ParseExtensions`
@ -107,16 +109,16 @@ type RevocationDB struct {
// to be used in the context of peer certificate verification.
func ParseExtensions(c TLSExtConfig, opts ParseExtOptions) (handlers ExtensionHandlers) {
if c.WhitelistSignedLeaf {
handlers = append(handlers, extensionHandler{
id: ExtensionIDs[SignedCertExtID],
verify: verifyCAWhitelistSignedLeafFunc(opts.CAWhitelist),
handlers = append(handlers, ExtensionHandler{
ID: ExtensionIDs[SignedCertExtID],
Verify: verifyCAWhitelistSignedLeafFunc(opts.CAWhitelist),
})
}
if c.Revocation {
handlers = append(handlers, extensionHandler{
id: ExtensionIDs[RevocationExtID],
verify: func(certExt pkix.Extension, chains [][]*x509.Certificate) error {
handlers = append(handlers, ExtensionHandler{
ID: ExtensionIDs[RevocationExtID],
Verify: func(certExt pkix.Extension, chains [][]*x509.Certificate) error {
if err := opts.RevDB.Put(chains[0], certExt); err != nil {
return err
}
@ -245,8 +247,8 @@ func (e ExtensionHandlers) VerifyFunc() PeerCertVerificationFunc {
}
for _, handler := range e {
if ext, ok := leafExts[handler.id.String()]; ok {
err := handler.verify(ext, parsedChains)
if ext, ok := leafExts[handler.ID.String()]; ok {
err := handler.Verify(ext, parsedChains)
if err != nil {
return ErrExtension.Wrap(err)
}

View File

@ -1,7 +1,7 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package peertls
package peertls_test
import (
"bytes"
@ -21,16 +21,19 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/zeebo/errs"
"storj.io/storj/internal/testpeertls"
"storj.io/storj/pkg/peertls"
)
func TestNewCert_CA(t *testing.T) {
caKey, err := NewKey()
caKey, err := peertls.NewKey()
assert.NoError(t, err)
caTemplate, err := CATemplate()
caTemplate, err := peertls.CATemplate()
assert.NoError(t, err)
caCert, err := NewCert(caKey, nil, caTemplate, nil)
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
assert.NoError(t, err)
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
@ -42,22 +45,22 @@ func TestNewCert_CA(t *testing.T) {
}
func TestNewCert_Leaf(t *testing.T) {
caKey, err := NewKey()
caKey, err := peertls.NewKey()
assert.NoError(t, err)
caTemplate, err := CATemplate()
caTemplate, err := peertls.CATemplate()
assert.NoError(t, err)
caCert, err := NewCert(caKey, nil, caTemplate, nil)
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
assert.NoError(t, err)
leafKey, err := NewKey()
leafKey, err := peertls.NewKey()
assert.NoError(t, err)
leafTemplate, err := LeafTemplate()
leafTemplate, err := peertls.LeafTemplate()
assert.NoError(t, err)
leafCert, err := NewCert(leafKey, caKey, leafTemplate, caCert)
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caCert)
assert.NoError(t, err)
assert.NotEmpty(t, caKey.(*ecdsa.PrivateKey))
@ -71,7 +74,7 @@ func TestNewCert_Leaf(t *testing.T) {
}
func TestVerifyPeerFunc(t *testing.T) {
_, chain, err := newCertChain(2)
_, chain, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
@ -97,88 +100,88 @@ func TestVerifyPeerFunc(t *testing.T) {
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)
}
func TestVerifyPeerCertChains(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
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)
wrongKey, err := NewKey()
wrongKey, err := peertls.NewKey()
assert.NoError(t, err)
leafCert, err = NewCert(leafKey, wrongKey, leafCert, caCert)
leafCert, err = peertls.NewCert(leafKey, wrongKey, leafCert, caCert)
assert.NoError(t, err)
err = VerifyPeerFunc(VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
assert.True(t, ErrVerifyPeerCert.Has(err))
assert.True(t, ErrVerifyCertificateChain.Has(err))
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
assert.True(t, peertls.ErrVerifyPeerCert.Has(err))
assert.True(t, peertls.ErrVerifyCertificateChain.Has(err))
}
func TestVerifyCAWhitelist(t *testing.T) {
_, chain2, err := newCertChain(2)
_, chain2, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
leafCert, caCert := chain2[0], chain2[1]
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)
})
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)
})
_, unrelatedChain, err := newCertChain(1)
_, unrelatedChain, err := testpeertls.NewCertChain(1)
if !assert.NoError(t, err) {
t.FailNow()
}
unrelatedCert := unrelatedChain[0]
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)
assert.True(t, ErrVerifyCAWhitelist.Has(err))
err = peertls.VerifyPeerFunc(peertls.VerifyCAWhitelist([]*x509.Certificate{unrelatedCert}))([][]byte{leafCert.Raw, caCert.Raw}, nil)
assert.True(t, peertls.ErrVerifyCAWhitelist.Has(err))
})
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)
})
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)
})
_, chain3, err := newCertChain(3)
_, chain3, err := testpeertls.NewCertChain(3)
if !assert.NoError(t, err) {
t.FailNow()
}
leaf2Cert, ca2Cert, rootCert := chain3[0], chain3[1], chain3[2]
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)
})
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)
})
}
func TestAddExtension(t *testing.T) {
_, chain, err := newCertChain(1)
_, chain, err := testpeertls.NewCertChain(1)
if !assert.NoError(t, err) {
t.FailNow()
}
@ -197,30 +200,30 @@ func TestAddExtension(t *testing.T) {
Value: randBytes,
}
err = AddExtension(chain[0], ext)
err = peertls.AddExtension(chain[0], ext)
assert.NoError(t, err)
assert.Len(t, chain[0].ExtraExtensions, 1)
assert.Equal(t, ext, chain[0].ExtraExtensions[0])
}
func TestAddSignedCertExt(t *testing.T) {
keys, chain, err := newCertChain(1)
keys, chain, err := testpeertls.NewCertChain(1)
if !assert.NoError(t, err) {
t.FailNow()
}
err = AddSignedCertExt(keys[0], chain[0])
err = peertls.AddSignedCertExt(keys[0], chain[0])
assert.NoError(t, err)
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)
if !assert.True(t, ok) {
t.FailNow()
}
err = VerifySignature(
err = peertls.VerifySignature(
chain[0].ExtraExtensions[0].Value,
chain[0].RawTBSCertificate,
&ecKey.PublicKey,
@ -229,35 +232,35 @@ func TestAddSignedCertExt(t *testing.T) {
}
func TestSignLeafExt(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
caKey, leafCert := keys[0], chain[0]
err = AddSignedCertExt(caKey, leafCert)
err = peertls.AddSignedCertExt(caKey, leafCert)
assert.NoError(t, err)
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)
if !assert.True(t, ok) {
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)
}
func TestRevocation_Sign(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
assert.NoError(t, err)
leafCert, caKey := chain[0], keys[0]
leafHash, err := SHA256Hash(leafCert.Raw)
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
assert.NoError(t, err)
rev := Revocation{
rev := peertls.Revocation{
Timestamp: time.Now().Unix(),
CertHash: make([]byte, len(leafHash)),
}
@ -268,14 +271,14 @@ func TestRevocation_Sign(t *testing.T) {
}
func TestRevocation_Verify(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
assert.NoError(t, err)
leafCert, caCert, caKey := chain[0], chain[1], keys[0]
leafHash, err := SHA256Hash(leafCert.Raw)
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
assert.NoError(t, err)
rev := Revocation{
rev := peertls.Revocation{
Timestamp: time.Now().Unix(),
CertHash: make([]byte, len(leafHash)),
}
@ -289,14 +292,14 @@ func TestRevocation_Verify(t *testing.T) {
}
func TestRevocation_Marshal(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
assert.NoError(t, err)
leafCert, caKey := chain[0], keys[0]
leafHash, err := SHA256Hash(leafCert.Raw)
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
assert.NoError(t, err)
rev := Revocation{
rev := peertls.Revocation{
Timestamp: time.Now().Unix(),
CertHash: make([]byte, len(leafHash)),
}
@ -309,7 +312,7 @@ func TestRevocation_Marshal(t *testing.T) {
assert.NoError(t, err)
assert.NotEmpty(t, revBytes)
decodedRev := new(Revocation)
decodedRev := new(peertls.Revocation)
decoder := gob.NewDecoder(bytes.NewBuffer(revBytes))
err = decoder.Decode(decodedRev)
assert.NoError(t, err)
@ -317,14 +320,14 @@ func TestRevocation_Marshal(t *testing.T) {
}
func TestRevocation_Unmarshal(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
assert.NoError(t, err)
leafCert, caKey := chain[0], keys[0]
leafHash, err := SHA256Hash(leafCert.Raw)
leafHash, err := peertls.SHA256Hash(leafCert.Raw)
assert.NoError(t, err)
rev := Revocation{
rev := peertls.Revocation{
Timestamp: time.Now().Unix(),
CertHash: make([]byte, len(leafHash)),
}
@ -338,7 +341,7 @@ func TestRevocation_Unmarshal(t *testing.T) {
err = encoder.Encode(rev)
assert.NoError(t, err)
unmarshaledRev := new(Revocation)
unmarshaledRev := new(peertls.Revocation)
err = unmarshaledRev.Unmarshal(encodedRev.Bytes())
assert.NoError(t, err)
assert.NotNil(t, rev)
@ -346,13 +349,13 @@ func TestRevocation_Unmarshal(t *testing.T) {
}
func TestNewRevocationExt(t *testing.T) {
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
assert.NoError(t, err)
ext, err := NewRevocationExt(keys[0], chain[0])
ext, err := peertls.NewRevocationExt(keys[0], chain[0])
assert.NoError(t, err)
var rev Revocation
var rev peertls.Revocation
err = rev.Unmarshal(ext.Value)
assert.NoError(t, err)
@ -364,29 +367,29 @@ func TestRevocationDB_Get(t *testing.T) {
tmp, err := ioutil.TempDir("", "TestRevocationDB_Get")
defer func() { _ = os.RemoveAll(tmp) }()
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
ext, err := NewRevocationExt(keys[0], chain[0])
ext, err := peertls.NewRevocationExt(keys[0], chain[0])
if !assert.NoError(t, err) {
t.FailNow()
}
revDB, err := NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
revDB, err := peertls.NewRevocationDBBolt(filepath.Join(tmp, "revocations.db"))
if !assert.NoError(t, err) {
t.FailNow()
}
var rev *Revocation
var rev *peertls.Revocation
t.Run("missing key", func(t *testing.T) {
rev, err = revDB.Get(chain)
assert.NoError(t, err)
assert.Nil(t, rev)
})
caHash, err := SHA256Hash(chain[1].Raw)
caHash, err := peertls.SHA256Hash(chain[1].Raw)
if !assert.NoError(t, err) {
t.FailNow()
}
@ -410,25 +413,25 @@ func TestRevocationDB_Put(t *testing.T) {
tmp, err := ioutil.TempDir("", "TestRevocationDB_Put")
defer func() { _ = os.RemoveAll(tmp) }()
keys, chain, err := newCertChain(2)
keys, chain, err := testpeertls.NewCertChain(2)
if !assert.NoError(t, err) {
t.FailNow()
}
olderExt, err := NewRevocationExt(keys[0], chain[0])
olderExt, err := peertls.NewRevocationExt(keys[0], chain[0])
assert.NoError(t, err)
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) {
t.FailNow()
}
time.Sleep(1 * time.Second)
newerExt, err := NewRevocationExt(keys[0], chain[0])
newerExt, err := peertls.NewRevocationExt(keys[0], chain[0])
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) {
t.FailNow()
}
@ -448,8 +451,8 @@ func TestRevocationDB_Put(t *testing.T) {
{
"existing key - older timestamp",
olderExt,
&ErrExtension,
ErrRevocationTimestamp,
&peertls.ErrExtension,
peertls.ErrRevocationTimestamp,
},
{
"existing key - newer timestamp",
@ -480,7 +483,7 @@ func TestRevocationDB_Put(t *testing.T) {
t.FailNow()
}
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) {
t2.FailNow()
}
@ -490,7 +493,7 @@ func TestRevocationDB_Put(t *testing.T) {
t2.FailNow()
}
rev := new(Revocation)
rev := new(peertls.Revocation)
err = rev.Unmarshal(revBytes)
assert.NoError(t2, err)
assert.True(t2, bytes.Equal(ext.Value, revBytes))
@ -515,7 +518,7 @@ func TestExtensionHandlers_VerifyFunc(t *testing.T) {
if !assert.NoError(t, err) {
t.FailNow()
}
err = AddSignedCertExt(keys[0], chain[0])
err = peertls.AddSignedCertExt(keys[0], chain[0])
if !assert.NoError(t, err) {
t.FailNow()
}
@ -525,24 +528,24 @@ func TestExtensionHandlers_VerifyFunc(t *testing.T) {
return extMock.verify(ext, chain)
}
handlers := ExtensionHandlers{
handlers := peertls.ExtensionHandlers{
{
id: ExtensionIDs[RevocationExtID],
verify: verify,
ID: peertls.ExtensionIDs[peertls.RevocationExtID],
Verify: verify,
},
{
id: ExtensionIDs[SignedCertExtID],
verify: verify,
ID: peertls.ExtensionIDs[peertls.SignedCertExtID],
Verify: verify,
},
}
extMock.On("verify", chains[0][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[0], chains).Return(nil)
extMock.On("verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains).Return(nil)
err = handlers.VerifyFunc()(nil, chains)
assert.NoError(t, err)
extMock.AssertCalled(t, "verify", chains[0][LeafIndex].ExtraExtensions[0], chains)
extMock.AssertCalled(t, "verify", chains[0][LeafIndex].ExtraExtensions[1], chains)
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[0], chains)
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains)
extMock.AssertExpectations(t)
// TODO: test error scenario(s)
@ -552,13 +555,13 @@ func TestParseExtensions(t *testing.T) {
revokedLeafKeys, revokedLeafChain, err := newRevokedLeafChain()
assert.NoError(t, err)
whitelistSignedKeys, whitelistSignedChain, err := newCertChain(3)
whitelistSignedKeys, whitelistSignedChain, err := testpeertls.NewCertChain(3)
assert.NoError(t, err)
err = AddSignedCertExt(whitelistSignedKeys[0], whitelistSignedChain[0])
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], whitelistSignedChain[0])
assert.NoError(t, err)
_, unrelatedChain, err := newCertChain(1)
_, unrelatedChain, err := testpeertls.NewCertChain(1)
assert.NoError(t, err)
tmp, err := ioutil.TempDir("", "TestParseExtensions")
@ -567,12 +570,12 @@ func TestParseExtensions(t *testing.T) {
}
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)
cases := []struct {
testID string
config TLSExtConfig
config peertls.TLSExtConfig
extLen int
certChain []*x509.Certificate
whitelist []*x509.Certificate
@ -581,7 +584,7 @@ func TestParseExtensions(t *testing.T) {
}{
{
"leaf whitelist signature - success",
TLSExtConfig{WhitelistSignedLeaf: true},
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
1,
whitelistSignedChain,
[]*x509.Certificate{whitelistSignedChain[2]},
@ -590,25 +593,25 @@ func TestParseExtensions(t *testing.T) {
},
{
"leaf whitelist signature - failure (empty whitelist)",
TLSExtConfig{WhitelistSignedLeaf: true},
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
1,
whitelistSignedChain,
nil,
&ErrVerifyCAWhitelist,
&peertls.ErrVerifyCAWhitelist,
nil,
},
{
"leaf whitelist signature - failure",
TLSExtConfig{WhitelistSignedLeaf: true},
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
1,
whitelistSignedChain,
unrelatedChain,
&ErrVerifyCAWhitelist,
&peertls.ErrVerifyCAWhitelist,
nil,
},
{
"certificate revocation - single revocation ",
TLSExtConfig{Revocation: true},
peertls.TLSExtConfig{Revocation: true},
1,
revokedLeafChain,
nil,
@ -617,10 +620,10 @@ func TestParseExtensions(t *testing.T) {
},
{
"certificate revocation - serial revocations",
TLSExtConfig{Revocation: true},
peertls.TLSExtConfig{Revocation: true},
1,
func() []*x509.Certificate {
rev := new(Revocation)
rev := new(peertls.Revocation)
time.Sleep(1 * time.Second)
_, chain, err := revokeLeaf(revokedLeafKeys, revokedLeafChain)
assert.NoError(t, err)
@ -636,13 +639,13 @@ func TestParseExtensions(t *testing.T) {
},
{
"certificate revocation - serial revocations error (older timestamp)",
TLSExtConfig{Revocation: true},
peertls.TLSExtConfig{Revocation: true},
1,
func() []*x509.Certificate {
keys, chain, err := newRevokedLeafChain()
assert.NoError(t, err)
rev := new(Revocation)
rev := new(peertls.Revocation)
err = rev.Unmarshal(chain[0].ExtraExtensions[0].Value)
assert.NoError(t, err)
@ -654,25 +657,25 @@ func TestParseExtensions(t *testing.T) {
assert.NoError(t, err)
err = revDB.Put(chain, pkix.Extension{
Id: ExtensionIDs[RevocationExtID],
Id: peertls.ExtensionIDs[peertls.RevocationExtID],
Value: revBytes,
})
assert.NoError(t, err)
return chain
}(),
nil,
&ErrExtension,
ErrRevocationTimestamp,
&peertls.ErrExtension,
peertls.ErrRevocationTimestamp,
},
{
"certificate revocation and leaf whitelist signature",
TLSExtConfig{Revocation: true, WhitelistSignedLeaf: true},
peertls.TLSExtConfig{Revocation: true, WhitelistSignedLeaf: true},
2,
func() []*x509.Certificate {
_, chain, err := newRevokedLeafChain()
assert.NoError(t, err)
err = AddSignedCertExt(whitelistSignedKeys[0], chain[0])
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], chain[0])
assert.NoError(t, err)
return chain
@ -685,12 +688,12 @@ func TestParseExtensions(t *testing.T) {
for _, c := range cases {
t.Run(c.testID, func(t *testing.T) {
opts := ParseExtOptions{
opts := peertls.ParseExtOptions{
CAWhitelist: c.whitelist,
RevDB: revDB,
}
handlers := ParseExtensions(c.config, opts)
handlers := peertls.ParseExtensions(c.config, opts)
assert.Equal(t, c.extLen, len(handlers))
err := handlers.VerifyFunc()(nil, [][]*x509.Certificate{c.certChain})
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) {
revokingKey, err := NewKey()
revokingKey, err := peertls.NewKey()
if err != nil {
return nil, nil, err
}
revokingTemplate, err := LeafTemplate()
revokingTemplate, err := peertls.LeafTemplate()
if err != nil {
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 {
return nil, nil, err
}
err = AddRevocationExt(keys[0], chain[0], revokingCert)
err = peertls.AddRevocationExt(keys[0], chain[0], revokingCert)
if err != nil {
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) {
keys2, certs2, err := newCertChain(2)
keys2, certs2, err := testpeertls.NewCertChain(2)
if err != nil {
return nil, nil, err
}

View File

@ -9,7 +9,8 @@ import (
"github.com/mr-tron/base58/base58"
"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) {
@ -26,8 +27,9 @@ func TestNewPieceID(t *testing.T) {
}
func TestDerivePieceID(t *testing.T) {
ctx := testcontext.New(t)
pid := NewPieceID()
fid, err := testidentity.NewTestIdentity()
fid, err := testidentity.NewTestIdentity(ctx)
assert.NoError(t, err)
did, err := pid.Derive(fid.ID.Bytes())
assert.NoError(t, err)

View File

@ -28,9 +28,9 @@ import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"storj.io/storj/internal/identity"
"storj.io/storj/internal/testidentity"
"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/storj"
)

View File

@ -21,7 +21,7 @@ import (
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
"storj.io/storj/internal/identity"
"storj.io/storj/internal/testidentity"
"storj.io/storj/pkg/auth"
"storj.io/storj/pkg/pb"
"storj.io/storj/pkg/storage/meta"