mirror of
https://github.com/RedTeamPentesting/adauth
synced 2026-06-08 12:20:46 +00:00
136 lines
3.3 KiB
Go
136 lines
3.3 KiB
Go
package pkinit
|
|
|
|
import (
|
|
cryptRand "crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/x509"
|
|
"encoding/asn1"
|
|
"fmt"
|
|
"math/big"
|
|
mathRand "math/rand"
|
|
"time"
|
|
|
|
"github.com/jcmturner/gokrb5/v8/config"
|
|
"github.com/jcmturner/gokrb5/v8/iana/nametype"
|
|
"github.com/jcmturner/gokrb5/v8/messages"
|
|
"github.com/jcmturner/gokrb5/v8/types"
|
|
)
|
|
|
|
// NewASReq generates an ASReq configured for PKINIT.
|
|
func NewASReq(
|
|
username string, domain string, cert *x509.Certificate, key *rsa.PrivateKey, dhKey *big.Int, config *config.Config,
|
|
) (asReq messages.ASReq, dhClientNonce []byte, err error) {
|
|
asReq, err = messages.NewASReqForTGT(domain, config, types.NewPrincipalName(nametype.KRB_NT_PRINCIPAL, username))
|
|
if err != nil {
|
|
return asReq, nil, fmt.Errorf("build generic asreq: %w", err)
|
|
}
|
|
|
|
dhClientNonce = NewDiffieHellmanNonce()
|
|
|
|
return asReq, dhClientNonce, ConfigureASReq(&asReq, cert, key, dhKey, dhClientNonce)
|
|
}
|
|
|
|
// ConfigureASReq configures an ASReq for PKINIT.
|
|
func ConfigureASReq(
|
|
asReq *messages.ASReq, cert *x509.Certificate, key *rsa.PrivateKey, dhKey *big.Int, dhClientNonce []byte,
|
|
) error {
|
|
pkAuthenticatorChecksum, err := calculatePKAuthenticatorChecksum(asReq.ReqBody)
|
|
if err != nil {
|
|
return fmt.Errorf("calculate checksum: %w", err)
|
|
}
|
|
|
|
publicKey := DiffieHellmanPublicKey(dhKey)
|
|
|
|
publicKeyBytes, err := asn1.MarshalWithParams(publicKey, "int")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
|
|
authPack := AuthPack{
|
|
PKAuthenticator: PKAuthenticator{
|
|
CUSec: int(now.UnixMicro() - now.Truncate(time.Millisecond).UnixMicro()),
|
|
CTime: now,
|
|
Nonce: int(mathRand.Uint32()), //nolint:gosec
|
|
Checksum: pkAuthenticatorChecksum,
|
|
},
|
|
ClientPublicValue: SubjectPublicKeyInfo{
|
|
Algorithm: AlgorithmIdentifier{
|
|
Algorithm: ephemeralStaticDiffieHellmanKeyAgreementAlgorithmOID,
|
|
Parameters: DomainParameters{
|
|
P: DiffieHellmanPrime,
|
|
G: 2,
|
|
Q: 0,
|
|
},
|
|
},
|
|
PublicKey: asn1.BitString{
|
|
Bytes: publicKeyBytes,
|
|
BitLength: len(publicKeyBytes) * 8,
|
|
},
|
|
},
|
|
SupportedCMSTypes: nil,
|
|
ClientDHNonce: dhClientNonce,
|
|
}
|
|
|
|
authPackBytes, err := asn1.Marshal(authPack)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal auth pack: %w", err)
|
|
}
|
|
|
|
signedAuthPack, err := PKCS7Sign(authPackBytes, key, cert)
|
|
if err != nil {
|
|
return fmt.Errorf("create signed authpack: %w", err)
|
|
}
|
|
|
|
pkASReq := struct {
|
|
SignedAuthPack []byte `asn1:"tag:0"`
|
|
TrustedIdentifiers types.PrincipalName `asn1:"tag:1,explicit,optional"`
|
|
KDCPKID []byte `asn1:"tag:2,optional"`
|
|
}{
|
|
SignedAuthPack: signedAuthPack,
|
|
}
|
|
|
|
pkASReqBytes, err := asn1.Marshal(pkASReq)
|
|
if err != nil {
|
|
return fmt.Errorf("marshal ASReq: %w", err)
|
|
}
|
|
|
|
const paDataTypePKASReq = 16
|
|
|
|
asReq.PAData = append(asReq.PAData, types.PAData{
|
|
PADataType: paDataTypePKASReq,
|
|
PADataValue: pkASReqBytes,
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
// NewDiffieHellmanNonce generates a nonce for the Diffie Hellman key exchange.
|
|
func NewDiffieHellmanNonce() []byte {
|
|
return randomBytes(32)
|
|
}
|
|
|
|
func calculatePKAuthenticatorChecksum(asReqBody messages.KDCReqBody) ([]byte, error) {
|
|
bodyBytes, err := asReqBody.Marshal()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("marshal ASReq body: %w", err)
|
|
}
|
|
|
|
hash := sha1.Sum(bodyBytes)
|
|
|
|
return hash[:], nil
|
|
}
|
|
|
|
func randomBytes(n int) []byte {
|
|
b := make([]byte, n)
|
|
|
|
_, err := cryptRand.Read(b)
|
|
if err != nil {
|
|
panic(err.Error())
|
|
}
|
|
|
|
return b
|
|
}
|