header_test.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. package hpkp
  2. import (
  3. "crypto/tls"
  4. "net/http"
  5. "testing"
  6. )
  7. func TestHeader_Matches(t *testing.T) {
  8. tests := []struct {
  9. name string
  10. header *Header
  11. pin string
  12. expected bool
  13. }{
  14. {
  15. name: "no match",
  16. header: &Header{},
  17. pin: "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  18. expected: false,
  19. },
  20. {
  21. name: "match",
  22. header: &Header{
  23. Sha256Pins: []string{
  24. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  25. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  26. },
  27. },
  28. pin: "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  29. expected: true,
  30. },
  31. }
  32. for _, test := range tests {
  33. out := test.header.Matches(test.pin)
  34. if out != test.expected {
  35. t.Logf("want:%v", test.expected)
  36. t.Logf("got:%v", out)
  37. t.Fatalf("test case failed: %s", test.name)
  38. }
  39. }
  40. }
  41. func TestParseHeader(t *testing.T) {
  42. tests := []struct {
  43. name string
  44. response *http.Response
  45. expected *Header
  46. }{
  47. {
  48. name: "nil everything",
  49. response: nil,
  50. expected: nil,
  51. },
  52. {
  53. name: "no header",
  54. response: &http.Response{
  55. StatusCode: 200,
  56. },
  57. expected: nil,
  58. },
  59. {
  60. name: "hpkp header, but over http",
  61. response: &http.Response{
  62. StatusCode: 200,
  63. Header: map[string][]string{
  64. "Public-Key-Pins": []string{`max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`},
  65. },
  66. },
  67. expected: nil,
  68. },
  69. {
  70. name: "multiple headers",
  71. response: &http.Response{
  72. StatusCode: 200,
  73. Header: map[string][]string{
  74. "Public-Key-Pins": []string{
  75. `max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`,
  76. `max-age=3001; pin-sha256="bad header"`,
  77. },
  78. },
  79. TLS: &tls.ConnectionState{},
  80. },
  81. expected: &Header{
  82. MaxAge: 3000,
  83. IncludeSubDomains: false,
  84. Permanent: false,
  85. Sha256Pins: []string{
  86. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  87. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  88. },
  89. },
  90. },
  91. // https://tools.ietf.org/html/rfc7469#section-2.1.5
  92. {
  93. name: "hpkp header (1)",
  94. response: &http.Response{
  95. StatusCode: 200,
  96. Header: map[string][]string{
  97. "Public-Key-Pins": []string{`max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`},
  98. },
  99. TLS: &tls.ConnectionState{},
  100. },
  101. expected: &Header{
  102. MaxAge: 3000,
  103. IncludeSubDomains: false,
  104. Permanent: false,
  105. Sha256Pins: []string{
  106. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  107. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  108. },
  109. },
  110. },
  111. {
  112. name: "hpkp header (2)",
  113. response: &http.Response{
  114. StatusCode: 200,
  115. Header: map[string][]string{
  116. "Public-Key-Pins": []string{`max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="`},
  117. },
  118. TLS: &tls.ConnectionState{},
  119. },
  120. expected: &Header{
  121. MaxAge: 2592000,
  122. IncludeSubDomains: false,
  123. Permanent: false,
  124. Sha256Pins: []string{
  125. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  126. "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  127. },
  128. },
  129. },
  130. {
  131. name: "hpkp header (3)",
  132. response: &http.Response{
  133. StatusCode: 200,
  134. Header: map[string][]string{
  135. "Public-Key-Pins": []string{`max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; report-uri="http://example.com/pkp-report"`},
  136. },
  137. TLS: &tls.ConnectionState{},
  138. },
  139. expected: &Header{
  140. MaxAge: 2592000,
  141. IncludeSubDomains: false,
  142. Permanent: false,
  143. Sha256Pins: []string{
  144. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  145. "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  146. },
  147. ReportURI: "http://example.com/pkp-report",
  148. },
  149. },
  150. {
  151. name: "hpkp header (4)",
  152. response: &http.Response{
  153. StatusCode: 200,
  154. Header: map[string][]string{
  155. "Public-Key-Pins-Report-Only": []string{`max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; report-uri="http://example.com/pkp-report"`},
  156. },
  157. TLS: &tls.ConnectionState{},
  158. },
  159. expected: nil,
  160. },
  161. {
  162. name: "hpkp header (5)",
  163. response: &http.Response{
  164. StatusCode: 200,
  165. Header: map[string][]string{
  166. "Public-Key-Pins": []string{`pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; max-age=259200`},
  167. },
  168. TLS: &tls.ConnectionState{},
  169. },
  170. expected: &Header{
  171. MaxAge: 259200,
  172. IncludeSubDomains: false,
  173. Permanent: false,
  174. Sha256Pins: []string{
  175. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  176. "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  177. },
  178. },
  179. },
  180. {
  181. name: "hpkp header (6)",
  182. response: &http.Response{
  183. StatusCode: 200,
  184. Header: map[string][]string{
  185. "Public-Key-Pins": []string{`pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; max-age=10000; includeSubDomains`},
  186. },
  187. TLS: &tls.ConnectionState{},
  188. },
  189. expected: &Header{
  190. MaxAge: 10000,
  191. IncludeSubDomains: true,
  192. Permanent: false,
  193. Sha256Pins: []string{
  194. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  195. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  196. "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  197. },
  198. },
  199. },
  200. }
  201. for _, test := range tests {
  202. out := ParseHeader(test.response)
  203. if !equalHeaders(out, test.expected) {
  204. t.Logf("want:%v", test.expected)
  205. t.Logf("got:%v", out)
  206. t.Fatalf("test case failed: %s", test.name)
  207. }
  208. }
  209. }
  210. func TestParseReportOnlyHeader(t *testing.T) {
  211. tests := []struct {
  212. name string
  213. response *http.Response
  214. expected *Header
  215. }{
  216. {
  217. name: "nil everything",
  218. response: nil,
  219. expected: nil,
  220. },
  221. {
  222. name: "no header",
  223. response: &http.Response{
  224. StatusCode: 200,
  225. },
  226. expected: nil,
  227. },
  228. {
  229. name: "hpkp header, but over http",
  230. response: &http.Response{
  231. StatusCode: 200,
  232. Header: map[string][]string{
  233. "Public-Key-Pins": []string{`max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`},
  234. },
  235. },
  236. expected: nil,
  237. },
  238. {
  239. name: "multiple headers",
  240. response: &http.Response{
  241. StatusCode: 200,
  242. Header: map[string][]string{
  243. "Public-Key-Pins-Report-Only": []string{
  244. `max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`,
  245. `max-age=3001; pin-sha256="bad header"`,
  246. },
  247. },
  248. TLS: &tls.ConnectionState{},
  249. },
  250. expected: &Header{
  251. MaxAge: 3000,
  252. IncludeSubDomains: false,
  253. Permanent: false,
  254. Sha256Pins: []string{
  255. "d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=",
  256. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  257. },
  258. },
  259. },
  260. // https://tools.ietf.org/html/rfc7469#section-2.1.5
  261. {
  262. name: "hpkp header (1)",
  263. response: &http.Response{
  264. StatusCode: 200,
  265. Header: map[string][]string{
  266. "Public-Key-Pins": []string{`max-age=3000; pin-sha256="d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM="; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="`},
  267. },
  268. TLS: &tls.ConnectionState{},
  269. },
  270. expected: nil,
  271. },
  272. {
  273. name: "hpkp header (4)",
  274. response: &http.Response{
  275. StatusCode: 200,
  276. Header: map[string][]string{
  277. "Public-Key-Pins-Report-Only": []string{`max-age=2592000; pin-sha256="E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g="; pin-sha256="LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="; report-uri="http://example.com/pkp-report"`},
  278. },
  279. TLS: &tls.ConnectionState{},
  280. },
  281. expected: &Header{
  282. MaxAge: 2592000,
  283. IncludeSubDomains: false,
  284. Permanent: false,
  285. Sha256Pins: []string{
  286. "E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=",
  287. "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ=",
  288. },
  289. ReportURI: "http://example.com/pkp-report",
  290. },
  291. },
  292. }
  293. for _, test := range tests {
  294. out := ParseReportOnlyHeader(test.response)
  295. if !equalHeaders(out, test.expected) {
  296. t.Logf("want:%v", test.expected)
  297. t.Logf("got:%v", out)
  298. t.Fatalf("test case failed: %s", test.name)
  299. }
  300. }
  301. }
  302. func equalHeaders(a, b *Header) bool {
  303. if a == nil && b == nil {
  304. return true
  305. }
  306. if a == nil || b == nil {
  307. return false
  308. }
  309. if a.IncludeSubDomains != b.IncludeSubDomains {
  310. return false
  311. }
  312. if a.MaxAge != b.MaxAge {
  313. return false
  314. }
  315. if a.Permanent != b.Permanent {
  316. return false
  317. }
  318. if a.ReportURI != b.ReportURI {
  319. return false
  320. }
  321. if len(a.Sha256Pins) != len(b.Sha256Pins) {
  322. return false
  323. }
  324. for i := range a.Sha256Pins {
  325. if a.Sha256Pins[i] != b.Sha256Pins[i] {
  326. return false
  327. }
  328. }
  329. return true
  330. }