pem_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. /*
  2. Copyright © The ESO Authors
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. https://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package template
  14. import (
  15. "os"
  16. "slices"
  17. "testing"
  18. )
  19. const (
  20. certData = `-----BEGIN CERTIFICATE-----
  21. MIIDHTCCAgWgAwIBAgIRAKC4yxy9QGocND+6avTf7BgwDQYJKoZIhvcNAQELBQAw
  22. EjEQMA4GA1UEChMHQWNtZSBDbzAeFw0yMTAzMjAyMDA4MDhaFw0yMTAzMjAyMDM4
  23. MDhaMBIxEDAOBgNVBAoTB0FjbWUgQ28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
  24. ggEKAoIBAQC3o6/JdZEqNbqNRkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4Nzj
  25. aG15owr92/11W0pxPUliRLti3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvW
  26. Y8jh8A0LQALZiV/9QsrJdXZdS47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE
  27. 1gEDqnKfRxXI8DEQKXr+CKPUwCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4e
  28. ugHe52vXHdh/HJ9VjNp0xOH1waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJa
  29. YOOonQSEswveSv6PcG9AHvpNPot2Xs6hAgMBAAGjbjBsMA4GA1UdDwEB/wQEAwIC
  30. pDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW
  31. BBR00805mrpoonp95RmC3B6oLl+cGTAVBgNVHREEDjAMggpnb29ibGUuY29tMA0G
  32. CSqGSIb3DQEBCwUAA4IBAQAipc1b6JrEDayPjpz5GM5krcI8dCWVd8re0a9bGjjN
  33. ioWGlu/eTr5El0ffwCNZ2WLmL9rewfHf/bMvYz3ioFZJ2OTxfazqYXNggQz6cMfa
  34. lbedDCdt5XLVX2TyerGvFram+9Uyvk3l0uM7rZnwAmdirG4Tv94QRaD3q4xTj/c0
  35. mv+AggtK0aRFb9o47z/BypLdk5mhbf3Mmr88C8XBzEnfdYyf4JpTlZrYLBmDCu5d
  36. 9RLLsjXxhag8xqMtd1uLUM8XOTGzVWacw8iGY+CTtBKqyA+AE6/bDwZvEwVtsKtC
  37. QJ85ioEpy00NioqcF0WyMZH80uMsPycfpnl5uF7RkW8u
  38. -----END CERTIFICATE-----
  39. `
  40. otherCert = `-----BEGIN CERTIFICATE-----
  41. MIIBqjCCAU+gAwIBAgIRAPnGGsBUMbZhmh5QdnYdBmUwCgYIKoZIzj0EAwIwGjEY
  42. MBYGA1UEAxMPaW50ZXJtZWRpYXRlLWNhMB4XDTIyMDIwOTEwMjUzMVoXDTIyMDIx
  43. MDEwMjUzMVowDjEMMAoGA1UEAxMDZm9vMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcD
  44. QgAEqnxdeInykx8JZsLi13rZLekoG2cosQ3F+2InVNy7hCQ7soMqdaJsGQ6LFtov
  45. ogUFtOOTRWrunblqNWGZsowHbKOBgTB/MA4GA1UdDwEB/wQEAwIHgDAdBgNVHSUE
  46. FjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwHQYDVR0OBBYEFLtundVbuKd73OWzo6SY
  47. by0Ajeb2MB8GA1UdIwQYMBaAFCLg80J/bZBbOd+Y8+V94l5xM2zEMA4GA1UdEQQH
  48. MAWCA2ZvbzAKBggqhkjOPQQDAgNJADBGAiEA4K4SbVNqrEtl7RfwBfJFMnWI+X8D
  49. zMPMc4Xqzp2qTxcCIQDsySgtiakypZfWakpB49zJph0kLwGK8xhWvGMUw1N1/w==
  50. -----END CERTIFICATE-----
  51. `
  52. keyData = `-----BEGIN PRIVATE KEY-----
  53. MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC3o6/JdZEqNbqN
  54. RkopHhJtJG5c4qS5d0tQ/kZYpfD/v/izAYum4NzjaG15owr92/11W0pxPUliRLti
  55. 3y6iScTs+ofm2D7p4UXj/Fnho/2xoWSOoWAodgvWY8jh8A0LQALZiV/9QsrJdXZd
  56. S47DYZLsQ3z9yFC/CdXkg1l7AQ3fIVGKdrQBr9kE1gEDqnKfRxXI8DEQKXr+CKPU
  57. wCAytegmy0SHp53zNAvY+kopHytzmJpXLoEhxq4eugHe52vXHdh/HJ9VjNp0xOH1
  58. waAgAGxHlltCW0PVd5AJ0SXROBS/a3V9sZCbCrJaYOOonQSEswveSv6PcG9AHvpN
  59. Pot2Xs6hAgMBAAECggEACTGPrmVNZDCWa1Y2hkJ0J7SoNcw+9O4M/jwMp4l/PD6P
  60. I98S78LYLCZhPLK17SmjUcnFO1AXKW1JeFS2D/fjfP256guvcqQNjLFoioxcOhVb
  61. ZGyd1Mi8JPqP5wfOj16gBeYDwTkjz9wqldcfiZaL9XoXetkZecbzR2JwC2FtIVuC
  62. 0njTjMNYpaBKnoLb8OTR0EQz7lYEo2MkQiWryz8wseONnFmdfh18p+p10YgCbuCH
  63. qesrWfDLLxaxZelNtDhDngg9LoCLmarYy7BgShacmUEgJTZ/x3xFC75thK3ln0OY
  64. +ktTgvVotYYaZi7qAjQiEsTvkTAPg5RMpQLd2UIWsQKBgQDCBp+1vURbwGzmTNUg
  65. HMipD6WDFdLc9DCacx6+ZqsEPTMWQbCpVZrDKiY0Rjt5F+xOCyMr00J5RDJXRC0G
  66. +L7NcJdywOFutT7vB+cmETg7l/6PHweNYBnE66706eTL/KVYZMi4tEinarPWhHmL
  67. jasfdLANtpDjdWkRt299TkPRbQKBgQDyS8Rr7KZdv04Csqkf+ASmiJpT5R6Y72kc
  68. 3XYpKETyB2FyPZkuh/zInMut9SkkSI9O/jA3zf956jj6sF1DHvp7T8KkIp5OAQeD
  69. J9AF65m2MnZfHFUeJ6ZQsggwMWqrD0ycIWP7YWtiBHH+D1wGkjYrssq+bvG/yNpA
  70. LtqdKq9lhQKBgQCZA2hIhy61vRckuEsLvCdzTGeW7UsR/XGnHEqOlaEhArKbRsrv
  71. gBdA+qiOaSTV5svw8E+YbE7sG6AnuhhYeyreEYEeeoZOLJmpIG5mUwYp2UBj1nC6
  72. SaOI7OVZOGu7g09SWokBQQxbG4cgEfFY4Sym7fs5lVTGTP3Dfwppo6NQMQKBgQCo
  73. J5NDP3Lafwk58BpV+H/pv8YzUUDh7M2rXbtCpxLqUdr8OOnVlEUISWFF8m5CIyVq
  74. MhjuscWLK9Wtjba7/YTjDaDM3sW05xv6lyfU5ATCoNTr/zLHgcb4HAZ4w+L+otiN
  75. RtMnxB2NYf5mzuwUF2cG/secUEzwyAlIH/xStSwTLQKBgQCRvqF+rqxnegoOgwVW
  76. qrWPv06wXD8dW2FlPpY5GXqA0l6erSK3YsQQToRmbem9ibPD7bd5P4gNbWfxwK4C
  77. Wt+1Rcb8OrDhDJbYz85bXBnPecKp4EN0b9SHO0/dsCqn2w30emc+9T/4m1ZDkpBd
  78. BixHvI/EJ8YK3ta5WdJWKC6hnA==
  79. -----END PRIVATE KEY-----
  80. `
  81. )
  82. const (
  83. filterPrivateKey = "private key"
  84. filterCert = "certificate"
  85. )
  86. func TestFilterPEM(t *testing.T) {
  87. type args struct {
  88. input string
  89. pemType string
  90. }
  91. tests := []struct {
  92. name string
  93. args args
  94. want string
  95. wantErr bool
  96. }{
  97. {
  98. name: "extract cert / cert first",
  99. args: args{
  100. input: certData + keyData,
  101. pemType: filterCert,
  102. },
  103. want: certData,
  104. },
  105. {
  106. name: "extract cert / key first",
  107. args: args{
  108. input: keyData + certData,
  109. pemType: filterCert,
  110. },
  111. want: certData,
  112. },
  113. {
  114. name: "extract multiple certs",
  115. args: args{
  116. input: keyData + certData + keyData + otherCert,
  117. pemType: filterCert,
  118. },
  119. want: certData + otherCert,
  120. },
  121. {
  122. name: "extract key",
  123. args: args{
  124. input: keyData + certData,
  125. pemType: filterPrivateKey,
  126. },
  127. want: keyData,
  128. },
  129. {
  130. name: "key with junk",
  131. args: args{
  132. input: certData + keyData + "some ---junk---",
  133. pemType: filterPrivateKey,
  134. },
  135. want: keyData,
  136. },
  137. {
  138. name: "begin/end with junk",
  139. args: args{
  140. // pem.Decode trims junk from the beginning of the input
  141. // so we are able to decode both cert & key
  142. input: "some junk" + certData + keyData + "some ---junk---",
  143. pemType: filterPrivateKey,
  144. },
  145. want: keyData,
  146. },
  147. {
  148. name: "interleaved junk",
  149. args: args{
  150. // can parse cert but not key due to junk
  151. input: certData + "some junk" + keyData,
  152. pemType: filterPrivateKey,
  153. },
  154. wantErr: true,
  155. },
  156. {
  157. name: "err when junk",
  158. args: args{
  159. input: "---junk---",
  160. pemType: filterPrivateKey,
  161. },
  162. wantErr: true,
  163. },
  164. }
  165. for _, tt := range tests {
  166. t.Run(tt.name, func(t *testing.T) {
  167. got, err := filterPEM(tt.args.pemType, tt.args.input)
  168. if (err != nil) != tt.wantErr {
  169. t.Errorf("filterPEM() error = %v, wantErr %v", err, tt.wantErr)
  170. return
  171. }
  172. if got != tt.want {
  173. t.Errorf("filterPEM() = %v, want %v", got, tt.want)
  174. }
  175. })
  176. }
  177. }
  178. type filterCertChainTestArgs struct {
  179. input []string
  180. certType string
  181. }
  182. type filterCertChainTest struct {
  183. name string
  184. args filterCertChainTestArgs
  185. want string
  186. wantErr bool
  187. }
  188. func TestFilterCertChain(t *testing.T) {
  189. const (
  190. leafCertPath = "_testdata/foo.crt"
  191. intermediateCertPath = "_testdata/intermediate-ca.crt"
  192. rootCertPath = "_testdata/root-ca.crt"
  193. rootKeyPath = "_testdata/root-ca.key"
  194. )
  195. tests := []filterCertChainTest{
  196. {
  197. name: "extract leaf cert / empty cert chain",
  198. args: filterCertChainTestArgs{
  199. input: []string{},
  200. certType: certTypeLeaf,
  201. },
  202. wantErr: true,
  203. },
  204. {
  205. name: "extract leaf cert / cert chain with pkey",
  206. args: filterCertChainTestArgs{
  207. input: []string{
  208. leafCertPath,
  209. rootKeyPath,
  210. },
  211. certType: certTypeLeaf,
  212. },
  213. wantErr: true,
  214. },
  215. {
  216. name: "extract leaf cert / leaf cert only",
  217. args: filterCertChainTestArgs{
  218. input: []string{
  219. leafCertPath,
  220. },
  221. certType: certTypeLeaf,
  222. },
  223. want: leafCertPath,
  224. },
  225. {
  226. name: "extract leaf cert / cert chain without root",
  227. args: filterCertChainTestArgs{
  228. input: []string{
  229. leafCertPath,
  230. intermediateCertPath,
  231. },
  232. certType: certTypeLeaf,
  233. },
  234. want: leafCertPath,
  235. },
  236. {
  237. name: "extract leaf cert / root cert only",
  238. args: filterCertChainTestArgs{
  239. input: []string{
  240. rootCertPath,
  241. },
  242. certType: certTypeLeaf,
  243. },
  244. want: "",
  245. },
  246. {
  247. name: "extract leaf cert / full cert chain",
  248. args: filterCertChainTestArgs{
  249. input: []string{
  250. leafCertPath,
  251. intermediateCertPath,
  252. rootCertPath,
  253. },
  254. certType: certTypeLeaf,
  255. },
  256. want: leafCertPath,
  257. },
  258. {
  259. name: "extract intermediate cert / leaf cert only",
  260. args: filterCertChainTestArgs{
  261. input: []string{
  262. leafCertPath,
  263. },
  264. certType: certTypeIntermediate,
  265. },
  266. want: "",
  267. },
  268. {
  269. name: "extract intermediate cert / cert chain without root",
  270. args: filterCertChainTestArgs{
  271. input: []string{
  272. leafCertPath,
  273. intermediateCertPath,
  274. },
  275. certType: certTypeIntermediate,
  276. },
  277. want: intermediateCertPath,
  278. },
  279. {
  280. name: "extract intermediate cert / full cert chain",
  281. args: filterCertChainTestArgs{
  282. input: []string{
  283. leafCertPath,
  284. intermediateCertPath,
  285. rootCertPath,
  286. },
  287. certType: certTypeIntermediate,
  288. },
  289. want: intermediateCertPath,
  290. },
  291. {
  292. name: "extract root cert / leaf cert only",
  293. args: filterCertChainTestArgs{
  294. input: []string{
  295. leafCertPath,
  296. },
  297. certType: certTypeRoot,
  298. },
  299. want: "",
  300. },
  301. {
  302. name: "extract root cert / root cert only",
  303. args: filterCertChainTestArgs{
  304. input: []string{
  305. rootCertPath,
  306. },
  307. certType: certTypeRoot,
  308. },
  309. want: rootCertPath,
  310. },
  311. {
  312. name: "extract root cert / full cert chain",
  313. args: filterCertChainTestArgs{
  314. input: []string{
  315. leafCertPath,
  316. intermediateCertPath,
  317. rootCertPath,
  318. },
  319. certType: certTypeRoot,
  320. },
  321. want: rootCertPath,
  322. },
  323. }
  324. for _, tt := range tests {
  325. runFilterCertChainTest(t, tt)
  326. }
  327. }
  328. func runFilterCertChainTest(t *testing.T, tt filterCertChainTest) {
  329. t.Run(tt.name, func(t *testing.T) {
  330. chainIn, err := readCertificates(tt.args.input)
  331. if err != nil {
  332. t.Error(err)
  333. }
  334. var expOut []byte
  335. if tt.want != "" {
  336. var err error
  337. expOut, err = os.ReadFile(tt.want)
  338. if err != nil {
  339. t.Error(err)
  340. }
  341. }
  342. got, err := filterCertChain(tt.args.certType, string(chainIn))
  343. if (err != nil) != tt.wantErr {
  344. t.Errorf("filterCertChain() error = %v, wantErr %v", err, tt.wantErr)
  345. return
  346. }
  347. if got != string(expOut) {
  348. t.Errorf("filterCertChain() = %v, want %v", got, string(expOut))
  349. }
  350. })
  351. }
  352. func readCertificates(certFiles []string) ([]byte, error) {
  353. var certificates []byte
  354. for _, f := range certFiles {
  355. c, err := os.ReadFile(f)
  356. if err != nil {
  357. return nil, err
  358. }
  359. certificates = append(certificates, c...)
  360. }
  361. return certificates, nil
  362. }
  363. func TestCertSANs(t *testing.T) {
  364. tests := []struct {
  365. name string
  366. input string
  367. want []string
  368. wantErr bool
  369. }{
  370. {
  371. name: "extract DNS SANs from cert",
  372. input: certData,
  373. want: []string{"gooble.com"},
  374. },
  375. {
  376. name: "invalid PEM input",
  377. input: "not a pem",
  378. wantErr: true,
  379. },
  380. {
  381. name: "empty input",
  382. input: "",
  383. wantErr: true,
  384. },
  385. {
  386. name: "cert with junk before PEM",
  387. input: "some junk\n" + certData,
  388. want: []string{"gooble.com"},
  389. },
  390. {
  391. name: "cert from file with all types of SANs",
  392. input: func() string {
  393. b, err := os.ReadFile("_testdata/sans.crt")
  394. if err != nil {
  395. panic("test setup failed: " + err.Error())
  396. }
  397. return string(b)
  398. }(),
  399. want: []string{"example.com", "www.example.com", "192.168.1.10", "10.0.0.1", "admin@example.com", "https://example.com"},
  400. },
  401. }
  402. for _, tt := range tests {
  403. t.Run(tt.name, func(t *testing.T) {
  404. got, err := certSANs(tt.input)
  405. if (err != nil) != tt.wantErr {
  406. t.Errorf("certSANs() error = %v, wantErr %v", err, tt.wantErr)
  407. return
  408. }
  409. if !tt.wantErr && !slices.Equal(got, tt.want) {
  410. t.Errorf("certSANs() = %v, want %v", got, tt.want)
  411. }
  412. })
  413. }
  414. }