Help us shape the future of Go! Take the 2026 Go Developer Survey

kdc

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: BSD-3-Clause Imports: 23 Imported by: 1

README

kdc

Go Reference License CI

Issue Kerberos tickets from a go-authn/directory, in Go, with no cgo.

srv, err := kdc.New(kdc.Config{
    Realm:    "EXAMPLE.ORG",
    People:   people,          // sqldir, hcldir — anything holding passwords
    Services: keytab,          // krbtgt/REALM, and a key per service
    Logf:     log.Printf,      // why each request was refused; nil says nothing
})
go srv.ServeUDP(pc)
go srv.ServeTCP(ln)

This is the issuing half. go-authn/krb5 is the accepting half, and a service that only verifies tickets needs this package not at all.

⛔ What can and cannot back a realm

A KDC must decrypt the client's pre-authentication with that person's long-term key. It follows that only a source holding the password can back one: sqldir and hcldir can, and a Verifier — a bind against somebody else's LDAP, or a hash comparison — cannot, however well it answers "is this the right password".

That is a property of Kerberos rather than a limitation here. New cannot see it — a directory answers person by person — so the refusal comes at the request: a person whose source holds no password is answered KDC_ERR_NULL_KEY (MIT's kinit says null key), never the code a wrong password gets, so it reads as a configuration to fix rather than a password to retype. What New does refuse up front is a realm with no name, no directory, no keytab, or a keytab without krbtgt/REALM.

What it implements

AS-REQ and TGS-REQ, over UDP and TCP, with encrypted-timestamp pre-authentication required.

Not implemented: cross-realm referrals, PKINIT, FAST, renewable or postdated tickets, and any kadmin protocol. Keys come from the directory and from a keytab, and they change where they live.

How long a ticket lives

Config.Lifetime caps a ticket, ten hours when zero (MIT's default), and a client asking for less gets less. Config.MaxSkew is how far a client's clock may be from this one, five minutes when zero.

Since nothing revokes a Kerberos ticket, its end is the whole of how a password change or a removed person comes to matter, so the TGS holds a TGT to it:

  • a TGT past its end time is refused with KRB_AP_ERR_TKT_EXPIRED, and one before its start time, or flagged invalid, with KRB_AP_ERR_TKT_NYV. Those times were written by this KDC's clock, so no skew is allowed them;
  • a ticket bought with a TGT ends no later than that TGT, so asking for krbtgt again cannot stretch a credential one lifetime at a time.

An AS-REQ without pre-authentication is answered without deriving the person's key: PBKDF2 runs only once there is a timestamp to check, so a spoofed UDP packet naming a real principal costs the realm no key derivation.

One encryption type

Tickets and keys are made with aes256-cts-hmac-sha1-96 (etype 18), and nothing else is issued. A request whose etype list does not include 18 is answered KDC_ERR_ETYPE_NOSUPP (RFC 4120 §3.1.3), before anybody is looked up, rather than handed a reply it could not decrypt. It is what every Kerberos implementation in use agrees on, so this costs no interoperability that a modern client would notice.

The realm says so rather than leaving it to be discovered. The KRB-ERROR that demands pre-authentication carries an ETYPE-INFO2 hint naming that etype and the salt (the realm followed by the name, which is what the key is derived with) — which is where a client looks before it derives a key, and why the exchange starts with a refusal rather than a reply. Before v0.3.0 the salt was left out: clients fell back to the default salt, which happens to be this one, so nothing failed and nothing showed it.

A client that lists 18 but encrypts its timestamp under something else fails pre-authentication and is answered KDC_ERR_PREAUTH_FAILED — the same code a wrong password gets, because an unauthenticated caller is told no more than no. With Logf set, the log records both etypes, its own and the client's (and, for ETYPE_NOSUPP, the list offered), so an operator can tell a mismatched cipher from a mistyped password.

The judge

MIT's own kinit and kvno. No MIT KDC is involved — the keytab is built in Go and only the client is borrowed, which is the point: a realm judged by a client it did not write.

go test ./...          # KDC_REQUIRE_JUDGE=1 turns a missing MIT into a failure

The tests also pin the refusals a real client never triggers: a timestamp under the wrong key, one from an hour ago, a three-byte cipher, a service asked for straight from an AS-REQ, and a realm backed by a verifier.

Licence

BSD-3-Clause.

Documentation

Overview

Package kdc issues Kerberos tickets from a go-authn/directory.

It is the other half of github.com/go-authn/krb5, which accepts them. A service that verifies tickets needs no KDC and should not import this; a site that wants its own realm, backed by the same people its LDAP and its NFS already know, does.

What it can and cannot be backed by

A KDC must DECRYPT the client's pre-authentication with that person's long-term key. It follows that only a directory source holding the PASSWORD can back one: sqldir and hcldir can, and a directory.Verifier — a bind against somebody else's LDAP, or a hash comparison — CANNOT, however well it answers "is this the right password".

That is a property of Kerberos rather than a limitation here, and this package refuses at configuration time rather than at the first kinit.

What it implements

AS-REQ and TGS-REQ over UDP and TCP, with encrypted-timestamp pre-authentication required. It does not implement cross-realm referrals, PKINIT, FAST, renewable or postdated tickets, or a kadmin protocol: keys come from the directory and from a keytab, and change where they live.

One encryption type

Tickets and keys are made with aes256-cts-hmac-sha1-96 (etype 18) and nothing else is issued: a request whose etype list lacks 18 is answered KDC_ERR_ETYPE_NOSUPP (RFC 4120 §3.1.3). The KRB-ERROR that demands pre-authentication carries an ETYPE-INFO2 hint naming that etype and the salt, so a client learns what to derive its key with rather than guessing. A client that encrypts its timestamp under another type is answered KDC_ERR_PREAUTH_FAILED, the same code a wrong password gets; with Config.Logf set, the log records both etypes so an operator can tell the two apart.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNoRealm reports a configuration with no realm name.
	ErrNoRealm = errors.New("kdc: no realm")
	// ErrNoPeople reports one with nowhere to look up users.
	ErrNoPeople = errors.New("kdc: no directory")
	// ErrNoServices reports one with no keytab, which means no krbtgt key
	// and therefore nothing to sign a TGT with.
	ErrNoServices = errors.New("kdc: no keytab")
	// ErrNoTGTKey reports a keytab without krbtgt/REALM@REALM. A KDC without
	// it can authenticate somebody and then have nothing to hand them.
	ErrNoTGTKey = errors.New("kdc: the keytab has no krbtgt key for this realm")
)

Errors a configuration can have.

View Source
var ErrClosed = errors.New("kdc: server closed")

ErrClosed is returned by ServeUDP and ServeTCP after Close.

View Source
var ErrNotAPerson = errors.New("kdc: not a user principal")

ErrNotAPerson reports a principal that is not a single name — a service like nfs/host asking for a TGT of its own. Services authenticate from a keytab, not from this directory.

Functions

This section is empty.

Types

type Config

type Config struct {
	// Realm is the realm name, conventionally the DNS domain in capitals.
	Realm string

	// People is where users come from. It must publish passwords; see the
	// package comment for why a verifier cannot serve Kerberos.
	People directory.Source

	// Services holds the keys for service principals, including the
	// krbtgt/REALM@REALM this KDC issues its own tickets under. It is the
	// same file a service like an NFS server reads, which is the point: one
	// keytab, two readers, no third place for a key to drift.
	Services *keytab.Keytab

	// Lifetime caps a ticket. Zero means ten hours, which is MIT's default.
	Lifetime time.Duration

	// MaxSkew is how far a client's clock may be from this one. Zero means
	// five minutes, which is what every Kerberos implementation assumes.
	MaxSkew time.Duration

	// Logf, when set, is told why a request was refused.
	//
	// It exists because the protocol cannot say: a client is given an error
	// CODE, and "pre-authentication failed" covers a wrong password, a wrong
	// salt, a clock an hour out and a malformed message. Only this side can
	// tell them apart, and only to an operator.
	//
	// ⛔ It is never given a password, a key, or a decrypted anything.
	Logf func(format string, args ...any)
}

Config describes one realm.

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server answers Kerberos requests for one realm.

The zero value is not usable; use New.

func New

func New(cfg Config) (*Server, error)

New checks a configuration and returns a server for it.

It refuses a realm it could not serve -- no realm, no directory, no keytab, a keytab without the krbtgt key -- rather than starting and failing at the first kinit, where the error reaches a person who cannot fix it. A person whose source holds no password (a verifier) is refused per request, with KDC_ERR_NULL_KEY: whether a source holds passwords is known only per person.

func (*Server) Close

func (s *Server) Close() error

Close stops the server and drops what it is serving.

func (*Server) ServeTCP

func (s *Server) ServeTCP(ln net.Listener) error

ServeTCP answers connections. Each carries a four-byte length in front of every message, which UDP does not (RFC 4120 §7.2.2).

func (*Server) ServeUDP

func (s *Server) ServeUDP(pc net.PacketConn) error

ServeUDP answers datagrams. It is the transport a client tries first: MIT's kinit sends UDP and falls back to TCP only when the reply does not fit or the KDC says so.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL