CA and identity commands (#235)

* wip ca/ident cmds

* minor improvements and commenting

* combine id and ca commands and add $CONFDIR

* add `NewIdenity` test

* refactor `NewCA` benchmarks

* linter fixes
This commit is contained in:
Bryan White 2018-08-28 00:23:48 +02:00 committed by GitHub
parent 58c810519f
commit 746b63f685
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 41 deletions

View File

@ -0,0 +1,63 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"fmt"
"github.com/spf13/cobra"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/process"
"storj.io/storj/pkg/provider"
)
var (
caCmd = &cobra.Command{
Use: "ca",
Short: "Manage certificate authorities",
}
newCACmd = &cobra.Command{
Use: "new",
Short: "Create a new certificate authority",
RunE: cmdNewCA,
}
getIDCmd = &cobra.Command{
Use: "id",
Short: "Get the id of a CA",
RunE: cmdGetID,
}
newCACfg struct {
CA provider.CASetupConfig
}
getIDCfg struct {
CA provider.PeerCAConfig
}
)
func init() {
rootCmd.AddCommand(caCmd)
caCmd.AddCommand(newCACmd)
caCmd.AddCommand(getIDCmd)
cfgstruct.Bind(newCACmd.Flags(), &newCACfg, cfgstruct.ConfDir(defaultConfDir))
cfgstruct.Bind(getIDCmd.Flags(), &getIDCfg, cfgstruct.ConfDir(defaultConfDir))
}
func cmdNewCA(cmd *cobra.Command, args []string) (error) {
_, err := newCACfg.CA.Create(process.Ctx(cmd))
return err
}
func cmdGetID(cmd *cobra.Command, args []string) (err error) {
p, err := getIDCfg.CA.Load()
if err != nil {
return err
}
fmt.Println(p.ID.String())
return nil
}

49
cmd/identity/identity.go Normal file
View File

@ -0,0 +1,49 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"github.com/spf13/cobra"
"storj.io/storj/pkg/provider"
"storj.io/storj/pkg/cfgstruct"
)
var (
idCmd = &cobra.Command{
Use: "id",
Short: "Manage identities",
}
newIDCmd = &cobra.Command{
Use: "new",
Short: "Creates a new identity from an existing certificate authority",
RunE: cmdNewID,
}
newIDCfg struct {
CA provider.FullCAConfig
Identity provider.IdentitySetupConfig
}
)
func init() {
rootCmd.AddCommand(idCmd)
idCmd.AddCommand(newIDCmd)
cfgstruct.Bind(newIDCmd.Flags(), &newIDCfg, cfgstruct.ConfDir(defaultConfDir))
}
func cmdNewID(cmd *cobra.Command, args []string) (err error) {
ca, err := newIDCfg.CA.Load()
if err != nil {
return err
}
s := newIDCfg.Identity.Stat()
if s == provider.NoCertNoKey || newIDCfg.Identity.Overwrite {
_, err := newIDCfg.Identity.Create(ca)
return err
}
return provider.ErrSetup.New("identity file(s) exist: %s", s)
}

23
cmd/identity/main.go Normal file
View File

@ -0,0 +1,23 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"github.com/spf13/cobra"
"storj.io/storj/pkg/process"
)
var (
rootCmd = &cobra.Command{
Use: "identity",
Short: "Identity management",
}
defaultConfDir = "$HOME/.storj/identity"
)
func main() {
process.Exec(rootCmd)
}

View File

@ -104,7 +104,8 @@ func VerifyPeerFunc(next ...PeerCertVerificationFunc) PeerCertVerificationFunc {
}
}
// VerifyPeerCertChains verifies chains
// VerifyPeerCertChains verifies that the first certificate chain contains certificates
// which are signed by their respective parents, ending with a self-signed root
func VerifyPeerCertChains(_ [][]byte, parsedChains [][]*x509.Certificate) error {
return verifyChainSignatures(parsedChains[0])
}

View File

