storj/pkg/identity/identity_test.go
paul cannon bb892d33d1
make cert creation a little easier to read (#1607)
Make separate "CreateCertificate" and "CreateSelfSignedCertificate"
functions to take the two roles of NewCert. These names should help
clarify that they actually make certificates and not just allocate new
"Cert" or "Certificate" objects.

Secondly, in the case of non-self-signed certs, require a public and a
private key to be passed in instead of two private keys, because it's
pretty hard to tell when reading code which one is meant to be the
signer and which one is the signee. With a public and private key, you
know.

(These are some changes I made in the course of the openssl port,
because the NewCert function kept being confusing to me. It's possible
I'm just being ridiculous, and this doesn't help improve readability for
anyone else, but if I'm not being ridiculous let's get this in)
2019-04-03 17:21:32 -06:00

252 lines
7.9 KiB
Go

// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
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"
)
func TestPeerIdentityFromCertChain(t *testing.T) {
caKey, err := pkcrypto.GeneratePrivateKey()
assert.NoError(t, err)
caTemplate, err := peertls.CATemplate()
assert.NoError(t, err)
caCert, err := peertls.CreateSelfSignedCertificate(caKey, caTemplate)
assert.NoError(t, err)
leafTemplate, err := peertls.LeafTemplate()
assert.NoError(t, err)
leafKey, err := pkcrypto.GeneratePrivateKey()
assert.NoError(t, err)
leafCert, err := peertls.CreateCertificate(pkcrypto.PublicKeyFromPrivate(leafKey), caKey, leafTemplate, caTemplate)
assert.NoError(t, err)
peerIdent, err := identity.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 := pkcrypto.GeneratePrivateKey()
assert.NoError(t, err)
caTemplate, err := peertls.CATemplate()
assert.NoError(t, err)
caCert, err := peertls.CreateSelfSignedCertificate(caKey, caTemplate)
assert.NoError(t, err)
assert.NoError(t, err)
assert.NotEmpty(t, caCert)
leafTemplate, err := peertls.LeafTemplate()
assert.NoError(t, err)
leafKey, err := pkcrypto.GeneratePrivateKey()
assert.NoError(t, err)
leafCert, err := peertls.CreateCertificate(pkcrypto.PublicKeyFromPrivate(leafKey), caKey, leafTemplate, caTemplate)
assert.NoError(t, err)
assert.NotEmpty(t, leafCert)
chainPEM := bytes.NewBuffer([]byte{})
assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, leafCert))
assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, caCert))
keyPEM := bytes.NewBuffer([]byte{})
assert.NoError(t, pkcrypto.WritePrivateKeyPEM(keyPEM, leafKey))
fullIdent, err := identity.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 TestConfig_SaveIdentity(t *testing.T) {
ctx := testcontext.New(t)
defer ctx.Cleanup()
ic := &identity.Config{
CertPath: ctx.File("chain.pem"),
KeyPath: ctx.File("key.pem"),
}
fi := pregeneratedIdentity(t)
chainPEM := bytes.NewBuffer([]byte{})
assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, fi.Leaf))
assert.NoError(t, pkcrypto.WriteCertPEM(chainPEM, fi.CA))
privateKey := fi.Key
assert.NotEmpty(t, privateKey)
keyPEM := bytes.NewBuffer([]byte{})
assert.NoError(t, pkcrypto.WritePrivateKeyPEM(keyPEM, privateKey))
{ // test saving
err := ic.Save(fi)
assert.NoError(t, err)
certInfo, err := os.Stat(ic.CertPath)
assert.NoError(t, err)
keyInfo, err := os.Stat(ic.KeyPath)
assert.NoError(t, err)
// TODO (windows): ignoring for windows due to different default permissions
if runtime.GOOS != "windows" {
assert.Equal(t, os.FileMode(0644), certInfo.Mode())
assert.Equal(t, os.FileMode(0600), keyInfo.Mode())
}
}
{ // test loading
loadedFi, err := ic.Load()
assert.NoError(t, err)
assert.Equal(t, fi.Key, loadedFi.Key)
assert.Equal(t, fi.Leaf, loadedFi.Leaf)
assert.Equal(t, fi.CA, loadedFi.CA)
assert.Equal(t, fi.ID, loadedFi.ID)
}
}
func TestVerifyPeer(t *testing.T) {
ca, err := identity.NewCA(context.Background(), identity.NewCAOptions{
Difficulty: 12,
Concurrency: 4,
})
assert.NoError(t, err)
fi, err := ca.NewIdentity()
assert.NoError(t, err)
err = peertls.VerifyPeerFunc(peertls.VerifyPeerCertChains)([][]byte{fi.Leaf.Raw, fi.CA.Raw}, nil)
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
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-----`
const key = `-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIKGjEetrxKrzl+AL1E5LXke+1ElyAdjAmr88/1Kx09+doAoGCCqGSM49
AwEHoUQDQgAEoLy/0hs5deTXZunRumsMkiHpF0g8wAc58aXANmr7Mxx9tzoIYFnx
0YN4VDKdCtUJa29yA6TIz1MiIDUAcB5YCA==
-----END EC PRIVATE KEY-----`
fi, err := identity.FullIdentityFromPEM([]byte(chain), []byte(key))
assert.NoError(t, err)
return fi
}