Files
2026-08-09 05:33:21 +08:00

369 lines
11 KiB
Go

package ike
import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"math/big"
"strings"
"vocat/internal/vowifi"
)
const (
authMethodRSASignature = 1
authMethodSharedKeyMIC = 2
authMethodECDSASHA256P256 = 9
authMethodECDSASHA384P384 = 10
authMethodECDSASHA512P521 = 11
authMethodDigitalSignature = 14
)
var (
oidSHA256WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
oidSHA384WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
oidSHA512WithRSA = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
oidECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
oidECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
oidECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
)
func responderSignedOctets(
initialResponse []byte,
initiatorNonce []byte,
suite negotiatedSuite,
skpr []byte,
idr payload,
) ([]byte, error) {
idHash, err := prf(suite, skpr, idr.Body)
if err != nil {
return nil, err
}
signed := make([]byte, 0, len(initialResponse)+len(initiatorNonce)+len(idHash))
signed = append(signed, initialResponse...)
signed = append(signed, initiatorNonce...)
signed = append(signed, idHash...)
return signed, nil
}
func initiatorSignedOctets(
initialRequest []byte,
responderNonce []byte,
suite negotiatedSuite,
skpi []byte,
idi payload,
) ([]byte, error) {
idHash, err := prf(suite, skpi, idi.Body)
if err != nil {
return nil, err
}
signed := make([]byte, 0, len(initialRequest)+len(responderNonce)+len(idHash))
signed = append(signed, initialRequest...)
signed = append(signed, responderNonce...)
signed = append(signed, idHash...)
return signed, nil
}
func makeEAPInitiatorAUTH(
msk []byte,
initialRequest []byte,
responderNonce []byte,
suite negotiatedSuite,
skpi []byte,
idi payload,
) (payload, error) {
signed, err := initiatorSignedOctets(initialRequest, responderNonce, suite, skpi, idi)
if err != nil {
return payload{}, err
}
paddedKey, err := prf(suite, msk, []byte("Key Pad for IKEv2"))
if err != nil {
return payload{}, err
}
authValue, err := prf(suite, paddedKey, signed)
if err != nil {
return payload{}, err
}
body := make([]byte, 4+len(authValue))
body[0] = authMethodSharedKeyMIC
copy(body[4:], authValue)
return payload{Type: payloadAuth, Body: body}, nil
}
func verifyEAPResponderAUTH(
auth payload,
msk []byte,
initialResponse []byte,
initiatorNonce []byte,
suite negotiatedSuite,
skpr []byte,
idr payload,
) error {
if len(auth.Body) < 4 || auth.Body[0] != authMethodSharedKeyMIC {
return errors.New("ike: final responder AUTH does not use the EAP shared-key MIC")
}
signed, err := responderSignedOctets(initialResponse, initiatorNonce, suite, skpr, idr)
if err != nil {
return err
}
paddedKey, err := prf(suite, msk, []byte("Key Pad for IKEv2"))
if err != nil {
return err
}
expected, err := prf(suite, paddedKey, signed)
if err != nil {
return err
}
if len(auth.Body[4:]) != len(expected) || subtle.ConstantTimeCompare(auth.Body[4:], expected) != 1 {
return errors.New("ike: final responder AUTH is invalid")
}
return nil
}
func validateInitialResponderAUTH(
payloads []payload,
initialResponse []byte,
initiatorNonce []byte,
suite negotiatedSuite,
skpr []byte,
expectedIDr string,
serverName string,
roots *x509.CertPool,
pinned crypto.PublicKey,
allowMissing bool,
) (vowifi.ResponderAUTHStatus, payload, error) {
idrPayloads := payloadsOfType(payloads, payloadIDr)
authPayloads := payloadsOfType(payloads, payloadAuth)
if len(authPayloads) == 0 {
if len(idrPayloads) > 1 {
return vowifi.ResponderAUTHInvalid, payload{}, errors.New("ike: duplicate responder identity payload")
}
if allowMissing {
if len(idrPayloads) == 1 {
return vowifi.ResponderAUTHMissing, idrPayloads[0], nil
}
return vowifi.ResponderAUTHMissing, payload{}, nil
}
return vowifi.ResponderAUTHMissing, payload{}, vowifi.ErrResponderAUTHRequired
}
if len(authPayloads) != 1 || len(idrPayloads) != 1 {
return vowifi.ResponderAUTHInvalid, payload{}, errors.New("ike: responder AUTH requires exactly one IDr and AUTH payload")
}
idr := idrPayloads[0]
auth := authPayloads[0]
if len(idr.Body) < 4 || len(auth.Body) < 5 {
return vowifi.ResponderAUTHInvalid, idr, errors.New("ike: responder IDr or AUTH payload is truncated")
}
if err := validateFQDNIDr(idr, expectedIDr, "initial ePDG"); err != nil {
return vowifi.ResponderAUTHInvalid, idr, err
}
publicKey := pinned
if publicKey == nil {
certificates, err := parseResponderCertificates(payloads)
if err != nil {
return vowifi.ResponderAUTHInvalid, idr, err
}
if len(certificates) == 0 {
return vowifi.ResponderAUTHInvalid, idr, errors.New("ike: responder AUTH has no certificate or pinned public key")
}
if err := verifyResponderCertificate(certificates, roots, serverName); err != nil {
return vowifi.ResponderAUTHInvalid, idr, err
}
publicKey = certificates[0].PublicKey
}
signed, err := responderSignedOctets(initialResponse, initiatorNonce, suite, skpr, idr)
if err != nil {
return vowifi.ResponderAUTHInvalid, idr, err
}
if err := verifyDigitalAUTH(publicKey, auth.Body[0], auth.Body[4:], signed); err != nil {
return vowifi.ResponderAUTHInvalid, idr, fmt.Errorf("ike: invalid responder AUTH: %w", err)
}
return vowifi.ResponderAUTHVerified, idr, nil
}
func validateFQDNIDr(idr payload, expectedIDr string, label string) error {
if len(idr.Body) < 4 {
return errors.New("ike: responder identity is truncated")
}
identityType := idr.Body[0]
identity := strings.TrimSpace(string(idr.Body[4:]))
if identityType != 2 {
return fmt.Errorf("ike: %s IDr must use ID_FQDN, got type %d", label, identityType)
}
if identity == "" {
return fmt.Errorf("ike: %s IDr is empty", label)
}
if expectedIDr != "" && !strings.EqualFold(strings.TrimSuffix(identity, "."), strings.TrimSuffix(expectedIDr, ".")) {
return fmt.Errorf("ike: %s IDr %q does not match %q", label, identity, expectedIDr)
}
return nil
}
func parseResponderCertificates(payloads []payload) ([]*x509.Certificate, error) {
var certificates []*x509.Certificate
for _, item := range payloadsOfType(payloads, payloadCert) {
if len(item.Body) < 2 {
return nil, errors.New("ike: responder certificate payload is truncated")
}
if item.Body[0] != 4 {
return nil, fmt.Errorf("ike: unsupported responder certificate encoding %d", item.Body[0])
}
certificate, err := x509.ParseCertificate(item.Body[1:])
if err != nil {
return nil, fmt.Errorf("ike: parse responder certificate: %w", err)
}
certificates = append(certificates, certificate)
}
return certificates, nil
}
func verifyResponderCertificate(certificates []*x509.Certificate, roots *x509.CertPool, serverName string) error {
if len(certificates) == 0 {
return errors.New("ike: no responder certificate")
}
if roots == nil {
var err error
roots, err = x509.SystemCertPool()
if err != nil {
return fmt.Errorf("ike: load system certificate roots: %w", err)
}
}
intermediates := x509.NewCertPool()
for _, certificate := range certificates[1:] {
intermediates.AddCert(certificate)
}
options := x509.VerifyOptions{
Roots: roots,
Intermediates: intermediates,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSName: strings.TrimSuffix(serverName, "."),
}
if _, err := certificates[0].Verify(options); err != nil {
return fmt.Errorf("ike: verify responder certificate: %w", err)
}
return nil
}
func verifyDigitalAUTH(publicKey crypto.PublicKey, method uint8, signature, signed []byte) error {
switch method {
case authMethodRSASignature:
key, ok := publicKey.(*rsa.PublicKey)
if !ok {
return errors.New("RSA AUTH used with a non-RSA public key")
}
digest := sha1.Sum(signed)
return rsa.VerifyPKCS1v15(key, crypto.SHA1, digest[:], signature)
case authMethodECDSASHA256P256:
return verifyRawECDSA(publicKey, crypto.SHA256, signature, signed)
case authMethodECDSASHA384P384:
return verifyRawECDSA(publicKey, crypto.SHA384, signature, signed)
case authMethodECDSASHA512P521:
return verifyRawECDSA(publicKey, crypto.SHA512, signature, signed)
case authMethodDigitalSignature:
return verifyGenericSignature(publicKey, signature, signed)
default:
return fmt.Errorf("unsupported responder AUTH method %d", method)
}
}
func verifyRawECDSA(publicKey crypto.PublicKey, algorithm crypto.Hash, signature, signed []byte) error {
key, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("ECDSA AUTH used with a non-ECDSA public key")
}
size := (key.Curve.Params().BitSize + 7) / 8
if len(signature) != size*2 {
return fmt.Errorf("ECDSA signature length %d does not match curve size %d", len(signature), size)
}
digest, err := hashSignedOctets(algorithm, signed)
if err != nil {
return err
}
r := new(big.Int).SetBytes(signature[:size])
s := new(big.Int).SetBytes(signature[size:])
if !ecdsa.Verify(key, digest, r, s) {
return errors.New("ECDSA signature verification failed")
}
return nil
}
func verifyGenericSignature(publicKey crypto.PublicKey, encoded, signed []byte) error {
var algorithm pkix.AlgorithmIdentifier
rest, err := asn1.Unmarshal(encoded, &algorithm)
if err != nil || len(rest) == 0 {
return errors.New("generic digital signature has an invalid AlgorithmIdentifier")
}
var hashAlgorithm crypto.Hash
var isRSA bool
switch {
case algorithm.Algorithm.Equal(oidSHA256WithRSA):
hashAlgorithm, isRSA = crypto.SHA256, true
case algorithm.Algorithm.Equal(oidSHA384WithRSA):
hashAlgorithm, isRSA = crypto.SHA384, true
case algorithm.Algorithm.Equal(oidSHA512WithRSA):
hashAlgorithm, isRSA = crypto.SHA512, true
case algorithm.Algorithm.Equal(oidECDSAWithSHA256):
hashAlgorithm = crypto.SHA256
case algorithm.Algorithm.Equal(oidECDSAWithSHA384):
hashAlgorithm = crypto.SHA384
case algorithm.Algorithm.Equal(oidECDSAWithSHA512):
hashAlgorithm = crypto.SHA512
default:
return fmt.Errorf("unsupported generic signature algorithm %s", algorithm.Algorithm.String())
}
digest, err := hashSignedOctets(hashAlgorithm, signed)
if err != nil {
return err
}
if isRSA {
key, ok := publicKey.(*rsa.PublicKey)
if !ok {
return errors.New("RSA signature used with a non-RSA public key")
}
return rsa.VerifyPKCS1v15(key, hashAlgorithm, digest, rest)
}
key, ok := publicKey.(*ecdsa.PublicKey)
if !ok {
return errors.New("ECDSA signature used with a non-ECDSA public key")
}
if !ecdsa.VerifyASN1(key, digest, rest) {
return errors.New("ECDSA generic signature verification failed")
}
return nil
}
func hashSignedOctets(algorithm crypto.Hash, signed []byte) ([]byte, error) {
switch algorithm {
case crypto.SHA1:
sum := sha1.Sum(signed)
return sum[:], nil
case crypto.SHA256:
sum := sha256.Sum256(signed)
return sum[:], nil
case crypto.SHA384:
sum := sha512.Sum384(signed)
return sum[:], nil
case crypto.SHA512:
sum := sha512.Sum512(signed)
return sum[:], nil
default:
return nil, fmt.Errorf("unsupported signature hash %v", algorithm)
}
}
func equalPublicKeys(first, second crypto.PublicKey) bool {
firstDER, firstErr := x509.MarshalPKIXPublicKey(first)
secondDER, secondErr := x509.MarshalPKIXPublicKey(second)
return firstErr == nil && secondErr == nil && bytes.Equal(firstDER, secondDER)
}