github.com/slackhq/nebula@v1.9.0/cmd/nebula-cert/sign_test.go (about)

     1  //go:build !windows
     2  // +build !windows
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/rand"
     9  	"errors"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/slackhq/nebula/cert"
    15  	"github.com/stretchr/testify/assert"
    16  	"golang.org/x/crypto/ed25519"
    17  )
    18  
    19  //TODO: test file permissions
    20  
    21  func Test_signSummary(t *testing.T) {
    22  	assert.Equal(t, "sign <flags>: create and sign a certificate", signSummary())
    23  }
    24  
    25  func Test_signHelp(t *testing.T) {
    26  	ob := &bytes.Buffer{}
    27  	signHelp(ob)
    28  	assert.Equal(
    29  		t,
    30  		"Usage of "+os.Args[0]+" sign <flags>: create and sign a certificate\n"+
    31  			"  -ca-crt string\n"+
    32  			"    \tOptional: path to the signing CA cert (default \"ca.crt\")\n"+
    33  			"  -ca-key string\n"+
    34  			"    \tOptional: path to the signing CA key (default \"ca.key\")\n"+
    35  			"  -duration duration\n"+
    36  			"    \tOptional: how long the cert should be valid for. The default is 1 second before the signing cert expires. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\"\n"+
    37  			"  -groups string\n"+
    38  			"    \tOptional: comma separated list of groups\n"+
    39  			"  -in-pub string\n"+
    40  			"    \tOptional (if out-key not set): path to read a previously generated public key\n"+
    41  			"  -ip string\n"+
    42  			"    \tRequired: ipv4 address and network in CIDR notation to assign the cert\n"+
    43  			"  -name string\n"+
    44  			"    \tRequired: name of the cert, usually a hostname\n"+
    45  			"  -out-crt string\n"+
    46  			"    \tOptional: path to write the certificate to\n"+
    47  			"  -out-key string\n"+
    48  			"    \tOptional (if in-pub not set): path to write the private key to\n"+
    49  			"  -out-qr string\n"+
    50  			"    \tOptional: output a qr code image (png) of the certificate\n"+
    51  			"  -subnets string\n"+
    52  			"    \tOptional: comma separated list of ipv4 address and network in CIDR notation. Subnets this cert can serve for\n",
    53  		ob.String(),
    54  	)
    55  }
    56  
    57  func Test_signCert(t *testing.T) {
    58  	ob := &bytes.Buffer{}
    59  	eb := &bytes.Buffer{}
    60  
    61  	nopw := &StubPasswordReader{
    62  		password: []byte(""),
    63  		err:      nil,
    64  	}
    65  
    66  	errpw := &StubPasswordReader{
    67  		password: []byte(""),
    68  		err:      errors.New("stub error"),
    69  	}
    70  
    71  	passphrase := []byte("DO NOT USE THIS KEY")
    72  	testpw := &StubPasswordReader{
    73  		password: passphrase,
    74  		err:      nil,
    75  	}
    76  
    77  	// required args
    78  	assertHelpError(t, signCert(
    79  		[]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-ip", "1.1.1.1/24", "-out-key", "nope", "-out-crt", "nope"}, ob, eb, nopw,
    80  	), "-name is required")
    81  	assert.Empty(t, ob.String())
    82  	assert.Empty(t, eb.String())
    83  
    84  	assertHelpError(t, signCert(
    85  		[]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-out-key", "nope", "-out-crt", "nope"}, ob, eb, nopw,
    86  	), "-ip is required")
    87  	assert.Empty(t, ob.String())
    88  	assert.Empty(t, eb.String())
    89  
    90  	// cannot set -in-pub and -out-key
    91  	assertHelpError(t, signCert(
    92  		[]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,
    93  	), "cannot set both -in-pub and -out-key")
    94  	assert.Empty(t, ob.String())
    95  	assert.Empty(t, eb.String())
    96  
    97  	// failed to read key
    98  	ob.Reset()
    99  	eb.Reset()
   100  	args := []string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
   101  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading ca-key: open ./nope: "+NoSuchFileError)
   102  
   103  	// failed to unmarshal key
   104  	ob.Reset()
   105  	eb.Reset()
   106  	caKeyF, err := os.CreateTemp("", "sign-cert.key")
   107  	assert.Nil(t, err)
   108  	defer os.Remove(caKeyF.Name())
   109  
   110  	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"}
   111  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing ca-key: input did not contain a valid PEM encoded block")
   112  	assert.Empty(t, ob.String())
   113  	assert.Empty(t, eb.String())
   114  
   115  	// Write a proper ca key for later
   116  	ob.Reset()
   117  	eb.Reset()
   118  	caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
   119  	caKeyF.Write(cert.MarshalEd25519PrivateKey(caPriv))
   120  
   121  	// failed to read cert
   122  	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"}
   123  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading ca-crt: open ./nope: "+NoSuchFileError)
   124  	assert.Empty(t, ob.String())
   125  	assert.Empty(t, eb.String())
   126  
   127  	// failed to unmarshal cert
   128  	ob.Reset()
   129  	eb.Reset()
   130  	caCrtF, err := os.CreateTemp("", "sign-cert.crt")
   131  	assert.Nil(t, err)
   132  	defer os.Remove(caCrtF.Name())
   133  
   134  	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"}
   135  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing ca-crt: input did not contain a valid PEM encoded block")
   136  	assert.Empty(t, ob.String())
   137  	assert.Empty(t, eb.String())
   138  
   139  	// write a proper ca cert for later
   140  	ca := cert.NebulaCertificate{
   141  		Details: cert.NebulaCertificateDetails{
   142  			Name:      "ca",
   143  			NotBefore: time.Now(),
   144  			NotAfter:  time.Now().Add(time.Minute * 200),
   145  			PublicKey: caPub,
   146  			IsCA:      true,
   147  		},
   148  	}
   149  	b, _ := ca.MarshalToPEM()
   150  	caCrtF.Write(b)
   151  
   152  	// failed to read pub
   153  	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"}
   154  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while reading in-pub: open ./nope: "+NoSuchFileError)
   155  	assert.Empty(t, ob.String())
   156  	assert.Empty(t, eb.String())
   157  
   158  	// failed to unmarshal pub
   159  	ob.Reset()
   160  	eb.Reset()
   161  	inPubF, err := os.CreateTemp("", "in.pub")
   162  	assert.Nil(t, err)
   163  	defer os.Remove(inPubF.Name())
   164  
   165  	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"}
   166  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while parsing in-pub: input did not contain a valid PEM encoded block")
   167  	assert.Empty(t, ob.String())
   168  	assert.Empty(t, eb.String())
   169  
   170  	// write a proper pub for later
   171  	ob.Reset()
   172  	eb.Reset()
   173  	inPub, _ := x25519Keypair()
   174  	inPubF.Write(cert.MarshalX25519PublicKey(inPub))
   175  
   176  	// bad ip cidr
   177  	ob.Reset()
   178  	eb.Reset()
   179  	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"}
   180  	assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: invalid CIDR address: a1.1.1.1/24")
   181  	assert.Empty(t, ob.String())
   182  	assert.Empty(t, eb.String())
   183  
   184  	ob.Reset()
   185  	eb.Reset()
   186  	args = []string{"-ca-crt", caCrtF.Name(), "-ca-key", caKeyF.Name(), "-name", "test", "-ip", "100::100/100", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
   187  	assertHelpError(t, signCert(args, ob, eb, nopw), "invalid ip definition: can only be ipv4, have 100::100/100")
   188  	assert.Empty(t, ob.String())
   189  	assert.Empty(t, eb.String())
   190  
   191  	// bad subnet cidr
   192  	ob.Reset()
   193  	eb.Reset()
   194  	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"}
   195  	assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: invalid CIDR address: a")
   196  	assert.Empty(t, ob.String())
   197  	assert.Empty(t, eb.String())
   198  
   199  	ob.Reset()
   200  	eb.Reset()
   201  	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"}
   202  	assertHelpError(t, signCert(args, ob, eb, nopw), "invalid subnet definition: can only be ipv4, have 100::100/100")
   203  	assert.Empty(t, ob.String())
   204  	assert.Empty(t, eb.String())
   205  
   206  	// mismatched ca key
   207  	_, caPriv2, _ := ed25519.GenerateKey(rand.Reader)
   208  	caKeyF2, err := os.CreateTemp("", "sign-cert-2.key")
   209  	assert.Nil(t, err)
   210  	defer os.Remove(caKeyF2.Name())
   211  	caKeyF2.Write(cert.MarshalEd25519PrivateKey(caPriv2))
   212  
   213  	ob.Reset()
   214  	eb.Reset()
   215  	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"}
   216  	assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to sign, root certificate does not match private key")
   217  	assert.Empty(t, ob.String())
   218  	assert.Empty(t, eb.String())
   219  
   220  	// failed key write
   221  	ob.Reset()
   222  	eb.Reset()
   223  	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"}
   224  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
   225  	assert.Empty(t, ob.String())
   226  	assert.Empty(t, eb.String())
   227  
   228  	// create temp key file
   229  	keyF, err := os.CreateTemp("", "test.key")
   230  	assert.Nil(t, err)
   231  	os.Remove(keyF.Name())
   232  
   233  	// failed cert write
   234  	ob.Reset()
   235  	eb.Reset()
   236  	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"}
   237  	assert.EqualError(t, signCert(args, ob, eb, nopw), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
   238  	assert.Empty(t, ob.String())
   239  	assert.Empty(t, eb.String())
   240  	os.Remove(keyF.Name())
   241  
   242  	// create temp cert file
   243  	crtF, err := os.CreateTemp("", "test.crt")
   244  	assert.Nil(t, err)
   245  	os.Remove(crtF.Name())
   246  
   247  	// test proper cert with removed empty groups and subnets
   248  	ob.Reset()
   249  	eb.Reset()
   250  	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"}
   251  	assert.Nil(t, signCert(args, ob, eb, nopw))
   252  	assert.Empty(t, ob.String())
   253  	assert.Empty(t, eb.String())
   254  
   255  	// read cert and key files
   256  	rb, _ := os.ReadFile(keyF.Name())
   257  	lKey, b, err := cert.UnmarshalX25519PrivateKey(rb)
   258  	assert.Len(t, b, 0)
   259  	assert.Nil(t, err)
   260  	assert.Len(t, lKey, 32)
   261  
   262  	rb, _ = os.ReadFile(crtF.Name())
   263  	lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb)
   264  	assert.Len(t, b, 0)
   265  	assert.Nil(t, err)
   266  
   267  	assert.Equal(t, "test", lCrt.Details.Name)
   268  	assert.Equal(t, "1.1.1.1/24", lCrt.Details.Ips[0].String())
   269  	assert.Len(t, lCrt.Details.Ips, 1)
   270  	assert.False(t, lCrt.Details.IsCA)
   271  	assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups)
   272  	assert.Len(t, lCrt.Details.Subnets, 3)
   273  	assert.Len(t, lCrt.Details.PublicKey, 32)
   274  	assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore))
   275  
   276  	sns := []string{}
   277  	for _, sn := range lCrt.Details.Subnets {
   278  		sns = append(sns, sn.String())
   279  	}
   280  	assert.Equal(t, []string{"10.1.1.1/32", "10.2.2.2/32", "10.5.5.5/32"}, sns)
   281  
   282  	issuer, _ := ca.Sha256Sum()
   283  	assert.Equal(t, issuer, lCrt.Details.Issuer)
   284  
   285  	assert.True(t, lCrt.CheckSignature(caPub))
   286  
   287  	// test proper cert with in-pub
   288  	os.Remove(keyF.Name())
   289  	os.Remove(crtF.Name())
   290  	ob.Reset()
   291  	eb.Reset()
   292  	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"}
   293  	assert.Nil(t, signCert(args, ob, eb, nopw))
   294  	assert.Empty(t, ob.String())
   295  	assert.Empty(t, eb.String())
   296  
   297  	// read cert file and check pub key matches in-pub
   298  	rb, _ = os.ReadFile(crtF.Name())
   299  	lCrt, b, err = cert.UnmarshalNebulaCertificateFromPEM(rb)
   300  	assert.Len(t, b, 0)
   301  	assert.Nil(t, err)
   302  	assert.Equal(t, lCrt.Details.PublicKey, inPub)
   303  
   304  	// test refuse to sign cert with duration beyond root
   305  	ob.Reset()
   306  	eb.Reset()
   307  	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"}
   308  	assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate")
   309  	assert.Empty(t, ob.String())
   310  	assert.Empty(t, eb.String())
   311  
   312  	// create valid cert/key for overwrite tests
   313  	os.Remove(keyF.Name())
   314  	os.Remove(crtF.Name())
   315  	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"}
   316  	assert.Nil(t, signCert(args, ob, eb, nopw))
   317  
   318  	// test that we won't overwrite existing key file
   319  	os.Remove(crtF.Name())
   320  	ob.Reset()
   321  	eb.Reset()
   322  	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"}
   323  	assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to overwrite existing key: "+keyF.Name())
   324  	assert.Empty(t, ob.String())
   325  	assert.Empty(t, eb.String())
   326  
   327  	// create valid cert/key for overwrite tests
   328  	os.Remove(keyF.Name())
   329  	os.Remove(crtF.Name())
   330  	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"}
   331  	assert.Nil(t, signCert(args, ob, eb, nopw))
   332  
   333  	// test that we won't overwrite existing certificate file
   334  	os.Remove(keyF.Name())
   335  	ob.Reset()
   336  	eb.Reset()
   337  	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"}
   338  	assert.EqualError(t, signCert(args, ob, eb, nopw), "refusing to overwrite existing cert: "+crtF.Name())
   339  	assert.Empty(t, ob.String())
   340  	assert.Empty(t, eb.String())
   341  
   342  	// create valid cert/key using encrypted CA key
   343  	os.Remove(caKeyF.Name())
   344  	os.Remove(caCrtF.Name())
   345  	os.Remove(keyF.Name())
   346  	os.Remove(crtF.Name())
   347  	ob.Reset()
   348  	eb.Reset()
   349  
   350  	caKeyF, err = os.CreateTemp("", "sign-cert.key")
   351  	assert.Nil(t, err)
   352  	defer os.Remove(caKeyF.Name())
   353  
   354  	caCrtF, err = os.CreateTemp("", "sign-cert.crt")
   355  	assert.Nil(t, err)
   356  	defer os.Remove(caCrtF.Name())
   357  
   358  	// generate the encrypted key
   359  	caPub, caPriv, _ = ed25519.GenerateKey(rand.Reader)
   360  	kdfParams := cert.NewArgon2Parameters(64*1024, 4, 3)
   361  	b, _ = cert.EncryptAndMarshalSigningPrivateKey(cert.Curve_CURVE25519, caPriv, passphrase, kdfParams)
   362  	caKeyF.Write(b)
   363  
   364  	ca = cert.NebulaCertificate{
   365  		Details: cert.NebulaCertificateDetails{
   366  			Name:      "ca",
   367  			NotBefore: time.Now(),
   368  			NotAfter:  time.Now().Add(time.Minute * 200),
   369  			PublicKey: caPub,
   370  			IsCA:      true,
   371  		},
   372  	}
   373  	b, _ = ca.MarshalToPEM()
   374  	caCrtF.Write(b)
   375  
   376  	// test with the proper password
   377  	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"}
   378  	assert.Nil(t, signCert(args, ob, eb, testpw))
   379  	assert.Equal(t, "Enter passphrase: ", ob.String())
   380  	assert.Empty(t, eb.String())
   381  
   382  	// test with the wrong password
   383  	ob.Reset()
   384  	eb.Reset()
   385  
   386  	testpw.password = []byte("invalid password")
   387  	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"}
   388  	assert.Error(t, signCert(args, ob, eb, testpw))
   389  	assert.Equal(t, "Enter passphrase: ", ob.String())
   390  	assert.Empty(t, eb.String())
   391  
   392  	// test with the user not entering a password
   393  	ob.Reset()
   394  	eb.Reset()
   395  
   396  	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"}
   397  	assert.Error(t, signCert(args, ob, eb, nopw))
   398  	// normally the user hitting enter on the prompt would add newlines between these
   399  	assert.Equal(t, "Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: Enter passphrase: ", ob.String())
   400  	assert.Empty(t, eb.String())
   401  
   402  	// test an error condition
   403  	ob.Reset()
   404  	eb.Reset()
   405  
   406  	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"}
   407  	assert.Error(t, signCert(args, ob, eb, errpw))
   408  	assert.Equal(t, "Enter passphrase: ", ob.String())
   409  	assert.Empty(t, eb.String())
   410  }