ca_test.go 8.6 KB

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