mirror of
https://github.com/RedTeamPentesting/adauth
synced 2026-06-08 12:20:46 +00:00
602 lines
17 KiB
Go
602 lines
17 KiB
Go
package ldapauth
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/ecdh"
|
|
"crypto/ecdsa"
|
|
"crypto/ed25519"
|
|
"crypto/md5"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/RedTeamPentesting/adauth"
|
|
"github.com/RedTeamPentesting/adauth/compat"
|
|
"github.com/RedTeamPentesting/adauth/pkinit"
|
|
"github.com/RedTeamPentesting/adauth/x509ext"
|
|
"github.com/go-ldap/ldap/v3"
|
|
"github.com/oiweiwei/gokrb5.fork/v9/client"
|
|
"github.com/oiweiwei/gokrb5.fork/v9/iana/etypeID"
|
|
"github.com/oiweiwei/gokrb5.fork/v9/iana/flags"
|
|
"github.com/oiweiwei/gokrb5.fork/v9/types"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
// Options holds LDAP specific options.
|
|
type Options struct {
|
|
// LDAP scheme (ldap or ldaps).
|
|
Scheme string
|
|
// Verify indicates whether TLS verification should be performed.
|
|
Verify bool
|
|
// Timeout sets the request timeout for the LDAP connection.
|
|
Timeout time.Duration
|
|
// Debug can be set to enable debug output, for example with
|
|
// adauth.NewDebugFunc(...).
|
|
Debug func(string, ...any)
|
|
// SimpleBind indicates that SimpleBind authentication should be used
|
|
// instead of NTLM, Kerberos or mTLS. For this, a cleartext password is
|
|
// required.
|
|
SimpleBind bool
|
|
// TLSConfig for LDAPS or LDAP+StartTLS. InsecureSkipVerify is ignored and
|
|
// set according to Options.Verify. MaxVersion will be changed to 1.2 unless
|
|
// Options.DisableChannelBinding is set.
|
|
TLSConfig *tls.Config
|
|
// DisableChannelBinding omits the TLS certificate hash in Kerberos and NTLM
|
|
// authentication.
|
|
DisableChannelBinding bool
|
|
// StartTLS indicates that a TLS connection should be established even for
|
|
// non-LDAPS connections before authenticating. For client-certificate
|
|
// authentication on regular LDAP connections, StartTLS will be used even if
|
|
// this option is disabled.
|
|
StartTLS bool
|
|
// DialOptions can be used to customize the connection. DialOptions is
|
|
// ignored when a custom LDAPDialer is set.
|
|
DialOptions []ldap.DialOpt
|
|
// KerberosDialer is a custom dialer that is used to request Kerberos
|
|
// tickets. DialContext is used if implemented.
|
|
KerberosDialer adauth.Dialer
|
|
// LDAPDialer is a custom dialer that is used to establish LDAP connections.
|
|
// DialContext is used if implemented.
|
|
LDAPDialer adauth.Dialer
|
|
}
|
|
|
|
// RegisterFlags registers LDAP specific flags to a pflag.FlagSet such as the
|
|
// default flagset pflag.CommandLine.
|
|
func (opts *Options) RegisterFlags(flagset *pflag.FlagSet) {
|
|
flagset.StringVar(&opts.Scheme, "scheme", "ldaps", "Scheme (ldap or ldaps)")
|
|
flagset.DurationVar(&opts.Timeout, "timeout", 5*time.Second, "LDAP connection timeout")
|
|
flagset.BoolVar(&opts.SimpleBind, "simple-bind", false, "Authenticate with simple bind")
|
|
flagset.BoolVar(&opts.Verify, "verify", false, "Verify LDAP TLS certificate")
|
|
flagset.BoolVar(&opts.StartTLS, "start-tls", false,
|
|
"Negotiate StartTLS before authenticating on regular LDAP connection")
|
|
}
|
|
|
|
// SetDialer configures a dialer for LDAP and Kerberos.
|
|
func (opts *Options) SetDialer(dialer adauth.Dialer) {
|
|
opts.KerberosDialer = dialer
|
|
opts.LDAPDialer = dialer
|
|
}
|
|
|
|
// Connect returns an authenticated LDAP connection to the domain controller's
|
|
// LDAP server.
|
|
func Connect(ctx context.Context, authOpts *adauth.Options, ldapOpts *Options) (conn *ldap.Conn, err error) {
|
|
creds, target, err := authOpts.WithDCTarget(ctx, ldapOpts.Scheme)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ConnectTo(ctx, creds, target, ldapOpts)
|
|
}
|
|
|
|
// Connect returns an authenticated LDAP connection to the specified target.
|
|
func ConnectTo(
|
|
ctx context.Context, creds *adauth.Credential, target *adauth.Target, ldapOpts *Options,
|
|
) (conn *ldap.Conn, err error) {
|
|
opts := ldapOpts
|
|
if opts.Debug == nil {
|
|
opts.Debug = func(s string, a ...any) {}
|
|
}
|
|
|
|
opts.TLSConfig, err = TLSConfig(ldapOpts, creds.ClientCert, creds.ClientCertKey, creds.CACerts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("configure TLS: %w", err)
|
|
}
|
|
|
|
if !ldapOpts.TLSConfig.InsecureSkipVerify && net.ParseIP(target.AddressWithoutPort()) != nil {
|
|
hostname, err := target.Hostname(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("determine target hostname for TLS verification: %w", err)
|
|
}
|
|
|
|
opts.TLSConfig.ServerName = hostname
|
|
}
|
|
|
|
conn, err = connect(ctx, target, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if opts.Timeout == 0 {
|
|
conn.SetTimeout(3 * time.Second)
|
|
} else {
|
|
conn.SetTimeout(opts.Timeout)
|
|
}
|
|
|
|
err = bind(ctx, conn, creds, target, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return conn, nil
|
|
}
|
|
|
|
func connect(ctx context.Context, target *adauth.Target, opts *Options) (conn *ldap.Conn, err error) {
|
|
switch {
|
|
case strings.EqualFold(opts.Scheme, "ldaps"):
|
|
if target.Port == "" {
|
|
target.Port = ldap.DefaultLdapsPort
|
|
}
|
|
|
|
if opts.LDAPDialer == nil {
|
|
conn, err = ldap.DialURL("ldaps://"+target.Address(),
|
|
append(opts.DialOptions, ldap.DialWithTLSConfig(opts.TLSConfig))...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LDAPS dial: %w", err)
|
|
}
|
|
} else {
|
|
tcpConn, err := adauth.AsContextDialer(opts.LDAPDialer).DialContext(ctx, "tcp", target.Address())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dial with custom dialer: %w", err)
|
|
}
|
|
|
|
tlsConn := tls.Client(tcpConn, opts.TLSConfig)
|
|
|
|
err = tlsConn.HandshakeContext(ctx)
|
|
if err != nil {
|
|
_ = tcpConn.Close()
|
|
|
|
return nil, err
|
|
}
|
|
|
|
conn = ldap.NewConn(tlsConn, true)
|
|
conn.Start()
|
|
}
|
|
|
|
opts.Debug("connected to LDAPS server %s", target.Address())
|
|
|
|
return conn, nil
|
|
case strings.EqualFold(opts.Scheme, "ldap"):
|
|
if target.Port == "" {
|
|
target.Port = ldap.DefaultLdapPort
|
|
}
|
|
|
|
if opts.LDAPDialer == nil {
|
|
conn, err = ldap.DialURL("ldap://"+target.Address(), opts.DialOptions...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("LDAP dial: %w", err)
|
|
}
|
|
} else {
|
|
tcpConn, err := adauth.AsContextDialer(opts.LDAPDialer).DialContext(ctx, "tcp", target.Address())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("dial with custom dialer: %w", err)
|
|
}
|
|
|
|
conn = ldap.NewConn(tcpConn, false)
|
|
conn.Start()
|
|
}
|
|
|
|
opts.Debug("connected to LDAP server %s", target.Address())
|
|
|
|
if opts.StartTLS {
|
|
opts.Debug("negotiating StartTLS")
|
|
|
|
err = conn.StartTLS(opts.TLSConfig)
|
|
if err != nil {
|
|
_ = conn.Close()
|
|
|
|
return nil, fmt.Errorf("StartTLS: %w", err)
|
|
}
|
|
}
|
|
|
|
return conn, nil
|
|
default:
|
|
return nil, fmt.Errorf("invalid scheme: %q", opts.Scheme)
|
|
}
|
|
}
|
|
|
|
func bind(
|
|
ctx context.Context, conn *ldap.Conn, creds *adauth.Credential, target *adauth.Target, opts *Options,
|
|
) (err error) {
|
|
switch {
|
|
case opts.SimpleBind:
|
|
switch {
|
|
case creds.Password == "" && !creds.PasswordIsEmptyString:
|
|
return fmt.Errorf("specify a password for simple bind or -p '' for an unauthenticated simple bind")
|
|
case creds.Password == "" && creds.PasswordIsEmptyString:
|
|
opts.Debug("using unauthenticated simple bind")
|
|
default:
|
|
opts.Debug("authenticating with simple bind")
|
|
}
|
|
|
|
_, err = conn.SimpleBind(&ldap.SimpleBindRequest{
|
|
Username: creds.UPN(),
|
|
Password: creds.Password,
|
|
AllowEmptyPassword: creds.PasswordIsEmptyString,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("simple bind: %w", err)
|
|
}
|
|
case !target.UseKerberos && creds.ClientCert == nil:
|
|
opts.Debug("authenticating using NTLM bind")
|
|
|
|
if !creds.PasswordIsEmptyString && (creds.Password == "" && creds.NTHash == "") {
|
|
return fmt.Errorf("no credentials available for NTLM")
|
|
}
|
|
|
|
bindRequest := &ldap.NTLMBindRequest{
|
|
Domain: creds.Domain,
|
|
Username: creds.Username,
|
|
Password: creds.Password,
|
|
Hash: creds.NTHash,
|
|
AllowEmptyPassword: creds.PasswordIsEmptyString,
|
|
}
|
|
|
|
tlsState, ok := conn.TLSConnectionState()
|
|
if ok && !opts.DisableChannelBinding {
|
|
bindRequest.Negotiator = ntlmNegotiatorWithChannelBinding(tlsState.PeerCertificates[0], creds.Domain)
|
|
} else {
|
|
bindRequest.Negotiator = ntlmNegotiatorForDomain(creds.Domain)
|
|
}
|
|
|
|
_, err = conn.NTLMChallengeBind(bindRequest)
|
|
if err != nil {
|
|
return fmt.Errorf("NTLM bind: %w", err)
|
|
}
|
|
case target.UseKerberos:
|
|
authClient, err := kerberosClient(ctx, conn, creds, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
spn, err := target.SPN(ctx)
|
|
if err != nil {
|
|
return fmt.Errorf("build SPN: %w", err)
|
|
}
|
|
|
|
err = conn.GSSAPIBindRequestWithAPOptions(authClient, &ldap.GSSAPIBindRequest{
|
|
ServicePrincipalName: spn,
|
|
AuthZID: creds.NTHash,
|
|
}, []int{flags.APOptionMutualRequired})
|
|
if err != nil {
|
|
return fmt.Errorf("GSSAPI bind: %w", err)
|
|
}
|
|
case creds.ClientCert != nil && strings.EqualFold(opts.Scheme, "ldap"):
|
|
opts.Debug("authenticating with client certificate via StartTLS")
|
|
|
|
_, ok := conn.TLSConnectionState()
|
|
if !ok {
|
|
opts.Debug("negotiating StartTLS")
|
|
|
|
err = conn.StartTLS(opts.TLSConfig)
|
|
if err != nil {
|
|
return fmt.Errorf("StartTLS: %w", err)
|
|
}
|
|
}
|
|
|
|
err = conn.ExternalBind()
|
|
if err != nil {
|
|
if creds.ClientCert.Issuer.CommonName == "" ||
|
|
strings.EqualFold(creds.ClientCert.Subject.CommonName, creds.ClientCert.Issuer.CommonName) {
|
|
return fmt.Errorf("external bind: %w "+
|
|
"(certificate likely belongs to a KeyCredentialLink, try PKINIT with -k instead)", err)
|
|
}
|
|
|
|
return fmt.Errorf("external bind: %w", err)
|
|
}
|
|
case creds.ClientCert != nil:
|
|
opts.Debug("authenticating with client certificate")
|
|
|
|
res, err := conn.WhoAmI(nil)
|
|
if err != nil {
|
|
return fmt.Errorf("send whoami to verify certificate authentication: %w", err)
|
|
}
|
|
|
|
if res.AuthzID == "" {
|
|
if creds.ClientCert.Issuer.CommonName == "" ||
|
|
strings.EqualFold(creds.ClientCert.Subject.CommonName, creds.ClientCert.Issuer.CommonName) {
|
|
return fmt.Errorf("client certificate authentication failed " +
|
|
"(certificate likely belongs to a KeyCredentialLink, try PKINIT with -k instead)")
|
|
}
|
|
|
|
return fmt.Errorf("client certificate authentication failed")
|
|
}
|
|
default:
|
|
return fmt.Errorf("no credentials available")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func kerberosClient(
|
|
ctx context.Context, conn *ldap.Conn, creds *adauth.Credential, opts *Options,
|
|
) (*gssapiClient, error) {
|
|
krbConf, err := creds.KerberosConfig(ctx)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("configure Kerberos: %w", err)
|
|
}
|
|
|
|
if opts.KerberosDialer == nil {
|
|
opts.KerberosDialer = &net.Dialer{Timeout: pkinit.DefaultKerberosRoundtripDeadline}
|
|
}
|
|
|
|
var (
|
|
authClient *gssapiClient
|
|
cert *x509.Certificate
|
|
)
|
|
|
|
tlsState, ok := conn.TLSConnectionState()
|
|
if ok && !opts.DisableChannelBinding {
|
|
cert = tlsState.PeerCertificates[0]
|
|
}
|
|
|
|
switch {
|
|
case creds.Password != "" || creds.PasswordIsEmptyString:
|
|
opts.Debug("authenticating using GSSAPI bind (password)")
|
|
|
|
authClient = &gssapiClient{
|
|
Client: client.NewWithPassword(
|
|
creds.Username,
|
|
strings.ToUpper(creds.Domain),
|
|
creds.Password,
|
|
compat.Gokrb5ForkV9KerberosConfig(krbConf),
|
|
client.DisablePAFXFAST(true),
|
|
client.Dialer(opts.KerberosDialer),
|
|
),
|
|
}
|
|
|
|
authClient.BindCertificate = cert
|
|
case creds.NTHash != "":
|
|
opts.Debug("authenticating using GSSAPI bind (NT hash)")
|
|
|
|
ntHash, err := hex.DecodeString(creds.NTHash)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode NT hash: %w", err)
|
|
}
|
|
|
|
authClient = &gssapiClient{
|
|
Client: client.NewWithEncryptionKey(
|
|
creds.Username,
|
|
strings.ToUpper(creds.Domain),
|
|
types.EncryptionKey{
|
|
KeyType: etypeID.RC4_HMAC,
|
|
KeyValue: ntHash,
|
|
},
|
|
compat.Gokrb5ForkV9KerberosConfig(krbConf),
|
|
client.DisablePAFXFAST(true),
|
|
client.Dialer(opts.KerberosDialer),
|
|
),
|
|
BindCertificate: cert,
|
|
}
|
|
|
|
authClient.BindCertificate = cert
|
|
case creds.AESKey != "":
|
|
opts.Debug("authenticating using GSSAPI bind (AES key)")
|
|
|
|
aesKey, err := hex.DecodeString(creds.AESKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode AES key: %w", err)
|
|
}
|
|
|
|
var keyType int32
|
|
|
|
switch len(aesKey) {
|
|
case 32:
|
|
keyType = etypeID.AES256_CTS_HMAC_SHA1_96
|
|
case 16:
|
|
keyType = etypeID.AES128_CTS_HMAC_SHA1_96
|
|
default:
|
|
return nil, fmt.Errorf("invalid AES128/AES256 key: key size is %d bytes", len(aesKey))
|
|
}
|
|
|
|
authClient = &gssapiClient{
|
|
Client: client.NewWithEncryptionKey(
|
|
creds.Username,
|
|
strings.ToUpper(creds.Domain),
|
|
types.EncryptionKey{
|
|
KeyType: keyType,
|
|
KeyValue: aesKey,
|
|
},
|
|
compat.Gokrb5ForkV9KerberosConfig(krbConf),
|
|
client.DisablePAFXFAST(true),
|
|
client.Dialer(opts.KerberosDialer),
|
|
),
|
|
BindCertificate: cert,
|
|
}
|
|
|
|
authClient.BindCertificate = cert
|
|
case creds.ClientCert != nil:
|
|
opts.Debug("authenticating using GSSAPI bind (PKINIT)")
|
|
|
|
rsaKey, ok := creds.ClientCertKey.(*rsa.PrivateKey)
|
|
if !ok {
|
|
return nil, fmt.Errorf("cannot use %T because PKINIT requires an RSA key", creds.ClientCertKey)
|
|
}
|
|
|
|
return newPKINITClient(ctx, creds.Username, strings.ToUpper(creds.Domain),
|
|
creds.ClientCert, rsaKey, krbConf, opts.KerberosDialer)
|
|
case creds.CCache != "":
|
|
opts.Debug("authenticating using GSSAPI bind (ccache)")
|
|
|
|
authClient, err = newClientFromCCache(
|
|
creds.Username, strings.ToUpper(creds.Domain), creds.CCache, krbConf, opts.KerberosDialer)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create GSSAPI client from CCACHE: %w", err)
|
|
}
|
|
|
|
authClient.BindCertificate = cert
|
|
default:
|
|
return nil, fmt.Errorf("no credentials available for Kerberos")
|
|
}
|
|
|
|
return authClient, nil
|
|
}
|
|
|
|
// TLSConfig returns a TLS config based on the default config in the provided
|
|
// LDAP options as well as PFX files.
|
|
func TLSConfig(
|
|
opts *Options, clientCert *x509.Certificate, clientCertKey crypto.PrivateKey, caCerts []*x509.Certificate,
|
|
) (*tls.Config, error) {
|
|
tlsConfig := opts.TLSConfig
|
|
if tlsConfig == nil {
|
|
tlsConfig = &tls.Config{}
|
|
}
|
|
|
|
tlsConfig.InsecureSkipVerify = !opts.Verify
|
|
if tlsConfig.MaxVersion == 0 && !opts.DisableChannelBinding {
|
|
tlsConfig.MaxVersion = tls.VersionTLS12 // channel binding is not supported for TLS1.3
|
|
}
|
|
|
|
if clientCert == nil {
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
var (
|
|
keyBytes []byte
|
|
err error
|
|
)
|
|
|
|
switch v := clientCertKey.(type) {
|
|
case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey, *ecdh.PrivateKey:
|
|
keyBytes, err = x509.MarshalPKCS8PrivateKey(v)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal private key: %w", err)
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported client certificate key type: %T", clientCertKey)
|
|
}
|
|
|
|
certPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: clientCert.Raw,
|
|
})
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "PRIVATE KEY",
|
|
Bytes: keyBytes,
|
|
})
|
|
|
|
clientCertificate, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load client certificate: %w", err)
|
|
}
|
|
|
|
tlsConfig.Certificates = append(tlsConfig.Certificates, clientCertificate)
|
|
|
|
if len(caCerts) == 0 {
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
if tlsConfig.RootCAs == nil {
|
|
tlsConfig.RootCAs = x509.NewCertPool()
|
|
}
|
|
|
|
for _, cert := range caCerts {
|
|
tlsConfig.RootCAs.AppendCertsFromPEM(pem.EncodeToMemory(&pem.Block{
|
|
Type: "CERTIFICATE",
|
|
Bytes: cert.Raw,
|
|
}))
|
|
}
|
|
|
|
return tlsConfig, nil
|
|
}
|
|
|
|
// UserAndDomainFromPFX extracts the username and domain from UPNs in the
|
|
// certificate's otherName SAN extension.
|
|
func UserAndDomainFromPFX(pfxFile string, password string) (user string, domain string, err error) {
|
|
pfxData, err := os.ReadFile(pfxFile)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("read PFX: %w", err)
|
|
}
|
|
|
|
_, cert, _, err := adauth.DecodePFX(pfxData, password)
|
|
if err != nil {
|
|
return "", "", fmt.Errorf("decode PFX: %w", err)
|
|
}
|
|
|
|
user, domain = userAndDomainFromCert(cert)
|
|
|
|
return user, domain, nil
|
|
}
|
|
|
|
func userAndDomainFromCert(cert *x509.Certificate) (user string, domain string) {
|
|
upns, err := x509ext.UPNsFromOtherNames(cert)
|
|
if err != nil {
|
|
return "", ""
|
|
}
|
|
|
|
for _, upn := range upns {
|
|
if !strings.Contains(upn, "@") {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(upn, "@")
|
|
if len(parts) != 2 {
|
|
continue
|
|
}
|
|
|
|
return parts[0], parts[1]
|
|
}
|
|
|
|
return "", ""
|
|
}
|
|
|
|
// ChannelBinding hash computes the channel binding token that can be included
|
|
// in the authentication handshake to make sure that the client has established
|
|
// a TLS connection to the correct server.
|
|
func ChannelBindingHash(cert *x509.Certificate) []byte {
|
|
hashType := crypto.SHA256
|
|
|
|
switch cert.SignatureAlgorithm {
|
|
case x509.SHA384WithRSA, x509.ECDSAWithSHA384, x509.SHA384WithRSAPSS:
|
|
hashType = crypto.SHA384
|
|
case x509.SHA512WithRSA, x509.ECDSAWithSHA512, x509.SHA512WithRSAPSS:
|
|
hashType = crypto.SHA512
|
|
}
|
|
|
|
certHasher := hashType.New()
|
|
_, _ = certHasher.Write(cert.Raw)
|
|
certHash := certHasher.Sum(nil)
|
|
prefix := "tls-server-end-point:"
|
|
|
|
// https://learn.microsoft.com/en-us/windows/win32/api/sspi/ns-sspi-sec_channel_bindings
|
|
// https://github.com/jborean93/Mailozaurr/blob
|
|
// /6b565c4a1debdf301a95b93674ff12acdf8c762c/Classes/Class.SaslMechanismWindowsAuth.ps1#L67C19-L68C1
|
|
channelBindingStructure := []byte{
|
|
0, 0, 0, 0, // InitiatorAddrType
|
|
0, 0, 0, 0, // InitiatorLength
|
|
|
|
0, 0, 0, 0, // AcceptorAddrType,
|
|
0, 0, 0, 0, // AcceptorLength,
|
|
}
|
|
|
|
channelBindingStructure = binary.LittleEndian.AppendUint32(channelBindingStructure,
|
|
uint32(len(prefix)+len(certHash))) // ApplicationDataLength
|
|
channelBindingStructure = append(channelBindingStructure, []byte(prefix)...)
|
|
channelBindingStructure = append(channelBindingStructure, certHash...)
|
|
|
|
channelBindingHasher := md5.New()
|
|
channelBindingHasher.Write(channelBindingStructure)
|
|
|
|
hash := channelBindingHasher.Sum(nil)
|
|
|
|
return hash
|
|
}
|