@ -80,7 +80,11 @@ func verifyChainSignatures(certs []*x509.Certificate) error {
}
func verifyCertSignature(parentCert, childCert *x509.Certificate) (bool, error) {
pubKey := parentCert.PublicKey.(*ecdsa.PublicKey)
pubKey, ok := parentCert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return false, ErrUnsupportedKey.New("%T", parentCert.PublicKey)
}
signature := new(ecdsaSignature)
if _, err := asn1.Unmarshal(childCert.Signature, signature); err != nil {

View File

@ -21,31 +21,45 @@ func TestNewCA(t *testing.T) {
assert.True(t, actualDifficulty >= expectedDifficulty)
}
func BenchmarkNewCA_Difficulty8_Concurrency1(b *testing.B) {
context.Background()
for i := 0; i < b.N; i++ {
expectedDifficulty := uint16(8)
NewCA(context.Background(), expectedDifficulty, 1)
func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
check := func(err error, v interface{}) {
if !assert.NoError(t, err) || !assert.NotEmpty(t, v) {
t.Fail()
}
}
ca, err := NewCA(context.Background(), 12, 5)
check(err, ca)
fi, err := ca.NewIdentity()
check(err, fi)
assert.Equal(t, ca.Cert, fi.CA)
assert.Equal(t, ca.ID, fi.ID)
assert.NotEqual(t, ca.Key, fi.Key)
assert.NotEqual(t, ca.Cert, fi.Leaf)
err = fi.Leaf.CheckSignatureFrom(ca.Cert)
assert.NoError(t, err)
}
func NewCABenchmark(b *testing.B, difficulty uint16, concurrency uint) {
for i := 0; i < b.N; i++ {
NewCA(context.Background(), difficulty, concurrency)
}
}
func BenchmarkNewCA_Difficulty8_Concurrency1(b *testing.B) {
NewCABenchmark(b, 8, 1)
}
func BenchmarkNewCA_Difficulty8_Concurrency2(b *testing.B) {
for i := 0; i < b.N; i++ {
expectedDifficulty := uint16(8)
NewCA(context.Background(), expectedDifficulty, 2)
}
NewCABenchmark(b, 8, 2)
}
func BenchmarkNewCA_Difficulty8_Concurrency5(b *testing.B) {
for i := 0; i < b.N; i++ {
expectedDifficulty := uint16(8)
NewCA(context.Background(), expectedDifficulty, 5)
}
NewCABenchmark(b, 8, 5)
}
func BenchmarkNewCA_Difficulty8_Concurrency10(b *testing.B) {
for i := 0; i < b.N; i++ {
expectedDifficulty := uint16(8)
NewCA(context.Background(), expectedDifficulty, 10)
}
NewCABenchmark(b, 8, 10)
}

View File

@ -11,8 +11,10 @@ import (
"encoding/pem"
"io/ioutil"
"os"
"reflect"
"github.com/zeebo/errs"
"storj.io/storj/pkg/peertls"
"storj.io/storj/pkg/utils"
)
@ -45,8 +47,13 @@ type CASetupConfig struct {
Concurrency uint `help:"number of concurrent workers for certificate authority generation" default:"4"`
}
// CAConfig is for locating the CA keys
type CAConfig struct {
// PeerCAConfig is for locating a CA certificate without a private key
type PeerCAConfig struct {
CertPath string `help:"path to the certificate chain for this identity" default:"$CONFDIR/ca.cert"`
}
// FullCAConfig is for locating a CA certificate and it's private key
type FullCAConfig struct {
CertPath string `help:"path to the certificate chain for this identity" default:"$CONFDIR/ca.cert"`
KeyPath string `help:"path to the private key for this identity" default:"$CONFDIR/ca.key"`
}
@ -57,12 +64,12 @@ func (caS CASetupConfig) Status() TLSFilesStatus {
}
// Create generates and saves a CA using the config
func (caS CASetupConfig) Create(ctx context.Context, concurrency uint) (*FullCertificateAuthority, error) {
ca, err := NewCA(ctx, uint16(caS.Difficulty), concurrency)
func (caS CASetupConfig) Create(ctx context.Context) (*FullCertificateAuthority, error) {
ca, err := NewCA(ctx, uint16(caS.Difficulty), caS.Concurrency)
if err != nil {
return nil, err
}
caC := CAConfig{
caC := FullCAConfig{
CertPath: caS.CertPath,
KeyPath: caS.KeyPath,
}
@ -70,12 +77,48 @@ func (caS CASetupConfig) Create(ctx context.Context, concurrency uint) (*FullCer
}
// Load loads a CA from the given configuration
func (caC CAConfig) Load() (*FullCertificateAuthority, error) {
cd, err := ioutil.ReadFile(caC.CertPath)
func (fc FullCAConfig) Load() (*FullCertificateAuthority, error) {
p, err := fc.PeerConfig().Load()
if err != nil {
return nil, err
}
kb, err := ioutil.ReadFile(fc.KeyPath)
if err != nil {
return nil, peertls.ErrNotExist.Wrap(err)
}
kb, err := ioutil.ReadFile(caC.KeyPath)
kp, _ := pem.Decode(kb)
k, err := x509.ParseECPrivateKey(kp.Bytes)
if err != nil {
return nil, errs.New("unable to parse EC private key", err)
}
ec, ok := p.Cert.PublicKey.(*ecdsa.PublicKey)
if !ok {
return nil, peertls.ErrUnsupportedKey.New("certificate public key type not supported: %T", k)
}
if !reflect.DeepEqual(k.PublicKey, *ec) {
return nil, errs.New("certificate public key and loaded")
}
return &FullCertificateAuthority{
Cert: p.Cert,
Key: k,
ID: p.ID,
}, nil
}
// PeerConfig converts a full ca config to a peer ca config
func (fc FullCAConfig) PeerConfig() PeerCAConfig {
return PeerCAConfig{
CertPath: fc.CertPath,
}
}
// Load loads a CA from the given configuration
func (pc PeerCAConfig) Load() (*PeerCertificateAuthority, error) {
cd, err := ioutil.ReadFile(pc.CertPath)
if err != nil {
return nil, peertls.ErrNotExist.Wrap(err)
}
@ -91,23 +134,17 @@ func (caC CAConfig) Load() (*FullCertificateAuthority, error) {
}
c, err := ParseCertChain(cb)
if err != nil {
return nil, errs.New("failed to load identity %#v, %#v: %v",
caC.CertPath, caC.KeyPath, err)
return nil, errs.New("failed to load identity %#v: %v",
pc.CertPath, err)
}
kp, _ := pem.Decode(kb)
k, err := x509.ParseECPrivateKey(kp.Bytes)
if err != nil {
return nil, errs.New("unable to parse EC private key: %v", err)
}
i, err := idFromKey(k)
i, err := idFromKey(c[len(c)-1].PublicKey)
if err != nil {
return nil, err
}
return &FullCertificateAuthority{
return &PeerCertificateAuthority{
Cert: c[0],
Key: k,
ID: i,
}, nil
}
@ -136,14 +173,14 @@ func NewCA(ctx context.Context, difficulty uint16, concurrency uint) (*FullCerti
}
// Save saves a CA with the given configuration
func (caC CAConfig) Save(ca *FullCertificateAuthority) error {
func (fc FullCAConfig) Save(ca *FullCertificateAuthority) error {
f := os.O_WRONLY | os.O_CREATE
c, err := openCert(caC.CertPath, f)
c, err := openCert(fc.CertPath, f)
if err != nil {
return err
}
defer utils.LogClose(c)
k, err := openKey(caC.KeyPath, f)
k, err := openKey(fc.KeyPath, f)
if err != nil {
return err
}

View File

@ -74,7 +74,7 @@ func SetupIdentity(ctx context.Context, c CASetupConfig, i IdentitySetupConfig)
defer cancel()
// Load or create a certificate authority
ca, err := c.Create(ctx, 4)
ca, err := c.Create(ctx)
if err != nil {
return err
}