| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package server
- import (
- "crypto/rand"
- "crypto/rsa"
- "crypto/tls"
- "crypto/x509"
- "crypto/x509/pkix"
- "encoding/pem"
- "math/big"
- "net"
- "os"
- "path/filepath"
- "testing"
- "time"
- )
- // generateTestCert creates a self-signed certificate and key and writes them
- // to PEM files in the given directory. Returns cert and key file paths.
- func generateTestCert(t *testing.T, dir, prefix string) (string, string) {
- t.Helper()
- key, err := rsa.GenerateKey(rand.Reader, 2048)
- if err != nil {
- t.Fatalf("failed to generate RSA key: %v", err)
- }
- serial := big.NewInt(time.Now().UnixNano())
- tmpl := &x509.Certificate{
- SerialNumber: serial,
- Subject: pkix.Name{
- CommonName: prefix + ".example.com",
- },
- NotBefore: time.Now().Add(-1 * time.Hour),
- NotAfter: time.Now().Add(1 * time.Hour),
- KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
- ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
- BasicConstraintsValid: true,
- IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
- }
- certDER, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key)
- if err != nil {
- t.Fatalf("failed to create certificate: %v", err)
- }
- certFile := filepath.Join(dir, prefix+".pem")
- keyFile := filepath.Join(dir, prefix+"-key.pem")
- certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
- keyPEM := pem.EncodeToMemory(&pem.Block{
- Type: "RSA PRIVATE KEY",
- Bytes: x509.MarshalPKCS1PrivateKey(key),
- })
- if err := os.WriteFile(certFile, certPEM, 0600); err != nil {
- t.Fatalf("failed to write cert file: %v", err)
- }
- if err := os.WriteFile(keyFile, keyPEM, 0600); err != nil {
- t.Fatalf("failed to write key file: %v", err)
- }
- return certFile, keyFile
- }
- // certLoaderSerial extracts the serial number of the first certificate
- // returned by the loader's getCertificate callback.
- func certLoaderSerial(cl *certificateLoader) *big.Int {
- cert, err := cl.getCertificate(nil)
- if err != nil || cert == nil {
- return nil
- }
- x509Cert, err := x509.ParseCertificate(cert.Certificate[0])
- if err != nil {
- return nil
- }
- return x509Cert.SerialNumber
- }
- // TestCertificateLoaderInitialLoad verifies that a new certificateLoader
- // loads the certificate successfully and serves it via getCertificate.
- func TestCertificateLoaderInitialLoad(t *testing.T) {
- dir := t.TempDir()
- certFile, keyFile := generateTestCert(t, dir, "initial")
- cl, err := newCertificateLoader(certFile, keyFile)
- if err != nil {
- t.Fatalf("newCertificateLoader failed: %v", err)
- }
- cert, err := cl.getCertificate(nil)
- if err != nil {
- t.Fatalf("getCertificate failed: %v", err)
- }
- if cert == nil {
- t.Fatal("getCertificate returned nil certificate")
- }
- if len(cert.Certificate) == 0 {
- t.Fatal("certificate chain is empty")
- }
- if certLoaderSerial(cl) == nil {
- t.Fatal("unable to parse certificate serial")
- }
- }
- // TestCertificateLoaderReload verifies that Reload picks up a new certificate
- // written to disk.
- func TestCertificateLoaderReload(t *testing.T) {
- dir := t.TempDir()
- certFile, keyFile := generateTestCert(t, dir, "reload")
- cl, err := newCertificateLoader(certFile, keyFile)
- if err != nil {
- t.Fatalf("newCertificateLoader failed: %v", err)
- }
- origSerial := certLoaderSerial(cl)
- if origSerial == nil {
- t.Fatal("unable to parse original certificate serial")
- }
- // Write a new certificate to the same file paths.
- generateTestCert(t, dir, "reload")
- cl.Reload()
- newSerial := certLoaderSerial(cl)
- if newSerial == nil {
- t.Fatal("unable to parse certificate serial after reload")
- }
- if origSerial.Cmp(newSerial) == 0 {
- t.Fatal("certificate serial did not change after reload")
- }
- }
- // TestCertificateLoaderReloadFailureKeepsOldCert verifies that if Reload fails
- // the old certificate is preserved.
- func TestCertificateLoaderReloadFailureKeepsOldCert(t *testing.T) {
- dir := t.TempDir()
- certFile, keyFile := generateTestCert(t, dir, "keep-old")
- cl, err := newCertificateLoader(certFile, keyFile)
- if err != nil {
- t.Fatalf("newCertificateLoader failed: %v", err)
- }
- origSerial := certLoaderSerial(cl)
- if origSerial == nil {
- t.Fatal("unable to parse original certificate serial")
- }
- // Corrupt the key file.
- if err := os.WriteFile(keyFile, []byte("not a valid PEM key"), 0600); err != nil {
- t.Fatalf("failed to write corrupted key file: %v", err)
- }
- cl.Reload()
- curSerial := certLoaderSerial(cl)
- if curSerial == nil {
- t.Fatal("unable to parse certificate serial after failed reload")
- }
- if origSerial.Cmp(curSerial) != 0 {
- t.Fatal("certificate changed after a failed reload")
- }
- }
- // TestCertificateLoaderNilClientHello verifies getCertificate handles a nil
- // *tls.ClientHelloInfo argument.
- func TestCertificateLoaderNilClientHello(t *testing.T) {
- dir := t.TempDir()
- certFile, keyFile := generateTestCert(t, dir, "nil-hello")
- cl, err := newCertificateLoader(certFile, keyFile)
- if err != nil {
- t.Fatalf("newCertificateLoader failed: %v", err)
- }
- cert, err := cl.getCertificate(nil)
- if err != nil {
- t.Fatalf("getCertificate(nil) returned error: %v", err)
- }
- if cert == nil {
- t.Fatal("getCertificate(nil) returned nil")
- }
- }
- // TestCertificateLoaderClientHelloInfo verifies that getCertificate works
- // when called with a real *tls.ClientHelloInfo.
- func TestCertificateLoaderClientHelloInfo(t *testing.T) {
- dir := t.TempDir()
- certFile, keyFile := generateTestCert(t, dir, "sni")
- cl, err := newCertificateLoader(certFile, keyFile)
- if err != nil {
- t.Fatalf("newCertificateLoader failed: %v", err)
- }
- hello := &tls.ClientHelloInfo{
- ServerName: "sni.example.com",
- }
- cert, err := cl.getCertificate(hello)
- if err != nil {
- t.Fatalf("getCertificate with ClientHelloInfo failed: %v", err)
- }
- if cert == nil {
- t.Fatal("getCertificate with ClientHelloInfo returned nil")
- }
- }
|