sign_test.go 23 KB

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