|
| 1 | +// Package sfu implements the Microsoft Service for User and Constrained |
| 2 | +// Delegation extensions ([MS-SFU]) that layer onto RFC 4120's TGS exchange: |
| 3 | +// PA-FOR-USER (S4U2Self) and the padata used for S4U2Proxy. Like the rest of |
| 4 | +// the MS extensions, these ride the RFC's padata / additional-tickets extension |
| 5 | +// points — the TGS-REQ/REP messages are unchanged. |
| 6 | +package sfu |
| 7 | + |
| 8 | +import ( |
| 9 | + "encoding/asn1" |
| 10 | + "encoding/binary" |
| 11 | + "fmt" |
| 12 | + |
| 13 | + kerbcrypto "github.com/TheManticoreProject/Manticore/network/kerberos/v5/crypto" |
| 14 | + "github.com/TheManticoreProject/Manticore/network/kerberos/v5/iana" |
| 15 | + "github.com/TheManticoreProject/Manticore/network/kerberos/v5/messages" |
| 16 | +) |
| 17 | + |
| 18 | +// authPackageKerberos is the fixed auth-package value in PA-FOR-USER ([MS-SFU] |
| 19 | +// 2.2.1): the authentication mechanism name, which MUST be "Kerberos". |
| 20 | +const authPackageKerberos = "Kerberos" |
| 21 | + |
| 22 | +// s4uByteArray builds the S4UByteArray checksummed by PA-FOR-USER ([MS-SFU] |
| 23 | +// 2.2.1): the userName.name-type as a 4-byte little-endian integer, followed by |
| 24 | +// each userName.name-string component, then userRealm, then auth-package — with |
| 25 | +// no null terminators. |
| 26 | +func s4uByteArray(userName messages.PrincipalName, userRealm, authPackage string) []byte { |
| 27 | + buf := make([]byte, 4) |
| 28 | + binary.LittleEndian.PutUint32(buf, uint32(userName.NameType)) |
| 29 | + for _, s := range userName.NameString { |
| 30 | + buf = append(buf, []byte(s)...) |
| 31 | + } |
| 32 | + buf = append(buf, []byte(userRealm)...) |
| 33 | + buf = append(buf, []byte(authPackage)...) |
| 34 | + return buf |
| 35 | +} |
| 36 | + |
| 37 | +// BuildPAForUser builds the PA-FOR-USER pre-authentication data element used in |
| 38 | +// an S4U2Self TGS-REQ, on behalf of the user (userName, userRealm). The keyed |
| 39 | +// checksum is computed with the requesting service's TGT session key at key |
| 40 | +// usage KERB_NON_KERB_CKSUM_SALT (17). |
| 41 | +// |
| 42 | +// [MS-SFU] 2.2.1 specifies KERB_CHECKSUM_HMAC_MD5, which is correct when the TGT |
| 43 | +// session key is RC4. For AES TGT session keys, modern KDCs expect the checksum |
| 44 | +// type paired with the session key's enctype; this function selects that type |
| 45 | +// from sessionKeyEType so it interoperates with current Active Directory. |
| 46 | +func BuildPAForUser(userName messages.PrincipalName, userRealm string, sessionKey []byte, sessionKeyEType int) (messages.PAData, error) { |
| 47 | + cksumType, ok := kerbcrypto.ChecksumTypeForEType(sessionKeyEType) |
| 48 | + if !ok { |
| 49 | + return messages.PAData{}, fmt.Errorf("sfu: unsupported session-key etype %d", sessionKeyEType) |
| 50 | + } |
| 51 | + buf := s4uByteArray(userName, userRealm, authPackageKerberos) |
| 52 | + sum, err := kerbcrypto.GetChecksum(cksumType, sessionKey, iana.KeyUsageKerbNonKerbCksumSalt, buf) |
| 53 | + if err != nil { |
| 54 | + return messages.PAData{}, fmt.Errorf("sfu: PA-FOR-USER checksum: %w", err) |
| 55 | + } |
| 56 | + |
| 57 | + userNameElem, err := asn1.MarshalWithParams(messages.MarshalPrincipalName(userName), "explicit,tag:0") |
| 58 | + if err != nil { |
| 59 | + return messages.PAData{}, err |
| 60 | + } |
| 61 | + userRealmElem, err := asn1.Marshal(messages.ExplicitGeneralString(1, userRealm)) |
| 62 | + if err != nil { |
| 63 | + return messages.PAData{}, err |
| 64 | + } |
| 65 | + cksumElem, err := asn1.MarshalWithParams(messages.Checksum{CKSumType: cksumType, Checksum: sum}, "explicit,tag:2") |
| 66 | + if err != nil { |
| 67 | + return messages.PAData{}, err |
| 68 | + } |
| 69 | + authPkgElem, err := asn1.Marshal(messages.ExplicitGeneralString(3, authPackageKerberos)) |
| 70 | + if err != nil { |
| 71 | + return messages.PAData{}, err |
| 72 | + } |
| 73 | + |
| 74 | + body := make([]byte, 0, len(userNameElem)+len(userRealmElem)+len(cksumElem)+len(authPkgElem)) |
| 75 | + body = append(body, userNameElem...) |
| 76 | + body = append(body, userRealmElem...) |
| 77 | + body = append(body, cksumElem...) |
| 78 | + body = append(body, authPkgElem...) |
| 79 | + |
| 80 | + seq, err := asn1.Marshal(asn1.RawValue{Class: asn1.ClassUniversal, Tag: asn1.TagSequence, IsCompound: true, Bytes: body}) |
| 81 | + if err != nil { |
| 82 | + return messages.PAData{}, err |
| 83 | + } |
| 84 | + return messages.PAData{PADataType: iana.PAForUser, PADataValue: seq}, nil |
| 85 | +} |
| 86 | + |
| 87 | +// PAForUser is the decoded content of a PA-FOR-USER element. |
| 88 | +type PAForUser struct { |
| 89 | + UserName messages.PrincipalName |
| 90 | + UserRealm string |
| 91 | + Cksum messages.Checksum |
| 92 | + AuthPackage string |
| 93 | +} |
| 94 | + |
| 95 | +type paForUserInner struct { |
| 96 | + UserName messages.PrincipalName `asn1:"explicit,tag:0"` |
| 97 | + UserRealm string `asn1:"explicit,tag:1,generalstring"` |
| 98 | + Cksum messages.Checksum `asn1:"explicit,tag:2"` |
| 99 | + AuthPackage string `asn1:"explicit,tag:3,generalstring"` |
| 100 | +} |
| 101 | + |
| 102 | +// ParsePAForUser decodes a PA-FOR-USER padata-value. |
| 103 | +func ParsePAForUser(b []byte) (*PAForUser, error) { |
| 104 | + var inner paForUserInner |
| 105 | + if _, err := asn1.Unmarshal(b, &inner); err != nil { |
| 106 | + return nil, fmt.Errorf("sfu: parse PA-FOR-USER: %w", err) |
| 107 | + } |
| 108 | + return &PAForUser{ |
| 109 | + UserName: inner.UserName, |
| 110 | + UserRealm: inner.UserRealm, |
| 111 | + Cksum: inner.Cksum, |
| 112 | + AuthPackage: inner.AuthPackage, |
| 113 | + }, nil |
| 114 | +} |
| 115 | + |
| 116 | +// VerifyPAForUser recomputes the PA-FOR-USER checksum with the given TGT session |
| 117 | +// key and reports whether it matches, defending against tampering with the |
| 118 | +// impersonated identity. |
| 119 | +func VerifyPAForUser(p *PAForUser, sessionKey []byte, sessionKeyEType int) bool { |
| 120 | + cksumType, ok := kerbcrypto.ChecksumTypeForEType(sessionKeyEType) |
| 121 | + if !ok || cksumType != p.Cksum.CKSumType { |
| 122 | + return false |
| 123 | + } |
| 124 | + buf := s4uByteArray(p.UserName, p.UserRealm, p.AuthPackage) |
| 125 | + return kerbcrypto.VerifyChecksum(cksumType, sessionKey, iana.KeyUsageKerbNonKerbCksumSalt, buf, p.Cksum.Checksum) |
| 126 | +} |
0 commit comments