mirror of
https://github.com/slackhq/nebula.git
synced 2025-01-25 17:48:25 +00:00
e0185c4b01
* Support NIST curve P256 This change adds support for NIST curve P256. When you use `nebula-cert ca` or `nebula-cert keygen`, you can specify `-curve P256` to enable it. The curve to use is based on the curve defined in your CA certificate. Internally, we use ECDSA P256 to sign certificates, and ECDH P256 to do Noise handshakes. P256 is not supported natively in Noise Protocol, so we define `DHP256` in the `noiseutil` package to implement support for it. You cannot have a mixed network of Curve25519 and P256 certificates, since the Noise protocol will only attempt to parse using the Curve defined in the host's certificate. * verify the curves match in VerifyPrivateKey This would have failed anyways once we tried to actually use the bytes in the private key, but its better to detect the issue up front with a better error message. * add cert.Curve argument to Sign method * fix mismerge * use crypto/ecdh This is the preferred method for doing ECDH functions now, and also has a boringcrypto specific codepath. * remove other ecdh uses of crypto/elliptic use crypto/ecdh instead
232 lines
8.5 KiB
Go
232 lines
8.5 KiB
Go
//go:build !windows
|
|
// +build !windows
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/pem"
|
|
"errors"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/slackhq/nebula/cert"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
//TODO: test file permissions
|
|
|
|
func Test_caSummary(t *testing.T) {
|
|
assert.Equal(t, "ca <flags>: create a self signed certificate authority", caSummary())
|
|
}
|
|
|
|
func Test_caHelp(t *testing.T) {
|
|
ob := &bytes.Buffer{}
|
|
caHelp(ob)
|
|
assert.Equal(
|
|
t,
|
|
"Usage of "+os.Args[0]+" ca <flags>: create a self signed certificate authority\n"+
|
|
" -argon-iterations uint\n"+
|
|
" \tOptional: Argon2 iterations parameter used for encrypted private key passphrase (default 1)\n"+
|
|
" -argon-memory uint\n"+
|
|
" \tOptional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase (default 2097152)\n"+
|
|
" -argon-parallelism uint\n"+
|
|
" \tOptional: Argon2 parallelism parameter used for encrypted private key passphrase (default 4)\n"+
|
|
" -curve string\n"+
|
|
" \tEdDSA/ECDSA Curve (25519, P256) (default \"25519\")\n"+
|
|
" -duration duration\n"+
|
|
" \tOptional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\" (default 8760h0m0s)\n"+
|
|
" -encrypt\n"+
|
|
" \tOptional: prompt for passphrase and write out-key in an encrypted format\n"+
|
|
" -groups string\n"+
|
|
" \tOptional: comma separated list of groups. This will limit which groups subordinate certs can use\n"+
|
|
" -ips string\n"+
|
|
" \tOptional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use for ip addresses\n"+
|
|
" -name string\n"+
|
|
" \tRequired: name of the certificate authority\n"+
|
|
" -out-crt string\n"+
|
|
" \tOptional: path to write the certificate to (default \"ca.crt\")\n"+
|
|
" -out-key string\n"+
|
|
" \tOptional: path to write the private key to (default \"ca.key\")\n"+
|
|
" -out-qr string\n"+
|
|
" \tOptional: output a qr code image (png) of the certificate\n"+
|
|
" -subnets string\n"+
|
|
" \tOptional: comma separated list of ipv4 address and network in CIDR notation. This will limit which ipv4 addresses and networks subordinate certs can use in subnets\n",
|
|
ob.String(),
|
|
)
|
|
}
|
|
|
|
func Test_ca(t *testing.T) {
|
|
ob := &bytes.Buffer{}
|
|
eb := &bytes.Buffer{}
|
|
|
|
nopw := &StubPasswordReader{
|
|
password: []byte(""),
|
|
err: nil,
|
|
}
|
|
|
|
errpw := &StubPasswordReader{
|
|
password: []byte(""),
|
|
err: errors.New("stub error"),
|
|
}
|
|
|
|
passphrase := []byte("DO NOT USE THIS KEY")
|
|
testpw := &StubPasswordReader{
|
|
password: passphrase,
|
|
err: nil,
|
|
}
|
|
|
|
pwPromptOb := "Enter passphrase: "
|
|
|
|
// required args
|
|
assertHelpError(t, ca(
|
|
[]string{"-out-key", "nope", "-out-crt", "nope", "duration", "100m"}, ob, eb, nopw,
|
|
), "-name is required")
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// ipv4 only ips
|
|
assertHelpError(t, ca([]string{"-name", "ipv6", "-ips", "100::100/100"}, ob, eb, nopw), "invalid ip definition: can only be ipv4, have 100::100/100")
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// ipv4 only subnets
|
|
assertHelpError(t, ca([]string{"-name", "ipv6", "-subnets", "100::100/100"}, ob, eb, nopw), "invalid subnet definition: can only be ipv4, have 100::100/100")
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// failed key write
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args := []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", "/do/not/write/pleasekey"}
|
|
assert.EqualError(t, ca(args, ob, eb, nopw), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// create temp key file
|
|
keyF, err := ioutil.TempFile("", "test.key")
|
|
assert.Nil(t, err)
|
|
os.Remove(keyF.Name())
|
|
|
|
// failed cert write
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", keyF.Name()}
|
|
assert.EqualError(t, ca(args, ob, eb, nopw), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// create temp cert file
|
|
crtF, err := ioutil.TempFile("", "test.crt")
|
|
assert.Nil(t, err)
|
|
os.Remove(crtF.Name())
|
|
os.Remove(keyF.Name())
|
|
|
|
// test proper cert with removed empty groups and subnets
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.Nil(t, ca(args, ob, eb, nopw))
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// read cert and key files
|
|
rb, _ := ioutil.ReadFile(keyF.Name())
|
|
lKey, b, err := cert.UnmarshalEd25519PrivateKey(rb)
|
|
assert.Len(t, b, 0)
|
|
assert.Nil(t, err)
|
|
assert.Len(t, lKey, 64)
|
|
|
|
rb, _ = ioutil.ReadFile(crtF.Name())
|
|
lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb)
|
|
assert.Len(t, b, 0)
|
|
assert.Nil(t, err)
|
|
|
|
assert.Equal(t, "test", lCrt.Details.Name)
|
|
assert.Len(t, lCrt.Details.Ips, 0)
|
|
assert.True(t, lCrt.Details.IsCA)
|
|
assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups)
|
|
assert.Len(t, lCrt.Details.Subnets, 0)
|
|
assert.Len(t, lCrt.Details.PublicKey, 32)
|
|
assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore))
|
|
assert.Equal(t, "", lCrt.Details.Issuer)
|
|
assert.True(t, lCrt.CheckSignature(lCrt.Details.PublicKey))
|
|
|
|
// test encrypted key
|
|
os.Remove(keyF.Name())
|
|
os.Remove(crtF.Name())
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.Nil(t, ca(args, ob, eb, testpw))
|
|
assert.Equal(t, pwPromptOb, ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// read encrypted key file and verify default params
|
|
rb, _ = ioutil.ReadFile(keyF.Name())
|
|
k, _ := pem.Decode(rb)
|
|
ned, err := cert.UnmarshalNebulaEncryptedData(k.Bytes)
|
|
assert.Nil(t, err)
|
|
// we won't know salt in advance, so just check start of string
|
|
assert.Equal(t, uint32(2*1024*1024), ned.EncryptionMetadata.Argon2Parameters.Memory)
|
|
assert.Equal(t, uint8(4), ned.EncryptionMetadata.Argon2Parameters.Parallelism)
|
|
assert.Equal(t, uint32(1), ned.EncryptionMetadata.Argon2Parameters.Iterations)
|
|
|
|
// verify the key is valid and decrypt-able
|
|
var curve cert.Curve
|
|
curve, lKey, b, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rb)
|
|
assert.Equal(t, cert.Curve_CURVE25519, curve)
|
|
assert.Nil(t, err)
|
|
assert.Len(t, b, 0)
|
|
assert.Len(t, lKey, 64)
|
|
|
|
// test when reading passsword results in an error
|
|
os.Remove(keyF.Name())
|
|
os.Remove(crtF.Name())
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.Error(t, ca(args, ob, eb, errpw))
|
|
assert.Equal(t, pwPromptOb, ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// test when user fails to enter a password
|
|
os.Remove(keyF.Name())
|
|
os.Remove(crtF.Name())
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.EqualError(t, ca(args, ob, eb, nopw), "no passphrase specified, remove -encrypt flag to write out-key in plaintext")
|
|
assert.Equal(t, strings.Repeat(pwPromptOb, 5), ob.String()) // prompts 5 times before giving up
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// create valid cert/key for overwrite tests
|
|
os.Remove(keyF.Name())
|
|
os.Remove(crtF.Name())
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.Nil(t, ca(args, ob, eb, nopw))
|
|
|
|
// test that we won't overwrite existing certificate file
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.EqualError(t, ca(args, ob, eb, nopw), "refusing to overwrite existing CA key: "+keyF.Name())
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
|
|
// test that we won't overwrite existing key file
|
|
os.Remove(keyF.Name())
|
|
ob.Reset()
|
|
eb.Reset()
|
|
args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()}
|
|
assert.EqualError(t, ca(args, ob, eb, nopw), "refusing to overwrite existing CA cert: "+crtF.Name())
|
|
assert.Equal(t, "", ob.String())
|
|
assert.Equal(t, "", eb.String())
|
|
os.Remove(keyF.Name())
|
|
|
|
}
|