mirror of
https://github.com/slackhq/nebula.git
synced 2026-02-14 08:44:24 +01:00
Add ability to encrypt CA private key at rest (#386)
Fixes #8. `nebula-cert ca` now supports encrypting the CA's private key with a passphrase. Pass `-encrypt` in order to be prompted for a passphrase. Encryption is performed using AES-256-GCM and Argon2id for KDF. KDF parameters default to RFC recommendations, but can be overridden via CLI flags `-argon-memory`, `-argon-parallelism`, and `-argon-iterations`.
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -17,15 +18,19 @@ import (
|
||||
)
|
||||
|
||||
type caFlags struct {
|
||||
set *flag.FlagSet
|
||||
name *string
|
||||
duration *time.Duration
|
||||
outKeyPath *string
|
||||
outCertPath *string
|
||||
outQRPath *string
|
||||
groups *string
|
||||
ips *string
|
||||
subnets *string
|
||||
set *flag.FlagSet
|
||||
name *string
|
||||
duration *time.Duration
|
||||
outKeyPath *string
|
||||
outCertPath *string
|
||||
outQRPath *string
|
||||
groups *string
|
||||
ips *string
|
||||
subnets *string
|
||||
argonMemory *uint
|
||||
argonIterations *uint
|
||||
argonParallelism *uint
|
||||
encryption *bool
|
||||
}
|
||||
|
||||
func newCaFlags() *caFlags {
|
||||
@@ -39,10 +44,28 @@ func newCaFlags() *caFlags {
|
||||
cf.groups = cf.set.String("groups", "", "Optional: comma separated list of groups. This will limit which groups subordinate certs can use")
|
||||
cf.ips = cf.set.String("ips", "", "Optional: 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")
|
||||
cf.subnets = cf.set.String("subnets", "", "Optional: 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")
|
||||
cf.argonMemory = cf.set.Uint("argon-memory", 2*1024*1024, "Optional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase")
|
||||
cf.argonParallelism = cf.set.Uint("argon-parallelism", 4, "Optional: Argon2 parallelism parameter used for encrypted private key passphrase")
|
||||
cf.argonIterations = cf.set.Uint("argon-iterations", 1, "Optional: Argon2 iterations parameter used for encrypted private key passphrase")
|
||||
cf.encryption = cf.set.Bool("encrypt", false, "Optional: prompt for passphrase and write out-key in an encrypted format")
|
||||
return &cf
|
||||
}
|
||||
|
||||
func ca(args []string, out io.Writer, errOut io.Writer) error {
|
||||
func parseArgonParameters(memory uint, parallelism uint, iterations uint) (*cert.Argon2Parameters, error) {
|
||||
if memory <= 0 || memory > math.MaxUint32 {
|
||||
return nil, newHelpErrorf("-argon-memory must be be greater than 0 and no more than %d KiB", uint32(math.MaxUint32))
|
||||
}
|
||||
if parallelism <= 0 || parallelism > math.MaxUint8 {
|
||||
return nil, newHelpErrorf("-argon-parallelism must be be greater than 0 and no more than %d", math.MaxUint8)
|
||||
}
|
||||
if iterations <= 0 || iterations > math.MaxUint32 {
|
||||
return nil, newHelpErrorf("-argon-iterations must be be greater than 0 and no more than %d", uint32(math.MaxUint32))
|
||||
}
|
||||
|
||||
return cert.NewArgon2Parameters(uint32(memory), uint8(parallelism), uint32(iterations)), nil
|
||||
}
|
||||
|
||||
func ca(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error {
|
||||
cf := newCaFlags()
|
||||
err := cf.set.Parse(args)
|
||||
if err != nil {
|
||||
@@ -58,6 +81,12 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
|
||||
if err := mustFlagString("out-crt", cf.outCertPath); err != nil {
|
||||
return err
|
||||
}
|
||||
var kdfParams *cert.Argon2Parameters
|
||||
if *cf.encryption {
|
||||
if kdfParams, err = parseArgonParameters(*cf.argonMemory, *cf.argonParallelism, *cf.argonIterations); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if *cf.duration <= 0 {
|
||||
return &helpError{"-duration must be greater than 0"}
|
||||
@@ -109,6 +138,28 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
|
||||
}
|
||||
}
|
||||
|
||||
var passphrase []byte
|
||||
if *cf.encryption {
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
|
||||
if err == ErrNoTerminal {
|
||||
return fmt.Errorf("out-key must be encrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading passphrase: %s", err)
|
||||
}
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(passphrase) == 0 {
|
||||
return fmt.Errorf("no passphrase specified, remove -encrypt flag to write out-key in plaintext")
|
||||
}
|
||||
}
|
||||
|
||||
pub, rawPriv, err := ed25519.GenerateKey(rand.Reader)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while generating ed25519 keys: %s", err)
|
||||
@@ -140,7 +191,17 @@ func ca(args []string, out io.Writer, errOut io.Writer) error {
|
||||
return fmt.Errorf("error while signing: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalEd25519PrivateKey(rawPriv), 0600)
|
||||
if *cf.encryption {
|
||||
b, err := cert.EncryptAndMarshalEd25519PrivateKey(rawPriv, passphrase, kdfParams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while encrypting out-key: %s", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(*cf.outKeyPath, b, 0600)
|
||||
} else {
|
||||
err = ioutil.WriteFile(*cf.outKeyPath, cert.MarshalEd25519PrivateKey(rawPriv), 0600)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while writing out-key: %s", err)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -26,8 +29,16 @@ func Test_caHelp(t *testing.T) {
|
||||
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"+
|
||||
" -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"+
|
||||
@@ -50,18 +61,38 @@ 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), "-name is required")
|
||||
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), "invalid ip definition: can only be ipv4, have 100::100/100")
|
||||
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), "invalid subnet definition: can only be ipv4, have 100::100/100")
|
||||
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())
|
||||
|
||||
@@ -69,7 +100,7 @@ func Test_ca(t *testing.T) {
|
||||
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), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
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())
|
||||
|
||||
@@ -82,7 +113,7 @@ func Test_ca(t *testing.T) {
|
||||
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), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
||||
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())
|
||||
|
||||
@@ -96,7 +127,7 @@ func Test_ca(t *testing.T) {
|
||||
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))
|
||||
assert.Nil(t, ca(args, ob, eb, nopw))
|
||||
assert.Equal(t, "", ob.String())
|
||||
assert.Equal(t, "", eb.String())
|
||||
|
||||
@@ -122,19 +153,65 @@ func Test_ca(t *testing.T) {
|
||||
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
|
||||
lKey, b, err = cert.DecryptAndUnmarshalEd25519PrivateKey(passphrase, rb)
|
||||
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))
|
||||
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), "refusing to overwrite existing CA 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())
|
||||
|
||||
@@ -143,7 +220,7 @@ func Test_ca(t *testing.T) {
|
||||
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), "refusing to overwrite existing CA cert: "+crtF.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())
|
||||
|
||||
@@ -62,11 +62,11 @@ func main() {
|
||||
|
||||
switch args[0] {
|
||||
case "ca":
|
||||
err = ca(args[1:], os.Stdout, os.Stderr)
|
||||
err = ca(args[1:], os.Stdout, os.Stderr, StdinPasswordReader{})
|
||||
case "keygen":
|
||||
err = keygen(args[1:], os.Stdout, os.Stderr)
|
||||
case "sign":
|
||||
err = signCert(args[1:], os.Stdout, os.Stderr)
|
||||
err = signCert(args[1:], os.Stdout, os.Stderr, StdinPasswordReader{})
|
||||
case "print":
|
||||
err = printCert(args[1:], os.Stdout, os.Stderr)
|
||||
case "verify":
|
||||
|
||||
28
cmd/nebula-cert/passwords.go
Normal file
28
cmd/nebula-cert/passwords.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var ErrNoTerminal = errors.New("cannot read password from nonexistent terminal")
|
||||
|
||||
type PasswordReader interface {
|
||||
ReadPassword() ([]byte, error)
|
||||
}
|
||||
|
||||
type StdinPasswordReader struct{}
|
||||
|
||||
func (pr StdinPasswordReader) ReadPassword() ([]byte, error) {
|
||||
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return nil, ErrNoTerminal
|
||||
}
|
||||
|
||||
password, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Println()
|
||||
|
||||
return password, err
|
||||
}
|
||||
10
cmd/nebula-cert/passwords_test.go
Normal file
10
cmd/nebula-cert/passwords_test.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package main
|
||||
|
||||
type StubPasswordReader struct {
|
||||
password []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func (pr *StubPasswordReader) ReadPassword() ([]byte, error) {
|
||||
return pr.password, pr.err
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/ed25519"
|
||||
"crypto/rand"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -49,7 +50,7 @@ func newSignFlags() *signFlags {
|
||||
|
||||
}
|
||||
|
||||
func signCert(args []string, out io.Writer, errOut io.Writer) error {
|
||||
func signCert(args []string, out io.Writer, errOut io.Writer, pr PasswordReader) error {
|
||||
sf := newSignFlags()
|
||||
err := sf.set.Parse(args)
|
||||
if err != nil {
|
||||
@@ -77,8 +78,36 @@ func signCert(args []string, out io.Writer, errOut io.Writer) error {
|
||||
return fmt.Errorf("error while reading ca-key: %s", err)
|
||||
}
|
||||
|
||||
caKey, _, err := cert.UnmarshalEd25519PrivateKey(rawCAKey)
|
||||
if err != nil {
|
||||
var caKey ed25519.PrivateKey
|
||||
|
||||
// naively attempt to decode the private key as though it is not encrypted
|
||||
caKey, _, err = cert.UnmarshalEd25519PrivateKey(rawCAKey)
|
||||
if err == cert.ErrPrivateKeyEncrypted {
|
||||
// ask for a passphrase until we get one
|
||||
var passphrase []byte
|
||||
for i := 0; i < 5; i++ {
|
||||
out.Write([]byte("Enter passphrase: "))
|
||||
passphrase, err = pr.ReadPassword()
|
||||
|
||||
if err == ErrNoTerminal {
|
||||
return fmt.Errorf("ca-key is encrypted and must be decrypted interactively")
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error reading password: %s", err)
|
||||
}
|
||||
|
||||
if len(passphrase) > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(passphrase) == 0 {
|
||||
return fmt.Errorf("cannot open encrypted ca-key without passphrase")
|
||||
}
|
||||
|
||||
caKey, _, err = cert.DecryptAndUnmarshalEd25519PrivateKey(passphrase, rawCAKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while parsing encrypted ca-key: %s", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("error while parsing ca-key: %s", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
@@ -58,17 +59,39 @@ func Test_signCert(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,
|
||||
}
|
||||
|
||||
// required args
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-ip", "1.1.1.1/24", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-name is required")
|
||||
assertHelpError(t, signCert(
|
||||
[]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-ip", "1.1.1.1/24", "-out-key", "nope", "-out-crt", "nope"}, ob, eb, nopw,
|
||||
), "-name is required")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-ip is required")
|
||||
assertHelpError(t, signCert(
|
||||
[]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-out-key", "nope", "-out-crt", "nope"}, ob, eb, nopw,
|
||||
), "-ip is required")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// cannot set -in-pub and -out-key
|
||||
assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-in-pub", "nope", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope"}, ob, eb), "cannot set both -in-pub and -out-key")
|
||||
assertHelpError(t, signCert(
|
||||
[]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-in-pub", "nope", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope"}, ob, eb, nopw,
|
||||
), "cannot set both -in-pub and -out-key")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -76,7 +99,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args := []string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-key: open ./nope: "+NoSuchFileError)
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading ca-key: open ./nope: "+NoSuchFileError)
|
||||
|
||||
// failed to unmarshal key
|
||||
ob.Reset()
|
||||
@@ -86,7 +109,7 @@ func Test_signCert(t *testing.T) {
|
||||
defer os.Remove(caKeyF.Name())
|
||||
|
||||
args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-key: input did not contain a valid PEM encoded block")
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing ca-key: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -98,7 +121,7 @@ func Test_signCert(t *testing.T) {
|
||||
|
||||
// failed to read cert
|
||||
args = []string{"-ca-crt", "./nope", "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-crt: open ./nope: "+NoSuchFileError)
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading ca-crt: open ./nope: "+NoSuchFileError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -110,7 +133,7 @@ func Test_signCert(t *testing.T) {
|
||||
defer os.Remove(caCrtF.Name())
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-crt: input did not contain a valid PEM encoded block")
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing ca-crt: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -129,7 +152,7 @@ func Test_signCert(t *testing.T) {
|
||||
|
||||
// failed to read pub
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-in-pub", "./nope", "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while reading in-pub: open ./nope: "+NoSuchFileError)
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading in-pub: open ./nope: "+NoSuchFileError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -141,7 +164,7 @@ func Test_signCert(t *testing.T) {
|
||||
defer os.Remove(inPubF.Name())
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-in-pub", inPubF.Name(), "-duration", "100m"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while parsing in-pub: input did not contain a valid PEM encoded block")
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing in-pub: input did not contain a valid PEM encoded block")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -155,14 +178,14 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "a1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid ip definition: invalid CIDR address: a1.1.1.1/24")
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: invalid CIDR address: a1.1.1.1/24")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "100::100/100", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid ip definition: can only be ipv4, have 100::100/100")
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: can only be ipv4, have 100::100/100")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -170,14 +193,14 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid subnet definition: invalid CIDR address: a")
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: invalid CIDR address: a")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "100::100/100"}
|
||||
assertHelpError(t, signCert(args, ob, eb), "invalid subnet definition: can only be ipv4, have 100::100/100")
|
||||
assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: can only be ipv4, have 100::100/100")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -191,7 +214,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF2.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m", "-subnets", "a"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to sign, root certificate does not match private key")
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to sign, root certificate does not match private key")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -199,7 +222,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "/do/not/write/pleasecrt", "-out-key", "/do/not/write/pleasekey", "-duration", "100m", "-subnets", "10.1.1.1/32"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -212,7 +235,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "/do/not/write/pleasecrt", "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
os.Remove(keyF.Name())
|
||||
@@ -226,7 +249,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Nil(t, signCert(args, ob, eb, nopw))
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -268,7 +291,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-in-pub", inPubF.Name(), "-duration", "100m", "-groups", "1"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Nil(t, signCert(args, ob, eb, nopw))
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -283,7 +306,7 @@ func Test_signCert(t *testing.T) {
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "1000m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate")
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate")
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -291,14 +314,14 @@ func Test_signCert(t *testing.T) {
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Nil(t, signCert(args, ob, eb, nopw))
|
||||
|
||||
// test that we won't overwrite existing key file
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing key: "+keyF.Name())
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to overwrite existing key: "+keyF.Name())
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
@@ -306,14 +329,83 @@ func Test_signCert(t *testing.T) {
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb))
|
||||
assert.Nil(t, signCert(args, ob, eb, nopw))
|
||||
|
||||
// test that we won't overwrite existing certificate file
|
||||
os.Remove(keyF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing cert: "+crtF.Name())
|
||||
assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to overwrite existing cert: "+crtF.Name())
|
||||
assert.Empty(t, ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// create valid cert/key using encrypted CA key
|
||||
os.Remove(caKeyF.Name())
|
||||
os.Remove(caCrtF.Name())
|
||||
os.Remove(keyF.Name())
|
||||
os.Remove(crtF.Name())
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
caKeyF, err = ioutil.TempFile("", "sign-cert.key")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(caKeyF.Name())
|
||||
|
||||
caCrtF, err = ioutil.TempFile("", "sign-cert.crt")
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(caCrtF.Name())
|
||||
|
||||
// generate the encrypted key
|
||||
caPub, caPriv, _ = ed25519.GenerateKey(rand.Reader)
|
||||
kdfParams := cert.NewArgon2Parameters(64*1024, 4, 3)
|
||||
b, _ = cert.EncryptAndMarshalEd25519PrivateKey(caPriv, passphrase, kdfParams)
|
||||
caKeyF.Write(b)
|
||||
|
||||
ca = cert.NebulaCertificate{
|
||||
Details: cert.NebulaCertificateDetails{
|
||||
Name: "ca",
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().Add(time.Minute * 200),
|
||||
PublicKey: caPub,
|
||||
IsCA: true,
|
||||
},
|
||||
}
|
||||
b, _ = ca.MarshalToPEM()
|
||||
caCrtF.Write(b)
|
||||
|
||||
// test with the proper password
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Nil(t, signCert(args, ob, eb, testpw))
|
||||
assert.Equal(t, "Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test with the wrong password
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
testpw.password = []byte("invalid password")
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Error(t, signCert(args, ob, eb, testpw))
|
||||
assert.Equal(t, "Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test with the user not entering a password
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Error(t, signCert(args, ob, eb, nopw))
|
||||
// normally the user hitting enter on the prompt would add newlines between these
|
||||
assert.Equal(t, "Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
|
||||
// test an error condition
|
||||
ob.Reset()
|
||||
eb.Reset()
|
||||
|
||||
args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", crtF.Name(), "-out-key", keyF.Name(), "-duration", "100m", "-subnets", "10.1.1.1/32, , 10.2.2.2/32 , , ,, 10.5.5.5/32", "-groups", "1,, 2 , ,,,3,4,5"}
|
||||
assert.Error(t, signCert(args, ob, eb, errpw))
|
||||
assert.Equal(t, "Enter passphrase: ", ob.String())
|
||||
assert.Empty(t, eb.String())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user