pkinit
import "github.com/TheManticoreProject/Manticore/network/kerberos/v5/pkinit"
Package pkinit implements the PKINIT (RFC 4556) Diffie-Hellman AS exchange for Kerberos v5: building a PA-PK-AS-REQ (a CMS SignedData wrapping an AuthPack with the client’s ephemeral DH public value) and parsing the corresponding PA-PK-AS-REP (dhInfo variant) to recover the KDC’s DH public value, compute the shared secret, and derive the AS reply key via RFC 4556 §3.2.3.1 octetstring2key.
It underpins certificate-based authentication and the Shadow Credentials technique (writing a public key to a target’s msDS-KeyCredentialLink and then PKINIT-authenticating as that target). All cryptography is native Go stdlib (crypto/rsa, crypto/rand, math/big, crypto/sha1, encoding/asn1); the CMS SignedData is hand-built (see cms.go) with no external PKCS#7 dependency.
Index
- Constants
- func AgilityKDF(newHash func() hash.Hash, z, otherInfo []byte, keyLen int) []byte
- func BuildSignedAuthPack(authPackDER []byte, priv *rsa.PrivateKey, certDER []byte) ([]byte, error)
- func GenerateSelfSignedCert(bits int, subjectCN string) (*rsa.PrivateKey, []byte, error)
- func OctetString2Key(x []byte, keyLen int) []byte
- type DHGroup
- type DHKeyPair
- type KDFInputs
- type PrincipalName
- type Reply
- type Request
- func BuildASReqPAData(reqBodyDER []byte, priv *rsa.PrivateKey, certDER []byte, group DHGroup, nonce int, now time.Time) ([]byte, *Request, error)
- func (r *Request) DeriveReplyKey(reply *Reply, keyLen int) ([]byte, error)
- func (r *Request) DeriveReplyKeyAgility(reply *Reply, keyLen int, in KDFInputs) ([]byte, error)
- func (r *Request) ReplyKeyCandidates(reply *Reply, keyLen int) ([][]byte, error)
- type VerifyOptions
Constants
DHNonceLen is the length in bytes of the client Diffie-Hellman nonce.
const DHNonceLen = 32
func AgilityKDF
func AgilityKDF(newHash func() hash.Hash, z, otherInfo []byte, keyLen int) []byte
AgilityKDF implements the RFC 8636 §3 PKINIT algorithm-agility key-derivation function: the NIST SP800-56A single-step (concatenation) KDF instantiated with a SHA-2 (or SHA-1) hash. It derives a reply key of keyLen bytes from the DH shared secret z and the DER-encoded OtherInfo structure (see BuildKDFOtherInfo):
reps = ceil(keyLen*8 / H_outputBits)
counter = 0x00000001 (32-bit big-endian, incrementing)
Hash_i = H(counter || z || OtherInfo) for i = 1..reps
key = (Hash_1 || Hash_2 || ...) truncated to keyLen bytes
newHash selects the hash primitive per the negotiated kdfID (see kdfHash). Unlike the RFC 4556 OctetString2Key above, the counter is a four-octet big-endian value that *precedes* the secret, and the OtherInfo (party info and suppPubInfo) is appended after z.
func BuildSignedAuthPack
func BuildSignedAuthPack(authPackDER []byte, priv *rsa.PrivateKey, certDER []byte) ([]byte, error)
BuildSignedAuthPack builds the CMS SignedData ContentInfo that goes into the PA-PK-AS-REQ signedAuthPack field: it signs the DER-encoded AuthPack with the client RSA private key, embedding certDER as the signer certificate. The digest algorithm is SHA-1 and the signature is RSA PKCS#1 v1.5, matching the algorithms Windows KDCs accept for PKINIT.
func GenerateSelfSignedCert
func GenerateSelfSignedCert(bits int, subjectCN string) (*rsa.PrivateKey, []byte, error)
GenerateSelfSignedCert generates a fresh RSA key pair and a self-signed X.509 certificate over it, suitable for the Shadow Credentials technique: the certificate’s public key is what gets registered in a target’s msDS-KeyCredentialLink, and the private key signs the PKINIT AuthPack. The certificate is not validated by the KDC in the key-trust model (the KDC maps the client via the registered key, not a PKI chain), so a self-signed certificate with an arbitrary subject is sufficient.
bits is the RSA modulus size (2048 recommended). subjectCN is the certificate subject common name (cosmetic).
func OctetString2Key
func OctetString2Key(x []byte, keyLen int) []byte
OctetString2Key implements the RFC 4556 §3.2.3.1 key-derivation function used to turn the PKINIT Diffie-Hellman shared secret into the AS reply key:
octetstring2key(x) == random-to-key(K-truncate(
SHA1(0x00 | x) | SHA1(0x01 | x) | SHA1(0x02 | x) | ... ))
where x = DHSharedSecret | n_c | n_k, K-truncate keeps the first K bits, and random-to-key() derives a protocol key of length keyLen bytes from that bitstring. For the AES and RC4 enctypes used by Windows KDCs random-to-key is the identity function, so the truncated SHA-1 stream is the key directly.
keyLen is the key-generation seed length in bytes for the AS reply key’s enctype (16 for AES128/RC4, 32 for AES256).
type DHGroup
DHGroup describes a MODP Diffie-Hellman group (RFC 2412 / RFC 3526) used for PKINIT key agreement. Only the prime modulus P and generator G are needed to perform the exchange; the subgroup order Q is advertised to the KDC in the DomainParameters (a value of 0 means “unspecified”, which the Windows KDC accepts, matching interoperable PKINIT clients).
type DHGroup struct {
// ID is a human-readable label ("modp2", "modp14").
ID string
// P is the prime modulus.
P *big.Int
// G is the generator.
G *big.Int
// Q is the subgroup order advertised in DomainParameters (0 = unspecified).
Q *big.Int
}
func MODPGroup14
func MODPGroup14() DHGroup
MODPGroup14 returns the 2048-bit MODP Diffie-Hellman group (RFC 4556 group 14).
func MODPGroup2
func MODPGroup2() DHGroup
MODPGroup2 returns the 1024-bit MODP Diffie-Hellman group (RFC 4556 group 2). This is the group most widely interoperable with Windows KDCs.
type DHKeyPair
DHKeyPair is an ephemeral Diffie-Hellman key pair for a PKINIT exchange.
type DHKeyPair struct {
Group DHGroup
// X is the private exponent.
X *big.Int
// Y is the public value G^X mod P.
Y *big.Int
}
func GenerateDHKeyPair
func GenerateDHKeyPair(group DHGroup) (*DHKeyPair, error)
GenerateDHKeyPair creates a fresh ephemeral DH key pair in the given group. The private exponent is drawn uniformly from [2, P-2].
func (*DHKeyPair) SharedSecret
func (kp *DHKeyPair) SharedSecret(peerY *big.Int) ([]byte, error)
SharedSecret computes the DH shared secret peerY^X mod P and returns it as a big-endian octet string left-padded with zeros to the length of the prime modulus, as required by RFC 4556 §3.2.3.1 (“padded with leading zeros … so its size in octets equals the modulus”).
type KDFInputs
KDFInputs carries the RFC 8636 §3 OtherInfo inputs the caller threads into the algorithm-agility KDF: the party identities (client and TGS, mirroring the AS-REQ cname/sname), the reply-key enctype, and the request/reply DER blobs bound into suppPubInfo. The DH shared secret (Z) is taken from the Request’s key pair and is not part of this struct.
type KDFInputs struct {
// ClientRealm / ClientName identify the client (partyUInfo).
ClientRealm string
ClientName PrincipalName
// ServerRealm / ServerName identify the ticket-granting server (partyVInfo).
ServerRealm string
ServerName PrincipalName
// EType is the AS reply-key enctype (suppPubInfo.enctype).
EType int
// ASReq is the DER of the AS-REQ (no TCP length prefix); suppPubInfo.as-REQ.
ASReq []byte
// PKASRep is the DER of the PA-PK-AS-REP value from the reply;
// suppPubInfo.pk-as-rep.
PKASRep []byte
}
type PrincipalName
PrincipalName identifies a Kerberos principal (name-type and components) for the RFC 8636 KDF party info, without the pkinit package depending on the messages package.
type PrincipalName struct {
NameType int
NameString []string
}
type Reply
Reply holds the values recovered from a PA-PK-AS-REP (dhInfo variant).
type Reply struct {
// KDCPublicValue is the KDC's DH public value.
KDCPublicValue *big.Int
// ServerDHNonce is the KDC's DH nonce (may be empty).
ServerDHNonce []byte
// KDFID is the RFC 8636 algorithm-agility KDF the KDC selected (the kdf-id
// OID from DHRepInfo.kdf), or nil when the KDC returned no kdf field. A nil
// KDFID means the legacy RFC 4556 SHA-1 OctetString2Key applies (the default
// on KDCs that do not implement RFC 8636, e.g. current Windows).
KDFID asn1.ObjectIdentifier
}
func ParseASRepPAData
func ParseASRepPAData(paValue []byte, opts *VerifyOptions) (*Reply, error)
ParseASRepPAData parses the PA-PK-AS-REP pre-authentication value (padata type 17), extracting the KDC’s DH public value and server DH nonce from the dhSignedData/KDCDHKeyInfo. It only supports the Diffie-Hellman (dhInfo) variant.
opts controls verification of the KDC’s CMS SignedData signature over the KDCDHKeyInfo (RFC 4556 §3.2.4): when it supplies a trust anchor the signature and certificate chain are verified and parsing fails closed on any mismatch; a nil opts (or one with InsecureSkipSignatureCheck) skips that check.
type Request
Request bundles the client state for one PKINIT AS exchange: the ephemeral DH key pair and the client DH nonce. It is produced by BuildASReqPAData and consumed by DeriveReplyKey after the reply is parsed.
type Request struct {
// KeyPair is the ephemeral client DH key pair.
KeyPair *DHKeyPair
// ClientDHNonce is the random client DH nonce sent in the AuthPack.
ClientDHNonce []byte
}
func BuildASReqPAData
func BuildASReqPAData(reqBodyDER []byte, priv *rsa.PrivateKey, certDER []byte, group DHGroup, nonce int, now time.Time) ([]byte, *Request, error)
BuildASReqPAData builds the PA-PK-AS-REQ pre-authentication value for an AS-REQ.
reqBodyDER is the DER encoding of the KDC-REQ-BODY that will be sent (the paChecksum is SHA-1 over exactly these bytes, RFC 4556 §3.2.1). priv/certDER are the client’s RSA key and (self-signed, for Shadow Credentials) certificate used to sign the AuthPack. group selects the MODP DH group. nonce/now are the PKAuthenticator nonce and timestamp.
It returns the PA-DATA value bytes (to be wrapped as PA-PK-AS-REQ, padata type 16) and a Request capturing the ephemeral DH state needed to derive the reply key.
func (*Request) DeriveReplyKey
func (r *Request) DeriveReplyKey(reply *Reply, keyLen int) ([]byte, error)
DeriveReplyKey computes the AS reply key from a parsed PKINIT reply: it derives the DH shared secret, then applies octetstring2key over (DHSharedSecret | clientDHNonce | serverDHNonce) truncated to keyLen bytes (RFC 4556 §3.2.3.1). keyLen is the key length of the AS-REP enctype.
func (*Request) DeriveReplyKeyAgility
func (r *Request) DeriveReplyKeyAgility(reply *Reply, keyLen int, in KDFInputs) ([]byte, error)
DeriveReplyKeyAgility derives the AS reply key via the RFC 8636 algorithm- agility KDF the KDC selected (reply.KDFID), keyed by the DH shared secret and the OtherInfo built from in. keyLen is the reply-key enctype’s key length in bytes. It returns an error if reply.KDFID is empty or names a KDF this client does not implement (callers use DeriveReplyKey / ReplyKeyCandidates for the legacy no-kdfID case).
func (*Request) ReplyKeyCandidates
func (r *Request) ReplyKeyCandidates(reply *Reply, keyLen int) ([][]byte, error)
ReplyKeyCandidates returns the plausible octetstring2key inputs, in preference order, so a caller can try each until the AS-REP enc-part decrypts. RFC 4556 says the nonces are included only when DH keys are reused and empty otherwise; Windows KDC behaviour varies, so the candidates cover both cases.
type VerifyOptions
VerifyOptions controls verification of the KDC’s CMS SignedData signature on the PKINIT reply (RFC 4556 §3.2.4). Verification is performed whenever a trust anchor is present and InsecureSkipSignatureCheck is not set: the KDC’s signature over the KDCDHKeyInfo is checked and the signer certificate must chain to (or be pinned by) one of Anchors. A nil *VerifyOptions means no verification (the legacy behaviour, kept for callers that cannot supply an anchor).
type VerifyOptions struct {
// Anchors are the trusted certificates the KDC signer certificate must
// chain to — the issuing CA (or root) certificate — or, when pinned
// directly, the KDC signing certificate itself (which also covers a
// self-signed KDC certificate). At least one is required unless
// InsecureSkipSignatureCheck is set.
Anchors []*x509.Certificate
// InsecureSkipSignatureCheck disables signature and chain verification for
// the anonymous / self-signed lab case where no anchor can be pinned. It is
// insecure: it removes the RFC 4556 §3.2.4 protection against a substituted
// KDC DH public value, so set it only knowingly.
InsecureSkipSignatureCheck bool
}