Allow non-RSA certificate keys

This commit is contained in:
Erik Geiser
2025-06-03 10:26:20 +02:00
parent 7075c4d69b
commit f0d4c96b59
5 changed files with 112 additions and 21 deletions
+1 -1
View File
@@ -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
+7 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+6 -1
View File
@@ -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