sign_test.go 17 KB

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