161 lines
5.7 KiB
Go
161 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"crypto/x509/pkix"
|
|
"encoding/json"
|
|
"math/big"
|
|
"net"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/health"
|
|
)
|
|
|
|
// selfSigned is a self-signed certificate over the key.
|
|
func selfSigned(t *testing.T, key any, pub any) tls.Certificate {
|
|
t.Helper()
|
|
tpl := &x509.Certificate{SerialNumber: big.NewInt(time.Now().UnixNano()), Subject: pkix.Name{CommonName: "printer.test"},
|
|
NotBefore: time.Now().Add(-time.Hour), NotAfter: time.Now().Add(24 * time.Hour), DNSNames: []string{"printer.test"}}
|
|
der, err := x509.CreateCertificate(rand.Reader, tpl, tpl, pub, key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key}
|
|
}
|
|
|
|
// twoCertPrinter is a TLS server with an ECDSA and an RSA certificate, as the printer has; it answers its address
|
|
// and the two fingerprints.
|
|
func twoCertPrinter(t *testing.T) (addr, ecdsaFP, rsaFP string) {
|
|
t.Helper()
|
|
ek, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
|
rk, _ := rsa.GenerateKey(rand.Reader, 2048)
|
|
ec, rc := selfSigned(t, ek, &ek.PublicKey), selfSigned(t, rk, &rk.PublicKey)
|
|
ln, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{Certificates: []tls.Certificate{ec, rc}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { ln.Close() })
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
go func() { c.(*tls.Conn).Handshake(); c.Close() }()
|
|
}
|
|
}()
|
|
return ln.Addr().String(), Fingerprint(ec.Certificate[0]), Fingerprint(rc.Certificate[0])
|
|
}
|
|
|
|
// 1510: both certificates pinned is healthy; a changed one is unhealthy and names the new fingerprint; none
|
|
// pinned names both.
|
|
func TestTheHealthComparesBothServedCertificatesWithThePins(t *testing.T) {
|
|
addr, e, r := twoCertPrinter(t)
|
|
ctx := context.Background()
|
|
if h := CheckCertificates(ctx, addr, e+", "+r); !h.Healthy || len(h.Served) != 2 || !h.Served[0].Pinned || !h.Served[1].Pinned {
|
|
t.Errorf("both pinned: %+v", h)
|
|
}
|
|
old := strings.Repeat("AB:", 31) + "AB"
|
|
h := CheckCertificates(ctx, addr, e+", "+old) // the RSA certificate changed
|
|
if h.Healthy || !strings.Contains(h.Why, r) || strings.Contains(h.Why, "ecdsa certificate is now") {
|
|
t.Errorf("rsa changed: %+v", h)
|
|
}
|
|
h = CheckCertificates(ctx, addr, old+", "+r) // the ECDSA certificate changed
|
|
if h.Healthy || !strings.Contains(h.Why, e) || strings.Contains(h.Why, "rsa certificate is now") {
|
|
t.Errorf("ecdsa changed: %+v", h)
|
|
}
|
|
h = CheckCertificates(ctx, addr, old)
|
|
if h.Healthy || !strings.Contains(h.Why, e) || !strings.Contains(h.Why, r) || !strings.Contains(h.Why, old) {
|
|
t.Errorf("none pinned: %+v", h)
|
|
}
|
|
}
|
|
|
|
// 1510: a printer that does not answer is not an unhealthy module: nothing can be compared, and the answer says so.
|
|
func TestAPrinterThatDoesNotAnswerIsNotUnhealthy(t *testing.T) {
|
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
|
addr := ln.Addr().String()
|
|
ln.Close()
|
|
h := CheckCertificates(context.Background(), addr, strings.Repeat("AB:", 31)+"AB")
|
|
if !h.Healthy || !strings.Contains(h.Why, "none was compared") {
|
|
t.Errorf("%+v", h)
|
|
}
|
|
}
|
|
|
|
// 1510: brother_health is a tool of the module, read from the settings at each call.
|
|
func TestBrotherHealthIsAToolThatReadsTheSettings(t *testing.T) {
|
|
addr, e, r := twoCertPrinter(t)
|
|
js := `{"printer":"` + addr + `","printer-certificate":"` + e + `, ` + r + `"}`
|
|
run := tool(t, newRig(t, js, "pw").tl, "brother_health").Run
|
|
got, err := run(map[string]any{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if h, ok := got.(CertHealth); !ok || !h.Healthy {
|
|
t.Errorf("answer = %+v", got)
|
|
}
|
|
bad := `{"printer":"` + addr + `","printer-certificate":"` + strings.Repeat("AB:", 31) + `AB"}`
|
|
got, _ = tool(t, newRig(t, bad, "pw").tl, "brother_health").Run(map[string]any{})
|
|
if h := got.(CertHealth); h.Healthy {
|
|
t.Errorf("a wrong pin is healthy: %+v", h)
|
|
}
|
|
}
|
|
|
|
// 1510: with the printer not given the answer is waits, read by the SDK as the engine reads it: state waits, each
|
|
// wait naming exactly one setting, a part and what is wanted; not an error and not unhealthy.
|
|
func TestHealthWaitsForTheSettingsTheOperatorHasYetToGive(t *testing.T) {
|
|
for js, want := range map[string][]string{`{}`: {"printer", "printer-certificate"}, `{"printer":"192.0.2.10"}`: {"printer-certificate"}} {
|
|
got, err := tool(t, newRig(t, js, "pw").tl, "brother_health").Run(map[string]any{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, _ := json.Marshal(got)
|
|
a, err := health.Read(raw)
|
|
if err != nil || a.State != health.StateWaits || len(a.Waits) != len(want) {
|
|
t.Fatalf("%s: read as %+v, %v", js, a, err)
|
|
}
|
|
for i, w := range a.Waits {
|
|
if w.Setting != want[i] || w.Check() != nil {
|
|
t.Errorf("%s: wait %d = %+v (%v)", js, i, w, w.Check())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 1510: each handshake is bounded by 3 s and the whole check by 7 s, and a printer that accepts and never answers
|
|
// ends the check at the bound with the module healthy (nothing compared).
|
|
func TestTheCheckIsBoundedAndAHungPrinterEndsItAtTheBound(t *testing.T) {
|
|
if certDial != 3*time.Second || certTotal != 7*time.Second {
|
|
t.Fatalf("bounds = %s and %s, want 3 s and 7 s", certDial, certTotal)
|
|
}
|
|
ln, _ := net.Listen("tcp", "127.0.0.1:0")
|
|
defer ln.Close()
|
|
go func() {
|
|
for {
|
|
c, err := ln.Accept()
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer c.Close() // held open, never answered
|
|
}
|
|
}()
|
|
oldD, oldT := certDial, certTotal
|
|
certDial, certTotal = 200*time.Millisecond, 500*time.Millisecond
|
|
t.Cleanup(func() { certDial, certTotal = oldD, oldT })
|
|
start := time.Now()
|
|
h := CheckCertificates(context.Background(), ln.Addr().String(), strings.Repeat("AB:", 31)+"AB")
|
|
if took := time.Since(start); took > 700*time.Millisecond {
|
|
t.Errorf("the check took %s", took)
|
|
}
|
|
if !h.Healthy || !strings.Contains(h.Why, "none was compared") {
|
|
t.Errorf("%+v", h)
|
|
}
|
|
}
|