| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- 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)
- }
|