mirror of
https://github.com/RedTeamPentesting/adauth
synced 2026-06-08 12:20:46 +00:00
Allow non-RSA certificate keys
This commit is contained in:
+1
-1
@@ -33,7 +33,7 @@ type Credential struct {
|
||||
// ClientCert holds a client certificate for Kerberos or LDAP authentication if available.
|
||||
ClientCert *x509.Certificate
|
||||
// ClientCertKey holds the private key that corresponds to ClientCert.
|
||||
ClientCertKey *rsa.PrivateKey
|
||||
ClientCertKey any
|
||||
// CACerts holds CA certificates that were loaded alongside the ClientCert.
|
||||
CACerts []*x509.Certificate
|
||||
dc string
|
||||
|
||||
@@ -2,6 +2,7 @@ package dcerpcauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
@@ -170,8 +171,13 @@ func DCERPCCredentials(ctx context.Context, creds *adauth.Credential, options *O
|
||||
dialer = &net.Dialer{Timeout: pkinit.DefaultKerberosRoundtripDeadline}
|
||||
}
|
||||
|
||||
rsaKey, ok := creds.ClientCertKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("cannot use %T because PKINIT requires an RSA key", creds.ClientCertKey)
|
||||
}
|
||||
|
||||
ccache, err := pkinit.Authenticate(ctx, creds.Username, strings.ToUpper(creds.Domain),
|
||||
creds.ClientCert, creds.ClientCertKey, krbConf, pkinit.WithDialer(adauth.AsContextDialer(dialer)))
|
||||
creds.ClientCert, rsaKey, krbConf, pkinit.WithDialer(adauth.AsContextDialer(dialer)))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("PKINIT: %w", err)
|
||||
}
|
||||
|
||||
+6
-1
@@ -425,8 +425,13 @@ func kerberosClient(
|
||||
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, creds.ClientCertKey, krbConf, opts.KerberosDialer)
|
||||
creds.ClientCert, rsaKey, krbConf, opts.KerberosDialer)
|
||||
case creds.CCache != "":
|
||||
opts.Debug("authenticating using GSSAPI bind (ccache)")
|
||||
|
||||
|
||||
+92
-17
@@ -2,8 +2,9 @@ package adauth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
@@ -28,8 +29,15 @@ type Options struct {
|
||||
CCache string
|
||||
DomainController string
|
||||
ForceKerberos bool
|
||||
PFXFileName string
|
||||
PFXPassword string
|
||||
|
||||
// It is possible to specify a cert/key pair directly, as PEM files or as a
|
||||
// single PFX file.
|
||||
Certificate *x509.Certificate
|
||||
CertificateKey any
|
||||
PFXFileName string
|
||||
PFXPassword string
|
||||
PEMCertFileName string
|
||||
PEMKeyFileName string
|
||||
|
||||
credential *Credential
|
||||
flagset *pflag.FlagSet
|
||||
@@ -273,25 +281,31 @@ func (opts *Options) preliminaryCredential() (*Credential, error) {
|
||||
Resolver: opts.Resolver,
|
||||
}
|
||||
|
||||
if opts.PFXFileName != "" {
|
||||
pfxData, err := os.ReadFile(opts.PFXFileName)
|
||||
switch {
|
||||
case opts.Certificate != nil && opts.CertificateKey == nil:
|
||||
return nil, fmt.Errorf("specify a key file for the client certificate")
|
||||
case opts.Certificate != nil && opts.CertificateKey != nil:
|
||||
cred.ClientCert = opts.Certificate
|
||||
cred.ClientCertKey = opts.CertificateKey
|
||||
case opts.PFXFileName != "":
|
||||
cert, key, caCerts, err := readPFX(opts.PFXFileName, opts.PFXPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read PFX: %w", err)
|
||||
}
|
||||
|
||||
key, cert, caCerts, err := pkcs12.DecodeChain(pfxData, opts.PFXPassword)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("decode PFX: %w", err)
|
||||
}
|
||||
|
||||
rsaKey, ok := key.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("PFX key is not an RSA private key but %T", rsaKey)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cred.ClientCert = cert
|
||||
cred.ClientCertKey = rsaKey
|
||||
cred.ClientCertKey = key
|
||||
cred.CACerts = caCerts
|
||||
case opts.PEMCertFileName != "" && opts.PEMKeyFileName == "":
|
||||
return nil, fmt.Errorf("specify a key file for the client certificate")
|
||||
case opts.PEMCertFileName != "" && opts.PEMKeyFileName != "":
|
||||
cert, key, err := readPEMCertAndKey(opts.PEMCertFileName, opts.PEMKeyFileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cred.ClientCert = cert
|
||||
cred.ClientCertKey = key
|
||||
}
|
||||
|
||||
//nolint:nestif
|
||||
@@ -313,6 +327,67 @@ func (opts *Options) preliminaryCredential() (*Credential, error) {
|
||||
return cred, nil
|
||||
}
|
||||
|
||||
func readPFX(fileName string, password string) (*x509.Certificate, any, []*x509.Certificate, error) {
|
||||
pfxData, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("read PFX: %w", err)
|
||||
}
|
||||
|
||||
key, cert, caCerts, err := pkcs12.DecodeChain(pfxData, password)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("decode PFX: %w", err)
|
||||
}
|
||||
|
||||
return cert, key, caCerts, nil
|
||||
}
|
||||
|
||||
func readPEMCertAndKey(certFileName string, certKeyFileName string) (*x509.Certificate, any, error) {
|
||||
certData, err := os.ReadFile(certFileName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("read cert file: %w", err)
|
||||
}
|
||||
|
||||
block, _ := pem.Decode(certData)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("could not PEM-decode certificate")
|
||||
}
|
||||
|
||||
if block.Type != "" && !strings.Contains(strings.ToLower(block.Type), "certificate") {
|
||||
return nil, nil, fmt.Errorf("unexpected block type for certificate: %q", block.Type)
|
||||
}
|
||||
|
||||
cert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("parse certificate: %w", err)
|
||||
}
|
||||
|
||||
certKeyData, err := os.ReadFile(certKeyFileName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("read cert key file: %w", err)
|
||||
}
|
||||
|
||||
block, _ = pem.Decode(certKeyData)
|
||||
if block == nil {
|
||||
return nil, nil, fmt.Errorf("could not PEM-decode certificate key")
|
||||
}
|
||||
|
||||
if block.Type != "" && !strings.Contains(strings.ToLower(block.Type), "key") {
|
||||
return nil, nil, fmt.Errorf("unexpected block type for key: %q", block.Type)
|
||||
}
|
||||
|
||||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
key, pkcs1Err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
||||
if pkcs1Err == nil {
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("parse private key: %w", err)
|
||||
}
|
||||
|
||||
return cert, key, nil
|
||||
}
|
||||
|
||||
// NewDebugFunc creates a debug output handler.
|
||||
func NewDebugFunc(enabled *bool, writer io.Writer, colored bool) func(string, ...any) {
|
||||
return func(format string, a ...any) {
|
||||
|
||||
@@ -58,7 +58,12 @@ func UnPACTheHashFromPFXData(
|
||||
return nil, nil, fmt.Errorf("configure Kerberos: %w", err)
|
||||
}
|
||||
|
||||
return UnPACTheHash(ctx, cred.Username, cred.Domain, cred.ClientCert, cred.ClientCertKey, krbConf, opts...)
|
||||
rsaKey, ok := cred.ClientCertKey.(*rsa.PrivateKey)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("cannot use %T because PKINIT requires an RSA key", cred.ClientCertKey)
|
||||
}
|
||||
|
||||
return UnPACTheHash(ctx, cred.Username, cred.Domain, cred.ClientCert, rsaKey, krbConf, opts...)
|
||||
}
|
||||
|
||||
// UnPACTheHash retrieves the user's NT hash via PKINIT using the provided
|
||||
|
||||
Reference in New Issue
Block a user