// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: MIT // The RPCSEC_GSSv3 structures of RFC 7861: the version three credential, // the CREATE and LIST control procedures and the assertion payloads. package rpc import ( "errors" "sourcedock.dev/petrbalvin/nfs/internal/xdr" ) // Control procedure numbers of the rpc_gss_proc_t enumeration, RFC 7861 // section 5.1. const ( GSSProcCreate = 5 GSSProcList = 6 ) // The credential version that carries the new control procedures. const GSSVersion3 = 3 // Assertion types of the rgss3_assertion_type enumeration. const ( AssertionLabel = 0 AssertionPrivs = 1 ) // ErrGSSv3 marks a malformed version three control message. var ErrGSSv3 = errors.New("rpc: malformed rpcsec gssv3 message") // A GSSv3Cred is the version three credential: the version field rides // in front of the version one shape, RFC 7861 section 5.1. type GSSv3Cred struct { Proc uint32 Seq uint32 Service uint32 Handle []byte } // AppendGSSv3Cred encodes the version three credential body. func AppendGSSv3Cred(b []byte, proc, seq, service uint32, handle []byte) []byte { b = xdr.AppendUint32(b, GSSVersion3) b = xdr.AppendUint32(b, proc) b = xdr.AppendUint32(b, seq) b = xdr.AppendUint32(b, service) return xdr.AppendVarOpaque(b, handle) } // DecodeGSSv3Cred decodes the version three credential body: the // leading version field is checked and skipped before the credential // proper. func DecodeGSSv3Cred(body []byte) (GSSv3Cred, error) { d := xdr.NewDecoder(body) var c GSSv3Cred var err error if c.Proc, err = d.Uint32(); err != nil { return c, err } if c.Proc != GSSVersion3 { return c, ErrGSSv3 } if c.Proc, err = d.Uint32(); err != nil { return c, err } if c.Seq, err = d.Uint32(); err != nil { return c, err } if c.Service, err = d.Uint32(); err != nil { return c, err } c.Handle, err = d.VarOpaque() return c, err } // A Label is the rgss3_label assertion: the label format specifier and // the opaque label payload. type Label struct { LfsId uint32 PiId uint32 Bytes []byte } // Privs is the rgss3_privs structured privilege: who grants what. type Privs struct { Who string Grant string Bytes []byte } // An Assertion is one rgss3_assertion_u union member. type Assertion struct { Type uint32 Label Label Privs Privs Ext []byte } // appendLabel and appendPrivs encode the assertion payloads. func appendLabel(b []byte, l Label) []byte { b = xdr.AppendUint32(b, l.LfsId) b = xdr.AppendUint32(b, l.PiId) return xdr.AppendVarOpaque(b, l.Bytes) } func appendPrivs(b []byte, p Privs) []byte { b = xdr.AppendString(b, p.Who) b = xdr.AppendString(b, p.Grant) return xdr.AppendVarOpaque(b, p.Bytes) } // AppendAssertion encodes one rgss3_assertion_u union. func AppendAssertion(b []byte, a Assertion) []byte { b = xdr.AppendUint32(b, a.Type) switch a.Type { case AssertionLabel: return appendLabel(b, a.Label) case AssertionPrivs: return appendPrivs(b, a.Privs) default: return xdr.AppendVarOpaque(b, a.Ext) } } // DecodeAssertion decodes one rgss3_assertion_u union. func DecodeAssertion(d *xdr.Decoder) (Assertion, error) { var a Assertion var err error if a.Type, err = d.Uint32(); err != nil { return a, err } switch a.Type { case AssertionLabel: if a.Label.LfsId, err = d.Uint32(); err != nil { return a, err } if a.Label.PiId, err = d.Uint32(); err != nil { return a, err } a.Label.Bytes, err = d.VarOpaque() return a, err case AssertionPrivs: if a.Privs.Who, err = d.String(); err != nil { return a, err } if a.Privs.Grant, err = d.String(); err != nil { return a, err } a.Privs.Bytes, err = d.VarOpaque() return a, err default: a.Ext, err = d.VarOpaque() return a, err } } // A MpAuth is the rgss3_gss_mp_auth multi-principal authentication // payload: the inner context handle and a MIC of the RPC header made // under the inner context. type MpAuth struct { InnerHandle []byte HeaderMic []byte } // appendOptional encodes an XDR optional: the presence flag and the // payload. func appendOptional(b []byte, present bool, enc func([]byte) []byte) []byte { b = xdr.AppendBool(b, present) if present { return enc(b) } return b } // AppendCreateArgs encodes the rgss3_create_args call data. func AppendCreateArgs(b []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion) []byte { b = appendOptional(b, mpAuth != nil, func(x []byte) []byte { x = xdr.AppendVarOpaque(x, mpAuth.InnerHandle) return xdr.AppendVarOpaque(x, mpAuth.HeaderMic) }) b = appendOptional(b, chanBinding != nil, func(x []byte) []byte { return xdr.AppendVarOpaque(x, chanBinding) }) b = xdr.AppendUint32(b, uint32(len(assertions))) for _, a := range assertions { b = AppendAssertion(b, a) } return b } // DecodeCreateArgs decodes the rgss3_create_args call data. func DecodeCreateArgs(payload []byte) (mpAuth *MpAuth, chanBinding []byte, assertions []Assertion, err error) { d := xdr.NewDecoder(payload) var present bool if present, err = d.Bool(); err != nil { return } if present { mpAuth = &MpAuth{} if mpAuth.InnerHandle, err = d.VarOpaque(); err != nil { return } if mpAuth.HeaderMic, err = d.VarOpaque(); err != nil { return } } if present, err = d.Bool(); err != nil { return } if present { if chanBinding, err = d.VarOpaque(); err != nil { return } } var n uint32 if n, err = d.Uint32(); err != nil { return } for i := uint32(0); i < n; i++ { var a Assertion if a, err = DecodeAssertion(d); err != nil { return } assertions = append(assertions, a) } return } // AppendCreateRes encodes the rgss3_create_res reply: the child handle, // the mirrored optional fields and the granted assertions in order. func AppendCreateRes(b []byte, handle []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion) []byte { b = xdr.AppendVarOpaque(b, handle) b = appendOptional(b, mpAuth != nil, func(x []byte) []byte { x = xdr.AppendVarOpaque(x, mpAuth.InnerHandle) return xdr.AppendVarOpaque(x, mpAuth.HeaderMic) }) b = appendOptional(b, chanBinding != nil, func(x []byte) []byte { return xdr.AppendVarOpaque(x, chanBinding) }) b = xdr.AppendUint32(b, uint32(len(assertions))) for _, a := range assertions { b = AppendAssertion(b, a) } return b } // DecodeCreateRes decodes the rgss3_create_res reply. func DecodeCreateRes(payload []byte) (handle []byte, mpAuth *MpAuth, chanBinding []byte, assertions []Assertion, err error) { d := xdr.NewDecoder(payload) if handle, err = d.VarOpaque(); err != nil { return } var present bool if present, err = d.Bool(); err != nil { return } if present { mpAuth = &MpAuth{} if mpAuth.InnerHandle, err = d.VarOpaque(); err != nil { return } if mpAuth.HeaderMic, err = d.VarOpaque(); err != nil { return } } if present, err = d.Bool(); err != nil { return } if present { if chanBinding, err = d.VarOpaque(); err != nil { return } } var n uint32 if n, err = d.Uint32(); err != nil { return } for i := uint32(0); i < n; i++ { var a Assertion if a, err = DecodeAssertion(d); err != nil { return } assertions = append(assertions, a) } return } // AppendListRes encodes the RPCSEC_GSS_LIST reply: the supported // assertion types, RFC 7861 section 5.3. func AppendListRes(b []byte, types []uint32) []byte { b = xdr.AppendUint32(b, uint32(len(types))) for _, t := range types { b = xdr.AppendUint32(b, t) } return b }