// Copyright (C) 2018 Storj Labs, Inc. // See LICENSE for copying information. package certificates import ( "bytes" "context" "crypto/rand" "crypto/x509" "encoding/gob" "fmt" "os" "github.com/btcsuite/btcutil/base58" "github.com/zeebo/errs" "go.uber.org/zap" "gopkg.in/spacemonkeygo/monkit.v2" "storj.io/storj/pkg/pb" "storj.io/storj/pkg/peertls" "storj.io/storj/pkg/provider" "storj.io/storj/pkg/utils" "storj.io/storj/storage" "storj.io/storj/storage/boltdb" "storj.io/storj/storage/redis" ) const ( // AuthorizationsBucket is the bucket used with a bolt-backed authorizations DB. AuthorizationsBucket = "authorizations" tokenLength = 64 // 2^(64*8) =~ 1.34E+154 ) var ( mon = monkit.Package() // ErrAuthorization is used when an error occurs involving an authorization. ErrAuthorization = errs.Class("authorization error") // ErrAuthorizationDB is used when an error occurs involving the authorization database. ErrAuthorizationDB = errs.Class("authorization db error") // ErrAuthorizationCount is used when attempting to create an invalid number of authorizations. ErrAuthorizationCount = ErrAuthorizationDB.New("cannot add less than one authorizations") ) // CertSignerConfig is a config struct for use with a certificate signing service 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"` } // CertificateSigner implements pb.CertificatesServer type CertificateSigner struct { Log *zap.Logger } // AuthorizationDB stores authorizations which may be claimed in exchange for a // certificate signature. type AuthorizationDB struct { DB storage.KeyValueStore } // Authorizations is a slice of authorizations for convenient de/serialization // and grouping. type Authorizations []*Authorization // Authorization represents a single-use authorization token and its status type Authorization struct { Token Token Claim *Claim } // Token is a random byte array to be used like a pre-shared key for claiming // certificate signatures. type Token [tokenLength]byte // Claim holds information about the circumstances under which an authorization // token was claimed. type Claim struct { IP string Timestamp int64 Identity *provider.PeerIdentity SignedCert *x509.Certificate } // NewServer creates a new certificate signing grpc server func NewServer(log *zap.Logger) pb.CertificatesServer { srv := CertificateSigner{ Log: log, } return &srv } // NewAuthorization creates a new, unclaimed authorization with a random token value func NewAuthorization() (*Authorization, error) { var token Token _, err := rand.Read(token[:]) if err != nil { return nil, ErrAuthorization.Wrap(err) } return &Authorization{ Token: token, }, nil } // NewAuthDB creates or opens the authorization database specified by the config func (c CertSignerConfig) NewAuthDB() (*AuthorizationDB, error) { // TODO: refactor db selection logic? driver, source, err := utils.SplitDBURL(c.AuthorizationDBURL) if err != nil { return nil, peertls.ErrRevocationDB.Wrap(err) } authDB := new(AuthorizationDB) switch driver { case "bolt": if c.Overwrite { if err := os.Remove(source); err != nil { return nil, err } } authDB.DB, err = boltdb.New(source, AuthorizationsBucket) if err != nil { return nil, ErrAuthorizationDB.Wrap(err) } case "redis": redisClient, err := redis.NewClientFrom(c.AuthorizationDBURL) if err != nil { return nil, ErrAuthorizationDB.Wrap(err) } if c.Overwrite { if err := redisClient.FlushDB(); err != nil { return nil, err } } authDB.DB = redisClient default: return nil, ErrAuthorizationDB.New("database scheme not supported: %s", driver) } return authDB, nil } // Run implements the responsibility interface, starting a certificate signing server. func (c CertSignerConfig) Run(ctx context.Context, server *provider.Provider) (err error) { defer mon.Task()(&ctx)(&err) srv := NewServer(zap.L()) pb.RegisterCertificatesServer(server.GRPC(), srv) return server.Run(ctx) } // 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 } // Close closes the authorization database's underlying store. func (a *AuthorizationDB) Close() error { return ErrAuthorizationDB.Wrap(a.DB.Close()) } // Create creates a new authorization and adds it to the authorization database. func (a *AuthorizationDB) Create(email string, count int) (Authorizations, error) { if len(email) == 0 { return nil, ErrAuthorizationDB.New("email cannot be empty") } if count < 1 { return nil, ErrAuthorizationCount } existingAuths, err := a.Get(email) if err != nil { return nil, err } var ( newAuths Authorizations authErrs utils.ErrorGroup ) for i := 0; i < count; i++ { auth, err := NewAuthorization() if err != nil { authErrs.Add(err) continue } newAuths = append(newAuths, auth) } if err := authErrs.Finish(); err != nil { 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(email), authsBytes); err != nil { return nil, ErrAuthorizationDB.Wrap(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)) if err != nil && !storage.ErrKeyNotFound.Has(err) { return nil, ErrAuthorizationDB.Wrap(err) } if authsBytes == nil { return nil, nil } var auths Authorizations if err := auths.Unmarshal(authsBytes); err != nil { return nil, ErrAuthorizationDB.Wrap(err) } return auths, nil } // Emails returns a list of all emails present in the authorization database. func (a *AuthorizationDB) Emails() ([]string, error) { keys, err := a.DB.List([]byte{}, 0) if err != nil { return nil, ErrAuthorizationDB.Wrap(err) } return keys.Strings(), nil } // Unmarshal deserializes a set of authorizations func (a *Authorizations) Unmarshal(data []byte) error { decoder := gob.NewDecoder(bytes.NewBuffer(data)) if err := decoder.Decode(a); err != nil { return ErrAuthorization.Wrap(err) } return nil } // Marshal serializes a set of authorizations func (a Authorizations) Marshal() ([]byte, error) { data := new(bytes.Buffer) encoder := gob.NewEncoder(data) err := encoder.Encode(a) if err != nil { return nil, ErrAuthorization.Wrap(err) } return data.Bytes(), nil } // Group separates a set of authorizations into a set of claimed and a set of open authorizations. func (a Authorizations) Group() (claimed, open Authorizations) { for _, auth := range a { if auth.Claim != nil { // TODO: check if claim is valid? what if not? claimed = append(claimed, auth) } else { open = append(open, auth) } } return claimed, open } // String implements the stringer interface and prevents authorization data // from completely leaking into logs. func (a Authorization) String() string { return fmt.Sprintf("%.5s..", a.Token.String()) } // String implements the stringer interface. Base68 w/ version and checksum bytes // are used for easy and reliable human transport. func (t *Token) String() string { return base58.CheckEncode(t[:], 0) }