使用 Kerberos 更改密码协议和 .NET Core 3 更改 Active Directory 密码

Change Active Directory password using Kerberos Change-Password protocol and .NET Core 3

可以使用 LDAP 协议更改 AD 用户的密码。但是,如果用户必须在下次登录时更改密码(或密码已过期),则除非使用管理员凭据,否则无法更改密码。我使用 WIN32 API 解决了这个问题 in the past。不幸的是,我无法在 linux 环境中使用 WINAPI。

根据this article

When a user changes his or her own password by pressing CTRL+ALT+DELETE and then clicking Change Password, Windows NT up to Windows 2003 the NetUserChangePassword mechanism (method 1) is used if the target is a domain. From Windows Vista onwards, the Kerberos change password protocol is used for domain accounts. If the target is a Kerberos realm, the Kerberos change-password protocol (method 3) is used.

我在 .NET Core 上寻找可以 运行 的 Kerberos 客户端,我找到了 SteveSyfuhs/Kerberos.NET

通过阅读Kerberos Change Password Protocol and Kerberos.NET Samples,我写了如下代码:

using Kerberos.NET;
using Kerberos.NET.Client;
using Kerberos.NET.Credentials;
using Kerberos.NET.Crypto;
using Kerberos.NET.Entities;
using System;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace KerberosDemo
{
    class Program
    {
        static async Task Main(string[] args)
        {
            string activeDirectoryServer = "10.12.34.56:88"; // kdc port
            string domain = "TEST.LOCAL";
            string username = "testuser@" + domain;
            string oldPassword = "123";
            string newPassword = "456";

            using var client = new KerberosClient(activeDirectoryServer);
            var kerbCred = new KerberosPasswordCredential(username, oldPassword, domain);
            await client.Authenticate(kerbCred);
            var serviceTicket = await client.GetServiceTicket("kadmin/changepw");

            var apReq = new KrbApReq
            {
                Ticket = serviceTicket.Ticket,
                Authenticator = KrbEncryptedData.Encrypt(Encoding.UTF8.GetBytes(newPassword).AsMemory(), kerbCred.CreateKey(), KeyUsage.ApReqAuthenticator),
                ProtocolVersionNumber = 1
            };

            var tcp = client.Transports.FirstOrDefault(t => t.Protocol == ProtocolType.Tcp);
            await tcp.SendMessage<KrbApReq, KrbAsRep>(domain, apReq);
        }
    }
}

我在 await tcp.SendMessage<KrbApReq, KrbAsRep>(domain, apReq); 遇到以下异常:

Unhandled exception. System.Security.Cryptography.CryptographicException: ASN1 corrupted data. at System.Security.Cryptography.Asn1.AsnReader.ReadTagAndLength(Nullable1& contentsLength, Int32& bytesRead) in d:\a\s\Kerberos.NET\Asn1\Experimental\AsnReader.cs:line 305 at Kerberos.NET.Entities.KrbError.CanDecode(ReadOnlyMemory1 encoded) in d:\a\s\Kerberos.NET\Entities\Krb\KrbError.cs:line 23 at Kerberos.NET.Transport.KerberosTransportBase.Decode[T](ReadOnlyMemory1 response) in d:\a\s\Kerberos.NET\Client\Transport\KerberosTransportBase.cs:line 41 at Kerberos.NET.Transport.TcpKerberosTransport.ReadResponse[T](NetworkStream stream, CancellationToken cancellation) in d:\a\s\Kerberos.NET\Client\Transport\TcpKerberosTransport.cs:line 69 at Kerberos.NET.Transport.TcpKerberosTransport.SendMessage[T](String domain, ReadOnlyMemory1 encoded, CancellationToken cancellation) in d:\a\s\Kerberos.NET\Client\Transport\TcpKerberosTransport.cs:line 58 at KerberosDemo.Program.Main(String[] args) at KerberosDemo.Program.(String[] args)

感谢您的帮助。谢谢。

不妨写成答案

if the user has to change his password at next logon (or the password is expired), then the password cannot be changed unless by using admin credentials

如果您使用用户自己的凭据向 AD 进行身份验证,这可能会成为问题,这是验证用户凭据的常用方法。如果用户密码已过期(或专门设置为在下次登录时强制更改),则身份验证将失败,您将无法查找用户帐户和更改密码。

要解决这个问题,您可以使用不同的帐户对 AD 进行身份验证。 不必是管理员帐户。任何域凭据都可以。然后就可以查询用户的账号了。

由于您必须知道旧密码才能更改密码,AD 认为 您需要更改它的所有授权。

这与重置密码不同,后者需要一个对该帐户具有"Reset password"权限的帐户。

正如您所发现的,您还需要安全地连接到 AD,这样新密码就不会以明文形式发送。

所有这些都记录在 documentation for the unicodePwd attribute 中。