Files
Privatae 8822d5df85
ci / personal-data (push) Successful in 5s
ci / test (push) Successful in 3m51s
ci / fuzz (push) Successful in 2m55s
bastion 0.55.3
2026-10-25 23:16:45 +00:00

127 lines
4.1 KiB
Go

package main
import (
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"net/http"
"net/http/httptest"
"path/filepath"
"regexp"
"strings"
"sync"
"testing"
)
func parseTestCSR(t *testing.T, csrPEM string) *x509.CertificateRequest {
t.Helper()
blk, _ := pem.Decode([]byte(csrPEM))
if blk == nil {
t.Fatalf("not PEM: %q", csrPEM)
}
csr, err := x509.ParseCertificateRequest(blk.Bytes)
if err != nil {
t.Fatal(err)
}
if err := csr.CheckSignature(); err != nil {
t.Fatalf("the CSR must be self-signed by the device key: %v", err)
}
return csr
}
func csrURIs(csr *x509.CertificateRequest) []string {
var out []string
for _, u := range csr.URIs {
out = append(out, u.String())
}
return out
}
// A platform redeem's CSR names the fingerprint once, under the platform's
// name. A redeem with a Host that has no platform names it under the
// platform's name first and the Host's second.
func TestTheOperatorCSRNamesTheFingerprintForItsSigner(t *testing.T) {
for _, c := range []struct {
local bool
want []string
}{
{false, []string{fpSANPrefix + "fp-test"}},
{true, []string{fpSANPrefix + "fp-test", hostFPSANPrefix + "fp-test"}},
} {
_, csrPEM, err := genOperatorCSR("fp-test", c.local)
if err != nil {
t.Fatal(err)
}
if got := csrURIs(parseTestCSR(t, csrPEM)); strings.Join(got, " ") != strings.Join(c.want, " ") {
t.Errorf("local=%v: URIs = %v, want %v", c.local, got, c.want)
}
}
}
// platformSignerRefusal is the platform's rule for the SANs of an operator
// CSR it signs (the bridge's _checked_operator_csr): one name, a URI,
// the device-fingerprint prefix (fpSANPrefix) and a fingerprint of 16 to 128
// base64url characters. Anything else is refused before anything is signed.
var platformFPRe = regexp.MustCompile(`^[A-Za-z0-9_-]{16,128}$`)
func platformSignerRefusal(csr *x509.CertificateRequest) string {
n := len(csr.URIs) + len(csr.DNSNames) + len(csr.EmailAddresses) + len(csr.IPAddresses)
if n == 0 {
return ""
}
if n != 1 || len(csr.URIs) != 1 {
return "operator_csr may name only its device fingerprint (urn:skema:hwfp:<fp>)"
}
s := csr.URIs[0].String()
if !strings.HasPrefix(s, fpSANPrefix) || !platformFPRe.MatchString(strings.TrimPrefix(s, fpSANPrefix)) {
return "operator_csr may name only its device fingerprint (urn:skema:hwfp:<fp>)"
}
return ""
}
// A platform pairing's CSR is one the platform signs. It reached the platform
// with a second fingerprint name in 0.52.1, and the platform refuses any
// second name, so every platform pair, --replace and claim-anchor failed at
// the redeem. The redeem here is served on loopback and refuses as the
// platform does.
func TestAPlatformRedeemSendsACSRThePlatformSigns(t *testing.T) {
uiTestConfigDir(t)
old := markerPathOverride
markerPathOverride = filepath.Join(t.TempDir(), "machine.json")
t.Cleanup(func() { markerPathOverride = old })
var mu sync.Mutex
var got []string
refusal := "no request"
redeem := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var req redeemReq
_ = json.NewDecoder(r.Body).Decode(&req)
mu.Lock()
defer mu.Unlock()
refusal = "the CSR does not parse"
if blk, _ := pem.Decode([]byte(req.OperatorCSR)); blk != nil {
if csr, err := x509.ParseCertificateRequest(blk.Bytes); err == nil {
got, refusal = csrURIs(csr), platformSignerRefusal(csr)
}
}
w.WriteHeader(http.StatusBadRequest)
_ = json.NewEncoder(w).Encode(map[string]string{"error": "the test stops here: " + refusal})
}))
defer redeem.Close()
oldBase := redeemURLBase
redeemURLBase = redeem.URL
t.Cleanup(func() { redeemURLBase = oldBase })
token := "skb_" + base64.RawURLEncoding.EncodeToString([]byte("acme.secret"))
if _, _, err := pairWithToken(token, false, false); err == nil {
t.Fatal("the stand-in redeem answers 400; the pairing must fail")
}
mu.Lock()
defer mu.Unlock()
if refusal != "" {
t.Fatalf("the platform would refuse this CSR: %s (URIs %v)", refusal, got)
}
if len(got) != 1 || !strings.HasPrefix(got[0], fpSANPrefix) {
t.Fatalf("a platform CSR names the fingerprint once, under %s: %v", fpSANPrefix, got)
}
}