extension serialization (#1554)
This commit is contained in:
parent
e2a43c3fb6
commit
fe476fdcf1
@ -111,7 +111,7 @@ func saveIdentityTar(path string, key crypto.PrivateKey, id storj.NodeID) error
|
||||
return err
|
||||
}
|
||||
|
||||
caCert, err := peertls.NewCert(key, nil, ct, nil)
|
||||
caCert, err := peertls.NewSelfSignedCert(key, ct)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ func cmdRevokeCA(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := extensions.AddRevocationExt(ca.Key, ca.Cert, ca.Cert); err != nil {
|
||||
if err := ca.Revoke(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -211,5 +211,5 @@ func cmdCAExtensions(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
return printExtensions(ca.Cert.Raw, ca.Cert.ExtraExtensions)
|
||||
return printExtensions(ca.Cert.Raw, ca.Cert.Extensions)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
|
||||
"storj.io/storj/pkg/cfgstruct"
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -57,7 +56,7 @@ var (
|
||||
|
||||
revokeLeafCfg struct {
|
||||
CA identity.FullCAConfig
|
||||
Identity identity.PeerConfig
|
||||
Identity identity.Config
|
||||
// TODO: add "broadcast" option to send revocation to network nodes
|
||||
}
|
||||
)
|
||||
@ -102,7 +101,7 @@ func cmdLeafExtensions(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
return printExtensions(ident.Leaf.Raw, ident.Leaf.ExtraExtensions)
|
||||
return printExtensions(ident.Leaf.Raw, ident.Leaf.Extensions)
|
||||
}
|
||||
|
||||
func cmdRevokeLeaf(cmd *cobra.Command, args []string) (err error) {
|
||||
@ -115,24 +114,17 @@ func cmdRevokeLeaf(cmd *cobra.Command, args []string) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedIdent, err := ca.NewIdentity()
|
||||
if err != nil {
|
||||
manageableIdent := identity.NewManageableFullIdentity(originalIdent, ca)
|
||||
if err := manageableIdent.Revoke(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := extensions.AddRevocationExt(ca.Key, originalIdent.Leaf, updatedIdent.Leaf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NB: backup original cert
|
||||
// NB: backup original cert and key.
|
||||
if err := revokeLeafCfg.Identity.SaveBackup(originalIdent); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updateCfg := identity.Config{
|
||||
CertPath: revokeLeafCfg.Identity.CertPath,
|
||||
}
|
||||
if err := updateCfg.Save(updatedIdent); err != nil {
|
||||
if err := revokeLeafCfg.Identity.Save(manageableIdent.FullIdentity); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -53,12 +53,7 @@ func cmdRevocations(cmd *cobra.Command, args []string) error {
|
||||
|
||||
revErrs := new(errs.Group)
|
||||
for _, rev := range revs {
|
||||
// TODO: figure out why this doesn't base64 encode []byte fields!
|
||||
//revJSON, err := json.MarshalIndent(rev.CertHash, "", "\t")
|
||||
//if err != nil {
|
||||
// revErrs.Add(err)
|
||||
//}
|
||||
fmt.Printf("certificate hash: %s\n", base64.StdEncoding.EncodeToString(rev.CertHash))
|
||||
fmt.Printf("certificate public key hash: %s\n", base64.StdEncoding.EncodeToString(rev.KeyHash))
|
||||
fmt.Printf("\timestamp: %s\n", time.Unix(rev.Timestamp, 0).String())
|
||||
fmt.Printf("\tsignature: %s\n", base64.StdEncoding.EncodeToString(rev.Signature))
|
||||
}
|
||||
|
@ -33,3 +33,31 @@ func NewTestCA(ctx context.Context) (*identity.FullCertificateAuthority, error)
|
||||
Concurrency: 1,
|
||||
})
|
||||
}
|
||||
|
||||
// NewTestManageablePeerIdentity returns a new manageable peer identity for use in tests.
|
||||
func NewTestManageablePeerIdentity(ctx context.Context) (*identity.ManageablePeerIdentity, error) {
|
||||
ca, err := NewTestCA(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ident, err := ca.NewIdentity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return identity.NewManageablePeerIdentity(ident.PeerIdentity(), ca), nil
|
||||
}
|
||||
|
||||
// NewTestManageableFullIdentity returns a new manageable full identity for use in tests.
|
||||
func NewTestManageableFullIdentity(ctx context.Context) (*identity.ManageableFullIdentity, error) {
|
||||
ca, err := NewTestCA(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ident, err := ca.NewIdentity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return identity.NewManageableFullIdentity(ident, ca), nil
|
||||
}
|
||||
|
@ -33,9 +33,9 @@ func NewCertChain(length int) (keys []crypto.PrivateKey, certs []*x509.Certifica
|
||||
|
||||
var cert *x509.Certificate
|
||||
if i == 0 {
|
||||
cert, err = peertls.NewCert(key, nil, template, nil)
|
||||
cert, err = peertls.NewSelfSignedCert(key, template)
|
||||
} else {
|
||||
cert, err = peertls.NewCert(key, keys[i-1], template, certs[i-1:][0])
|
||||
cert, err = peertls.NewCert(pkcrypto.PublicKeyFromPrivate(key), keys[i-1], template, certs[i-1:][0])
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -8,50 +8,84 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/pkcrypto"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
)
|
||||
|
||||
// RevokeLeaf revokes the leaf certificate in the passed chain and replaces it
|
||||
// with a "revoking" certificate, which contains a revocation extension recording
|
||||
// this action.
|
||||
func RevokeLeaf(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]*x509.Certificate, pkix.Extension, error) {
|
||||
var revocation pkix.Extension
|
||||
revokingKey, err := pkcrypto.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return nil, revocation, err
|
||||
func RevokeLeaf(caKey crypto.PrivateKey, chain []*x509.Certificate) ([]*x509.Certificate, pkix.Extension, error) {
|
||||
if len(chain) < 2 {
|
||||
return nil, pkix.Extension{}, extensions.Error.New("revoking leaf implies a CA exists; chain too short")
|
||||
}
|
||||
ca := &identity.FullCertificateAuthority{
|
||||
Key: caKey,
|
||||
Cert: chain[peertls.CAIndex],
|
||||
RestChain: chain[:peertls.CAIndex+1],
|
||||
}
|
||||
|
||||
revokingTemplate, err := peertls.LeafTemplate()
|
||||
if err != nil {
|
||||
return nil, revocation, err
|
||||
}
|
||||
|
||||
revokingCert, err := peertls.NewCert(revokingKey, keys[0], revokingTemplate, chain[peertls.CAIndex])
|
||||
if err != nil {
|
||||
return nil, revocation, err
|
||||
}
|
||||
|
||||
err = extensions.AddRevocationExt(keys[0], chain[peertls.LeafIndex], revokingCert)
|
||||
if err != nil {
|
||||
return nil, revocation, err
|
||||
}
|
||||
|
||||
revocation = revokingCert.ExtraExtensions[0]
|
||||
return append([]*x509.Certificate{revokingCert}, chain[peertls.CAIndex:]...), revocation, nil
|
||||
}
|
||||
|
||||
// RevokeCA revokes the CA certificate in the passed chain and adds a revocation
|
||||
// extension to that certificate, recording this action.
|
||||
func RevokeCA(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]*x509.Certificate, pkix.Extension, error) {
|
||||
caCert := chain[peertls.CAIndex]
|
||||
err := extensions.AddRevocationExt(keys[0], caCert, caCert)
|
||||
var err error
|
||||
ca.ID, err = identity.NodeIDFromKey(ca.Cert.PublicKey)
|
||||
if err != nil {
|
||||
return nil, pkix.Extension{}, err
|
||||
}
|
||||
|
||||
return append([]*x509.Certificate{caCert}, chain[peertls.CAIndex:]...), caCert.ExtraExtensions[0], nil
|
||||
ident := &identity.FullIdentity{
|
||||
Leaf: chain[peertls.LeafIndex],
|
||||
CA: ca.Cert,
|
||||
ID: ca.ID,
|
||||
RestChain: ca.RestChain,
|
||||
}
|
||||
|
||||
manageableIdent := identity.NewManageableFullIdentity(ident, ca)
|
||||
if err := manageableIdent.Revoke(); err != nil {
|
||||
return nil, pkix.Extension{}, err
|
||||
}
|
||||
|
||||
revokingCert := manageableIdent.Leaf
|
||||
var revocationExt *pkix.Extension
|
||||
for _, ext := range revokingCert.Extensions {
|
||||
if extensions.RevocationExtID.Equal(ext.Id) {
|
||||
revocationExt = &ext
|
||||
break
|
||||
}
|
||||
}
|
||||
if revocationExt == nil {
|
||||
return nil, pkix.Extension{}, extensions.ErrRevocation.New("no revocation extension found")
|
||||
}
|
||||
|
||||
return append([]*x509.Certificate{revokingCert}, chain[peertls.CAIndex:]...), *revocationExt, nil
|
||||
}
|
||||
|
||||
// RevokeCA revokes the CA certificate in the passed chain and adds a revocation
|
||||
// extension to that certificate, recording this action.
|
||||
func RevokeCA(caKey crypto.PrivateKey, chain []*x509.Certificate) ([]*x509.Certificate, pkix.Extension, error) {
|
||||
caCert := chain[peertls.CAIndex]
|
||||
nodeID, err := identity.NodeIDFromKey(caCert.PublicKey)
|
||||
if err != nil {
|
||||
return nil, pkix.Extension{}, err
|
||||
}
|
||||
|
||||
ca := &identity.FullCertificateAuthority{
|
||||
ID: nodeID,
|
||||
Cert: caCert,
|
||||
Key: caKey,
|
||||
RestChain: chain[peertls.CAIndex+1:],
|
||||
}
|
||||
|
||||
if err = ca.Revoke(); err != nil {
|
||||
return nil, pkix.Extension{}, err
|
||||
}
|
||||
|
||||
extMap := tlsopts.NewExtensionsMap(ca.Cert)
|
||||
revocationExt, ok := extMap[extensions.RevocationExtID.String()]
|
||||
if !ok {
|
||||
return nil, pkix.Extension{}, extensions.ErrRevocation.New("no revocation extension found")
|
||||
}
|
||||
return append([]*x509.Certificate{chain[peertls.LeafIndex], ca.Cert}, ca.RestChain...), revocationExt, nil
|
||||
}
|
||||
|
||||
// NewRevokedLeafChain creates a certificate chain (of length 2) with a leaf
|
||||
@ -62,6 +96,6 @@ func NewRevokedLeafChain() ([]crypto.PrivateKey, []*x509.Certificate, pkix.Exten
|
||||
return nil, nil, pkix.Extension{}, err
|
||||
}
|
||||
|
||||
newChain, revocation, err := RevokeLeaf(keys, certs)
|
||||
newChain, revocation, err := RevokeLeaf(keys[0], certs)
|
||||
return keys, newChain, revocation, err
|
||||
}
|
||||
|
@ -1,22 +1,30 @@
|
||||
// Copyright (C) 2019 Storj Labs, Inc.
|
||||
// See LICENSE for copying information.
|
||||
|
||||
package identity
|
||||
package identity_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/internal/testidentity"
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
)
|
||||
|
||||
func TestNewCA(t *testing.T) {
|
||||
const expectedDifficulty = 4
|
||||
|
||||
ca, err := NewCA(context.Background(), NewCAOptions{
|
||||
ca, err := identity.NewCA(context.Background(), identity.NewCAOptions{
|
||||
Difficulty: expectedDifficulty,
|
||||
Concurrency: 5,
|
||||
})
|
||||
@ -30,7 +38,7 @@ func TestNewCA(t *testing.T) {
|
||||
|
||||
func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
ca, err := NewCA(ctx, NewCAOptions{
|
||||
ca, err := identity.NewCA(ctx, identity.NewCAOptions{
|
||||
Difficulty: 12,
|
||||
Concurrency: 4,
|
||||
})
|
||||
@ -54,17 +62,17 @@ func TestFullCertificateAuthority_NewIdentity(t *testing.T) {
|
||||
|
||||
func TestFullCertificateAuthority_Sign(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
caOpts := NewCAOptions{
|
||||
caOpts := identity.NewCAOptions{
|
||||
Difficulty: 12,
|
||||
Concurrency: 4,
|
||||
}
|
||||
|
||||
ca, err := NewCA(ctx, caOpts)
|
||||
ca, err := identity.NewCA(ctx, caOpts)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, ca) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
toSign, err := NewCA(ctx, caOpts)
|
||||
toSign, err := identity.NewCA(ctx, caOpts)
|
||||
if !assert.NoError(t, err) || !assert.NotNil(t, toSign) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@ -96,7 +104,7 @@ func BenchmarkNewCA(b *testing.B) {
|
||||
test := fmt.Sprintf("%d/%d", difficulty, concurrency)
|
||||
b.Run(test, func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, _ = NewCA(ctx, NewCAOptions{
|
||||
_, _ = identity.NewCA(ctx, identity.NewCAOptions{
|
||||
Difficulty: difficulty,
|
||||
Concurrency: concurrency,
|
||||
})
|
||||
@ -105,3 +113,71 @@ func BenchmarkNewCA(b *testing.B) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullCertificateAuthority_AddExtension(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
ca, err := testidentity.NewTestCA(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
oldCert := ca.Cert
|
||||
assert.Len(t, ca.Cert.ExtraExtensions, 0)
|
||||
|
||||
randBytes := make([]byte, 10)
|
||||
rand.Read(randBytes)
|
||||
randExt := pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{2, 999, int(randBytes[0])},
|
||||
Value: randBytes,
|
||||
}
|
||||
|
||||
err = ca.AddExtension(randExt)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, ca.Cert.ExtraExtensions, 0)
|
||||
assert.Len(t, ca.Cert.Extensions, len(oldCert.Extensions)+1)
|
||||
|
||||
assert.Equal(t, oldCert.SerialNumber, ca.Cert.SerialNumber)
|
||||
assert.Equal(t, oldCert.IsCA, ca.Cert.IsCA)
|
||||
assert.Equal(t, oldCert.PublicKey, ca.Cert.PublicKey)
|
||||
assert.Equal(t, randExt, tlsopts.NewExtensionsMap(ca.Cert)[randExt.Id.String()])
|
||||
|
||||
assert.NotEqual(t, oldCert.Raw, ca.Cert.Raw)
|
||||
assert.NotEqual(t, oldCert.RawTBSCertificate, ca.Cert.RawTBSCertificate)
|
||||
assert.NotEqual(t, oldCert.Signature, ca.Cert.Signature)
|
||||
}
|
||||
|
||||
func TestFullCertificateAuthority_Revoke(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
ca, err := testidentity.NewTestCA(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
oldCert := ca.Cert
|
||||
assert.Len(t, ca.Cert.ExtraExtensions, 0)
|
||||
|
||||
err = ca.Revoke()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, ca.Cert.ExtraExtensions, 0)
|
||||
assert.Len(t, ca.Cert.Extensions, len(oldCert.Extensions)+1)
|
||||
|
||||
assert.Equal(t, oldCert.SerialNumber, ca.Cert.SerialNumber)
|
||||
assert.Equal(t, oldCert.IsCA, ca.Cert.IsCA)
|
||||
assert.Equal(t, oldCert.PublicKey, ca.Cert.PublicKey)
|
||||
|
||||
assert.NotEqual(t, oldCert.Raw, ca.Cert.Raw)
|
||||
assert.NotEqual(t, oldCert.RawTBSCertificate, ca.Cert.RawTBSCertificate)
|
||||
assert.NotEqual(t, oldCert.Signature, ca.Cert.Signature)
|
||||
|
||||
revocationExt := tlsopts.NewExtensionsMap(ca.Cert)[extensions.RevocationExtID.String()]
|
||||
assert.True(t, extensions.RevocationExtID.Equal(revocationExt.Id))
|
||||
|
||||
var rev extensions.Revocation
|
||||
err = rev.Unmarshal(revocationExt.Value)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rev.Verify(ca.Cert)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -161,7 +162,12 @@ func NewCA(ctx context.Context, opts NewCAOptions) (_ *FullCertificateAuthority,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := peertls.NewCert(selectedKey, opts.ParentKey, ct, opts.ParentCert)
|
||||
|
||||
if opts.ParentKey == nil {
|
||||
opts.ParentKey = selectedKey
|
||||
}
|
||||
|
||||
c, err := peertls.NewCert(pkcrypto.PublicKeyFromPrivate(selectedKey), opts.ParentKey, ct, opts.ParentCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -350,7 +356,7 @@ func (pc PeerCAConfig) SaveBackup(ca *PeerCertificateAuthority) 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(exts ...pkix.Extension) (*FullIdentity, error) {
|
||||
leafTemplate, err := peertls.LeafTemplate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -359,16 +365,14 @@ func (ca *FullCertificateAuthority) NewIdentity() (*FullIdentity, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leafCert, err := peertls.NewCert(leafKey, ca.Key, leafTemplate, ca.Cert)
|
||||
if err != nil {
|
||||
|
||||
if err := extensions.AddExtraExtension(leafTemplate, exts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ca.RestChain != nil && len(ca.RestChain) > 0 {
|
||||
err := extensions.AddSignedCert(ca.Key, leafCert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
leafCert, err := peertls.NewCert(pkcrypto.PublicKeyFromPrivate(leafKey), ca.Key, leafTemplate, ca.Cert)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &FullIdentity{
|
||||
@ -428,3 +432,34 @@ func (ca *FullCertificateAuthority) Sign(cert *x509.Certificate) (*x509.Certific
|
||||
|
||||
return signedCert, nil
|
||||
}
|
||||
|
||||
// AddExtension adds extensions to certificate authority certificate. Extensions
|
||||
// are serialized into the certificate's raw bytes and it is re-signed by itself.
|
||||
func (ca *FullCertificateAuthority) AddExtension(ext ...pkix.Extension) error {
|
||||
// TODO: how to properly handle this?
|
||||
if len(ca.RestChain) > 0 {
|
||||
return errs.New("adding extensions requires parent certificate's private key")
|
||||
}
|
||||
|
||||
if err := extensions.AddExtraExtension(ca.Cert, ext...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedCert, err := peertls.NewSelfSignedCert(ca.Key, ca.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ca.Cert = updatedCert
|
||||
return nil
|
||||
}
|
||||
|
||||
// Revoke extends the certificate authority certificate with a certificate revocation extension.
|
||||
func (ca *FullCertificateAuthority) Revoke() error {
|
||||
ext, err := extensions.NewRevocationExt(ca.Key, ca.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ca.AddExtension(ext)
|
||||
}
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
@ -21,6 +21,7 @@ import (
|
||||
"google.golang.org/grpc/peer"
|
||||
|
||||
"storj.io/storj/pkg/peertls"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/pkcrypto"
|
||||
"storj.io/storj/pkg/storj"
|
||||
)
|
||||
@ -52,6 +53,22 @@ type FullIdentity struct {
|
||||
Key crypto.PrivateKey
|
||||
}
|
||||
|
||||
// ManageablePeerIdentity is a `PeerIdentity` and its corresponding `FullCertificateAuthority`
|
||||
// in a single struct. It is used for making changes to the identity that require CA
|
||||
// authorization; e.g. adding extensions.
|
||||
type ManageablePeerIdentity struct {
|
||||
*PeerIdentity
|
||||
CA *FullCertificateAuthority
|
||||
}
|
||||
|
||||
// ManageableFullIdentity is a `FullIdentity` and its corresponding `FullCertificateAuthority`
|
||||
// in a single struct. It is used for making changes to the identity that require CA
|
||||
// authorization and the leaf private key; e.g. revoking a leaf cert (private key changes).
|
||||
type ManageableFullIdentity struct {
|
||||
*FullIdentity
|
||||
CA *FullCertificateAuthority
|
||||
}
|
||||
|
||||
// SetupConfig allows you to run a set of Responsibilities with the given
|
||||
// identity. You can also just load an Identity from disk.
|
||||
type SetupConfig struct {
|
||||
@ -234,14 +251,11 @@ func NodeIDFromPEM(pemBytes []byte) (storj.NodeID, error) {
|
||||
|
||||
// NodeIDFromKey hashes a public key and creates a node ID from it
|
||||
func NodeIDFromKey(k crypto.PublicKey) (storj.NodeID, error) {
|
||||
// id = sha256(sha256(pkix(k)))
|
||||
kb, err := x509.MarshalPKIXPublicKey(k)
|
||||
idBytes, err := peertls.DoubleSHA256PublicKey(k)
|
||||
if err != nil {
|
||||
return storj.NodeID{}, storj.ErrNodeID.Wrap(err)
|
||||
}
|
||||
mid := sha256.Sum256(kb)
|
||||
end := sha256.Sum256(mid[:])
|
||||
return storj.NodeID(end), nil
|
||||
return storj.NodeID(idBytes), nil
|
||||
}
|
||||
|
||||
// NewFullIdentity creates a new ID for nodes with difficulty and concurrency params
|
||||
@ -269,6 +283,22 @@ func ToChains(chains ...[]*x509.Certificate) [][]*x509.Certificate {
|
||||
return combinedChains
|
||||
}
|
||||
|
||||
// NewManageablePeerIdentity returns a manageable identity given a full identity and a full certificate authority.
|
||||
func NewManageablePeerIdentity(ident *PeerIdentity, ca *FullCertificateAuthority) *ManageablePeerIdentity {
|
||||
return &ManageablePeerIdentity{
|
||||
PeerIdentity: ident,
|
||||
CA: ca,
|
||||
}
|
||||
}
|
||||
|
||||
// NewManageableFullIdentity returns a manageable identity given a full identity and a full certificate authority.
|
||||
func NewManageableFullIdentity(ident *FullIdentity, ca *FullCertificateAuthority) *ManageableFullIdentity {
|
||||
return &ManageableFullIdentity{
|
||||
FullIdentity: ident,
|
||||
CA: ca,
|
||||
}
|
||||
}
|
||||
|
||||
// Status returns the status of the identity cert/key files for the config
|
||||
func (is SetupConfig) Status() (TLSFilesStatus, error) {
|
||||
return statTLSFiles(is.CertPath, is.KeyPath)
|
||||
@ -375,14 +405,14 @@ func (ic PeerConfig) Load() (*PeerIdentity, error) {
|
||||
}
|
||||
|
||||
// Save saves a PeerIdentity according to the config
|
||||
func (ic PeerConfig) Save(fi *PeerIdentity) error {
|
||||
func (ic PeerConfig) Save(peerIdent *PeerIdentity) error {
|
||||
var (
|
||||
certData bytes.Buffer
|
||||
writeChainErr, writeChainDataErr error
|
||||
)
|
||||
|
||||
chain := []*x509.Certificate{fi.Leaf, fi.CA}
|
||||
chain = append(chain, fi.RestChain...)
|
||||
chain := []*x509.Certificate{peerIdent.Leaf, peerIdent.CA}
|
||||
chain = append(chain, peerIdent.RestChain...)
|
||||
|
||||
if ic.CertPath != "" {
|
||||
writeChainErr = peertls.WriteChain(&certData, chain...)
|
||||
@ -440,6 +470,40 @@ func (fi *FullIdentity) PeerIdentity() *PeerIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
// AddExtension adds extensions to the leaf cert of an identity. Extensions
|
||||
// are serialized into the certificate's raw bytes and is re-signed by it's
|
||||
// certificate authority.
|
||||
func (manageableIdent *ManageablePeerIdentity) AddExtension(ext ...pkix.Extension) error {
|
||||
if err := extensions.AddExtraExtension(manageableIdent.Leaf, ext...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
updatedCert, err := peertls.NewCert(manageableIdent.Leaf.PublicKey, manageableIdent.CA.Key, manageableIdent.Leaf, manageableIdent.CA.Cert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manageableIdent.Leaf = updatedCert
|
||||
return nil
|
||||
}
|
||||
|
||||
// Revoke extends the CA certificate with a certificate revocation extension.
|
||||
func (manageableIdent *ManageableFullIdentity) Revoke() error {
|
||||
ext, err := extensions.NewRevocationExt(manageableIdent.CA.Key, manageableIdent.Leaf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
revokingIdent, err := manageableIdent.CA.NewIdentity(ext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
manageableIdent.Leaf = revokingIdent.Leaf
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func backupPath(path string) string {
|
||||
pathExt := filepath.Ext(path)
|
||||
base := strings.TrimSuffix(path, pathExt)
|
||||
|
@ -6,15 +6,22 @@ package identity_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/internal/testidentity"
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
"storj.io/storj/pkg/pkcrypto"
|
||||
)
|
||||
|
||||
@ -25,7 +32,7 @@ func TestPeerIdentityFromCertChain(t *testing.T) {
|
||||
caTemplate, err := peertls.CATemplate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
|
||||
caCert, err := peertls.NewSelfSignedCert(caKey, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafTemplate, err := peertls.LeafTemplate()
|
||||
@ -34,7 +41,7 @@ func TestPeerIdentityFromCertChain(t *testing.T) {
|
||||
leafKey, err := pkcrypto.GeneratePrivateKey()
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
|
||||
leafCert, err := peertls.NewCert(pkcrypto.PublicKeyFromPrivate(leafKey), caKey, leafTemplate, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
peerIdent, err := identity.PeerIdentityFromCerts(leafCert, caCert, nil)
|
||||
@ -51,7 +58,7 @@ func TestFullIdentityFromPEM(t *testing.T) {
|
||||
caTemplate, err := peertls.CATemplate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
|
||||
caCert, err := peertls.NewSelfSignedCert(caKey, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, caCert)
|
||||
@ -62,7 +69,7 @@ func TestFullIdentityFromPEM(t *testing.T) {
|
||||
leafKey, err := pkcrypto.GeneratePrivateKey()
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
|
||||
leafCert, err := peertls.NewCert(pkcrypto.PublicKeyFromPrivate(leafKey), caKey, leafTemplate, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, leafCert)
|
||||
|
||||
@ -141,6 +148,76 @@ func TestVerifyPeer(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestManageablePeerIdentity_AddExtension(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
manageablePeerIdentity, err := testidentity.NewTestManageablePeerIdentity(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
oldLeaf := manageablePeerIdentity.Leaf
|
||||
assert.Len(t, manageablePeerIdentity.CA.Cert.ExtraExtensions, 0)
|
||||
|
||||
randBytes := make([]byte, 10)
|
||||
_, err = rand.Read(randBytes)
|
||||
require.NoError(t, err)
|
||||
randExt := pkix.Extension{
|
||||
Id: asn1.ObjectIdentifier{2, 999, int(randBytes[0])},
|
||||
Value: randBytes,
|
||||
}
|
||||
|
||||
err = manageablePeerIdentity.AddExtension(randExt)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, manageablePeerIdentity.Leaf.ExtraExtensions, 0)
|
||||
assert.Len(t, manageablePeerIdentity.Leaf.Extensions, len(oldLeaf.Extensions)+1)
|
||||
|
||||
assert.Equal(t, oldLeaf.SerialNumber, manageablePeerIdentity.Leaf.SerialNumber)
|
||||
assert.Equal(t, oldLeaf.IsCA, manageablePeerIdentity.Leaf.IsCA)
|
||||
assert.Equal(t, oldLeaf.PublicKey, manageablePeerIdentity.Leaf.PublicKey)
|
||||
|
||||
assert.Equal(t, randExt, tlsopts.NewExtensionsMap(manageablePeerIdentity.Leaf)[randExt.Id.String()])
|
||||
|
||||
assert.NotEqual(t, oldLeaf.Raw, manageablePeerIdentity.Leaf.Raw)
|
||||
assert.NotEqual(t, oldLeaf.RawTBSCertificate, manageablePeerIdentity.Leaf.RawTBSCertificate)
|
||||
assert.NotEqual(t, oldLeaf.Signature, manageablePeerIdentity.Leaf.Signature)
|
||||
}
|
||||
|
||||
func TestManageableFullIdentity_Revoke(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
manageableFullIdentity, err := testidentity.NewTestManageableFullIdentity(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
oldLeaf := manageableFullIdentity.Leaf
|
||||
assert.Len(t, manageableFullIdentity.CA.Cert.ExtraExtensions, 0)
|
||||
|
||||
err = manageableFullIdentity.Revoke()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, manageableFullIdentity.Leaf.ExtraExtensions, 0)
|
||||
assert.Len(t, manageableFullIdentity.Leaf.Extensions, len(oldLeaf.Extensions)+1)
|
||||
|
||||
assert.Equal(t, oldLeaf.IsCA, manageableFullIdentity.Leaf.IsCA)
|
||||
|
||||
assert.NotEqual(t, oldLeaf.PublicKey, manageableFullIdentity.Leaf.PublicKey)
|
||||
assert.NotEqual(t, oldLeaf.SerialNumber, manageableFullIdentity.Leaf.SerialNumber)
|
||||
assert.NotEqual(t, oldLeaf.Raw, manageableFullIdentity.Leaf.Raw)
|
||||
assert.NotEqual(t, oldLeaf.RawTBSCertificate, manageableFullIdentity.Leaf.RawTBSCertificate)
|
||||
assert.NotEqual(t, oldLeaf.Signature, manageableFullIdentity.Leaf.Signature)
|
||||
|
||||
revocationExt := tlsopts.NewExtensionsMap(manageableFullIdentity.Leaf)[extensions.RevocationExtID.String()]
|
||||
assert.True(t, extensions.RevocationExtID.Equal(revocationExt.Id))
|
||||
|
||||
var rev extensions.Revocation
|
||||
err = rev.Unmarshal(revocationExt.Value)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = rev.Verify(manageableFullIdentity.CA.Cert)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func pregeneratedIdentity(t *testing.T) *identity.FullIdentity {
|
||||
const chain = `-----BEGIN CERTIFICATE-----
|
||||
MIIBQDCB56ADAgECAhB+u3d03qyW/ROgwy/ZsPccMAoGCCqGSM49BAMCMAAwIhgP
|
||||
|
@ -117,7 +117,7 @@ func AddSignedCert(key crypto.PrivateKey, cert *x509.Certificate) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = AddExtension(cert, pkix.Extension{
|
||||
err = AddExtraExtension(cert, pkix.Extension{
|
||||
Id: SignedCertExtID,
|
||||
Value: signature,
|
||||
})
|
||||
@ -127,8 +127,11 @@ func AddSignedCert(key crypto.PrivateKey, cert *x509.Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddExtension adds one or more extensions to a certificate
|
||||
func AddExtension(cert *x509.Certificate, exts ...pkix.Extension) (err error) {
|
||||
// AddExtraExtension adds one or more extensions to a certificate for serialization.
|
||||
// NB: this *does not* serialize or persist the extension into the certificates's
|
||||
// raw bytes. To add a persistent extension use `FullCertificateAuthority.AddExtension`
|
||||
// or `ManageableIdentity.AddExtension`.
|
||||
func AddExtraExtension(cert *x509.Certificate, exts ...pkix.Extension) (err error) {
|
||||
if len(exts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -8,169 +8,16 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zeebo/errs"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/internal/testpeertls"
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
)
|
||||
|
||||
func TestParseExtensions(t *testing.T) {
|
||||
// TODO: separate this into multiple tests!
|
||||
// TODO: this is not a great test
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
revokedLeafKeys, revokedLeafChain, _, err := testpeertls.NewRevokedLeafChain()
|
||||
assert.NoError(t, err)
|
||||
|
||||
whitelistSignedKeys, whitelistSignedChain, err := testpeertls.NewCertChain(3)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = extensions.AddSignedCert(whitelistSignedKeys[0], whitelistSignedChain[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, unrelatedChain, err := testpeertls.NewCertChain(1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
revDB, err := identity.NewRevocationDB("bolt://" + ctx.File("revocations.db"))
|
||||
assert.NoError(t, err)
|
||||
defer ctx.Check(revDB.Close)
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
config extensions.Config
|
||||
certChain []*x509.Certificate
|
||||
whitelist []*x509.Certificate
|
||||
errClass *errs.Class
|
||||
err error
|
||||
}{
|
||||
{
|
||||
"leaf whitelist signature - success",
|
||||
extensions.Config{WhitelistSignedLeaf: true},
|
||||
whitelistSignedChain,
|
||||
[]*x509.Certificate{whitelistSignedChain[2]},
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"leaf whitelist signature - failure (empty whitelist)",
|
||||
extensions.Config{WhitelistSignedLeaf: true},
|
||||
whitelistSignedChain,
|
||||
nil,
|
||||
&extensions.Error,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"leaf whitelist signature - failure",
|
||||
extensions.Config{WhitelistSignedLeaf: true},
|
||||
whitelistSignedChain,
|
||||
unrelatedChain,
|
||||
&extensions.Error,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"certificate revocation - single revocation ",
|
||||
extensions.Config{Revocation: true},
|
||||
revokedLeafChain,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"certificate revocation - serial revocations",
|
||||
extensions.Config{Revocation: true},
|
||||
func() []*x509.Certificate {
|
||||
rev := new(extensions.Revocation)
|
||||
time.Sleep(1 * time.Second)
|
||||
chain, revocationExt, err := testpeertls.RevokeLeaf(revokedLeafKeys, revokedLeafChain)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = rev.Unmarshal(revocationExt.Value)
|
||||
assert.NoError(t, err)
|
||||
|
||||
return chain
|
||||
}(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"certificate revocation - serial revocations error (older timestamp)",
|
||||
extensions.Config{Revocation: true},
|
||||
func() []*x509.Certificate {
|
||||
keys, chain, _, err := testpeertls.NewRevokedLeafChain()
|
||||
assert.NoError(t, err)
|
||||
|
||||
rev := new(extensions.Revocation)
|
||||
err = rev.Unmarshal(chain[0].ExtraExtensions[0].Value)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rev.Timestamp = rev.Timestamp + 300
|
||||
err = rev.Sign(keys[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
revBytes, err := rev.Marshal()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = revDB.Put(chain, pkix.Extension{
|
||||
Id: extensions.RevocationExtID,
|
||||
Value: revBytes,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
return chain
|
||||
}(),
|
||||
nil,
|
||||
&extensions.Error,
|
||||
extensions.ErrRevocationTimestamp,
|
||||
},
|
||||
{
|
||||
"certificate revocation and leaf whitelist signature",
|
||||
extensions.Config{Revocation: true, WhitelistSignedLeaf: true},
|
||||
func() []*x509.Certificate {
|
||||
_, chain, _, err := testpeertls.NewRevokedLeafChain()
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = extensions.AddSignedCert(whitelistSignedKeys[0], chain[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
return chain
|
||||
}(),
|
||||
[]*x509.Certificate{whitelistSignedChain[2]},
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
opts := &extensions.Options{
|
||||
PeerCAWhitelist: testcase.whitelist,
|
||||
RevDB: revDB,
|
||||
}
|
||||
|
||||
handlerFuncMap := extensions.AllHandlers.WithOptions(opts)
|
||||
extensionsMap := tlsopts.NewExtensionsMap(testcase.certChain...)
|
||||
err := extensionsMap.HandleExtensions(handlerFuncMap, identity.ToChains(testcase.certChain))
|
||||
if testcase.errClass != nil {
|
||||
assert.True(t, testcase.errClass.Has(err))
|
||||
}
|
||||
if testcase.err != nil {
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
if testcase.errClass == nil && testcase.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlers_Register(t *testing.T) {
|
||||
var (
|
||||
handlers = extensions.HandlerFactories{}
|
||||
|
@ -40,10 +40,10 @@ var ErrRevokedCert = ErrRevocation.New("a certificate in the chain is revoked")
|
||||
var ErrRevocationTimestamp = Error.New("revocation timestamp is older than last known revocation")
|
||||
|
||||
// Revocation represents a certificate revocation for storage in the revocation
|
||||
// database and for use in a TLS extension
|
||||
// database and for use in a TLS extension.
|
||||
type Revocation struct {
|
||||
Timestamp int64
|
||||
CertHash []byte
|
||||
KeyHash []byte
|
||||
Signature []byte
|
||||
}
|
||||
|
||||
@ -67,10 +67,13 @@ func init() {
|
||||
func NewRevocationExt(key crypto.PrivateKey, revokedCert *x509.Certificate) (pkix.Extension, error) {
|
||||
nowUnix := time.Now().Unix()
|
||||
|
||||
hash := pkcrypto.SHA256Hash(revokedCert.Raw)
|
||||
keyHash, err := peertls.DoubleSHA256PublicKey(revokedCert.PublicKey)
|
||||
if err != nil {
|
||||
return pkix.Extension{}, err
|
||||
}
|
||||
rev := Revocation{
|
||||
Timestamp: nowUnix,
|
||||
CertHash: hash,
|
||||
KeyHash: keyHash[:],
|
||||
}
|
||||
|
||||
if err := rev.Sign(key); err != nil {
|
||||
@ -90,24 +93,9 @@ func NewRevocationExt(key crypto.PrivateKey, revokedCert *x509.Certificate) (pki
|
||||
return ext, nil
|
||||
}
|
||||
|
||||
// AddRevocationExt generates a revocation extension for a cert and attaches it
|
||||
// to the cert which will replace the revoked cert.
|
||||
func AddRevocationExt(key crypto.PrivateKey, revokedCert, newCert *x509.Certificate) error {
|
||||
ext, err := NewRevocationExt(key, revokedCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = AddExtension(newCert, ext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func revocationChecker(opts *Options) HandlerFunc {
|
||||
return func(_ pkix.Extension, chains [][]*x509.Certificate) error {
|
||||
leaf := chains[0][peertls.LeafIndex]
|
||||
ca := chains[0][peertls.CAIndex]
|
||||
ca, leaf := chains[0][peertls.CAIndex], chains[0][peertls.LeafIndex]
|
||||
lastRev, lastRevErr := opts.RevDB.Get(chains[0])
|
||||
if lastRevErr != nil {
|
||||
return Error.Wrap(lastRevErr)
|
||||
@ -116,15 +104,21 @@ func revocationChecker(opts *Options) HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
caHash := pkcrypto.SHA256Hash(ca.Raw)
|
||||
leafHash := pkcrypto.SHA256Hash(leaf.Raw)
|
||||
nodeID, err := peertls.DoubleSHA256PublicKey(ca.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leaf.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// NB: we trust that anything that made it into the revocation DB is valid
|
||||
// (i.e. no need for further verification)
|
||||
switch {
|
||||
case bytes.Equal(lastRev.CertHash, caHash):
|
||||
return ErrRevokedCert
|
||||
case bytes.Equal(lastRev.CertHash, leafHash):
|
||||
case bytes.Equal(lastRev.KeyHash, nodeID[:]):
|
||||
fallthrough
|
||||
case bytes.Equal(lastRev.KeyHash, leafKeyHash[:]):
|
||||
return ErrRevokedCert
|
||||
default:
|
||||
return nil
|
||||
@ -155,12 +149,12 @@ func (r Revocation) Verify(signingCert *x509.Certificate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TBSBytes (ToBeSigned) returns the hash of the revoked certificate hash and
|
||||
// the timestamp (i.e. hash(hash(cert bytes) + timestamp)).
|
||||
// TBSBytes (ToBeSigned) returns the hash of the revoked certificate key hash
|
||||
// and the timestamp (i.e. hash(hash(cert bytes) + timestamp)).
|
||||
func (r *Revocation) TBSBytes() []byte {
|
||||
var tsBytes [binary.MaxVarintLen64]byte
|
||||
binary.PutVarint(tsBytes[:], r.Timestamp)
|
||||
toHash := append(append([]byte{}, r.CertHash...), tsBytes[:]...)
|
||||
toHash := append(append([]byte{}, r.KeyHash...), tsBytes[:]...)
|
||||
|
||||
return pkcrypto.SHA256Hash(toHash)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/internal/testidentity"
|
||||
"storj.io/storj/internal/testpeertls"
|
||||
"storj.io/storj/pkg/identity"
|
||||
@ -21,9 +20,6 @@ import (
|
||||
)
|
||||
|
||||
func TestRevocationCheckHandler(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
testidentity.RevocationDBsTest(t, func(t *testing.T, revDB extensions.RevocationDB, _ storage.KeyValueStore) {
|
||||
keys, chain, err := testpeertls.NewCertChain(2)
|
||||
assert.NoError(t, err)
|
||||
@ -37,68 +33,97 @@ func TestRevocationCheckHandler(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
revokedLeafChain, leafRevocationExt, err := testpeertls.RevokeLeaf(keys, chain)
|
||||
revokingChain, leafRevocationExt, err := testpeertls.RevokeLeaf(keys[0], chain)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, chain[peertls.CAIndex].Raw, revokedLeafChain[peertls.CAIndex].Raw)
|
||||
assert.Equal(t, chain[peertls.CAIndex].Raw, revokingChain[peertls.CAIndex].Raw)
|
||||
|
||||
{
|
||||
t.Log("revoked leaf success")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokedLeafChain))
|
||||
t.Log("revoked leaf success (original chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(chain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("revoked leaf success (revoking chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokingChain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// NB: add leaf revocation to revocation DB
|
||||
err = revDB.Put(revokedLeafChain, leafRevocationExt)
|
||||
t.Log("revocation DB put leaf revocation")
|
||||
err = revDB.Put(revokingChain, leafRevocationExt)
|
||||
require.NoError(t, err)
|
||||
|
||||
{
|
||||
t.Log("revoked leaf error")
|
||||
t.Log("revoked leaf success (revoking chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokingChain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("revoked leaf error (original chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(chain))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
|
||||
// NB: timestamp must be different because the NodeID is the same
|
||||
time.Sleep(time.Second)
|
||||
testidentity.RevocationDBsTest(t, func(t *testing.T, revDB extensions.RevocationDB, _ storage.KeyValueStore) {
|
||||
keys, chain, err := testpeertls.NewCertChain(2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
revokedCAChain, caRevocationExt, err := testpeertls.RevokeCA(keys, chain)
|
||||
opts := &extensions.Options{RevDB: revDB}
|
||||
revocationChecker := extensions.RevocationCheckHandler.NewHandlerFunc(opts)
|
||||
revokingChain, caRevocationExt, err := testpeertls.RevokeCA(keys[0], chain)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, chain[peertls.CAIndex].Raw, revokingChain[peertls.CAIndex].Raw)
|
||||
|
||||
{
|
||||
t.Log("revoked CA success")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokedCAChain))
|
||||
t.Log("revoked CA error (original chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(chain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("revoked CA success (revokingChain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokingChain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// NB: add CA revocation to revocation DB
|
||||
err = revDB.Put(revokedCAChain, caRevocationExt)
|
||||
t.Log("revocation DB put CA revocation")
|
||||
err = revDB.Put(revokingChain, caRevocationExt)
|
||||
require.NoError(t, err)
|
||||
|
||||
{
|
||||
t.Log("revoked CA error")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokedCAChain))
|
||||
t.Log("revoked CA error (revoking CA chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(revokingChain))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
{
|
||||
t.Log("revoked CA error (original chain)")
|
||||
err := revocationChecker(pkix.Extension{}, identity.ToChains(chain))
|
||||
assert.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRevocationUpdateHandler(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
testidentity.RevocationDBsTest(t, func(t *testing.T, revDB extensions.RevocationDB, _ storage.KeyValueStore) {
|
||||
keys, chain, err := testpeertls.NewCertChain(2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
olderRevokedChain, olderRevocation, err := testpeertls.RevokeLeaf(keys, chain)
|
||||
olderRevokedChain, olderRevocation, err := testpeertls.RevokeLeaf(keys[0], chain)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
revokedLeafChain, newerRevocation, err := testpeertls.RevokeLeaf(keys, chain)
|
||||
revokedLeafChain, newerRevocation, err := testpeertls.RevokeLeaf(keys[0], chain)
|
||||
require.NoError(t, err)
|
||||
|
||||
time.Sleep(time.Second)
|
||||
newestRevokedChain, newestRevocation, err := testpeertls.RevokeLeaf(keys, revokedLeafChain)
|
||||
newestRevokedChain, newestRevocation, err := testpeertls.RevokeLeaf(keys[0], revokedLeafChain)
|
||||
require.NoError(t, err)
|
||||
|
||||
opts := &extensions.Options{RevDB: revDB}
|
||||
|
@ -6,6 +6,7 @@ package peertls // import "storj.io/storj/pkg/peertls"
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@ -114,12 +115,6 @@ func WriteChain(w io.Writer, chain ...*x509.Certificate) error {
|
||||
if err := pkcrypto.WriteCertPEM(w, c); err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
for _, e := range c.ExtraExtensions {
|
||||
if err := pkcrypto.WritePKIXExtensionPEM(w, &e); err != nil {
|
||||
extErrs.Add(errs.Wrap(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
return extErrs.Err()
|
||||
}
|
||||
@ -131,26 +126,29 @@ func ChainBytes(chain ...*x509.Certificate) ([]byte, error) {
|
||||
return data.Bytes(), err
|
||||
}
|
||||
|
||||
// NewSelfSignedCert returns a new x509 self-signed certificate using the provided // template and key,
|
||||
func NewSelfSignedCert(key crypto.PrivateKey, template *x509.Certificate) (*x509.Certificate, error) {
|
||||
return NewCert(pkcrypto.PublicKeyFromPrivate(key), key, template, template)
|
||||
}
|
||||
|
||||
// NewCert returns a new x509 certificate using the provided templates and key,
|
||||
// signed by the parent cert if provided; otherwise, self-signed.
|
||||
func NewCert(key, parentKey crypto.PrivateKey, template, parent *x509.Certificate) (*x509.Certificate, error) {
|
||||
var signingKey crypto.PrivateKey
|
||||
if parentKey != nil {
|
||||
signingKey = parentKey
|
||||
} else {
|
||||
signingKey = key
|
||||
}
|
||||
|
||||
func NewCert(publicKey crypto.PublicKey, parentKey crypto.PrivateKey, template, parent *x509.Certificate) (*x509.Certificate, error) {
|
||||
if parent == nil {
|
||||
parent = template
|
||||
}
|
||||
|
||||
publicECKey, ok := publicKey.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errs.New("unsupported public key type %T", publicKey)
|
||||
}
|
||||
|
||||
cb, err := x509.CreateCertificate(
|
||||
rand.Reader,
|
||||
template,
|
||||
parent,
|
||||
pkcrypto.PublicKeyFromPrivate(key),
|
||||
signingKey,
|
||||
publicECKey,
|
||||
parentKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
|
@ -30,7 +30,7 @@ func TestNewCert_CA(t *testing.T) {
|
||||
caTemplate, err := peertls.CATemplate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
|
||||
caCert, err := peertls.NewSelfSignedCert(caKey, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, caKey)
|
||||
@ -48,7 +48,7 @@ func TestNewCert_Leaf(t *testing.T) {
|
||||
caTemplate, err := peertls.CATemplate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
caCert, err := peertls.NewCert(caKey, nil, caTemplate, nil)
|
||||
caCert, err := peertls.NewSelfSignedCert(caKey, caTemplate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafKey, err := pkcrypto.GeneratePrivateKey()
|
||||
@ -57,7 +57,8 @@ func TestNewCert_Leaf(t *testing.T) {
|
||||
leafTemplate, err := peertls.LeafTemplate()
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caCert)
|
||||
leafPublicKey := pkcrypto.PublicKeyFromPrivate(leafKey)
|
||||
leafCert, err := peertls.NewCert(leafPublicKey, caKey, leafTemplate, caCert)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NotEmpty(t, caKey)
|
||||
@ -110,7 +111,7 @@ func TestVerifyPeerCertChains(t *testing.T) {
|
||||
wrongKey, err := pkcrypto.GeneratePrivateKey()
|
||||
assert.NoError(t, err)
|
||||
|
||||
leafCert, err = peertls.NewCert(leafKey, wrongKey, leafCert, caCert)
|
||||
leafCert, err = peertls.NewCert(pkcrypto.PublicKeyFromPrivate(leafKey), wrongKey, leafCert, caCert)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{leafCert.Raw, caCert.Raw}, nil)
|
||||
@ -177,70 +178,30 @@ func TestVerifyCAWhitelist(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddExtension(t *testing.T) {
|
||||
func TestAddExtraExtension(t *testing.T) {
|
||||
_, chain, err := testpeertls.NewCertChain(1)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
// NB: there's nothing special about length 32
|
||||
randBytes := make([]byte, 32)
|
||||
exampleID := asn1.ObjectIdentifier{2, 999}
|
||||
i, err := rand.Read(randBytes)
|
||||
cert := chain[0]
|
||||
extLen := len(cert.Extensions)
|
||||
|
||||
randBytes := make([]byte, 10)
|
||||
_, err = rand.Read(randBytes)
|
||||
require.NoError(t, err)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, 32, i)
|
||||
|
||||
ext := pkix.Extension{
|
||||
Id: exampleID,
|
||||
Id: asn1.ObjectIdentifier{2, 999, int(randBytes[0])},
|
||||
Value: randBytes,
|
||||
}
|
||||
|
||||
err = extensions.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 := testpeertls.NewCertChain(1)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
err = extensions.AddSignedCert(keys[0], chain[0])
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, chain[0].ExtraExtensions, 1)
|
||||
assert.True(t, extensions.SignedCertExtID.Equal(chain[0].ExtraExtensions[0].Id))
|
||||
|
||||
err = pkcrypto.HashAndVerifySignature(
|
||||
pkcrypto.PublicKeyFromPrivate(keys[0]),
|
||||
chain[0].RawTBSCertificate,
|
||||
chain[0].ExtraExtensions[0].Value,
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestSignLeafExt(t *testing.T) {
|
||||
keys, chain, err := testpeertls.NewCertChain(2)
|
||||
if !assert.NoError(t, err) {
|
||||
t.FailNow()
|
||||
}
|
||||
caKey, leafCert := keys[0], chain[0]
|
||||
|
||||
err = extensions.AddSignedCert(caKey, leafCert)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(leafCert.ExtraExtensions))
|
||||
assert.True(t, extensions.SignedCertExtID.Equal(leafCert.ExtraExtensions[0].Id))
|
||||
|
||||
err = pkcrypto.HashAndVerifySignature(
|
||||
pkcrypto.PublicKeyFromPrivate(caKey),
|
||||
leafCert.RawTBSCertificate,
|
||||
leafCert.ExtraExtensions[0].Value,
|
||||
)
|
||||
err = extensions.AddExtraExtension(cert, ext)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, cert.ExtraExtensions, 1)
|
||||
require.Len(t, cert.Extensions, extLen)
|
||||
assert.Equal(t, ext, cert.ExtraExtensions[0])
|
||||
}
|
||||
|
||||
func TestRevocation_Sign(t *testing.T) {
|
||||
@ -248,13 +209,14 @@ func TestRevocation_Sign(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
leafCert, caKey := chain[0], keys[0]
|
||||
|
||||
leafHash := pkcrypto.SHA256Hash(leafCert.Raw)
|
||||
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leafCert.PublicKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
rev := extensions.Revocation{
|
||||
Timestamp: time.Now().Unix(),
|
||||
CertHash: make([]byte, len(leafHash)),
|
||||
KeyHash: make([]byte, len(leafKeyHash)),
|
||||
}
|
||||
copy(rev.CertHash, leafHash)
|
||||
copy(rev.KeyHash, leafKeyHash[:])
|
||||
err = rev.Sign(caKey)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rev.Signature)
|
||||
@ -265,13 +227,14 @@ func TestRevocation_Verify(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
leafCert, caCert, caKey := chain[0], chain[1], keys[0]
|
||||
|
||||
leafHash := pkcrypto.SHA256Hash(leafCert.Raw)
|
||||
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leafCert.PublicKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
rev := extensions.Revocation{
|
||||
Timestamp: time.Now().Unix(),
|
||||
CertHash: make([]byte, len(leafHash)),
|
||||
KeyHash: make([]byte, len(leafKeyHash)),
|
||||
}
|
||||
copy(rev.CertHash, leafHash)
|
||||
copy(rev.KeyHash, leafKeyHash[:])
|
||||
err = rev.Sign(caKey)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rev.Signature)
|
||||
@ -285,13 +248,14 @@ func TestRevocation_Marshal(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
leafCert, caKey := chain[0], keys[0]
|
||||
|
||||
leafHash := pkcrypto.SHA256Hash(leafCert.Raw)
|
||||
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leafCert.PublicKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
rev := extensions.Revocation{
|
||||
Timestamp: time.Now().Unix(),
|
||||
CertHash: make([]byte, len(leafHash)),
|
||||
KeyHash: make([]byte, len(leafKeyHash)),
|
||||
}
|
||||
copy(rev.CertHash, leafHash)
|
||||
copy(rev.KeyHash, leafKeyHash[:])
|
||||
err = rev.Sign(caKey)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rev.Signature)
|
||||
@ -312,13 +276,14 @@ func TestRevocation_Unmarshal(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
leafCert, caKey := chain[0], keys[0]
|
||||
|
||||
leafHash := pkcrypto.SHA256Hash(leafCert.Raw)
|
||||
leafKeyHash, err := peertls.DoubleSHA256PublicKey(leafCert.PublicKey)
|
||||
require.NoError(t, err)
|
||||
|
||||
rev := extensions.Revocation{
|
||||
Timestamp: time.Now().Unix(),
|
||||
CertHash: make([]byte, len(leafHash)),
|
||||
KeyHash: make([]byte, len(leafKeyHash)),
|
||||
}
|
||||
copy(rev.CertHash, leafHash)
|
||||
copy(rev.KeyHash, leafKeyHash[:])
|
||||
err = rev.Sign(caKey)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, rev.Signature)
|
||||
|
@ -64,7 +64,7 @@ func NewOptions(i *identity.FullIdentity, c Config) (*Options, error) {
|
||||
func NewExtensionsMap(chain ...*x509.Certificate) ExtensionMap {
|
||||
extensionMap := make(ExtensionMap)
|
||||
for _, cert := range chain {
|
||||
for _, ext := range cert.ExtraExtensions {
|
||||
for _, ext := range cert.Extensions {
|
||||
extensionMap[ext.Id.String()] = ext
|
||||
}
|
||||
}
|
||||
|
@ -4,15 +4,23 @@
|
||||
package tlsopts_test
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"storj.io/storj/internal/testcontext"
|
||||
"storj.io/storj/internal/testidentity"
|
||||
"storj.io/storj/internal/testpeertls"
|
||||
"storj.io/storj/internal/testplanet"
|
||||
"storj.io/storj/pkg/identity"
|
||||
"storj.io/storj/pkg/peertls"
|
||||
"storj.io/storj/pkg/peertls/extensions"
|
||||
"storj.io/storj/pkg/peertls/tlsopts"
|
||||
"storj.io/storj/pkg/storj"
|
||||
"storj.io/storj/storage"
|
||||
)
|
||||
|
||||
func TestVerifyIdentity_success(t *testing.T) {
|
||||
@ -58,3 +66,90 @@ func TestVerifyIdentity_error(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtensionMap_HandleExtensions(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
keys, originalChain, err := testpeertls.NewCertChain(2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
rev := new(extensions.Revocation)
|
||||
|
||||
// TODO: `keys[peertls.CAIndex]`
|
||||
oldRevokedLeafChain, revocationExt, err := testpeertls.RevokeLeaf(keys[0], originalChain)
|
||||
require.NoError(t, err)
|
||||
err = rev.Unmarshal(revocationExt.Value)
|
||||
require.NoError(t, err)
|
||||
err = rev.Verify(oldRevokedLeafChain[peertls.CAIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
// NB: node ID is the same, timestamp must change
|
||||
// (see: identity.RevocationDB#Put)
|
||||
time.Sleep(1 * time.Second)
|
||||
// TODO: `keys[peertls.CAIndex]`
|
||||
newRevokedLeafChain, revocationExt, err := testpeertls.RevokeLeaf(keys[0], oldRevokedLeafChain)
|
||||
require.NoError(t, err)
|
||||
err = rev.Unmarshal(revocationExt.Value)
|
||||
require.NoError(t, err)
|
||||
err = rev.Verify(newRevokedLeafChain[peertls.CAIndex])
|
||||
require.NoError(t, err)
|
||||
|
||||
testidentity.RevocationDBsTest(t, func(t *testing.T, revDB extensions.RevocationDB, db storage.KeyValueStore) {
|
||||
opts := &extensions.Options{
|
||||
RevDB: revDB,
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
chain []*x509.Certificate
|
||||
}{
|
||||
{"no extensions", originalChain},
|
||||
{"leaf revocation", oldRevokedLeafChain},
|
||||
{"double leaf revocation", newRevokedLeafChain},
|
||||
// TODO: more and more diverse extensions in cases
|
||||
}
|
||||
|
||||
{
|
||||
handlerFuncMap := extensions.AllHandlers.WithOptions(opts)
|
||||
for _, testcase := range testcases {
|
||||
t.Log(testcase.name)
|
||||
extensionsMap := tlsopts.NewExtensionsMap(testcase.chain...)
|
||||
err := extensionsMap.HandleExtensions(handlerFuncMap, identity.ToChains(testcase.chain))
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtensionMap_HandleExtensions_error(t *testing.T) {
|
||||
ctx := testcontext.New(t)
|
||||
defer ctx.Cleanup()
|
||||
|
||||
testidentity.RevocationDBsTest(t, func(t *testing.T, revDB extensions.RevocationDB, db storage.KeyValueStore) {
|
||||
keys, chain, oldRevocation, err := testpeertls.NewRevokedLeafChain()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// NB: node ID is the same, timestamp must change
|
||||
// (see: identity.RevocationDB#Put)
|
||||
time.Sleep(time.Second)
|
||||
_, newRevocation, err := testpeertls.RevokeLeaf(keys[0], chain)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.NotEqual(t, oldRevocation, newRevocation)
|
||||
|
||||
err = revDB.Put(chain, newRevocation)
|
||||
assert.NoError(t, err)
|
||||
|
||||
opts := &extensions.Options{RevDB: revDB}
|
||||
handlerFuncMap := extensions.HandlerFactories{
|
||||
extensions.RevocationUpdateHandler,
|
||||
}.WithOptions(opts)
|
||||
extensionsMap := tlsopts.NewExtensionsMap(chain[peertls.LeafIndex])
|
||||
|
||||
assert.Equal(t, oldRevocation, extensionsMap[extensions.RevocationExtID.String()])
|
||||
|
||||
err = extensionsMap.HandleExtensions(handlerFuncMap, identity.ToChains(chain))
|
||||
assert.Errorf(t, err, extensions.ErrRevocationTimestamp.Error())
|
||||
})
|
||||
}
|
||||
|
@ -12,7 +12,9 @@ package peertls
|
||||
// (see https://en.wikipedia.org/wiki/Privacy-enhanced_Electronic_Mail)
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"math/big"
|
||||
|
||||
@ -35,6 +37,18 @@ func NewNonTemporaryError(err error) NonTemporaryError {
|
||||
}
|
||||
}
|
||||
|
||||
// DoubleSHA256PublicKey returns the hash of the hash of (double-hash, SHA226)
|
||||
// the binary format of the given public key.
|
||||
func DoubleSHA256PublicKey(k crypto.PublicKey) ([sha256.Size]byte, error) {
|
||||
kb, err := x509.MarshalPKIXPublicKey(k)
|
||||
if err != nil {
|
||||
return [sha256.Size]byte{}, err
|
||||
}
|
||||
mid := sha256.Sum256(kb)
|
||||
end := sha256.Sum256(mid[:])
|
||||
return end, nil
|
||||
}
|
||||
|
||||
// Temporary returns false to indicate that is is a non-temporary error
|
||||
func (nte NonTemporaryError) Temporary() bool {
|
||||
return false
|
||||
|
@ -6,7 +6,6 @@ package pkcrypto
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"io"
|
||||
@ -184,9 +183,6 @@ func CertsFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
|
||||
switch pemBlock.Type {
|
||||
case BlockLabelCertificate:
|
||||
encChain.AddCert(pemBlock.Bytes)
|
||||
case BlockLabelExtension:
|
||||
err := encChain.AddExtension(pemBlock.Bytes)
|
||||
blockErrs.Add(err)
|
||||
}
|
||||
}
|
||||
if err := blockErrs.Err(); err != nil {
|
||||
@ -197,24 +193,11 @@ func CertsFromPEM(pemBytes []byte) ([]*x509.Certificate, error) {
|
||||
}
|
||||
|
||||
type encodedChain struct {
|
||||
chain [][]byte
|
||||
extensions [][][]byte
|
||||
chain [][]byte
|
||||
}
|
||||
|
||||
func (e *encodedChain) AddCert(b []byte) {
|
||||
e.chain = append(e.chain, b)
|
||||
e.extensions = append(e.extensions, [][]byte{})
|
||||
}
|
||||
|
||||
func (e *encodedChain) AddExtension(b []byte) error {
|
||||
chainLen := len(e.chain)
|
||||
if chainLen < 1 {
|
||||
return ErrChainLength.New("expected: >= 1; actual: %d", chainLen)
|
||||
}
|
||||
|
||||
i := chainLen - 1
|
||||
e.extensions[i] = append(e.extensions[i], b)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *encodedChain) Parse() ([]*x509.Certificate, error) {
|
||||
@ -223,72 +206,9 @@ func (e *encodedChain) Parse() ([]*x509.Certificate, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var extErrs errs.Group
|
||||
for i, cert := range chain {
|
||||
for _, ee := range e.extensions[i] {
|
||||
ext, err := PKIXExtensionFromASN1(ee)
|
||||
extErrs.Add(err) // TODO: is this correct?
|
||||
cert.ExtraExtensions = append(cert.ExtraExtensions, *ext)
|
||||
}
|
||||
}
|
||||
if err := extErrs.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
// WritePKIXExtensionPEM writes the certificate extension to the writer, in a PEM-
|
||||
// enveloped PKIX form.
|
||||
func WritePKIXExtensionPEM(w io.Writer, extension *pkix.Extension) error {
|
||||
extBytes, err := PKIXExtensionToASN1(extension)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
err = pem.Encode(w, &pem.Block{Type: BlockLabelExtension, Bytes: extBytes})
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
// PKIXExtensionToPEM serializes a PKIX certificate extension to PEM-
|
||||
// enveloped ASN.1 bytes.
|
||||
func PKIXExtensionToPEM(extension *pkix.Extension) ([]byte, error) {
|
||||
asn, err := PKIXExtensionToASN1(extension)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pem.EncodeToMemory(&pem.Block{Type: BlockLabelExtension, Bytes: asn}), nil
|
||||
}
|
||||
|
||||
// PKIXExtensionToASN1 serializes a PKIX certificate extension to the
|
||||
// appropriate ASN.1 structure for such things. See RFC 5280, section 4.1.1.2.
|
||||
func PKIXExtensionToASN1(extension *pkix.Extension) ([]byte, error) {
|
||||
extBytes, err := asn1.Marshal(extension)
|
||||
return extBytes, errs.Wrap(err)
|
||||
}
|
||||
|
||||
// PKIXExtensionFromASN1 deserializes a PKIX certificate extension from
|
||||
// the appropriate ASN.1 structure for such things.
|
||||
func PKIXExtensionFromASN1(extData []byte) (*pkix.Extension, error) {
|
||||
var extension pkix.Extension
|
||||
if _, err := asn1.Unmarshal(extData, &extension); err != nil {
|
||||
return nil, ErrParse.New("unable to unmarshal PKIX extension: %v", err)
|
||||
}
|
||||
return &extension, nil
|
||||
}
|
||||
|
||||
// PKIXExtensionFromPEM parses a PKIX certificate extension from
|
||||
// PEM-enveloped ASN.1 bytes.
|
||||
func PKIXExtensionFromPEM(pemBytes []byte) (*pkix.Extension, error) {
|
||||
pb, _ := pem.Decode(pemBytes)
|
||||
if pb == nil {
|
||||
return nil, ErrParse.New("unable to parse PEM block")
|
||||
}
|
||||
if pb.Type != BlockLabelExtension {
|
||||
return nil, ErrParse.New("can not parse PKIX cert extension from PEM block labeled %q", pb.Type)
|
||||
}
|
||||
return PKIXExtensionFromASN1(pb.Bytes)
|
||||
}
|
||||
|
||||
type ecdsaSignature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user