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:
parent
58c810519f
commit
746b63f685
63
cmd/identity/certificate_authority.go
Normal file
63
cmd/identity/certificate_authority.go
Normal 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
49
cmd/identity/identity.go
Normal 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
23
cmd/identity/main.go
Normal 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)
|
||||
}
|
@ -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])
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user