summaryrefslogtreecommitdiffhomepage
path: root/util/winutil/s4u/lsa_windows.go
diff options
context:
space:
mode:
Diffstat (limited to 'util/winutil/s4u/lsa_windows.go')
-rw-r--r--util/winutil/s4u/lsa_windows.go399
1 files changed, 0 insertions, 399 deletions
diff --git a/util/winutil/s4u/lsa_windows.go b/util/winutil/s4u/lsa_windows.go
deleted file mode 100644
index a26a7bcf0..000000000
--- a/util/winutil/s4u/lsa_windows.go
+++ /dev/null
@@ -1,399 +0,0 @@
-// Copyright (c) Tailscale Inc & contributors
-// SPDX-License-Identifier: BSD-3-Clause
-
-package s4u
-
-import (
- "errors"
- "fmt"
- "os"
- "os/user"
- "path/filepath"
- "strings"
- "unicode"
- "unsafe"
-
- "github.com/dblohm7/wingoes"
- "golang.org/x/sys/windows"
- "tailscale.com/types/lazy"
- "tailscale.com/util/winutil"
- "tailscale.com/util/winutil/winenv"
-)
-
-const (
- _MICROSOFT_KERBEROS_NAME = "Kerberos"
- _MSV1_0_PACKAGE_NAME = "MICROSOFT_AUTHENTICATION_PACKAGE_V1_0"
-)
-
-type _LSAHANDLE windows.Handle
-type _LSA_OPERATIONAL_MODE uint32
-
-type _KERB_LOGON_SUBMIT_TYPE int32
-
-const (
- _KerbInteractiveLogon _KERB_LOGON_SUBMIT_TYPE = 2
- _KerbSmartCardLogon _KERB_LOGON_SUBMIT_TYPE = 6
- _KerbWorkstationUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 7
- _KerbSmartCardUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 8
- _KerbProxyLogon _KERB_LOGON_SUBMIT_TYPE = 9
- _KerbTicketLogon _KERB_LOGON_SUBMIT_TYPE = 10
- _KerbTicketUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 11
- _KerbS4ULogon _KERB_LOGON_SUBMIT_TYPE = 12
- _KerbCertificateLogon _KERB_LOGON_SUBMIT_TYPE = 13
- _KerbCertificateS4ULogon _KERB_LOGON_SUBMIT_TYPE = 14
- _KerbCertificateUnlockLogon _KERB_LOGON_SUBMIT_TYPE = 15
- _KerbNoElevationLogon _KERB_LOGON_SUBMIT_TYPE = 83
- _KerbLuidLogon _KERB_LOGON_SUBMIT_TYPE = 84
-)
-
-type _KERB_S4U_LOGON_FLAGS uint32
-
-const (
- _KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS _KERB_S4U_LOGON_FLAGS = 0x2
- //lint:ignore U1000 maps to a win32 API
- _KERB_S4U_LOGON_FLAG_IDENTIFY _KERB_S4U_LOGON_FLAGS = 0x8
-)
-
-type _KERB_S4U_LOGON struct {
- MessageType _KERB_LOGON_SUBMIT_TYPE
- Flags _KERB_S4U_LOGON_FLAGS
- ClientUpn windows.NTUnicodeString
- ClientRealm windows.NTUnicodeString
-}
-
-type _MSV1_0_LOGON_SUBMIT_TYPE int32
-
-const (
- _MsV1_0InteractiveLogon _MSV1_0_LOGON_SUBMIT_TYPE = 2
- _MsV1_0Lm20Logon _MSV1_0_LOGON_SUBMIT_TYPE = 3
- _MsV1_0NetworkLogon _MSV1_0_LOGON_SUBMIT_TYPE = 4
- _MsV1_0SubAuthLogon _MSV1_0_LOGON_SUBMIT_TYPE = 5
- _MsV1_0WorkstationUnlockLogon _MSV1_0_LOGON_SUBMIT_TYPE = 7
- _MsV1_0S4ULogon _MSV1_0_LOGON_SUBMIT_TYPE = 12
- _MsV1_0VirtualLogon _MSV1_0_LOGON_SUBMIT_TYPE = 82
- _MsV1_0NoElevationLogon _MSV1_0_LOGON_SUBMIT_TYPE = 83
- _MsV1_0LuidLogon _MSV1_0_LOGON_SUBMIT_TYPE = 84
-)
-
-type _MSV1_0_S4U_LOGON_FLAGS uint32
-
-const (
- _MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS _MSV1_0_S4U_LOGON_FLAGS = 0x2
-)
-
-type _MSV1_0_S4U_LOGON struct {
- MessageType _MSV1_0_LOGON_SUBMIT_TYPE
- Flags _MSV1_0_S4U_LOGON_FLAGS
- UserPrincipalName windows.NTUnicodeString
- DomainName windows.NTUnicodeString
-}
-
-type _SECURITY_LOGON_TYPE int32
-
-const (
- _UndefinedLogonType _SECURITY_LOGON_TYPE = 0
- _Interactive _SECURITY_LOGON_TYPE = 2
- _Network _SECURITY_LOGON_TYPE = 3
- _Batch _SECURITY_LOGON_TYPE = 4
- _Service _SECURITY_LOGON_TYPE = 5
- _Proxy _SECURITY_LOGON_TYPE = 6
- _Unlock _SECURITY_LOGON_TYPE = 7
- _NetworkCleartext _SECURITY_LOGON_TYPE = 8
- _NewCredentials _SECURITY_LOGON_TYPE = 9
- _RemoteInteractive _SECURITY_LOGON_TYPE = 10
- _CachedInteractive _SECURITY_LOGON_TYPE = 11
- _CachedRemoteInteractive _SECURITY_LOGON_TYPE = 12
- _CachedUnlock _SECURITY_LOGON_TYPE = 13
-)
-
-const _TOKEN_SOURCE_LENGTH = 8
-
-type _TOKEN_SOURCE struct {
- SourceName [_TOKEN_SOURCE_LENGTH]byte
- SourceIdentifier windows.LUID
-}
-
-type _QUOTA_LIMITS struct {
- PagedPoolLimit uintptr
- NonPagedPoolLimit uintptr
- MinimumWorkingSetSize uintptr
- MaximumWorkingSetSize uintptr
- PagefileLimit uintptr
- TimeLimit int64
-}
-
-var (
- // ErrBadSrcName is returned if srcName contains non-ASCII characters, is
- // empty, or is too long. It may be wrapped with additional information; use
- // errors.Is when checking for it.
- ErrBadSrcName = errors.New("srcName must be ASCII with length > 0 and <= 8")
-)
-
-// LSA packages (and their IDs) are always initialized during system startup,
-// so we can retain their resolved IDs for the lifetime of our process.
-var (
- authPkgIDKerberos lazy.SyncValue[uint32]
- authPkgIDMSV1_0 lazy.SyncValue[uint32]
-)
-
-type lsaSession struct {
- handle _LSAHANDLE
-}
-
-func newLSASessionForQuery() (lsa *lsaSession, err error) {
- var h _LSAHANDLE
- if e := wingoes.ErrorFromNTStatus(lsaConnectUntrusted(&h)); e.Failed() {
- return nil, e
- }
-
- return &lsaSession{handle: h}, nil
-}
-
-func newLSASessionForLogon(processName string) (lsa *lsaSession, err error) {
- // processName is used by LSA for audit logging purposes.
- // If empty, the current process name is used.
- if processName == "" {
- exe, err := os.Executable()
- if err != nil {
- return nil, err
- }
-
- processName = strings.TrimSuffix(filepath.Base(exe), filepath.Ext(exe))
- }
-
- if err := checkASCII(processName); err != nil {
- return nil, err
- }
-
- logonProcessName, err := windows.NewNTString(processName)
- if err != nil {
- return nil, err
- }
-
- var h _LSAHANDLE
- var mode _LSA_OPERATIONAL_MODE
- if e := wingoes.ErrorFromNTStatus(lsaRegisterLogonProcess(logonProcessName, &h, &mode)); e.Failed() {
- return nil, e
- }
-
- return &lsaSession{handle: h}, nil
-}
-
-func (ls *lsaSession) getAuthPkgID(pkgName string) (id uint32, err error) {
- ntPkgName, err := windows.NewNTString(pkgName)
- if err != nil {
- return 0, err
- }
-
- if e := wingoes.ErrorFromNTStatus(lsaLookupAuthenticationPackage(ls.handle, ntPkgName, &id)); e.Failed() {
- return 0, e
- }
-
- return id, nil
-}
-
-func (ls *lsaSession) Close() error {
- if e := wingoes.ErrorFromNTStatus(lsaDeregisterLogonProcess(ls.handle)); e.Failed() {
- return e
- }
- ls.handle = 0
- return nil
-}
-
-func checkASCII(s string) error {
- for _, c := range []byte(s) {
- if c > unicode.MaxASCII {
- return fmt.Errorf("%q must be ASCII but contains value 0x%02X", s, c)
- }
- }
-
- return nil
-}
-
-var (
- thisComputer = []uint16{'.', 0}
- computerName lazy.SyncValue[string]
-)
-
-func getComputerName() (string, error) {
- var buf [windows.MAX_COMPUTERNAME_LENGTH + 1]uint16
- size := uint32(len(buf))
- if err := windows.GetComputerName(&buf[0], &size); err != nil {
- return "", err
- }
-
- return windows.UTF16ToString(buf[:size]), nil
-}
-
-// checkDomainAccount strips out the computer name (if any) from
-// username and returns the result in sanitizedUserName. isDomainAccount is set
-// to true if username contains a domain component that does not refer to the
-// local computer.
-func checkDomainAccount(username string) (sanitizedUserName string, isDomainAccount bool, err error) {
- before, after, hasBackslash := strings.Cut(username, `\`)
- if !hasBackslash {
- return username, false, nil
- }
- if before == "." {
- return after, false, nil
- }
-
- comp, err := computerName.GetErr(getComputerName)
- if err != nil {
- return username, false, err
- }
-
- if strings.EqualFold(before, comp) {
- return after, false, nil
- }
- return username, true, nil
-}
-
-// logonAs performs a S4U logon for u on behalf of srcName, and returns an
-// access token for the user if successful. srcName must be non-empty, ASCII,
-// and no more than 8 characters long. If srcName does not meet this criteria,
-// LogonAs will return ErrBadSrcName wrapped with additional information; use
-// errors.Is to check for it. When capLevel == CapCreateProcess, the logon
-// enforces the user's logon hours policy (when present).
-func (ls *lsaSession) logonAs(srcName string, u *user.User, capLevel CapabilityLevel) (token windows.Token, err error) {
- if ln := len(srcName); ln == 0 || ln > _TOKEN_SOURCE_LENGTH {
- return 0, fmt.Errorf("%w, actual length is %d", ErrBadSrcName, ln)
- }
- if err := checkASCII(srcName); err != nil {
- return 0, fmt.Errorf("%w: %v", ErrBadSrcName, err)
- }
-
- sanitizedUserName, isDomainUser, err := checkDomainAccount(u.Username)
- if err != nil {
- return 0, err
- }
- if isDomainUser && !winenv.IsDomainJoined() {
- return 0, fmt.Errorf("%w: cannot logon as domain user without being joined to a domain", os.ErrInvalid)
- }
-
- var pkgID uint32
- var authInfo unsafe.Pointer
- var authInfoLen uint32
- enforceLogonHours := capLevel == CapCreateProcess
- if isDomainUser {
- pkgID, err = authPkgIDKerberos.GetErr(func() (uint32, error) {
- return ls.getAuthPkgID(_MICROSOFT_KERBEROS_NAME)
- })
- if err != nil {
- return 0, err
- }
-
- upn16, err := samToUPN16(sanitizedUserName)
- if err != nil {
- return 0, fmt.Errorf("samToUPN16: %w", err)
- }
-
- logonInfo, logonInfoLen, slcs := winutil.AllocateContiguousBuffer[_KERB_S4U_LOGON](upn16)
- logonInfo.MessageType = _KerbS4ULogon
- if enforceLogonHours {
- logonInfo.Flags = _KERB_S4U_LOGON_FLAG_CHECK_LOGONHOURS
- }
- winutil.SetNTString(&logonInfo.ClientUpn, slcs[0])
-
- authInfo = unsafe.Pointer(logonInfo)
- authInfoLen = logonInfoLen
- } else {
- pkgID, err = authPkgIDMSV1_0.GetErr(func() (uint32, error) {
- return ls.getAuthPkgID(_MSV1_0_PACKAGE_NAME)
- })
- if err != nil {
- return 0, err
- }
-
- upn16, err := windows.UTF16FromString(sanitizedUserName)
- if err != nil {
- return 0, err
- }
-
- logonInfo, logonInfoLen, slcs := winutil.AllocateContiguousBuffer[_MSV1_0_S4U_LOGON](upn16, thisComputer)
- logonInfo.MessageType = _MsV1_0S4ULogon
- if enforceLogonHours {
- logonInfo.Flags = _MSV1_0_S4U_LOGON_FLAG_CHECK_LOGONHOURS
- }
- for i, nts := range []*windows.NTUnicodeString{&logonInfo.UserPrincipalName, &logonInfo.DomainName} {
- winutil.SetNTString(nts, slcs[i])
- }
-
- authInfo = unsafe.Pointer(logonInfo)
- authInfoLen = logonInfoLen
- }
-
- var srcContext _TOKEN_SOURCE
- copy(srcContext.SourceName[:], []byte(srcName))
- if err := allocateLocallyUniqueId(&srcContext.SourceIdentifier); err != nil {
- return 0, err
- }
-
- originName, err := windows.NewNTString(srcName)
- if err != nil {
- return 0, err
- }
-
- var profileBuf uintptr
- var profileBufLen uint32
- var logonID windows.LUID
- var quotas _QUOTA_LIMITS
- var subNTStatus windows.NTStatus
- ntStatus := lsaLogonUser(ls.handle, originName, _Network, pkgID, authInfo, authInfoLen, nil, &srcContext, &profileBuf, &profileBufLen, &logonID, &token, &quotas, &subNTStatus)
- if e := wingoes.ErrorFromNTStatus(ntStatus); e.Failed() {
- return 0, fmt.Errorf("LsaLogonUser(%q): %w, SubStatus: %v", u.Username, e, subNTStatus)
- }
- if profileBuf != 0 {
- lsaFreeReturnBuffer(profileBuf)
- }
- return token, nil
-}
-
-// samToUPN16 converts SAM-style account name samName to a UPN account name,
-// returned as a UTF-16 slice.
-func samToUPN16(samName string) (upn16 []uint16, err error) {
- _, samAccount, hasSep := strings.Cut(samName, `\`)
- if !hasSep {
- return nil, fmt.Errorf("%w: expected samName to contain a backslash", os.ErrInvalid)
- }
-
- // This is essentially the same algorithm used by Win32-OpenSSH:
- // First, try obtaining a UPN directly...
- upn16, err = translateName(samName, windows.NameSamCompatible, windows.NameUserPrincipal)
- if err == nil {
- return upn16, err
- }
-
- // Fallback: Try manually composing a UPN. First obtain the canonical name...
- canonical16, err := translateName(samName, windows.NameSamCompatible, windows.NameCanonical)
- if err != nil {
- return nil, err
- }
- canonical := windows.UTF16ToString(canonical16)
-
- // Extract the domain name...
- domain, _, _ := strings.Cut(canonical, "/")
-
- // ...and finally create the UPN by joining the samAccount and domain.
- upn := strings.Join([]string{samAccount, domain}, "@")
- return windows.UTF16FromString(upn)
-}
-
-func translateName(from string, fromFmt uint32, toFmt uint32) (result []uint16, err error) {
- from16, err := windows.UTF16PtrFromString(from)
- if err != nil {
- return nil, err
- }
-
- var to16Len uint32
- if err := windows.TranslateName(from16, fromFmt, toFmt, nil, &to16Len); err != nil {
- return nil, err
- }
-
- to16Buf := make([]uint16, to16Len)
- if err := windows.TranslateName(from16, fromFmt, toFmt, unsafe.SliceData(to16Buf), &to16Len); err != nil {
- return nil, err
- }
-
- return to16Buf, nil
-}