mirror of
https://github.com/fatedier/frp.git
synced 2026-10-18 16:13:27 +08:00
transport.tls.trustedCaFile is installed as RootCAs, and omitting it sets InsecureSkipVerify, so system-root verification cannot be selected directly. Add an opt-in transport.tls.useSystemRoots that enables certificate verification with RootCAs nil when no trusted CA file is set, allowing Go to use system roots and native verification on macOS and Windows. Defaults are unchanged.
180 lines
4.5 KiB
Go
180 lines
4.5 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()
|
|
|
|
caCrt, err := os.ReadFile(caPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !pool.AppendCertsFromPEM(caCrt) {
|
|
return nil, fmt.Errorf("failed to parse CA certificate from file %q: no valid PEM certificates found", caPath)
|
|
}
|
|
|
|
return pool, 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 NewClientTLSConfigWithSystemRoots(certPath, keyPath, caPath, serverName, false)
|
|
}
|
|
|
|
// NewClientTLSConfigWithSystemRoots behaves like NewClientTLSConfig, except that when
|
|
// caPath is empty and useSystemRoots is true, the server certificate is verified against
|
|
// the host's root store.
|
|
func NewClientTLSConfigWithSystemRoots(certPath, keyPath, caPath, serverName string, useSystemRoots bool) (*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 != "":
|
|
pool, err := newCertPool(caPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
base.RootCAs = pool
|
|
base.InsecureSkipVerify = false
|
|
case useSystemRoots:
|
|
// 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
|
|
}
|