Skip to content

Commit 2950c7f

Browse files
Fix PFX handling when cert order is reversed
1 parent 06c63b3 commit 2950c7f

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed

credentials.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package adauth
22

33
import (
4+
"bytes"
45
"context"
6+
"crypto/ecdsa"
7+
"crypto/ed25519"
58
"crypto/rsa"
69
"crypto/x509"
710
"fmt"
@@ -73,7 +76,7 @@ func CredentialFromPFXBytes(
7376
Domain: domain,
7477
}
7578

76-
key, cert, caCerts, err := pkcs12.DecodeChain(pfxData, pfxPassword)
79+
key, cert, caCerts, err := DecodePFX(pfxData, pfxPassword)
7780
if err != nil {
7881
return nil, fmt.Errorf("decode PFX: %w", err)
7982
}
@@ -258,3 +261,76 @@ func splitUserIntoDomainAndUsername(user string) (domain string, username string
258261
return "", user
259262
}
260263
}
264+
265+
// DecodePFX loads the private key, certificate and certificate chain from PFX
266+
// bytes that may or may not be protected by a password.
267+
func DecodePFX(pfxData []byte, password string) (privateKey any, cert *x509.Certificate, chain []*x509.Certificate, err error) {
268+
// In some PFXs, especially those create by Microsoft tools, the cert and
269+
// chain order is reversed such that pkcs12.DecodeChain returns the CA cert
270+
// as "cert" and the leaf certificate in the chain (see
271+
// https://github.com/SSLMate/go-pkcs12/issues/54). Our strategy is that we
272+
// swap certifiates such that "cert" is the certificate that belongs to the
273+
// private key and "chain" contains all other certificates.
274+
privateKey, cert, chain, err = pkcs12.DecodeChain(pfxData, password)
275+
if err != nil || certMatchesKey(privateKey, cert) {
276+
return privateKey, cert, chain, err
277+
}
278+
279+
for i := range chain {
280+
if !certMatchesKey(privateKey, chain[i]) {
281+
continue
282+
}
283+
284+
newCert := chain[i]
285+
chain[i] = cert
286+
287+
return privateKey, newCert, chain, nil
288+
}
289+
290+
return privateKey, cert, chain, fmt.Errorf("private key does not match any of the %d certificates in PFX", len(chain)+1)
291+
}
292+
293+
func certMatchesKey(key any, cert *x509.Certificate) bool {
294+
switch pub := cert.PublicKey.(type) {
295+
case *rsa.PublicKey:
296+
priv, ok := key.(*rsa.PrivateKey)
297+
if !ok {
298+
return false
299+
}
300+
301+
if pub.N.Cmp(priv.N) != 0 {
302+
return false
303+
}
304+
305+
return true
306+
case *ecdsa.PublicKey:
307+
priv, ok := key.(*ecdsa.PrivateKey)
308+
if !ok {
309+
return false
310+
}
311+
312+
if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 {
313+
return false
314+
}
315+
316+
return true
317+
case ed25519.PublicKey:
318+
priv, ok := key.(ed25519.PrivateKey)
319+
if !ok {
320+
return false
321+
}
322+
323+
privPublicKey, ok := priv.Public().(ed25519.PublicKey)
324+
if !ok {
325+
return false
326+
}
327+
328+
if !bytes.Equal(privPublicKey, pub) {
329+
return false
330+
}
331+
332+
return true
333+
default:
334+
return false
335+
}
336+
}

ldapauth/ldap.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import (
2929
"github.com/oiweiwei/gokrb5.fork/v9/iana/flags"
3030
"github.com/oiweiwei/gokrb5.fork/v9/types"
3131
"github.com/spf13/pflag"
32-
"software.sslmate.com/src/go-pkcs12"
3332
)
3433

3534
// Options holds LDAP specific options.
@@ -161,7 +160,7 @@ func connect(ctx context.Context, target *adauth.Target, opts *Options) (conn *l
161160

162161
tlsConn := tls.Client(tcpConn, opts.TLSConfig)
163162

164-
err = tlsConn.Handshake()
163+
err = tlsConn.HandshakeContext(ctx)
165164
if err != nil {
166165
return nil, err
167166
}
@@ -525,7 +524,7 @@ func UserAndDomainFromPFX(pfxFile string, password string) (user string, domain
525524
return "", "", fmt.Errorf("read PFX: %w", err)
526525
}
527526

528-
_, cert, _, err := pkcs12.DecodeChain(pfxData, password)
527+
_, cert, _, err := adauth.DecodePFX(pfxData, password)
529528
if err != nil {
530529
return "", "", fmt.Errorf("decode PFX: %w", err)
531530
}

options.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414

1515
"github.com/RedTeamPentesting/adauth/x509ext"
1616
"github.com/spf13/pflag"
17-
"software.sslmate.com/src/go-pkcs12"
1817
)
1918

2019
// Options holds command line options that are used to determine authentication
@@ -333,7 +332,7 @@ func readPFX(fileName string, password string) (*x509.Certificate, any, []*x509.
333332
return nil, nil, nil, fmt.Errorf("read PFX: %w", err)
334333
}
335334

336-
key, cert, caCerts, err := pkcs12.DecodeChain(pfxData, password)
335+
key, cert, caCerts, err := DecodePFX(pfxData, password)
337336
if err != nil {
338337
return nil, nil, nil, fmt.Errorf("decode PFX: %w", err)
339338
}

0 commit comments

Comments
 (0)