print_test.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/ed25519"
  5. "crypto/rand"
  6. "encoding/hex"
  7. "net/netip"
  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. )
  15. func Test_printSummary(t *testing.T) {
  16. assert.Equal(t, "print <flags>: prints details about a certificate", printSummary())
  17. }
  18. func Test_printHelp(t *testing.T) {
  19. ob := &bytes.Buffer{}
  20. printHelp(ob)
  21. assert.Equal(
  22. t,
  23. "Usage of "+os.Args[0]+" print <flags>: prints details about a certificate\n"+
  24. " Pass \"-\" to any path flag to read from stdin or write to stdout.\n"+
  25. " -json\n"+
  26. " \tOptional: outputs certificates in json format\n"+
  27. " -out-qr string\n"+
  28. " \tOptional: output a qr code image (png) of the certificate\n"+
  29. " -path string\n"+
  30. " \tRequired: path to the certificate\n",
  31. ob.String(),
  32. )
  33. }
  34. func Test_printCert(t *testing.T) {
  35. // Orient our local time and avoid headaches
  36. time.Local = time.UTC
  37. ob := &bytes.Buffer{}
  38. eb := &bytes.Buffer{}
  39. // no path
  40. err := printCert([]string{}, ob, eb)
  41. assert.Empty(t, ob.String())
  42. assert.Empty(t, eb.String())
  43. assertHelpError(t, err, "-path is required")
  44. // no cert at path
  45. ob.Reset()
  46. eb.Reset()
  47. err = printCert([]string{"-path", "does_not_exist"}, ob, eb)
  48. assert.Empty(t, ob.String())
  49. assert.Empty(t, eb.String())
  50. require.EqualError(t, err, "unable to read cert; open does_not_exist: "+NoSuchFileError)
  51. // invalid cert at path
  52. ob.Reset()
  53. eb.Reset()
  54. tf, err := os.CreateTemp("", "print-cert")
  55. require.NoError(t, err)
  56. defer os.Remove(tf.Name())
  57. tf.WriteString("-----BEGIN NOPE-----")
  58. err = printCert([]string{"-path", tf.Name()}, ob, eb)
  59. assert.Empty(t, ob.String())
  60. assert.Empty(t, eb.String())
  61. require.EqualError(t, err, "error while unmarshaling cert: input did not contain a valid PEM encoded block")
  62. // test multiple certs
  63. ob.Reset()
  64. eb.Reset()
  65. tf.Truncate(0)
  66. tf.Seek(0, 0)
  67. ca, caKey := NewTestCaCert("test ca", nil, nil, time.Time{}, time.Time{}, nil, nil, nil)
  68. c, _ := NewTestCert(ca, caKey, "test", time.Time{}, time.Time{}, []netip.Prefix{netip.MustParsePrefix("10.0.0.123/8")}, nil, []string{"hi"})
  69. p, _ := c.MarshalPEM()
  70. tf.Write(p)
  71. tf.Write(p)
  72. tf.Write(p)
  73. err = printCert([]string{"-path", tf.Name()}, ob, eb)
  74. fp, _ := c.Fingerprint()
  75. pk := hex.EncodeToString(c.PublicKey())
  76. sig := hex.EncodeToString(c.Signature())
  77. require.NoError(t, err)
  78. assert.Equal(
  79. t,
  80. //"NebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\nNebulaCertificate {\n\tDetails {\n\t\tName: test\n\t\tIps: []\n\t\tSubnets: []\n\t\tGroups: [\n\t\t\t\"hi\"\n\t\t]\n\t\tNot before: 0001-01-01 00:00:00 +0000 UTC\n\t\tNot After: 0001-01-01 00:00:00 +0000 UTC\n\t\tIs CA: false\n\t\tIssuer: "+c.Issuer()+"\n\t\tPublic key: "+pk+"\n\t\tCurve: CURVE25519\n\t}\n\tFingerprint: "+fp+"\n\tSignature: "+sig+"\n}\n",
  81. `{
  82. "details": {
  83. "curve": "CURVE25519",
  84. "groups": [
  85. "hi"
  86. ],
  87. "isCa": false,
  88. "issuer": "`+c.Issuer()+`",
  89. "name": "test",
  90. "networks": [
  91. "10.0.0.123/8"
  92. ],
  93. "notAfter": "0001-01-01T00:00:00Z",
  94. "notBefore": "0001-01-01T00:00:00Z",
  95. "publicKey": "`+pk+`",
  96. "unsafeNetworks": []
  97. },
  98. "fingerprint": "`+fp+`",
  99. "signature": "`+sig+`",
  100. "version": 1
  101. }
  102. {
  103. "details": {
  104. "curve": "CURVE25519",
  105. "groups": [
  106. "hi"
  107. ],
  108. "isCa": false,
  109. "issuer": "`+c.Issuer()+`",
  110. "name": "test",
  111. "networks": [
  112. "10.0.0.123/8"
  113. ],
  114. "notAfter": "0001-01-01T00:00:00Z",
  115. "notBefore": "0001-01-01T00:00:00Z",
  116. "publicKey": "`+pk+`",
  117. "unsafeNetworks": []
  118. },
  119. "fingerprint": "`+fp+`",
  120. "signature": "`+sig+`",
  121. "version": 1
  122. }
  123. {
  124. "details": {
  125. "curve": "CURVE25519",
  126. "groups": [
  127. "hi"
  128. ],
  129. "isCa": false,
  130. "issuer": "`+c.Issuer()+`",
  131. "name": "test",
  132. "networks": [
  133. "10.0.0.123/8"
  134. ],
  135. "notAfter": "0001-01-01T00:00:00Z",
  136. "notBefore": "0001-01-01T00:00:00Z",
  137. "publicKey": "`+pk+`",
  138. "unsafeNetworks": []
  139. },
  140. "fingerprint": "`+fp+`",
  141. "signature": "`+sig+`",
  142. "version": 1
  143. }
  144. `,
  145. ob.String(),
  146. )
  147. assert.Empty(t, eb.String())
  148. // test json
  149. ob.Reset()
  150. eb.Reset()
  151. tf.Truncate(0)
  152. tf.Seek(0, 0)
  153. tf.Write(p)
  154. tf.Write(p)
  155. tf.Write(p)
  156. err = printCert([]string{"-json", "-path", tf.Name()}, ob, eb)
  157. fp, _ = c.Fingerprint()
  158. pk = hex.EncodeToString(c.PublicKey())
  159. sig = hex.EncodeToString(c.Signature())
  160. require.NoError(t, err)
  161. assert.Equal(
  162. t,
  163. `[{"details":{"curve":"CURVE25519","groups":["hi"],"isCa":false,"issuer":"`+c.Issuer()+`","name":"test","networks":["10.0.0.123/8"],"notAfter":"0001-01-01T00:00:00Z","notBefore":"0001-01-01T00:00:00Z","publicKey":"`+pk+`","unsafeNetworks":[]},"fingerprint":"`+fp+`","signature":"`+sig+`","version":1},{"details":{"curve":"CURVE25519","groups":["hi"],"isCa":false,"issuer":"`+c.Issuer()+`","name":"test","networks":["10.0.0.123/8"],"notAfter":"0001-01-01T00:00:00Z","notBefore":"0001-01-01T00:00:00Z","publicKey":"`+pk+`","unsafeNetworks":[]},"fingerprint":"`+fp+`","signature":"`+sig+`","version":1},{"details":{"curve":"CURVE25519","groups":["hi"],"isCa":false,"issuer":"`+c.Issuer()+`","name":"test","networks":["10.0.0.123/8"],"notAfter":"0001-01-01T00:00:00Z","notBefore":"0001-01-01T00:00:00Z","publicKey":"`+pk+`","unsafeNetworks":[]},"fingerprint":"`+fp+`","signature":"`+sig+`","version":1}]
  164. `,
  165. ob.String(),
  166. )
  167. assert.Empty(t, eb.String())
  168. // read cert from stdin
  169. ob.Reset()
  170. eb.Reset()
  171. withStdin(t, bytes.NewReader(p))
  172. err = printCert([]string{"-json", "-path", "-"}, ob, eb)
  173. require.NoError(t, err)
  174. assert.Equal(
  175. t,
  176. `[{"details":{"curve":"CURVE25519","groups":["hi"],"isCa":false,"issuer":"`+c.Issuer()+`","name":"test","networks":["10.0.0.123/8"],"notAfter":"0001-01-01T00:00:00Z","notBefore":"0001-01-01T00:00:00Z","publicKey":"`+pk+`","unsafeNetworks":[]},"fingerprint":"`+fp+`","signature":"`+sig+`","version":1}]
  177. `,
  178. ob.String(),
  179. )
  180. assert.Empty(t, eb.String())
  181. // -out-qr - sends only the PNG to stdout, suppressing the cert dump
  182. ob.Reset()
  183. eb.Reset()
  184. withStdin(t, bytes.NewReader(p))
  185. err = printCert([]string{"-path", "-", "-out-qr", "-"}, ob, eb)
  186. require.NoError(t, err)
  187. assert.Empty(t, eb.String())
  188. stdout := ob.Bytes()
  189. require.NotEmpty(t, stdout)
  190. // PNG magic, no PEM/JSON noise prepended
  191. assert.Equal(t, []byte{0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a}, stdout[:8])
  192. assert.NotContains(t, string(stdout), "NebulaCertificate")
  193. assert.NotContains(t, string(stdout), `"details"`)
  194. // json + out-qr - still suppresses json
  195. ob.Reset()
  196. eb.Reset()
  197. withStdin(t, bytes.NewReader(p))
  198. err = printCert([]string{"-json", "-path", "-", "-out-qr", "-"}, ob, eb)
  199. require.NoError(t, err)
  200. assert.Empty(t, eb.String())
  201. assert.Equal(t, []byte{0x89, 'P', 'N', 'G'}, ob.Bytes()[:4])
  202. assert.NotContains(t, ob.String(), `"details"`)
  203. }
  204. // NewTestCaCert will generate a CA cert
  205. func NewTestCaCert(name string, pubKey, privKey []byte, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {
  206. var err error
  207. if pubKey == nil || privKey == nil {
  208. pubKey, privKey, err = ed25519.GenerateKey(rand.Reader)
  209. if err != nil {
  210. panic(err)
  211. }
  212. }
  213. t := &cert.TBSCertificate{
  214. Version: cert.Version1,
  215. Name: name,
  216. NotBefore: time.Unix(before.Unix(), 0),
  217. NotAfter: time.Unix(after.Unix(), 0),
  218. PublicKey: pubKey,
  219. Networks: networks,
  220. UnsafeNetworks: unsafeNetworks,
  221. Groups: groups,
  222. IsCA: true,
  223. }
  224. c, err := t.Sign(nil, cert.Curve_CURVE25519, privKey)
  225. if err != nil {
  226. panic(err)
  227. }
  228. return c, privKey
  229. }
  230. func NewTestCert(ca cert.Certificate, signerKey []byte, name string, before, after time.Time, networks, unsafeNetworks []netip.Prefix, groups []string) (cert.Certificate, []byte) {
  231. if before.IsZero() {
  232. before = ca.NotBefore()
  233. }
  234. if after.IsZero() {
  235. after = ca.NotAfter()
  236. }
  237. if len(networks) == 0 {
  238. networks = []netip.Prefix{netip.MustParsePrefix("10.0.0.123/8")}
  239. }
  240. pub, rawPriv := x25519Keypair()
  241. nc := &cert.TBSCertificate{
  242. Version: cert.Version1,
  243. Name: name,
  244. Networks: networks,
  245. UnsafeNetworks: unsafeNetworks,
  246. Groups: groups,
  247. NotBefore: time.Unix(before.Unix(), 0),
  248. NotAfter: time.Unix(after.Unix(), 0),
  249. PublicKey: pub,
  250. IsCA: false,
  251. }
  252. c, err := nc.Sign(ca, ca.Curve(), signerKey)
  253. if err != nil {
  254. panic(err)
  255. }
  256. return c, rawPriv
  257. }