github.com/slackhq/nebula@v1.9.0/cmd/nebula-cert/ca_test.go (about) 1 //go:build !windows 2 // +build !windows 3 4 package main 5 6 import ( 7 "bytes" 8 "encoding/pem" 9 "errors" 10 "os" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/slackhq/nebula/cert" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 //TODO: test file permissions 20 21 func Test_caSummary(t *testing.T) { 22 assert.Equal(t, "ca <flags>: create a self signed certificate authority", caSummary()) 23 } 24 25 func Test_caHelp(t *testing.T) { 26 ob := &bytes.Buffer{} 27 caHelp(ob) 28 assert.Equal( 29 t, 30 "Usage of "+os.Args[0]+" ca <flags>: create a self signed certificate authority\n"+ 31 " -argon-iterations uint\n"+ 32 " \tOptional: Argon2 iterations parameter used for encrypted private key passphrase (default 1)\n"+ 33 " -argon-memory uint\n"+ 34 " \tOptional: Argon2 memory parameter (in KiB) used for encrypted private key passphrase (default 2097152)\n"+ 35 " -argon-parallelism uint\n"+ 36 " \tOptional: Argon2 parallelism parameter used for encrypted private key passphrase (default 4)\n"+ 37 " -curve string\n"+ 38 " \tEdDSA/ECDSA Curve (25519, P256) (default \"25519\")\n"+ 39 " -duration duration\n"+ 40 " \tOptional: amount of time the certificate should be valid for. Valid time units are seconds: \"s\", minutes: \"m\", hours: \"h\" (default 8760h0m0s)\n"+ 41 " -encrypt\n"+ 42 " \tOptional: prompt for passphrase and write out-key in an encrypted format\n"+ 43 " -groups string\n"+ 44 " \tOptional: comma separated list of groups. This will limit which groups subordinate certs can use\n"+ 45 " -ips string\n"+ 46 " \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"+ 47 " -name string\n"+ 48 " \tRequired: name of the certificate authority\n"+ 49 " -out-crt string\n"+ 50 " \tOptional: path to write the certificate to (default \"ca.crt\")\n"+ 51 " -out-key string\n"+ 52 " \tOptional: path to write the private key to (default \"ca.key\")\n"+ 53 " -out-qr string\n"+ 54 " \tOptional: output a qr code image (png) of the certificate\n"+ 55 " -subnets string\n"+ 56 " \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", 57 ob.String(), 58 ) 59 } 60 61 func Test_ca(t *testing.T) { 62 ob := &bytes.Buffer{} 63 eb := &bytes.Buffer{} 64 65 nopw := &StubPasswordReader{ 66 password: []byte(""), 67 err: nil, 68 } 69 70 errpw := &StubPasswordReader{ 71 password: []byte(""), 72 err: errors.New("stub error"), 73 } 74 75 passphrase := []byte("DO NOT USE THIS KEY") 76 testpw := &StubPasswordReader{ 77 password: passphrase, 78 err: nil, 79 } 80 81 pwPromptOb := "Enter passphrase: " 82 83 // required args 84 assertHelpError(t, ca( 85 []string{"-out-key", "nope", "-out-crt", "nope", "duration", "100m"}, ob, eb, nopw, 86 ), "-name is required") 87 assert.Equal(t, "", ob.String()) 88 assert.Equal(t, "", eb.String()) 89 90 // ipv4 only ips 91 assertHelpError(t, ca([]string{"-name", "ipv6", "-ips", "100::100/100"}, ob, eb, nopw), "invalid ip definition: can only be ipv4, have 100::100/100") 92 assert.Equal(t, "", ob.String()) 93 assert.Equal(t, "", eb.String()) 94 95 // ipv4 only subnets 96 assertHelpError(t, ca([]string{"-name", "ipv6", "-subnets", "100::100/100"}, ob, eb, nopw), "invalid subnet definition: can only be ipv4, have 100::100/100") 97 assert.Equal(t, "", ob.String()) 98 assert.Equal(t, "", eb.String()) 99 100 // failed key write 101 ob.Reset() 102 eb.Reset() 103 args := []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", "/do/not/write/pleasekey"} 104 assert.EqualError(t, ca(args, ob, eb, nopw), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError) 105 assert.Equal(t, "", ob.String()) 106 assert.Equal(t, "", eb.String()) 107 108 // create temp key file 109 keyF, err := os.CreateTemp("", "test.key") 110 assert.Nil(t, err) 111 os.Remove(keyF.Name()) 112 113 // failed cert write 114 ob.Reset() 115 eb.Reset() 116 args = []string{"-name", "test", "-duration", "100m", "-out-crt", "/do/not/write/pleasecrt", "-out-key", keyF.Name()} 117 assert.EqualError(t, ca(args, ob, eb, nopw), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError) 118 assert.Equal(t, "", ob.String()) 119 assert.Equal(t, "", eb.String()) 120 121 // create temp cert file 122 crtF, err := os.CreateTemp("", "test.crt") 123 assert.Nil(t, err) 124 os.Remove(crtF.Name()) 125 os.Remove(keyF.Name()) 126 127 // test proper cert with removed empty groups and subnets 128 ob.Reset() 129 eb.Reset() 130 args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 131 assert.Nil(t, ca(args, ob, eb, nopw)) 132 assert.Equal(t, "", ob.String()) 133 assert.Equal(t, "", eb.String()) 134 135 // read cert and key files 136 rb, _ := os.ReadFile(keyF.Name()) 137 lKey, b, err := cert.UnmarshalEd25519PrivateKey(rb) 138 assert.Len(t, b, 0) 139 assert.Nil(t, err) 140 assert.Len(t, lKey, 64) 141 142 rb, _ = os.ReadFile(crtF.Name()) 143 lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb) 144 assert.Len(t, b, 0) 145 assert.Nil(t, err) 146 147 assert.Equal(t, "test", lCrt.Details.Name) 148 assert.Len(t, lCrt.Details.Ips, 0) 149 assert.True(t, lCrt.Details.IsCA) 150 assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups) 151 assert.Len(t, lCrt.Details.Subnets, 0) 152 assert.Len(t, lCrt.Details.PublicKey, 32) 153 assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore)) 154 assert.Equal(t, "", lCrt.Details.Issuer) 155 assert.True(t, lCrt.CheckSignature(lCrt.Details.PublicKey)) 156 157 // test encrypted key 158 os.Remove(keyF.Name()) 159 os.Remove(crtF.Name()) 160 ob.Reset() 161 eb.Reset() 162 args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 163 assert.Nil(t, ca(args, ob, eb, testpw)) 164 assert.Equal(t, pwPromptOb, ob.String()) 165 assert.Equal(t, "", eb.String()) 166 167 // read encrypted key file and verify default params 168 rb, _ = os.ReadFile(keyF.Name()) 169 k, _ := pem.Decode(rb) 170 ned, err := cert.UnmarshalNebulaEncryptedData(k.Bytes) 171 assert.Nil(t, err) 172 // we won't know salt in advance, so just check start of string 173 assert.Equal(t, uint32(2*1024*1024), ned.EncryptionMetadata.Argon2Parameters.Memory) 174 assert.Equal(t, uint8(4), ned.EncryptionMetadata.Argon2Parameters.Parallelism) 175 assert.Equal(t, uint32(1), ned.EncryptionMetadata.Argon2Parameters.Iterations) 176 177 // verify the key is valid and decrypt-able 178 var curve cert.Curve 179 curve, lKey, b, err = cert.DecryptAndUnmarshalSigningPrivateKey(passphrase, rb) 180 assert.Equal(t, cert.Curve_CURVE25519, curve) 181 assert.Nil(t, err) 182 assert.Len(t, b, 0) 183 assert.Len(t, lKey, 64) 184 185 // test when reading passsword results in an error 186 os.Remove(keyF.Name()) 187 os.Remove(crtF.Name()) 188 ob.Reset() 189 eb.Reset() 190 args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 191 assert.Error(t, ca(args, ob, eb, errpw)) 192 assert.Equal(t, pwPromptOb, ob.String()) 193 assert.Equal(t, "", eb.String()) 194 195 // test when user fails to enter a password 196 os.Remove(keyF.Name()) 197 os.Remove(crtF.Name()) 198 ob.Reset() 199 eb.Reset() 200 args = []string{"-encrypt", "-name", "test", "-duration", "100m", "-groups", "1,2,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 201 assert.EqualError(t, ca(args, ob, eb, nopw), "no passphrase specified, remove -encrypt flag to write out-key in plaintext") 202 assert.Equal(t, strings.Repeat(pwPromptOb, 5), ob.String()) // prompts 5 times before giving up 203 assert.Equal(t, "", eb.String()) 204 205 // create valid cert/key for overwrite tests 206 os.Remove(keyF.Name()) 207 os.Remove(crtF.Name()) 208 ob.Reset() 209 eb.Reset() 210 args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 211 assert.Nil(t, ca(args, ob, eb, nopw)) 212 213 // test that we won't overwrite existing certificate file 214 ob.Reset() 215 eb.Reset() 216 args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 217 assert.EqualError(t, ca(args, ob, eb, nopw), "refusing to overwrite existing CA key: "+keyF.Name()) 218 assert.Equal(t, "", ob.String()) 219 assert.Equal(t, "", eb.String()) 220 221 // test that we won't overwrite existing key file 222 os.Remove(keyF.Name()) 223 ob.Reset() 224 eb.Reset() 225 args = []string{"-name", "test", "-duration", "100m", "-groups", "1,, 2 , ,,,3,4,5", "-out-crt", crtF.Name(), "-out-key", keyF.Name()} 226 assert.EqualError(t, ca(args, ob, eb, nopw), "refusing to overwrite existing CA cert: "+crtF.Name()) 227 assert.Equal(t, "", ob.String()) 228 assert.Equal(t, "", eb.String()) 229 os.Remove(keyF.Name()) 230 231 }