sign_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // +build !windows
  2. package main
  3. import (
  4. "bytes"
  5. "crypto/rand"
  6. "io/ioutil"
  7. "os"
  8. "testing"
  9. "time"
  10. "github.com/slackhq/nebula/cert"
  11. "github.com/stretchr/testify/assert"
  12. "golang.org/x/crypto/ed25519"
  13. )
  14. //TODO: test file permissions
  15. func Test_signSummary(t *testing.T) {
  16. assert.Equal(t, "sign <flags>: create and sign a certificate", signSummary())
  17. }
  18. func Test_signHelp(t *testing.T) {
  19. ob := &bytes.Buffer{}
  20. signHelp(ob)
  21. assert.Equal(
  22. t,
  23. "Usage of "+os.Args[0]+" sign <flags>: create and sign a certificate\n"+
  24. " -ca-crt string\n"+
  25. " \tOptional: path to the signing CA cert (default \"ca.crt\")\n"+
  26. " -ca-key string\n"+
  27. " \tOptional: path to the signing CA key (default \"ca.key\")\n"+
  28. " -duration duration\n"+
  29. " \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"+
  30. " -groups string\n"+
  31. " \tOptional: comma separated list of groups\n"+
  32. " -in-pub string\n"+
  33. " \tOptional (if out-key not set): path to read a previously generated public key\n"+
  34. " -ip string\n"+
  35. " \tRequired: ip and network in CIDR notation to assign the cert\n"+
  36. " -name string\n"+
  37. " \tRequired: name of the cert, usually a hostname\n"+
  38. " -out-crt string\n"+
  39. " \tOptional: path to write the certificate to\n"+
  40. " -out-key string\n"+
  41. " \tOptional (if in-pub not set): path to write the private key to\n"+
  42. " -out-qr string\n"+
  43. " \tOptional: output a qr code image (png) of the certificate\n"+
  44. " -subnets string\n"+
  45. " \tOptional: comma seperated list of subnet this cert can serve for\n",
  46. ob.String(),
  47. )
  48. }
  49. func Test_signCert(t *testing.T) {
  50. ob := &bytes.Buffer{}
  51. eb := &bytes.Buffer{}
  52. // required args
  53. assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-ip", "1.1.1.1/24", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-name is required")
  54. assert.Empty(t, ob.String())
  55. assert.Empty(t, eb.String())
  56. assertHelpError(t, signCert([]string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-out-key", "nope", "-out-crt", "nope"}, ob, eb), "-ip is required")
  57. assert.Empty(t, ob.String())
  58. assert.Empty(t, eb.String())
  59. // cannot set -in-pub and -out-key
  60. assertHelpError(t, signCert([]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), "cannot set both -in-pub and -out-key")
  61. assert.Empty(t, ob.String())
  62. assert.Empty(t, eb.String())
  63. // failed to read key
  64. ob.Reset()
  65. eb.Reset()
  66. args := []string{"-ca-crt", "./nope", "-ca-key", "./nope", "-name", "test", "-ip", "1.1.1.1/24", "-out-crt", "nope", "-out-key", "nope", "-duration", "100m"}
  67. assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-key: open ./nope: "+NoSuchFileError)
  68. // failed to unmarshal key
  69. ob.Reset()
  70. eb.Reset()
  71. caKeyF, err := ioutil.TempFile("", "sign-cert.key")
  72. assert.Nil(t, err)
  73. defer os.Remove(caKeyF.Name())
  74. 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"}
  75. assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-key: input did not contain a valid PEM encoded block")
  76. assert.Empty(t, ob.String())
  77. assert.Empty(t, eb.String())
  78. // Write a proper ca key for later
  79. ob.Reset()
  80. eb.Reset()
  81. caPub, caPriv, _ := ed25519.GenerateKey(rand.Reader)
  82. caKeyF.Write(cert.MarshalEd25519PrivateKey(caPriv))
  83. // failed to read cert
  84. 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"}
  85. assert.EqualError(t, signCert(args, ob, eb), "error while reading ca-crt: open ./nope: "+NoSuchFileError)
  86. assert.Empty(t, ob.String())
  87. assert.Empty(t, eb.String())
  88. // failed to unmarshal cert
  89. ob.Reset()
  90. eb.Reset()
  91. caCrtF, err := ioutil.TempFile("", "sign-cert.crt")
  92. assert.Nil(t, err)
  93. defer os.Remove(caCrtF.Name())
  94. 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"}
  95. assert.EqualError(t, signCert(args, ob, eb), "error while parsing ca-crt: input did not contain a valid PEM encoded block")
  96. assert.Empty(t, ob.String())
  97. assert.Empty(t, eb.String())
  98. // write a proper ca cert for later
  99. ca := cert.NebulaCertificate{
  100. Details: cert.NebulaCertificateDetails{
  101. Name: "ca",
  102. NotBefore: time.Now(),
  103. NotAfter: time.Now().Add(time.Minute * 200),
  104. PublicKey: caPub,
  105. IsCA: true,
  106. },
  107. }
  108. b, _ := ca.MarshalToPEM()
  109. caCrtF.Write(b)
  110. // failed to read pub
  111. 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"}
  112. assert.EqualError(t, signCert(args, ob, eb), "error while reading in-pub: open ./nope: "+NoSuchFileError)
  113. assert.Empty(t, ob.String())
  114. assert.Empty(t, eb.String())
  115. // failed to unmarshal pub
  116. ob.Reset()
  117. eb.Reset()
  118. inPubF, err := ioutil.TempFile("", "in.pub")
  119. assert.Nil(t, err)
  120. defer os.Remove(inPubF.Name())
  121. 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"}
  122. assert.EqualError(t, signCert(args, ob, eb), "error while parsing in-pub: input did not contain a valid PEM encoded block")
  123. assert.Empty(t, ob.String())
  124. assert.Empty(t, eb.String())
  125. // write a proper pub for later
  126. ob.Reset()
  127. eb.Reset()
  128. inPub, _ := x25519Keypair()
  129. inPubF.Write(cert.MarshalX25519PublicKey(inPub))
  130. // bad ip cidr
  131. ob.Reset()
  132. eb.Reset()
  133. 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"}
  134. assertHelpError(t, signCert(args, ob, eb), "invalid ip definition: invalid CIDR address: a1.1.1.1/24")
  135. assert.Empty(t, ob.String())
  136. assert.Empty(t, eb.String())
  137. // bad subnet cidr
  138. ob.Reset()
  139. eb.Reset()
  140. 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"}
  141. assertHelpError(t, signCert(args, ob, eb), "invalid subnet definition: invalid CIDR address: a")
  142. assert.Empty(t, ob.String())
  143. assert.Empty(t, eb.String())
  144. // failed key write
  145. ob.Reset()
  146. eb.Reset()
  147. 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"}
  148. assert.EqualError(t, signCert(args, ob, eb), "error while writing out-key: open /do/not/write/pleasekey: "+NoSuchDirError)
  149. assert.Empty(t, ob.String())
  150. assert.Empty(t, eb.String())
  151. // create temp key file
  152. keyF, err := ioutil.TempFile("", "test.key")
  153. assert.Nil(t, err)
  154. os.Remove(keyF.Name())
  155. // failed cert write
  156. ob.Reset()
  157. eb.Reset()
  158. 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"}
  159. assert.EqualError(t, signCert(args, ob, eb), "error while writing out-crt: open /do/not/write/pleasecrt: "+NoSuchDirError)
  160. assert.Empty(t, ob.String())
  161. assert.Empty(t, eb.String())
  162. os.Remove(keyF.Name())
  163. // create temp cert file
  164. crtF, err := ioutil.TempFile("", "test.crt")
  165. assert.Nil(t, err)
  166. os.Remove(crtF.Name())
  167. // test proper cert with removed empty groups and subnets
  168. ob.Reset()
  169. eb.Reset()
  170. 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"}
  171. assert.Nil(t, signCert(args, ob, eb))
  172. assert.Empty(t, ob.String())
  173. assert.Empty(t, eb.String())
  174. // read cert and key files
  175. rb, _ := ioutil.ReadFile(keyF.Name())
  176. lKey, b, err := cert.UnmarshalX25519PrivateKey(rb)
  177. assert.Len(t, b, 0)
  178. assert.Nil(t, err)
  179. assert.Len(t, lKey, 32)
  180. rb, _ = ioutil.ReadFile(crtF.Name())
  181. lCrt, b, err := cert.UnmarshalNebulaCertificateFromPEM(rb)
  182. assert.Len(t, b, 0)
  183. assert.Nil(t, err)
  184. assert.Equal(t, "test", lCrt.Details.Name)
  185. assert.Equal(t, "1.1.1.1/24", lCrt.Details.Ips[0].String())
  186. assert.Len(t, lCrt.Details.Ips, 1)
  187. assert.False(t, lCrt.Details.IsCA)
  188. assert.Equal(t, []string{"1", "2", "3", "4", "5"}, lCrt.Details.Groups)
  189. assert.Len(t, lCrt.Details.Subnets, 3)
  190. assert.Len(t, lCrt.Details.PublicKey, 32)
  191. assert.Equal(t, time.Duration(time.Minute*100), lCrt.Details.NotAfter.Sub(lCrt.Details.NotBefore))
  192. sns := []string{}
  193. for _, sn := range lCrt.Details.Subnets {
  194. sns = append(sns, sn.String())
  195. }
  196. assert.Equal(t, []string{"10.1.1.1/32", "10.2.2.2/32", "10.5.5.5/32"}, sns)
  197. issuer, _ := ca.Sha256Sum()
  198. assert.Equal(t, issuer, lCrt.Details.Issuer)
  199. assert.True(t, lCrt.CheckSignature(caPub))
  200. // test proper cert with in-pub
  201. os.Remove(keyF.Name())
  202. os.Remove(crtF.Name())
  203. ob.Reset()
  204. eb.Reset()
  205. 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"}
  206. assert.Nil(t, signCert(args, ob, eb))
  207. assert.Empty(t, ob.String())
  208. assert.Empty(t, eb.String())
  209. // read cert file and check pub key matches in-pub
  210. rb, _ = ioutil.ReadFile(crtF.Name())
  211. lCrt, b, err = cert.UnmarshalNebulaCertificateFromPEM(rb)
  212. assert.Len(t, b, 0)
  213. assert.Nil(t, err)
  214. assert.Equal(t, lCrt.Details.PublicKey, inPub)
  215. // test refuse to sign cert with duration beyond root
  216. ob.Reset()
  217. eb.Reset()
  218. 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"}
  219. assert.EqualError(t, signCert(args, ob, eb), "refusing to sign, root certificate constraints violated: certificate expires after signing certificate")
  220. assert.Empty(t, ob.String())
  221. assert.Empty(t, eb.String())
  222. // create valid cert/key for overwrite tests
  223. os.Remove(keyF.Name())
  224. os.Remove(crtF.Name())
  225. 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"}
  226. assert.Nil(t, signCert(args, ob, eb))
  227. // test that we won't overwrite existing key file
  228. os.Remove(crtF.Name())
  229. ob.Reset()
  230. eb.Reset()
  231. 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"}
  232. assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing key: "+keyF.Name())
  233. assert.Empty(t, ob.String())
  234. assert.Empty(t, eb.String())
  235. // create valid cert/key for overwrite tests
  236. os.Remove(keyF.Name())
  237. os.Remove(crtF.Name())
  238. 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"}
  239. assert.Nil(t, signCert(args, ob, eb))
  240. // test that we won't overwrite existing certificate file
  241. os.Remove(keyF.Name())
  242. ob.Reset()
  243. eb.Reset()
  244. 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"}
  245. assert.EqualError(t, signCert(args, ob, eb), "refusing to overwrite existing cert: "+crtF.Name())
  246. assert.Empty(t, ob.String())
  247. assert.Empty(t, eb.String())
  248. }