330 lines
8.6 KiB
Go
330 lines
8.6 KiB
Go
// Copyright (C) 2018 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package provider
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"storj.io/storj/pkg/peertls"
|
|
)
|
|
|
|
func TestPeerIdentityFromCertChain(t *testing.T) {
|
|
caKey, err := peertls.NewKey()
|
|
assert.NoError(t, err)
|
|
|
|
caTemplate, err := peertls.CATemplate()
|
|
assert.NoError(t, err)
|
|
|
|
caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
|
|
assert.NoError(t, err)
|
|
|
|
leafTemplate, err := peertls.LeafTemplate()
|
|
assert.NoError(t, err)
|
|
|
|
leafKey, err := peertls.NewKey()
|
|
assert.NoError(t, err)
|
|
|
|
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
|
|
assert.NoError(t, err)
|
|
|
|
peerIdent, err := PeerIdentityFromCerts(leafCert, caCert, nil)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, caCert, peerIdent.CA)
|
|
assert.Equal(t, leafCert, peerIdent.Leaf)
|
|
assert.NotEmpty(t, peerIdent.ID)
|
|
}
|
|
|
|
func TestFullIdentityFromPEM(t *testing.T) {
|
|
caKey, err := peertls.NewKey()
|
|
assert.NoError(t, err)
|
|
|
|
caTemplate, err := peertls.CATemplate()
|
|
assert.NoError(t, err)
|
|
|
|
caCert, err := peertls.NewCert(caKey, caKey, caTemplate, nil)
|
|
assert.NoError(t, err)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, caCert)
|
|
|
|
leafTemplate, err := peertls.LeafTemplate()
|
|
assert.NoError(t, err)
|
|
|
|
leafKey, err := peertls.NewKey()
|
|
assert.NoError(t, err)
|
|
|
|
leafCert, err := peertls.NewCert(leafKey, caKey, leafTemplate, caTemplate)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, leafCert)
|
|
|
|
chainPEM := bytes.NewBuffer([]byte{})
|
|
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(leafCert.Raw)))
|
|
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(caCert.Raw)))
|
|
|
|
leafECKey, ok := leafKey.(*ecdsa.PrivateKey)
|
|
assert.True(t, ok)
|
|
assert.NotEmpty(t, leafECKey)
|
|
|
|
leafKeyBytes, err := x509.MarshalECPrivateKey(leafECKey)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, leafKeyBytes)
|
|
|
|
keyPEM := bytes.NewBuffer([]byte{})
|
|
assert.NoError(t, pem.Encode(keyPEM, peertls.NewKeyBlock(leafKeyBytes)))
|
|
|
|
fullIdent, err := FullIdentityFromPEM(chainPEM.Bytes(), keyPEM.Bytes())
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, leafCert.Raw, fullIdent.Leaf.Raw)
|
|
assert.Equal(t, caCert.Raw, fullIdent.CA.Raw)
|
|
assert.Equal(t, leafKey, fullIdent.Key)
|
|
}
|
|
|
|
func TestIdentityConfig_SaveIdentity(t *testing.T) {
|
|
done, ic, fi, _ := tempIdentity(t)
|
|
defer done()
|
|
|
|
chainPEM := bytes.NewBuffer([]byte{})
|
|
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(fi.Leaf.Raw)))
|
|
assert.NoError(t, pem.Encode(chainPEM, peertls.NewCertBlock(fi.CA.Raw)))
|
|
|
|
privateKey, ok := fi.Key.(*ecdsa.PrivateKey)
|
|
assert.True(t, ok)
|
|
assert.NotEmpty(t, privateKey)
|
|
|
|
keyBytes, err := x509.MarshalECPrivateKey(privateKey)
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, keyBytes)
|
|
|
|
keyPEM := bytes.NewBuffer([]byte{})
|
|
assert.NoError(t, pem.Encode(keyPEM, peertls.NewKeyBlock(keyBytes)))
|
|
|
|
err = ic.Save(fi)
|
|
assert.NoError(t, err)
|
|
|
|
if runtime.GOOS != "windows" {
|
|
// TODO (windows): ignoring for windows due to different default permissions
|
|
certInfo, err := os.Stat(ic.CertPath)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, os.FileMode(0644), certInfo.Mode())
|
|
|
|
keyInfo, err := os.Stat(ic.KeyPath)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, os.FileMode(0600), keyInfo.Mode())
|
|
}
|
|
|
|
savedChainPEM, err := ioutil.ReadFile(ic.CertPath)
|
|
assert.NoError(t, err)
|
|
|
|
savedKeyPEM, err := ioutil.ReadFile(ic.KeyPath)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, chainPEM.Bytes(), savedChainPEM)
|
|
assert.Equal(t, keyPEM.Bytes(), savedKeyPEM)
|
|
}
|
|
|
|
func TestIdentityConfig_LoadIdentity(t *testing.T) {
|
|
done, ic, expectedFI, _ := tempIdentity(t)
|
|
defer done()
|
|
|
|
err := ic.Save(expectedFI)
|
|
assert.NoError(t, err)
|
|
|
|
fi, err := ic.Load()
|
|
assert.NoError(t, err)
|
|
assert.NotEmpty(t, fi)
|
|
assert.NotEmpty(t, fi.Key)
|
|
assert.NotEmpty(t, fi.Leaf)
|
|
assert.NotEmpty(t, fi.CA)
|
|
assert.NotEmpty(t, fi.ID.Bytes())
|
|
|
|
assert.Equal(t, expectedFI.Key, fi.Key)
|
|
assert.Equal(t, expectedFI.Leaf, fi.Leaf)
|
|
assert.Equal(t, expectedFI.CA, fi.CA)
|
|
assert.Equal(t, expectedFI.ID.Bytes(), fi.ID.Bytes())
|
|
}
|
|
|
|
func TestNodeID_Difficulty(t *testing.T) {
|
|
done, _, fi, knownDifficulty := tempIdentity(t)
|
|
defer done()
|
|
|
|
difficulty, err := fi.ID.Difficulty()
|
|
assert.NoError(t, err)
|
|
assert.True(t, difficulty >= knownDifficulty)
|
|
}
|
|
|
|
func TestVerifyPeer(t *testing.T) {
|
|
check := func(e error) {
|
|
if !assert.NoError(t, e) {
|
|
t.Fail()
|
|
}
|
|
}
|
|
|
|
ca, err := newTestCA(context.Background())
|
|
check(err)
|
|
fi, err := ca.NewIdentity()
|
|
check(err)
|
|
|
|
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{fi.Leaf.Raw, fi.CA.Raw}, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestNewServerOptions(t *testing.T) {
|
|
done, _, fi, _ := tempIdentity(t)
|
|
defer done()
|
|
|
|
tmp, err := ioutil.TempDir("", "TestNewServerOptions")
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
defer func() { _ = os.RemoveAll(tmp) }()
|
|
|
|
whitelistPath := filepath.Join(tmp, "whitelist.pem")
|
|
w, err := os.Create(whitelistPath)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
err = peertls.WriteChain(w, fi.CA)
|
|
if !assert.NoError(t, err) {
|
|
t.FailNow()
|
|
}
|
|
|
|
cases := []struct {
|
|
testID string
|
|
config ServerConfig
|
|
pcvFuncsLen int
|
|
}{
|
|
{
|
|
"default",
|
|
ServerConfig{},
|
|
0,
|
|
},
|
|
{
|
|
"revocation processing",
|
|
ServerConfig{
|
|
RevocationDBURL: "bolt://" + filepath.Join(tmp, "revocation1.db"),
|
|
Extensions: peertls.TLSExtConfig{
|
|
Revocation: true,
|
|
},
|
|
},
|
|
2,
|
|
},
|
|
{
|
|
"ca whitelist verification",
|
|
ServerConfig{
|
|
PeerCAWhitelistPath: whitelistPath,
|
|
},
|
|
1,
|
|
},
|
|
{
|
|
"ca whitelist verification and whitelist signed leaf verification",
|
|
ServerConfig{
|
|
// NB: file doesn't actually exist
|
|
PeerCAWhitelistPath: filepath.Join(tmp, "whitelist.pem"),
|
|
Extensions: peertls.TLSExtConfig{
|
|
WhitelistSignedLeaf: true,
|
|
},
|
|
},
|
|
2,
|
|
},
|
|
{
|
|
"revocation processing and whitelist verification",
|
|
ServerConfig{
|
|
// NB: file doesn't actually exist
|
|
PeerCAWhitelistPath: whitelistPath,
|
|
RevocationDBURL: "bolt://" + filepath.Join(tmp, "revocation2.db"),
|
|
Extensions: peertls.TLSExtConfig{
|
|
Revocation: true,
|
|
},
|
|
},
|
|
3,
|
|
},
|
|
{
|
|
"revocation processing, whitelist, and signed leaf verification",
|
|
ServerConfig{
|
|
// NB: file doesn't actually exist
|
|
PeerCAWhitelistPath: whitelistPath,
|
|
RevocationDBURL: "bolt://" + filepath.Join(tmp, "revocation3.db"),
|
|
Extensions: peertls.TLSExtConfig{
|
|
Revocation: true,
|
|
WhitelistSignedLeaf: true,
|
|
},
|
|
},
|
|
3,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.testID, func(t *testing.T) {
|
|
opts, err := NewServerOptions(fi, c.config)
|
|
assert.NoError(t, err)
|
|
assert.True(t, reflect.DeepEqual(fi, opts.Ident))
|
|
assert.Equal(t, c.config, opts.Config)
|
|
assert.Len(t, opts.PCVFuncs, c.pcvFuncsLen)
|
|
})
|
|
}
|
|
}
|
|
|
|
func tempIdentityConfig() (*IdentityConfig, func(), error) {
|
|
tmpDir, err := ioutil.TempDir("", "storj-identity")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cleanup := func() { _ = os.RemoveAll(tmpDir) }
|
|
|
|
return &IdentityConfig{
|
|
CertPath: filepath.Join(tmpDir, "chain.pem"),
|
|
KeyPath: filepath.Join(tmpDir, "key.pem"),
|
|
}, cleanup, nil
|
|
}
|
|
|
|
func tempIdentity(t *testing.T) (func(), *IdentityConfig, *FullIdentity, uint16) {
|
|
// NB: known difficulty
|
|
difficulty := uint16(12)
|
|
|
|
chain := `-----BEGIN CERTIFICATE-----
|
|
MIIBQDCB56ADAgECAhB+u3d03qyW/ROgwy/ZsPccMAoGCCqGSM49BAMCMAAwIhgP
|
|
MDAwMTAxMDEwMDAwMDBaGA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEG
|
|
CCqGSM49AwEHA0IABIZrEPV/ExEkF0qUF0fJ3qSeGt5oFUX231v02NSUywcQ/Ve0
|
|
v3nHbmcJdjWBis2AkfL25mYDVC25jLl4tylMKumjPzA9MA4GA1UdDwEB/wQEAwIF
|
|
oDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAK
|
|
BggqhkjOPQQDAgNIADBFAiEA2ZvsR0ncw4mHRIg2Isavd+XVEoMo/etXQRAkDy9n
|
|
wyoCIDykUsqjshc9kCrXOvPSN8GuO2bNoLu5C7K1GlE/HI2X
|
|
-----END CERTIFICATE-----
|
|
-----BEGIN CERTIFICATE-----
|
|
MIIBODCB4KADAgECAhAOcvhKe5TWT44LqFfgA1f8MAoGCCqGSM49BAMCMAAwIhgP
|
|
MDAwMTAxMDEwMDAwMDBaGA8wMDAxMDEwMTAwMDAwMFowADBZMBMGByqGSM49AgEG
|
|
CCqGSM49AwEHA0IABIZrEPV/ExEkF0qUF0fJ3qSeGt5oFUX231v02NSUywcQ/Ve0
|
|
v3nHbmcJdjWBis2AkfL25mYDVC25jLl4tylMKumjODA2MA4GA1UdDwEB/wQEAwIC
|
|
BDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49
|
|
BAMCA0cAMEQCIGAZfPT1qvlnkTacojTtP20ZWf6XbnSztJHIKlUw6AE+AiB5Vcjj
|
|
awRaC5l1KBPGqiKB0coVXDwhW+K70l326MPUcg==
|
|
-----END CERTIFICATE-----`
|
|
|
|
key := `-----BEGIN EC PRIVATE KEY-----
|
|
MHcCAQEEIKGjEetrxKrzl+AL1E5LXke+1ElyAdjAmr88/1Kx09+doAoGCCqGSM49
|
|
AwEHoUQDQgAEoLy/0hs5deTXZunRumsMkiHpF0g8wAc58aXANmr7Mxx9tzoIYFnx
|
|
0YN4VDKdCtUJa29yA6TIz1MiIDUAcB5YCA==
|
|
-----END EC PRIVATE KEY-----`
|
|
|
|
ic, cleanup, err := tempIdentityConfig()
|
|
assert.NoError(t, err)
|
|
|
|
fi, err := FullIdentityFromPEM([]byte(chain), []byte(key))
|
|
assert.NoError(t, err)
|
|
|
|
return cleanup, ic, fi, difficulty
|
|
}
|