Test / test (push) Successful in 2m4s
Release / gates (push) Successful in 2m5s
Release / build (amd64, freebsd) (push) Successful in 1m27s
Release / build (amd64, linux) (push) Successful in 1m22s
Release / build (amd64, netbsd) (push) Successful in 1m19s
Release / build (amd64, openbsd) (push) Successful in 1m20s
Release / build (arm64, darwin) (push) Successful in 1m21s
Release / build (arm64, freebsd) (push) Successful in 1m26s
Release / build (arm64, linux) (push) Successful in 1m25s
Release / build (arm64, netbsd) (push) Successful in 1m31s
Release / build (arm64, openbsd) (push) Successful in 1m27s
Release / build (loong64, linux) (push) Successful in 1m37s
Release / build (riscv64, linux) (push) Successful in 1m21s
Release / release (push) Successful in 40s
Assisted-by: GLM 5.3 Flash
107 lines
3.6 KiB
Go
107 lines
3.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package rpc
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"testing"
|
|
|
|
"sourcedock.dev/petrbalvin/nfs/internal/krb5"
|
|
)
|
|
|
|
// The version one credential pins the exact wire order RFC 2203
|
|
// section 5.2.1 fixes: version, procedure, sequence, service, handle.
|
|
func TestGSSCredWireOrder(t *testing.T) {
|
|
data := AppendGSSCred(nil, GSSProcData, 7, SvcIntegrity, []byte("handle-1"))
|
|
want, err := hex.DecodeString("00000001" + "00000000" + "00000007" + "00000002" +
|
|
"00000008" + "68616e646c652d31")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !bytes.Equal(data, want) {
|
|
t.Fatalf("cred bytes %x, want %x", data, want)
|
|
}
|
|
cred, err := DecodeGSSCred(data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if cred.Proc != GSSProcData || cred.Version != GSSVersion1 || cred.Service != SvcIntegrity ||
|
|
string(cred.Handle) != "handle-1" || cred.Seq != 7 {
|
|
t.Fatalf("cred %+v", cred)
|
|
}
|
|
|
|
// The control credentials a conformant peer sends: the same order,
|
|
// an empty handle and the token in the procedure arguments.
|
|
init := AppendGSSCred(nil, GSSProcInit, 0, 0, nil)
|
|
if cred, err = DecodeGSSCred(init); err != nil || cred.Proc != GSSProcInit || len(cred.Handle) != 0 {
|
|
t.Fatalf("init cred %+v %v", cred, err)
|
|
}
|
|
cont := AppendGSSCred(nil, GSSProcContinue, 0, 0, nil)
|
|
if cred, err = DecodeGSSCred(cont); err != nil || cred.Proc != GSSProcContinue {
|
|
t.Fatalf("continue cred %+v %v", cred, err)
|
|
}
|
|
dest := AppendGSSCred(nil, GSSProcDestroy, 8, SvcIntegrity, []byte("handle-1"))
|
|
if cred, err = DecodeGSSCred(dest); err != nil || cred.Proc != GSSProcDestroy || string(cred.Handle) != "handle-1" {
|
|
t.Fatalf("destroy cred %+v %v", cred, err)
|
|
}
|
|
|
|
// A credential that opens with another version or names an unknown
|
|
// procedure is refused.
|
|
bad := append([]byte{}, dest...)
|
|
bad[0] = 3 // a version three body belongs to the GSSv3 decoder
|
|
if _, err = DecodeGSSCred(bad); err == nil {
|
|
t.Fatal("version three accepted by the version one decoder")
|
|
}
|
|
bad[0] = GSSVersion1
|
|
bad[4] = 9
|
|
if _, err = DecodeGSSCred(bad); err == nil {
|
|
t.Fatal("unknown procedure accepted")
|
|
}
|
|
}
|
|
|
|
// A full DATA call: the header with an empty verifier is checksummed,
|
|
// then the call is re-encoded with the MIC as the verifier.
|
|
func TestGSSCredAndVerf(t *testing.T) {
|
|
data := AppendGSSCred(nil, GSSProcData, 7, SvcIntegrity, []byte("handle-1"))
|
|
call := Call{XID: 99, Program: 100003, Version: 4, Procedure: 1,
|
|
Cred: Auth{Flavor: FlavorGSS, Body: data}}
|
|
prefix, err := AppendCall(nil, call)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Two halves of one established context share the session key.
|
|
clientCtx := &krb5.Context{Etype: krb5.EtypeAES256, Key: make([]byte, 32)}
|
|
serverCtx := &krb5.Context{Etype: krb5.EtypeAES256, Key: clientCtx.Key, Accepting: true}
|
|
mic, err := clientCtx.GetMIC(prefix)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
call.Verifier = Auth{Flavor: FlavorGSS, Body: mic}
|
|
full, err := AppendCall(nil, call)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decoded, args, err := DecodeCall(full)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if decoded.Verifier.Flavor != FlavorGSS {
|
|
t.Fatalf("verifier flavor %d", decoded.Verifier.Flavor)
|
|
}
|
|
// The receiver re-derives the prefix by re-encoding with an empty
|
|
// verifier and verifies the MIC over it.
|
|
again, err := AppendCall(nil, Call{XID: decoded.XID, Program: decoded.Program,
|
|
Version: decoded.Version, Procedure: decoded.Procedure, Cred: decoded.Cred})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := serverCtx.VerifyMIC(again, decoded.Verifier.Body); err != nil {
|
|
t.Fatalf("verifier MIC: %v", err)
|
|
}
|
|
if len(args) != 0 {
|
|
t.Fatal("stray arguments after the header")
|
|
}
|
|
}
|