Documentation
¶
Overview ¶
Package krb5 accepts Kerberos tickets.
It is the ACCEPTOR half of Kerberos, and only that half: it holds a keytab, verifies the AP-REQ a client presents, and then signs and verifies messages under the session key that exchange established. It never talks to a KDC and never issues a ticket. Whose tickets it accepts is therefore somebody else's decision — MIT, Heimdal, Active Directory and FreeIPA all produce tickets this package verifies, because verification only needs the service key, which the keytab already holds.
Why this exists ¶
github.com/jcmturner/gokrb5 is a CLIENT library. It verifies an AP-REQ (service.VerifyAPREQ) but it cannot build the AP-REP that answers one, and says so itself:
case TOK_ID_KRB_AP_REP:
return []byte{}, errors.New("marshal of AP_REP GSSAPI MechToken not supported by gokrb5")
Every GSS-API client asks for mutual authentication — MIT's own gss-client sets GSS_C_MUTUAL_FLAG with no option to turn it off — so a service that cannot produce an AP-REP cannot complete a single context. Building it is most of what this package adds.
What it does not do ¶
Anything a KDC does: no AS-REQ, no TGS-REQ, no principal database.
Context.Unwrap still refuses a SEALED token, with a distinct error. A caller reading an integrity-only token must not be handed ciphertext that reads like a message; confidentiality goes through Context.Seal and Context.Unseal, which together are sec=krb5p.
It also asserts no acceptor subkey. The context key is the subkey the initiator put in its authenticator, or the ticket's session key when it sent none, which is what RFC 4121 §4.3 allows and what keeps the key usage numbers on both sides unambiguous.
Index ¶
- Variables
- type Acceptor
- type Context
- func (c *Context) Expires() time.Time
- func (c *Context) MIC(msg []byte) ([]byte, error)
- func (c *Context) PeerSequence() uint64
- func (c *Context) Principal() string
- func (c *Context) Realm() string
- func (c *Context) Seal(msg []byte) ([]byte, error)
- func (c *Context) Unseal(token []byte) ([]byte, error)
- func (c *Context) Unwrap(token []byte) ([]byte, error)
- func (c *Context) User() string
- func (c *Context) VerifyMIC(msg, token []byte) error
- func (c *Context) Wrap(msg []byte) ([]byte, error)
- type Option
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotAToken reports bytes that are not a GSS-API krb5 token at all. ErrNotAToken = errors.New("krb5: not a GSS-API krb5 token") // ErrNotAPReq reports a well-formed token that is not an AP-REQ — a // client sending its own AP-REP, or a KRB-ERROR. ErrNotAPReq = errors.New("krb5: token is not an AP-REQ") // ErrRejected reports a ticket this service will not accept: wrong // service, wrong key, expired, replayed, or a clock too far out. ErrRejected = errors.New("krb5: ticket rejected") )
Errors an acceptor returns. They are deliberately coarse: a client learns that its ticket was refused, never which of the many checks refused it.
var ErrBadMIC = errors.New("krb5: bad message signature")
ErrBadMIC reports a message whose signature does not match. It is the only answer a verifier gives: which of the length, the header, the sequence or the checksum disagreed is not a client's business.
var ErrNoKeytab = errors.New("krb5: keytab")
ErrNoKeytab reports a keytab that could not be read.
var ( // ErrNotSealed reports a wrap token without confidentiality handed to a // function that requires it. ErrNotSealed = errors.New("krb5: wrap token is not sealed") )
var ErrSealed = errors.New("krb5: wrap token is sealed (sec=krb5p): read it with Unseal, not Unwrap")
ErrSealed reports that Context.Unwrap was given a wrap token carrying confidentiality: its payload is ciphertext, which Context.Unseal reads. It is a DISTINCT error from ErrBadMIC on purpose: a sealed token is well-formed and correctly signed, and returning it as a bad signature would send an operator looking at keys and clocks for the wrong call.
Functions ¶
This section is empty.
Types ¶
type Acceptor ¶
type Acceptor struct {
// contains filtered or unexported fields
}
Acceptor verifies tickets presented for one service.
It is safe for concurrent use: the keytab is read-only after loading, and each accepted ticket produces its own Context.
func Load ¶
Load reads a keytab from disk and returns an acceptor for it.
A keytab is a secret at rest. Its path may come from a configuration file or from KRB5_KTNAME; the KEY inside it must never be logged, printed, or carried in an environment variable.
func (*Acceptor) Accept ¶
Accept verifies one AP-REQ and returns the context it establishes.
The second result is the token to send back to the client. It is non-empty whenever the client asked for mutual authentication, which in practice is always; sending it is not optional, because the client will not consider the context open until it has verified the AP-REP.
An error means no context: the returned token is then empty, and the caller should refuse the operation rather than fall back to something weaker.
type Context ¶
type Context struct {
// contains filtered or unexported fields
}
Context is one authenticated conversation with one client.
It is created by Acceptor.Accept and holds the key that exchange established. Everything it signs or verifies is bound to that key, so a context is exactly as long-lived as the ticket behind it — see Context.Expires.
A Context is NOT safe for concurrent use.
func (*Context) Expires ¶
Expires is when the ticket behind this context stops being valid. A server holding long-lived connections should stop honouring a context past it.
func (*Context) MIC ¶
MIC signs msg as the acceptor and returns the token to send back.
The key usage differs from the one Context.VerifyMIC checks — 23 here, 25 there. That asymmetry is what stops a token this server produced from being replayed back at it as though the client had signed it.
func (*Context) PeerSequence ¶
PeerSequence is the sequence number the client started at, from its authenticator. Protocols that carry their own sequence — RPCSEC_GSS does — check messages against it.
func (*Context) Principal ¶
Principal is who the client proved itself to be, in full Kerberos spelling ("alice@EXAMPLE.ORG").
This is an authenticated answer, which is the whole point of the exchange: unlike an AUTH_UNIX uid or an SMB username, the client could not have produced it without holding the key.
func (*Context) Seal ¶ added in v0.2.0
Seal produces a wrap token carrying confidentiality, as the acceptor.
EC and RRC are zero, which is what MIT emits and what every implementation accepts; a non-zero rotation buys nothing here and would only be code no test reaches.
func (*Context) Unseal ¶ added in v0.2.0
Unseal opens a wrap token that carries confidentiality.
It refuses an unsealed one with ErrNotSealed rather than returning its payload: a caller that asked to unseal is a caller whose protocol promised confidentiality, and handing back something that crossed the wire in the clear would keep that promise in name only.
func (*Context) User ¶
User is the principal without its realm, and Realm is the realm alone.
Mapping a principal onto a local account is the CALLER's decision and a policy question, not a string operation: alice@EXAMPLE.ORG and alice@PARTNER.ORG are different people who both answer to "alice".
type Option ¶
type Option func(*Acceptor)
Option configures an Acceptor.
func ClockSkew ¶
ClockSkew is how far the client's clock may be from this one, in seconds. Zero means gokrb5's default of five minutes, which is also MIT's.
func Principal ¶
Principal restricts the acceptor to one service principal, named without its realm ("nfs/host.example.org").
Without it, any principal in the keytab may be addressed. A keytab that holds exactly one service does not need this; one that holds several, and wants a port to answer for only one of them, does.