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
711 lines
20 KiB
Go
711 lines
20 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
// The Kerberos V5 GSS-API mechanism of RFC 4121 over the raw krb5
|
|
// mechanism of RFC 1964 framing: the context establishment through
|
|
// AP-REQ and AP-REP (RFC 4120), and the per-message MIC and Wrap
|
|
// tokens (RFC 4121 sections 4.2.4 to 4.2.6).
|
|
|
|
package krb5
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// GSS token identifiers and flag bits of RFC 4121.
|
|
const (
|
|
TokenMIC = 0x0404
|
|
TokenWrap = 0x0504
|
|
|
|
FlagSentByAcceptor = 0x01
|
|
FlagSealed = 0x02
|
|
FlagAcceptorSubkey = 0x04
|
|
)
|
|
|
|
// clockSkew is the window the acceptor tolerates between the client
|
|
// clock and its own, five minutes, the customary maximum of RFC 4120
|
|
// section 10.1.
|
|
const clockSkew = 5 * time.Minute
|
|
|
|
// ErrToken marks a malformed or unusable GSS token.
|
|
var ErrToken = errors.New("krb5: bad gss token")
|
|
|
|
// ErrContext marks a mismatch against the established context.
|
|
var ErrContext = errors.New("krb5: context mismatch")
|
|
|
|
// ErrReplay marks an authenticator the acceptor has already seen.
|
|
var ErrReplay = errors.New("krb5: replayed token")
|
|
|
|
// ErrExpired marks a ticket whose endtime has passed.
|
|
var ErrExpired = errors.New("krb5: ticket expired")
|
|
|
|
// ErrSkew marks an authenticator outside the clock skew window.
|
|
var ErrSkew = errors.New("krb5: clock skew too great")
|
|
|
|
// A Context is one established security context: the session key, the
|
|
// etype, the per-peer sequence counters and the acceptor role. The
|
|
// counters and the token operations are guarded by the internal mutex, so
|
|
// one context serves concurrent callers.
|
|
type Context struct {
|
|
Key []byte
|
|
Etype uint32
|
|
Accepting bool
|
|
SendSeq uint64
|
|
RecvSeq uint64
|
|
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// appTag wraps a payload in the DER application tag of RFC 4120.
|
|
func appTag(number byte, payload []byte) []byte {
|
|
return derTLV(0x60|number, payload)
|
|
}
|
|
|
|
// principalName encodes a PrincipalName of name type one, the name of
|
|
// a single component service.
|
|
func principalName(name string) []byte {
|
|
names := derTLV(tagSequence, derGeneralString(name))
|
|
return derTLV(tagSequence, append(derInt(1), names...))
|
|
}
|
|
|
|
// encryptedData encodes an EncryptedData with a version number.
|
|
func encryptedData(etype uint32, cipher []byte, kvno uint64) []byte {
|
|
body := append(derInt(uint64(etype)), derInt(kvno)...)
|
|
body = append(body, derTLV(tagOctet, cipher)...)
|
|
return derTLV(tagSequence, body)
|
|
}
|
|
|
|
// checksum encodes a Checksum of type 0x8003, the GSS-API checksum.
|
|
func gssChecksum(sum []byte) []byte {
|
|
return derTLV(tagSequence, append(derInt(0x8003), derTLV(tagOctet, sum)...))
|
|
}
|
|
|
|
// bitFlags encodes the BIT STRING form of the Kerberos flags.
|
|
func bitFlags(flags uint32) []byte {
|
|
var bits [6]byte
|
|
binary.BigEndian.PutUint32(bits[2:], flags)
|
|
bits[0] = 5 // construction
|
|
bits[1] = 0 // unused bit count; the leading octets of a 32 bit field
|
|
bits[2] = 0
|
|
bits[3] = byte(flags >> 24)
|
|
bits[4] = byte(flags >> 16)
|
|
bits[5] = byte(flags >> 8)
|
|
bits[5] = byte(flags)
|
|
return derTLV(tagBitString, append([]byte{0}, bits[2:6]...))
|
|
}
|
|
|
|
// kerberosTime encodes a GeneralizedTime of whole seconds.
|
|
func kerberosTime(t time.Time) []byte {
|
|
return derTLV(0x18, []byte(t.UTC().Format("20060102150405Z")))
|
|
}
|
|
|
|
// parseKerberosTime reads the GeneralizedTime of whole seconds.
|
|
func parseKerberosTime(payload []byte) (time.Time, error) {
|
|
t, err := time.Parse("20060102150405Z", string(payload))
|
|
if err != nil {
|
|
return time.Time{}, ErrToken
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// buildTicket produces the ticket part of an AP-REQ: the encrypted part
|
|
// carries the session key under the service long term key, valid until
|
|
// the given endtime.
|
|
func buildTicket(realm, service string, session, serviceKey []byte, etype uint32, endtime time.Time) ([]byte, error) {
|
|
flags := bitFlags(0)
|
|
key := derTLV(tagSequence, append(derInt(uint64(etype)), derTLV(tagOctet, session)...))
|
|
inner := append(append(append(append([]byte{}, flags...), key...),
|
|
derGeneralString(realm)...), principalName(service)...)
|
|
inner = append(inner, derTLV(tagSequence, nil)...) // transited: empty
|
|
inner = append(inner, kerberosTime(time.Unix(0, 0))...) // authtime
|
|
inner = append(inner, kerberosTime(endtime)...) // endtime
|
|
inner = append(inner, derGeneralString(realm)...) // srealm
|
|
inner = append(inner, principalName(service)...) // sname
|
|
cipher, cerr := Encrypt(etype, serviceKey, UsageTicket, inner)
|
|
if cerr != nil {
|
|
return nil, cerr
|
|
}
|
|
ticket := appTag(1, derTLV(tagSequence, append(append(
|
|
derInt(5), derGeneralString(realm)...),
|
|
append(principalName(service), encryptedData(etype, cipher, 1)...)...)))
|
|
return ticket, nil
|
|
}
|
|
|
|
// buildAuthenticator produces the encrypted authenticator of an AP-REQ
|
|
// for the given client time, with the 0x8003 checksum of RFC 4121
|
|
// section 4.1.1: the little endian length of Bnd, the sixteen Bnd octets
|
|
// of an unused channel binding, and the little endian Flags, so a
|
|
// conformant acceptor can read it.
|
|
func buildAuthenticator(realm, client string, session []byte, etype uint32, seq uint64, ctime time.Time) ([]byte, error) {
|
|
inner := append(derInt(5), derGeneralString(realm)...)
|
|
inner = append(inner, principalName(client)...)
|
|
sum := make([]byte, 0, 24)
|
|
var word [4]byte
|
|
binary.LittleEndian.PutUint32(word[:], 16)
|
|
sum = append(sum, word[:]...) // length of Bnd
|
|
sum = append(sum, make([]byte, 16)...) // Bnd: no channel binding
|
|
binary.LittleEndian.PutUint32(word[:], 0)
|
|
sum = append(sum, word[:]...) // Flags: none
|
|
inner = append(inner, gssChecksum(sum)...)
|
|
inner = append(inner, derInt(uint64(ctime.Nanosecond())/1000)...)
|
|
inner = append(inner, kerberosTime(ctime)...)
|
|
inner = append(inner, derInt(seq)...)
|
|
cipher, err := Encrypt(etype, session, UsageAPReqAuth, inner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return encryptedData(etype, cipher, 0), nil
|
|
}
|
|
|
|
// ClientInit builds the context establishment token the initiator sends
|
|
// in RPCSEC_GSS_INIT: the GSS framed AP-REQ. The service key is the
|
|
// long term key the ticket is sealed with, as a KDC would have it.
|
|
func ClientInit(etype uint32, serviceKey []byte, realm, service, client string) (*Context, []byte, error) {
|
|
session := make([]byte, len(serviceKey))
|
|
if _, err := rand.Read(session); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ctx := &Context{Key: session, Etype: etype}
|
|
ticket, err := buildTicket(realm, service, session, serviceKey, etype, time.Now().Add(time.Hour))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
auth, err := buildAuthenticator(realm, client, session, etype, 0, time.Now())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
apreq := appTag(14, derTLV(tagSequence, append(append(
|
|
derInt(5), derInt(14)...),
|
|
append(bitFlags(0), append(ticket, auth...)...)...)))
|
|
inner := append(append([]byte{}, derOID...), apreq...)
|
|
return ctx, appTag(0, inner), nil
|
|
}
|
|
|
|
// checkGSSChecksum validates the 0x8003 checksum body of RFC 4121
|
|
// section 4.1.1: the little endian length of Bnd, the sixteen Bnd octets
|
|
// and the little endian Flags.
|
|
func checkGSSChecksum(payload []byte) error {
|
|
r := newDERReader(payload)
|
|
typeRaw, err := r.expect(tagInteger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctype, err := derUint(typeRaw)
|
|
if err != nil || ctype != 0x8003 {
|
|
return ErrToken
|
|
}
|
|
body, err := r.expect(tagOctet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(body) != 24 || binary.LittleEndian.Uint32(body[0:4]) != 16 {
|
|
return ErrToken
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parsePrincipal reads a PrincipalName and answers its components joined
|
|
// with a slash.
|
|
func parsePrincipal(payload []byte) (string, error) {
|
|
r := newDERReader(payload)
|
|
if _, err := r.expect(tagInteger); err != nil { // name type
|
|
return "", err
|
|
}
|
|
strs, err := r.expect(tagSequence)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sr := newDERReader(strs)
|
|
var parts []string
|
|
for {
|
|
tag, raw, err := sr.next()
|
|
if err != nil {
|
|
break
|
|
}
|
|
if tag != 0x1b {
|
|
return "", ErrDER
|
|
}
|
|
parts = append(parts, string(raw))
|
|
}
|
|
return strings.Join(parts, "/"), nil
|
|
}
|
|
|
|
// parseAuthenticator reads the fields of a decrypted authenticator the
|
|
// acceptor checks: the client principal, the timestamp, the microseconds
|
|
// and the sequence number when one is present.
|
|
func parseAuthenticator(plain []byte) (client string, ctime time.Time, cusec uint64, seq int64, err error) {
|
|
r := newDERReader(plain)
|
|
vnoRaw, err := r.expect(tagInteger)
|
|
if err != nil {
|
|
return
|
|
}
|
|
vno, err := derUint(vnoRaw)
|
|
if err != nil || vno != 5 {
|
|
err = ErrToken
|
|
return
|
|
}
|
|
if _, err = r.expect(0x1b); err != nil { // crealm
|
|
return
|
|
}
|
|
cnameRaw, err := r.expect(tagSequence)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if client, err = parsePrincipal(cnameRaw); err != nil {
|
|
return
|
|
}
|
|
cksumRaw, err := r.expect(tagSequence) // the GSS checksum, required
|
|
if err != nil {
|
|
return
|
|
}
|
|
if err = checkGSSChecksum(cksumRaw); err != nil {
|
|
return
|
|
}
|
|
cusecRaw, err := r.expect(tagInteger)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if cusec, err = derUint(cusecRaw); err != nil {
|
|
return
|
|
}
|
|
ctimeRaw, err := r.expect(0x18)
|
|
if err != nil {
|
|
return
|
|
}
|
|
if ctime, err = parseKerberosTime(ctimeRaw); err != nil {
|
|
return
|
|
}
|
|
// Past the timestamp this profile writes at most the sequence number,
|
|
// an integer; anything else ends the fields it reads.
|
|
if tag, payload, terr := r.next(); terr == nil && tag == tagInteger {
|
|
v, verr := derUint(payload)
|
|
if verr != nil {
|
|
err = ErrToken
|
|
return
|
|
}
|
|
seq = int64(v)
|
|
}
|
|
return
|
|
}
|
|
|
|
// replayKey identifies one authenticator of one client: the principal
|
|
// plus the timestamp, microseconds and sequence number it carried.
|
|
type replayKey struct {
|
|
client string
|
|
ctime int64
|
|
cusec int64
|
|
seq int64
|
|
}
|
|
|
|
// replays is the acceptor wide replay cache. Entries live for the clock
|
|
// skew window, beyond which a token is refused for its age anyway.
|
|
var replays = struct {
|
|
mu sync.Mutex
|
|
seen map[replayKey]time.Time
|
|
}{seen: make(map[replayKey]time.Time)}
|
|
|
|
// seenReplay records the authenticator and reports whether one with the
|
|
// same key was already accepted.
|
|
func seenReplay(k replayKey) bool {
|
|
now := time.Now()
|
|
replays.mu.Lock()
|
|
defer replays.mu.Unlock()
|
|
for key, until := range replays.seen {
|
|
if now.After(until) {
|
|
delete(replays.seen, key)
|
|
}
|
|
}
|
|
if _, ok := replays.seen[k]; ok {
|
|
return true
|
|
}
|
|
replays.seen[k] = now.Add(clockSkew)
|
|
return false
|
|
}
|
|
|
|
// AcceptInit verifies the initiator token and answers the AP-REP and
|
|
// the established context of the acceptor side. The ticket must not have
|
|
// expired, the authenticator must sit inside the clock skew window and
|
|
// must never have been accepted before.
|
|
func AcceptInit(token []byte, serviceKey []byte) (*Context, []byte, error) {
|
|
r := newDERReader(token)
|
|
outer, err := r.expect(0x60 | 0)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
ir := newDERReader(outer)
|
|
if _, err = ir.expect(tagOID); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
apreq, err := ir.expect(0x60 | 14)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sr := newDERReader(apreq)
|
|
seqPayload, err := sr.expect(tagSequence)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
br := newDERReader(seqPayload)
|
|
if _, err = br.expect(tagInteger); err != nil { // pvno
|
|
return nil, nil, err
|
|
}
|
|
if _, err = br.expect(tagInteger); err != nil { // msg-type
|
|
return nil, nil, err
|
|
}
|
|
if _, err = br.expect(tagBitString); err != nil { // ap-options
|
|
return nil, nil, err
|
|
}
|
|
ticketRaw, err := br.expect(0x60 | 1)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
authRaw, err := br.expect(tagSequence)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// The ticket: decrypt the encrypted part with the service key.
|
|
tr := newDERReader(ticketRaw)
|
|
ticketSeq, terr := tr.expect(tagSequence)
|
|
if terr != nil {
|
|
return nil, nil, terr
|
|
}
|
|
t := newDERReader(ticketSeq)
|
|
if _, err = t.expect(tagInteger); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if _, err = t.expect(0x1b); err != nil { // realm
|
|
return nil, nil, err
|
|
}
|
|
if _, err = t.expect(tagSequence); err != nil { // sname
|
|
return nil, nil, err
|
|
}
|
|
encTag, encPayload, err := t.next()
|
|
if err != nil || encTag != tagSequence {
|
|
return nil, nil, ErrDER
|
|
}
|
|
er := newDERReader(encPayload)
|
|
_, epayload, eerr := er.next()
|
|
if eerr != nil {
|
|
return nil, nil, eerr
|
|
}
|
|
etype, eerr2 := derUint(epayload)
|
|
if eerr2 != nil {
|
|
return nil, nil, eerr2
|
|
}
|
|
if _, err = er.expect(tagInteger); err != nil { // kvno
|
|
return nil, nil, err
|
|
}
|
|
cipherPayload, err := er.expect(tagOctet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
inner, err := Decrypt(uint32(etype), serviceKey, UsageTicket, cipherPayload)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
// The decrypted ticket part: flags, key, crealm, cname, transited,
|
|
// authtime, endtime, srealm, sname. The key and the endtime matter
|
|
// here; this profile writes no starttime, so the second time is the
|
|
// endtime.
|
|
ir2 := newDERReader(inner)
|
|
if _, err = ir2.expect(tagBitString); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
_, keyPayload, err := ir2.next()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
kr := newDERReader(keyPayload)
|
|
if _, err = kr.expect(tagInteger); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
keyValue, err := kr.expect(tagOctet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if _, err = ir2.expect(0x1b); err != nil { // crealm
|
|
return nil, nil, err
|
|
}
|
|
if _, err = ir2.expect(tagSequence); err != nil { // cname
|
|
return nil, nil, err
|
|
}
|
|
if _, err = ir2.expect(tagSequence); err != nil { // transited
|
|
return nil, nil, err
|
|
}
|
|
if _, err = ir2.expect(0x18); err != nil { // authtime
|
|
return nil, nil, err
|
|
}
|
|
endRaw, err := ir2.expect(0x18) // endtime
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
endtime, err := parseKerberosTime(endRaw)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if time.Now().After(endtime) {
|
|
return nil, nil, ErrExpired
|
|
}
|
|
// The authenticator: decrypt with the session key.
|
|
ar := newDERReader(authRaw)
|
|
atag, apayload, aerr := ar.next()
|
|
if aerr != nil {
|
|
return nil, nil, aerr
|
|
}
|
|
if atag != tagInteger {
|
|
return nil, nil, ErrDER
|
|
}
|
|
authEtype, aerr2 := derUint(apayload)
|
|
if aerr2 != nil {
|
|
return nil, nil, aerr2
|
|
}
|
|
if _, err = ar.expect(tagInteger); err != nil { // kvno
|
|
return nil, nil, err
|
|
}
|
|
authCipher, err := ar.expect(tagOctet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
plain, err := Decrypt(uint32(authEtype), keyValue, UsageAPReqAuth, authCipher)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
client, ctime, cusec, seq, perr := parseAuthenticator(plain)
|
|
if perr != nil {
|
|
return nil, nil, perr
|
|
}
|
|
authTime := ctime.Add(time.Duration(cusec) * time.Microsecond)
|
|
if d := time.Since(authTime); d > clockSkew || d < -clockSkew {
|
|
return nil, nil, ErrSkew
|
|
}
|
|
if seenReplay(replayKey{client: client, ctime: ctime.Unix(), cusec: int64(cusec), seq: seq}) {
|
|
return nil, nil, ErrReplay
|
|
}
|
|
ctx := &Context{Key: keyValue, Etype: uint32(authEtype), Accepting: true, RecvSeq: 0}
|
|
// The AP-REP: pvno, msg-type, the encrypted part under usage twelve
|
|
// with the echo of the timestamp and the sequence number zero.
|
|
enc, err := Encrypt(uint32(authEtype), keyValue, UsageAPRepAuth,
|
|
append(append(derInt(0), derInt(0)...), derInt(0)...))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
aprep := appTag(15, derTLV(tagSequence, append(append(
|
|
derInt(5), derInt(15)...), encryptedData(uint32(authEtype), enc, 0)...)))
|
|
return ctx, aprep, nil
|
|
}
|
|
|
|
// ClientAcceptRep verifies the AP-REP the acceptor answers the
|
|
// establishment with and completes the client half of the context: the
|
|
// encrypted part must decrypt under the session key with the AP-REP
|
|
// usage and carry the message type and version of RFC 4120.
|
|
func (c *Context) ClientAcceptRep(token []byte) error {
|
|
if c.Key == nil {
|
|
return ErrContext
|
|
}
|
|
r := newDERReader(token)
|
|
aprep, err := r.expect(0x60 | 15)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pr := newDERReader(aprep)
|
|
seqPayload, err := pr.expect(tagSequence)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sr := newDERReader(seqPayload)
|
|
pvnoRaw, err := sr.expect(tagInteger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
pvno, err := derUint(pvnoRaw)
|
|
if err != nil || pvno != 5 {
|
|
return ErrToken
|
|
}
|
|
msgTypeRaw, err := sr.expect(tagInteger)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
msgType, err := derUint(msgTypeRaw)
|
|
if err != nil || msgType != 15 {
|
|
return ErrToken
|
|
}
|
|
encTag, encPayload, err := sr.next()
|
|
if err != nil || encTag != tagSequence {
|
|
return ErrDER
|
|
}
|
|
er := newDERReader(encPayload)
|
|
_, epayload, eerr := er.next()
|
|
if eerr != nil {
|
|
return eerr
|
|
}
|
|
etype, eerr2 := derUint(epayload)
|
|
if eerr2 != nil {
|
|
return eerr2
|
|
}
|
|
if _, err = er.expect(tagInteger); err != nil { // kvno
|
|
return err
|
|
}
|
|
cipher, err := er.expect(tagOctet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if uint32(etype) != c.Etype {
|
|
return ErrContext
|
|
}
|
|
plain, err := Decrypt(uint32(etype), c.Key, UsageAPRepAuth, cipher)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// The decrypted part answers with the client time and usec echo;
|
|
// three integers in this profile.
|
|
tr := newDERReader(plain)
|
|
for range 3 {
|
|
if _, err = tr.expect(tagInteger); err != nil {
|
|
return ErrToken
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// acceptMIC computes the MIC checksum body of RFC 4121 section 4.2.6.1:
|
|
// the checksum operation over the signed data and the token header.
|
|
func (c *Context) micSum(header, data []byte) ([]byte, error) {
|
|
sum, err := Checksum(c.Etype, c.Key, micUsage(c.Accepting), append(append([]byte{}, data...), header...))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return sum, nil
|
|
}
|
|
|
|
// micUsage answers the key usage of the given side: the initiator
|
|
// signs with seven, the acceptor with eight; a verifier always uses the
|
|
// usage of the peer who produced the token.
|
|
func micUsage(accepting bool) uint32 {
|
|
if accepting {
|
|
return UsageAcceptorSign
|
|
}
|
|
return UsageInitiatorSign
|
|
}
|
|
|
|
// sealUsage answers the seal key usage of a side.
|
|
func sealUsage(accepting bool) uint32 {
|
|
if accepting {
|
|
return UsageAcceptorSeal
|
|
}
|
|
return UsageInitiatorSeal
|
|
}
|
|
|
|
// GetMIC produces the per-message MIC token: the fixed header with the
|
|
// sequence number and the checksum over data and header.
|
|
func (c *Context) GetMIC(data []byte) ([]byte, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
header := make([]byte, 16)
|
|
binary.BigEndian.PutUint16(header[0:], TokenMIC)
|
|
flags := byte(0)
|
|
if c.Accepting {
|
|
flags = FlagSentByAcceptor
|
|
}
|
|
header[2] = flags
|
|
for i := 3; i < 8; i++ {
|
|
header[i] = 0xff
|
|
}
|
|
binary.BigEndian.PutUint64(header[8:], c.SendSeq)
|
|
c.SendSeq++
|
|
sum, err := c.micSum(header, data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(header, sum...), nil
|
|
}
|
|
|
|
// VerifyMIC checks a peer MIC token against the data.
|
|
func (c *Context) VerifyMIC(data, token []byte) error {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if len(token) < 28 || binary.BigEndian.Uint16(token[0:]) != TokenMIC {
|
|
return ErrToken
|
|
}
|
|
peerFlags := byte(0)
|
|
if !c.Accepting {
|
|
peerFlags = FlagSentByAcceptor
|
|
}
|
|
if token[2] != peerFlags {
|
|
return ErrContext
|
|
}
|
|
seq := binary.BigEndian.Uint64(token[8:])
|
|
if seq != c.RecvSeq {
|
|
return ErrContext
|
|
}
|
|
c.RecvSeq++
|
|
// The token was signed by the peer, whose signing usage differs.
|
|
sum, err := Checksum(c.Etype, c.Key, micUsage(!c.Accepting), append(append([]byte{}, data...), token[:16]...))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !hmac.Equal(sum, token[16:28]) {
|
|
return ErrIntegrity
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Wrap produces the confidential Wrap token: the header, then the
|
|
// plaintext with the header appended encrypted by the profile. No
|
|
// filler octets are needed with AES, whose cipher keeps the length.
|
|
func (c *Context) Wrap(data []byte) ([]byte, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
header := make([]byte, 16)
|
|
binary.BigEndian.PutUint16(header[0:], TokenWrap)
|
|
flags := byte(FlagSealed)
|
|
if c.Accepting {
|
|
flags |= FlagSentByAcceptor
|
|
}
|
|
header[2] = flags
|
|
header[3] = 0xff
|
|
binary.BigEndian.PutUint64(header[8:], c.SendSeq)
|
|
c.SendSeq++
|
|
sealed, err := Encrypt(c.Etype, c.Key, sealUsage(c.Accepting), append(append([]byte{}, data...), header...))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return append(header, sealed...), nil
|
|
}
|
|
|
|
// Unwrap reverses Wrap and returns the plaintext.
|
|
func (c *Context) Unwrap(token []byte) ([]byte, error) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if len(token) < 16+16+12 || binary.BigEndian.Uint16(token[0:]) != TokenWrap {
|
|
return nil, ErrToken
|
|
}
|
|
peerFlags := byte(FlagSealed)
|
|
if !c.Accepting {
|
|
peerFlags |= FlagSentByAcceptor
|
|
}
|
|
if token[2]&0x03 != peerFlags&0x03 {
|
|
return nil, ErrContext
|
|
}
|
|
// The Wrap token carries its own sequence number, which advances the
|
|
// receive counter like a MIC does.
|
|
if binary.BigEndian.Uint64(token[8:]) != c.RecvSeq {
|
|
return nil, ErrContext
|
|
}
|
|
c.RecvSeq++
|
|
// The token was sealed by the peer, whose seal usage differs.
|
|
data, err := Decrypt(c.Etype, c.Key, sealUsage(!c.Accepting), token[16:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if string(data[len(data)-16:]) != string(token[:16]) {
|
|
return nil, ErrContext
|
|
}
|
|
return data[:len(data)-16], nil
|
|
}
|