package cert import ( "crypto/rand" "fmt" "io" "net" "testing" "time" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "golang.org/x/crypto/curve25519" "golang.org/x/crypto/ed25519" ) func TestMarshalingNebulaCertificate(t *testing.T) { before := time.Now().Add(time.Second * -60).Round(time.Second) after := time.Now().Add(time.Second * 60).Round(time.Second) pubKey := []byte("1234567890abcedfghij1234567890ab") nc := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "testing", Ips: []*net.IPNet{ {IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, {IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, }, Subnets: []*net.IPNet{ {IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, {IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, }, Groups: []string{"test-group1", "test-group2", "test-group3"}, NotBefore: before, NotAfter: after, PublicKey: pubKey, IsCA: false, Issuer: "1234567890abcedfghij1234567890ab", }, Signature: []byte("1234567890abcedfghij1234567890ab"), } b, err := nc.Marshal() assert.Nil(t, err) t.Log("Cert size:", len(b)) nc2, err := UnmarshalNebulaCertificate(b) assert.Nil(t, err) assert.Equal(t, nc.Signature, nc2.Signature) assert.Equal(t, nc.Details.Name, nc2.Details.Name) assert.Equal(t, nc.Details.NotBefore, nc2.Details.NotBefore) assert.Equal(t, nc.Details.NotAfter, nc2.Details.NotAfter) assert.Equal(t, nc.Details.PublicKey, nc2.Details.PublicKey) assert.Equal(t, nc.Details.IsCA, nc2.Details.IsCA) // IP byte arrays can be 4 or 16 in length so we have to go this route assert.Equal(t, len(nc.Details.Ips), len(nc2.Details.Ips)) for i, wIp := range nc.Details.Ips { assert.Equal(t, wIp.String(), nc2.Details.Ips[i].String()) } assert.Equal(t, len(nc.Details.Subnets), len(nc2.Details.Subnets)) for i, wIp := range nc.Details.Subnets { assert.Equal(t, wIp.String(), nc2.Details.Subnets[i].String()) } assert.EqualValues(t, nc.Details.Groups, nc2.Details.Groups) } func TestNebulaCertificate_Sign(t *testing.T) { before := time.Now().Add(time.Second * -60).Round(time.Second) after := time.Now().Add(time.Second * 60).Round(time.Second) pubKey := []byte("1234567890abcedfghij1234567890ab") nc := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "testing", Ips: []*net.IPNet{ {IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, {IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, }, Subnets: []*net.IPNet{ {IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, {IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, }, Groups: []string{"test-group1", "test-group2", "test-group3"}, NotBefore: before, NotAfter: after, PublicKey: pubKey, IsCA: false, Issuer: "1234567890abcedfghij1234567890ab", }, } pub, priv, err := ed25519.GenerateKey(rand.Reader) assert.Nil(t, err) assert.False(t, nc.CheckSignature(pub)) assert.Nil(t, nc.Sign(priv)) assert.True(t, nc.CheckSignature(pub)) b, err := nc.Marshal() assert.Nil(t, err) t.Log("Cert size:", len(b)) } func TestNebulaCertificate_Expired(t *testing.T) { nc := NebulaCertificate{ Details: NebulaCertificateDetails{ NotBefore: time.Now().Add(time.Second * -60).Round(time.Second), NotAfter: time.Now().Add(time.Second * 60).Round(time.Second), }, } assert.True(t, nc.Expired(time.Now().Add(time.Hour))) assert.True(t, nc.Expired(time.Now().Add(-time.Hour))) assert.False(t, nc.Expired(time.Now())) } func TestNebulaCertificate_MarshalJSON(t *testing.T) { time.Local = time.UTC pubKey := []byte("1234567890abcedfghij1234567890ab") nc := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "testing", Ips: []*net.IPNet{ {IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, {IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, }, Subnets: []*net.IPNet{ {IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, {IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, }, Groups: []string{"test-group1", "test-group2", "test-group3"}, NotBefore: time.Date(1, 0, 0, 1, 0, 0, 0, time.UTC), NotAfter: time.Date(1, 0, 0, 2, 0, 0, 0, time.UTC), PublicKey: pubKey, IsCA: false, Issuer: "1234567890abcedfghij1234567890ab", }, Signature: []byte("1234567890abcedfghij1234567890ab"), } b, err := nc.MarshalJSON() assert.Nil(t, err) assert.Equal( t, "{\"details\":{\"groups\":[\"test-group1\",\"test-group2\",\"test-group3\"],\"ips\":[\"10.1.1.1/24\",\"10.1.1.2/16\",\"10.1.1.3/ff00ff00\"],\"isCa\":false,\"issuer\":\"1234567890abcedfghij1234567890ab\",\"name\":\"testing\",\"notAfter\":\"0000-11-30T02:00:00Z\",\"notBefore\":\"0000-11-30T01:00:00Z\",\"publicKey\":\"313233343536373839306162636564666768696a313233343536373839306162\",\"subnets\":[\"9.1.1.1/ff00ff00\",\"9.1.1.2/24\",\"9.1.1.3/16\"]},\"fingerprint\":\"26cb1c30ad7872c804c166b5150fa372f437aa3856b04edb4334b4470ec728e4\",\"signature\":\"313233343536373839306162636564666768696a313233343536373839306162\"}", string(b), ) } func TestNebulaCertificate_Verify(t *testing.T) { ca, _, caKey, err := newTestCaCert() assert.Nil(t, err) c, _, _, err := newTestCert(ca, caKey) assert.Nil(t, err) h, err := ca.Sha256Sum() assert.Nil(t, err) caPool := NewCAPool() caPool.CAs[h] = ca f, err := c.Sha256Sum() assert.Nil(t, err) caPool.BlacklistFingerprint(f) v, err := c.Verify(time.Now(), caPool) assert.False(t, v) assert.EqualError(t, err, "certificate has been blacklisted") caPool.ResetCertBlacklist() v, err = c.Verify(time.Now(), caPool) assert.True(t, v) assert.Nil(t, err) v, err = c.Verify(time.Now().Add(time.Hour*1000), caPool) assert.False(t, v) assert.EqualError(t, err, "root certificate is expired") } func TestNebulaVerifyPrivateKey(t *testing.T) { ca, _, caKey, err := newTestCaCert() assert.Nil(t, err) c, _, priv, err := newTestCert(ca, caKey) err = c.VerifyPrivateKey(priv) assert.Nil(t, err) _, priv2 := x25519Keypair() err = c.VerifyPrivateKey(priv2) assert.NotNil(t, err) } func TestNewCAPoolFromBytes(t *testing.T) { noNewLines := ` # Current provisional, Remove once everything moves over to the real root. -----BEGIN NEBULA CERTIFICATE----- CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB -----END NEBULA CERTIFICATE----- # root-ca01 -----BEGIN NEBULA CERTIFICATE----- CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf 8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF -----END NEBULA CERTIFICATE----- ` withNewLines := ` # Current provisional, Remove once everything moves over to the real root. -----BEGIN NEBULA CERTIFICATE----- CkAKDm5lYnVsYSByb290IGNhKJfap9AFMJfg1+YGOiCUQGByMuNRhIlQBOyzXWbL vcKBwDhov900phEfJ5DN3kABEkDCq5R8qBiu8sl54yVfgRcQXEDt3cHr8UTSLszv bzBEr00kERQxxTzTsH8cpYEgRoipvmExvg8WP8NdAJEYJosB -----END NEBULA CERTIFICATE----- # root-ca01 -----BEGIN NEBULA CERTIFICATE----- CkMKEW5lYnVsYSByb290IGNhIDAxKJL2u9EFMJL86+cGOiDPXMH4oU6HZTk/CqTG BVG+oJpAoqokUBbI4U0N8CSfpUABEkB/Pm5A2xyH/nc8mg/wvGUWG3pZ7nHzaDMf 8/phAUt+FLzqTECzQKisYswKvE3pl9mbEYKbOdIHrxdIp95mo4sF -----END NEBULA CERTIFICATE----- ` rootCA := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "nebula root ca", }, } rootCA01 := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "nebula root ca 01", }, } p, err := NewCAPoolFromBytes([]byte(noNewLines)) assert.Nil(t, err) assert.Equal(t, p.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name) assert.Equal(t, p.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name) pp, err := NewCAPoolFromBytes([]byte(withNewLines)) assert.Nil(t, err) assert.Equal(t, pp.CAs[string("c9bfaf7ce8e84b2eeda2e27b469f4b9617bde192efd214b68891ecda6ed49522")].Details.Name, rootCA.Details.Name) assert.Equal(t, pp.CAs[string("5c9c3f23e7ee7fe97637cbd3a0a5b854154d1d9aaaf7b566a51f4a88f76b64cd")].Details.Name, rootCA01.Details.Name) } // Ensure that upgrading the protobuf library does not change how certificates // are marshalled, since this would break signature verification func TestMarshalingNebulaCertificateConsistency(t *testing.T) { before := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) after := time.Date(2017, time.January, 18, 28, 40, 0, 0, time.UTC) pubKey := []byte("1234567890abcedfghij1234567890ab") nc := NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "testing", Ips: []*net.IPNet{ {IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, {IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, }, Subnets: []*net.IPNet{ {IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, {IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, }, Groups: []string{"test-group1", "test-group2", "test-group3"}, NotBefore: before, NotAfter: after, PublicKey: pubKey, IsCA: false, Issuer: "1234567890abcedfghij1234567890ab", }, Signature: []byte("1234567890abcedfghij1234567890ab"), } b, err := nc.Marshal() assert.Nil(t, err) t.Log("Cert size:", len(b)) assert.Equal(t, "0aa2010a0774657374696e67121b8182845080feffff0f828284508080fcff0f8382845080fe83f80f1a1b8182844880fe83f80f8282844880feffff0f838284488080fcff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328f0e0e7d70430a08681c4053a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf1220313233343536373839306162636564666768696a313233343536373839306162", fmt.Sprintf("%x", b)) b, err = proto.Marshal(nc.getRawDetails()) assert.Nil(t, err) t.Log("Raw cert size:", len(b)) assert.Equal(t, "0a0774657374696e67121b8182845080feffff0f828284508080fcff0f8382845080fe83f80f1a1b8182844880fe83f80f8282844880feffff0f838284488080fcff0f220b746573742d67726f757031220b746573742d67726f757032220b746573742d67726f75703328f0e0e7d70430a08681c4053a20313233343536373839306162636564666768696a3132333435363738393061624a081234567890abcedf", fmt.Sprintf("%x", b)) } func newTestCaCert() (*NebulaCertificate, []byte, []byte, error) { pub, priv, err := ed25519.GenerateKey(rand.Reader) before := time.Now().Add(time.Second * -60).Round(time.Second) after := time.Now().Add(time.Second * 60).Round(time.Second) nc := &NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "test ca", NotBefore: before, NotAfter: after, PublicKey: pub, IsCA: true, }, } err = nc.Sign(priv) if err != nil { return nil, nil, nil, err } return nc, pub, priv, nil } func newTestCert(ca *NebulaCertificate, key []byte) (*NebulaCertificate, []byte, []byte, error) { issuer, err := ca.Sha256Sum() if err != nil { return nil, nil, nil, err } before := time.Now().Add(time.Second * -60).Round(time.Second) after := time.Now().Add(time.Second * 60).Round(time.Second) pub, rawPriv := x25519Keypair() nc := &NebulaCertificate{ Details: NebulaCertificateDetails{ Name: "testing", Ips: []*net.IPNet{ {IP: net.ParseIP("10.1.1.1"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("10.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, {IP: net.ParseIP("10.1.1.3"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, }, Subnets: []*net.IPNet{ {IP: net.ParseIP("9.1.1.1"), Mask: net.IPMask(net.ParseIP("255.0.255.0"))}, {IP: net.ParseIP("9.1.1.2"), Mask: net.IPMask(net.ParseIP("255.255.255.0"))}, {IP: net.ParseIP("9.1.1.3"), Mask: net.IPMask(net.ParseIP("255.255.0.0"))}, }, Groups: []string{"test-group1", "test-group2", "test-group3"}, NotBefore: before, NotAfter: after, PublicKey: pub, IsCA: false, Issuer: issuer, }, } err = nc.Sign(key) if err != nil { return nil, nil, nil, err } return nc, pub, rawPriv, nil } func x25519Keypair() ([]byte, []byte) { var pubkey, privkey [32]byte if _, err := io.ReadFull(rand.Reader, privkey[:]); err != nil { panic(err) } curve25519.ScalarBaseMult(&pubkey, &privkey) return pubkey[:], privkey[:] }