mirror of
https://github.com/fatedier/frp.git
synced 2026-10-18 20:13:27 +08:00
Move the frpc-only TLS option into the shared TLSConfig as transport.tls.useSystemCa, replacing the unpublished useSystemRoots. When set, frpc verifies the server with the system CAs; when trustedCaFile is also set, its CAs are appended to the system pool so both are trusted. Failures to load either source abort the connection instead of falling back. Enabling the option without effective TLS is a configuration error, and the field is rejected in frps transport.tls and in both webServer.tls sections. Fixes #5545
212 lines
5.7 KiB
Go
212 lines
5.7 KiB
Go
// Copyright 2023 The frp Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package transport
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"math/big"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
func newCustomTLSKeyPair(certfile, keyfile string) (*tls.Certificate, error) {
|
|
tlsCert, err := tls.LoadX509KeyPair(certfile, keyfile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &tlsCert, nil
|
|
}
|
|
|
|
func newRandomTLSKeyPair() (*tls.Certificate, error) {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Generate a random positive serial number with 128 bits of entropy.
|
|
// RFC 5280 requires serial numbers to be positive integers (not zero).
|
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Ensure serial number is positive (not zero)
|
|
if serialNumber.Sign() == 0 {
|
|
serialNumber = big.NewInt(1)
|
|
}
|
|
|
|
template := x509.Certificate{
|
|
SerialNumber: serialNumber,
|
|
NotBefore: time.Now().Add(-1 * time.Hour),
|
|
NotAfter: time.Now().Add(365 * 24 * time.Hour * 10),
|
|
}
|
|
|
|
certDER, err := x509.CreateCertificate(
|
|
rand.Reader,
|
|
&template,
|
|
&template,
|
|
&key.PublicKey,
|
|
key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
|
certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER})
|
|
|
|
tlsCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &tlsCert, nil
|
|
}
|
|
|
|
// Only support one ca file to add
|
|
func newCertPool(caPath string) (*x509.CertPool, error) {
|
|
pool := x509.NewCertPool()
|
|
if err := appendCertsFromFile(pool, caPath); err != nil {
|
|
return nil, err
|
|
}
|
|
return pool, nil
|
|
}
|
|
|
|
func appendCertsFromFile(pool *x509.CertPool, caPath string) error {
|
|
caCrt, err := os.ReadFile(caPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !pool.AppendCertsFromPEM(caCrt) {
|
|
return fmt.Errorf("failed to parse CA certificate from file %q: no valid PEM certificates found", caPath)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func NewServerTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
|
|
base := &tls.Config{}
|
|
|
|
if certPath == "" || keyPath == "" {
|
|
// server will generate tls conf by itself
|
|
cert, err := newRandomTLSKeyPair()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
base.Certificates = []tls.Certificate{*cert}
|
|
} else {
|
|
cert, err := newCustomTLSKeyPair(certPath, keyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base.Certificates = []tls.Certificate{*cert}
|
|
}
|
|
|
|
if caPath != "" {
|
|
pool, err := newCertPool(caPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base.ClientAuth = tls.RequireAndVerifyClientCert
|
|
base.ClientCAs = pool
|
|
}
|
|
|
|
return base, nil
|
|
}
|
|
|
|
func NewClientTLSConfig(certPath, keyPath, caPath, serverName string) (*tls.Config, error) {
|
|
return NewClientTLSConfigWithSystemCA(certPath, keyPath, caPath, serverName, false)
|
|
}
|
|
|
|
// NewClientTLSConfigWithSystemCA enables verification against system CAs when
|
|
// useSystemCa is true, adding certificates from caPath if it is also set.
|
|
// Without either trust source, server certificate verification remains disabled.
|
|
func NewClientTLSConfigWithSystemCA(certPath, keyPath, caPath, serverName string, useSystemCa bool) (*tls.Config, error) {
|
|
return newClientTLSConfig(certPath, keyPath, caPath, serverName, useSystemCa, x509.SystemCertPool)
|
|
}
|
|
|
|
func newClientTLSConfig(
|
|
certPath, keyPath, caPath, serverName string,
|
|
useSystemCa bool,
|
|
systemCertPool func() (*x509.CertPool, error),
|
|
) (*tls.Config, error) {
|
|
base := &tls.Config{}
|
|
|
|
if certPath != "" && keyPath != "" {
|
|
cert, err := newCustomTLSKeyPair(certPath, keyPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base.Certificates = []tls.Certificate{*cert}
|
|
}
|
|
|
|
base.ServerName = serverName
|
|
|
|
switch {
|
|
case caPath != "" && useSystemCa:
|
|
// SystemCertPool returns a copy, so appending does not change the system pool.
|
|
// Design basis: Go 1.26.7 crypto/x509/verify.go:559-573 first uses the
|
|
// Windows/macOS verifier, then falls back to Go verification using only the
|
|
// added CAs if platform verification fails. This platform behavior has not been
|
|
// tested here; preserve the system pool instead of copying its subjects.
|
|
pool, err := systemCertPool()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load system CA pool: %w", err)
|
|
}
|
|
if pool == nil {
|
|
return nil, fmt.Errorf("failed to load system CA pool: no pool returned")
|
|
}
|
|
if err := appendCertsFromFile(pool, caPath); err != nil {
|
|
return nil, err
|
|
}
|
|
base.RootCAs = pool
|
|
base.InsecureSkipVerify = false
|
|
case caPath != "":
|
|
pool, err := newCertPool(caPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base.RootCAs = pool
|
|
base.InsecureSkipVerify = false
|
|
case useSystemCa:
|
|
// A nil RootCAs makes crypto/tls use the host root store, through the
|
|
// platform verifier on macOS and Windows.
|
|
base.InsecureSkipVerify = false
|
|
default:
|
|
base.InsecureSkipVerify = true
|
|
}
|
|
|
|
return base, nil
|
|
}
|
|
|
|
func NewRandomPrivateKey() ([]byte, error) {
|
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
keyPEM := pem.EncodeToMemory(&pem.Block{
|
|
Type: "RSA PRIVATE KEY",
|
|
Bytes: x509.MarshalPKCS1PrivateKey(key),
|
|
})
|
|
return keyPEM, nil
|
|
}
|