2019-02-06 16:40:55 +00:00
|
|
|
// Copyright (C) 2019 Storj Labs, Inc.
|
|
|
|
// See LICENSE for copying information.
|
|
|
|
|
|
|
|
package identity
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
|
|
|
|
"storj.io/storj/internal/testcontext"
|
|
|
|
"storj.io/storj/internal/testpeertls"
|
|
|
|
"storj.io/storj/pkg/peertls"
|
2019-02-07 09:04:29 +00:00
|
|
|
"storj.io/storj/pkg/pkcrypto"
|
2019-02-06 16:40:55 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type extensionHandlerMock struct {
|
|
|
|
mock.Mock
|
|
|
|
}
|
|
|
|
|
|
|
|
func (m *extensionHandlerMock) verify(ext pkix.Extension, chain [][]*x509.Certificate) error {
|
|
|
|
args := m.Called(ext, chain)
|
|
|
|
return args.Error(0)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExtensionHandlers_VerifyFunc(t *testing.T) {
|
|
|
|
keys, chain, err := newRevokedLeafChain()
|
|
|
|
require.NoError(t, err)
|
|
|
|
err = peertls.AddSignedCertExt(keys[0], chain[0])
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
extMock := new(extensionHandlerMock)
|
|
|
|
verify := func(ext pkix.Extension, chain [][]*x509.Certificate) error {
|
|
|
|
return extMock.verify(ext, chain)
|
|
|
|
}
|
|
|
|
|
|
|
|
handlers := peertls.ExtensionHandlers{
|
|
|
|
{
|
|
|
|
ID: peertls.ExtensionIDs[peertls.RevocationExtID],
|
|
|
|
Verify: verify,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ID: peertls.ExtensionIDs[peertls.SignedCertExtID],
|
|
|
|
Verify: verify,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
chains := [][]*x509.Certificate{chain}
|
|
|
|
extMock.On("verify", chains[0][peertls.LeafIndex].ExtraExtensions[0], chains).Return(nil)
|
|
|
|
extMock.On("verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains).Return(nil)
|
|
|
|
|
|
|
|
err = handlers.VerifyFunc()(nil, chains)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[0], chains)
|
|
|
|
extMock.AssertCalled(t, "verify", chains[0][peertls.LeafIndex].ExtraExtensions[1], chains)
|
|
|
|
assert.True(t, extMock.AssertExpectations(t))
|
|
|
|
|
|
|
|
// TODO: test error scenario(s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseExtensions(t *testing.T) {
|
|
|
|
ctx := testcontext.New(t)
|
|
|
|
defer ctx.Cleanup()
|
|
|
|
|
|
|
|
revokedLeafKeys, revokedLeafChain, err := newRevokedLeafChain()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
whitelistSignedKeys, whitelistSignedChain, err := testpeertls.NewCertChain(3)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], whitelistSignedChain[0])
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
_, unrelatedChain, err := testpeertls.NewCertChain(1)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
revDB, err := NewRevocationDBBolt(ctx.File("revocations.db"))
|
|
|
|
assert.NoError(t, err)
|
2019-02-08 09:13:32 +00:00
|
|
|
defer ctx.Check(revDB.Close)
|
2019-02-06 16:40:55 +00:00
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
testID string
|
|
|
|
config peertls.TLSExtConfig
|
|
|
|
extLen int
|
|
|
|
certChain []*x509.Certificate
|
|
|
|
whitelist []*x509.Certificate
|
|
|
|
errClass *errs.Class
|
|
|
|
err error
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"leaf whitelist signature - success",
|
|
|
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
|
|
|
1,
|
|
|
|
whitelistSignedChain,
|
|
|
|
[]*x509.Certificate{whitelistSignedChain[2]},
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"leaf whitelist signature - failure (empty whitelist)",
|
|
|
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
|
|
|
1,
|
|
|
|
whitelistSignedChain,
|
|
|
|
nil,
|
|
|
|
&peertls.ErrVerifyCAWhitelist,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"leaf whitelist signature - failure",
|
|
|
|
peertls.TLSExtConfig{WhitelistSignedLeaf: true},
|
|
|
|
1,
|
|
|
|
whitelistSignedChain,
|
|
|
|
unrelatedChain,
|
|
|
|
&peertls.ErrVerifyCAWhitelist,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"certificate revocation - single revocation ",
|
|
|
|
peertls.TLSExtConfig{Revocation: true},
|
|
|
|
1,
|
|
|
|
revokedLeafChain,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"certificate revocation - serial revocations",
|
|
|
|
peertls.TLSExtConfig{Revocation: true},
|
|
|
|
1,
|
|
|
|
func() []*x509.Certificate {
|
|
|
|
rev := new(peertls.Revocation)
|
|
|
|
time.Sleep(1 * time.Second)
|
|
|
|
_, chain, err := revokeLeaf(revokedLeafKeys, revokedLeafChain)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
err = rev.Unmarshal(chain[0].ExtraExtensions[0].Value)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return chain
|
|
|
|
}(),
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"certificate revocation - serial revocations error (older timestamp)",
|
|
|
|
peertls.TLSExtConfig{Revocation: true},
|
|
|
|
1,
|
|
|
|
func() []*x509.Certificate {
|
|
|
|
keys, chain, err := newRevokedLeafChain()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
rev := new(peertls.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: peertls.ExtensionIDs[peertls.RevocationExtID],
|
|
|
|
Value: revBytes,
|
|
|
|
})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
return chain
|
|
|
|
}(),
|
|
|
|
nil,
|
|
|
|
&peertls.ErrExtension,
|
|
|
|
peertls.ErrRevocationTimestamp,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"certificate revocation and leaf whitelist signature",
|
|
|
|
peertls.TLSExtConfig{Revocation: true, WhitelistSignedLeaf: true},
|
|
|
|
2,
|
|
|
|
func() []*x509.Certificate {
|
|
|
|
_, chain, err := newRevokedLeafChain()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
err = peertls.AddSignedCertExt(whitelistSignedKeys[0], chain[0])
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
return chain
|
|
|
|
}(),
|
|
|
|
[]*x509.Certificate{whitelistSignedChain[2]},
|
|
|
|
nil,
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
t.Run(c.testID, func(t *testing.T) {
|
|
|
|
opts := peertls.ParseExtOptions{
|
|
|
|
CAWhitelist: c.whitelist,
|
|
|
|
RevDB: revDB,
|
|
|
|
}
|
|
|
|
|
|
|
|
handlers := peertls.ParseExtensions(c.config, opts)
|
|
|
|
assert.Equal(t, c.extLen, len(handlers))
|
|
|
|
err := handlers.VerifyFunc()(nil, [][]*x509.Certificate{c.certChain})
|
|
|
|
if c.errClass != nil {
|
|
|
|
assert.True(t, c.errClass.Has(err))
|
|
|
|
}
|
|
|
|
if c.err != nil {
|
|
|
|
assert.NotNil(t, err)
|
|
|
|
}
|
|
|
|
if c.errClass == nil && c.err == nil {
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func revokeLeaf(keys []crypto.PrivateKey, chain []*x509.Certificate) ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
2019-02-07 09:04:29 +00:00
|
|
|
revokingKey, err := pkcrypto.GeneratePrivateKey()
|
2019-02-06 16:40:55 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
revokingTemplate, err := peertls.LeafTemplate()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
revokingCert, err := peertls.NewCert(revokingKey, keys[0], revokingTemplate, chain[1])
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = peertls.AddRevocationExt(keys[0], chain[0], revokingCert)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return keys, append([]*x509.Certificate{revokingCert}, chain[1:]...), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newRevokedLeafChain() ([]crypto.PrivateKey, []*x509.Certificate, error) {
|
|
|
|
keys2, certs2, err := testpeertls.NewCertChain(2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return revokeLeaf(keys2, certs2)
|
|
|
|
}
|