tls_certificate_loader_test.go 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package server
  4. import (
  5. "crypto/rand"
  6. "crypto/rsa"
  7. "crypto/tls"
  8. "crypto/x509"
  9. "crypto/x509/pkix"
  10. "encoding/pem"
  11. "math/big"
  12. "net"
  13. "os"
  14. "path/filepath"
  15. "testing"
  16. "time"
  17. )
  18. // generateTestCert creates a self-signed certificate and key and writes them
  19. // to PEM files in the given directory. Returns cert and key file paths.
  20. func generateTestCert(t *testing.T, dir, prefix string) (string, string) {
  21. t.Helper()
  22. key, err := rsa.GenerateKey(rand.Reader, 2048)
  23. if err != nil {
  24. t.Fatalf("failed to generate RSA key: %v", err)
  25. }
  26. serial := big.NewInt(time.Now().UnixNano())
  27. tmpl := &x509.Certificate{
  28. SerialNumber: serial,
  29. Subject: pkix.Name{
  30. CommonName: prefix + ".example.com",
  31. },
  32. NotBefore: time.Now().Add(-1 * time.Hour),
  33. NotAfter: time.Now().Add(1 * time.Hour),
  34. KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
  35. ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
  36. BasicConstraintsValid: true,
  37. IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
  38. }
  39. certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
  40. if err != nil {
  41. t.Fatalf("failed to create certificate: %v", err)
  42. }
  43. certFile := filepath.Join(dir, prefix+".pem")
  44. keyFile := filepath.Join(dir, prefix+"-key.pem")
  45. certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
  46. keyPEM := pem.EncodeToMemory(&pem.Block{
  47. Type: "RSA PRIVATE KEY",
  48. Bytes: x509.MarshalPKCS1PrivateKey(key),
  49. })
  50. if err := os.WriteFile(certFile, certPEM, 0600); err != nil {
  51. t.Fatalf("failed to write cert file: %v", err)
  52. }
  53. if err := os.WriteFile(keyFile, keyPEM, 0600); err != nil {
  54. t.Fatalf("failed to write key file: %v", err)
  55. }
  56. return certFile, keyFile
  57. }
  58. // certLoaderSerial extracts the serial number of the first certificate
  59. // returned by the loader's getCertificate callback.
  60. func certLoaderSerial(cl *certificateLoader) *big.Int {
  61. cert, err := cl.getCertificate(nil)
  62. if err != nil || cert == nil {
  63. return nil
  64. }
  65. x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
  66. if err != nil {
  67. return nil
  68. }
  69. return x509Cert.SerialNumber
  70. }
  71. // TestCertificateLoaderInitialLoad verifies that a new certificateLoader
  72. // loads the certificate successfully and serves it via getCertificate.
  73. func TestCertificateLoaderInitialLoad(t *testing.T) {
  74. dir := t.TempDir()
  75. certFile, keyFile := generateTestCert(t, dir, "initial")
  76. cl, err := newCertificateLoader(certFile, keyFile)
  77. if err != nil {
  78. t.Fatalf("newCertificateLoader failed: %v", err)
  79. }
  80. cert, err := cl.getCertificate(nil)
  81. if err != nil {
  82. t.Fatalf("getCertificate failed: %v", err)
  83. }
  84. if cert == nil {
  85. t.Fatal("getCertificate returned nil certificate")
  86. }
  87. if len(cert.Certificate) == 0 {
  88. t.Fatal("certificate chain is empty")
  89. }
  90. if certLoaderSerial(cl) == nil {
  91. t.Fatal("unable to parse certificate serial")
  92. }
  93. }
  94. // TestCertificateLoaderReload verifies that Reload picks up a new certificate
  95. // written to disk.
  96. func TestCertificateLoaderReload(t *testing.T) {
  97. dir := t.TempDir()
  98. certFile, keyFile := generateTestCert(t, dir, "reload")
  99. cl, err := newCertificateLoader(certFile, keyFile)
  100. if err != nil {
  101. t.Fatalf("newCertificateLoader failed: %v", err)
  102. }
  103. origSerial := certLoaderSerial(cl)
  104. if origSerial == nil {
  105. t.Fatal("unable to parse original certificate serial")
  106. }
  107. // Write a new certificate to the same file paths.
  108. generateTestCert(t, dir, "reload")
  109. cl.Reload()
  110. newSerial := certLoaderSerial(cl)
  111. if newSerial == nil {
  112. t.Fatal("unable to parse certificate serial after reload")
  113. }
  114. if origSerial.Cmp(newSerial) == 0 {
  115. t.Fatal("certificate serial did not change after reload")
  116. }
  117. }
  118. // TestCertificateLoaderReloadFailureKeepsOldCert verifies that if Reload fails
  119. // the old certificate is preserved.
  120. func TestCertificateLoaderReloadFailureKeepsOldCert(t *testing.T) {
  121. dir := t.TempDir()
  122. certFile, keyFile := generateTestCert(t, dir, "keep-old")
  123. cl, err := newCertificateLoader(certFile, keyFile)
  124. if err != nil {
  125. t.Fatalf("newCertificateLoader failed: %v", err)
  126. }
  127. origSerial := certLoaderSerial(cl)
  128. if origSerial == nil {
  129. t.Fatal("unable to parse original certificate serial")
  130. }
  131. // Corrupt the key file.
  132. if err := os.WriteFile(keyFile, []byte("not a valid PEM key"), 0600); err != nil {
  133. t.Fatalf("failed to write corrupted key file: %v", err)
  134. }
  135. cl.Reload()
  136. curSerial := certLoaderSerial(cl)
  137. if curSerial == nil {
  138. t.Fatal("unable to parse certificate serial after failed reload")
  139. }
  140. if origSerial.Cmp(curSerial) != 0 {
  141. t.Fatal("certificate changed after a failed reload")
  142. }
  143. }
  144. // TestCertificateLoaderNilClientHello verifies getCertificate handles a nil
  145. // *tls.ClientHelloInfo argument.
  146. func TestCertificateLoaderNilClientHello(t *testing.T) {
  147. dir := t.TempDir()
  148. certFile, keyFile := generateTestCert(t, dir, "nil-hello")
  149. cl, err := newCertificateLoader(certFile, keyFile)
  150. if err != nil {
  151. t.Fatalf("newCertificateLoader failed: %v", err)
  152. }
  153. cert, err := cl.getCertificate(nil)
  154. if err != nil {
  155. t.Fatalf("getCertificate(nil) returned error: %v", err)
  156. }
  157. if cert == nil {
  158. t.Fatal("getCertificate(nil) returned nil")
  159. }
  160. }
  161. // TestCertificateLoaderClientHelloInfo verifies that getCertificate works
  162. // when called with a real *tls.ClientHelloInfo.
  163. func TestCertificateLoaderClientHelloInfo(t *testing.T) {
  164. dir := t.TempDir()
  165. certFile, keyFile := generateTestCert(t, dir, "sni")
  166. cl, err := newCertificateLoader(certFile, keyFile)
  167. if err != nil {
  168. t.Fatalf("newCertificateLoader failed: %v", err)
  169. }
  170. hello := &tls.ClientHelloInfo{
  171. ServerName: "sni.example.com",
  172. }
  173. cert, err := cl.getCertificate(hello)
  174. if err != nil {
  175. t.Fatalf("getCertificate with ClientHelloInfo failed: %v", err)
  176. }
  177. if cert == nil {
  178. t.Fatal("getCertificate with ClientHelloInfo returned nil")
  179. }
  180. }