Skip to content
This repository has been archived by the owner on Feb 27, 2023. It is now read-only.

feature: Hijack HTTPS by generating leaf TLS certs on the fly issued by user provided CA. #1181

Merged
merged 1 commit into from
Feb 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions dfdaemon/proxy/cert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Copyright The Dragonfly 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 proxy

import (
"crypto"
"crypto/rand"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"math/big"
"net"
"time"

"github.com/sirupsen/logrus"
)

type LeafCertSpec struct {
publicKey crypto.PublicKey

privateKey crypto.PrivateKey

signatureAlgorithm x509.SignatureAlgorithm
}

// genLeafCert generates a Leaf TLS certificate and sign it with given CA
func genLeafCert(ca *tls.Certificate, leafCertSpec *LeafCertSpec, host string) (*tls.Certificate, error) {
now := time.Now().Add(-1 * time.Hour).UTC()
if !ca.Leaf.IsCA {
return nil, errors.New("CA cert is not a CA")
}
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
logrus.Errorf("failed to generate serial number: %s", err)
return nil, err
}
tmpl := &x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{CommonName: host},
NotBefore: now,
NotAfter: now.Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageDataEncipherment | x509.KeyUsageKeyAgreement,
BasicConstraintsValid: true,
SignatureAlgorithm: leafCertSpec.signatureAlgorithm,
}
ip := net.ParseIP(host)
if ip == nil {
tmpl.DNSNames = []string{host}
} else {
tmpl.IPAddresses = []net.IP{ip}
}
newCert, err := x509.CreateCertificate(rand.Reader, tmpl, ca.Leaf, leafCertSpec.publicKey, ca.PrivateKey)
if err != nil {
logrus.Errorf("failed to generate leaf cert %s", err)
return nil, err
}
cert := new(tls.Certificate)
cert.Certificate = append(cert.Certificate, newCert)
cert.PrivateKey = leafCertSpec.privateKey
cert.Leaf, _ = x509.ParseCertificate(newCert)
return cert, nil
}
43 changes: 41 additions & 2 deletions dfdaemon/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package proxy

import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net"
Expand All @@ -31,7 +32,7 @@ import (
"github.com/dragonflyoss/Dragonfly/dfdaemon/downloader"
"github.com/dragonflyoss/Dragonfly/dfdaemon/downloader/dfget"
"github.com/dragonflyoss/Dragonfly/dfdaemon/transport"

"github.com/golang/groupcache/lru"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
Expand Down Expand Up @@ -72,6 +73,11 @@ func WithCertFromFile(certFile, keyFile string) Option {
return errors.Wrap(err, "load cert")
}
logrus.Infof("use self-signed certificate (%s, %s) for https hijacking", certFile, keyFile)
leaf, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return errors.Wrap(err, "load leaf cert")
}
cert.Leaf = leaf
p.cert = &cert
return nil
}
Expand Down Expand Up @@ -168,6 +174,8 @@ type Proxy struct {
httpsHosts []*config.HijackHost
// cert is the certificate used to hijack https proxy requests
cert *tls.Certificate
// certCache is a in-memory cache store for TLS certs used in HTTPS hijack. Lazy init.
certCache *lru.Cache
// directHandler are used to handle non proxy requests
directHandler http.Handler
// downloadFactory returns the downloader used for p2p downloading
Expand Down Expand Up @@ -314,7 +322,38 @@ func (proxy *Proxy) handleHTTPS(w http.ResponseWriter, r *http.Request) {
logrus.Debugln("hijack https request to", r.Host)

sConfig := new(tls.Config)
sConfig.Certificates = []tls.Certificate{*proxy.cert}
if proxy.cert.Leaf != nil && proxy.cert.Leaf.IsCA {
if proxy.certCache == nil { // Initialize proxy.certCache on first access. (Lazy init)
proxy.certCache = lru.New(100) // Default max entries size = 100
}
logrus.Debugf("hijack https request with CA <%s>", proxy.cert.Leaf.Subject.CommonName)
leafCertSpec := LeafCertSpec{
proxy.cert.Leaf.PublicKey,
proxy.cert.PrivateKey,
proxy.cert.Leaf.SignatureAlgorithm}
host, _, _ := net.SplitHostPort(r.Host)
sConfig.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
cConfig.ServerName = host
logrus.Debugf("Generate temporal leaf TLS cert for ServerName <%s>, host <%s>", hello.ServerName, host)
// It's assumed that `hello.ServerName` is always same as `host`, in practice.
cacheKey := host
cached, hit := proxy.certCache.Get(cacheKey)
if hit && time.Now().Before(cached.(*tls.Certificate).Leaf.NotAfter) { // If cache hit and the cert is not expired
logrus.Debugf("TLS Cache hit, cacheKey = <%s>", cacheKey)
return cached.(*tls.Certificate), nil
}
cert, err := genLeafCert(proxy.cert, &leafCertSpec, host)
if err == nil {
// Put cert in cache only if there is no error. So all certs in cache are always valid.
// But certs in cache maybe expired (After 24 hours, see the default duration of generated certs)
proxy.certCache.Add(cacheKey, cert)
}
// If err != nil, means unrecoverable error happened in genLeafCert(...)
return cert, err
}
} else {
sConfig.Certificates = []tls.Certificate{*proxy.cert}
}

sConn, err := handshake(w, sConfig)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/go-openapi/strfmt v0.0.0-20171222154016-4dd3d302e100
github.com/go-openapi/swag v0.0.0-20170606142751-f3f9494671f9
github.com/go-openapi/validate v0.0.0-20170705144413-8a82927c942c
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef
github.com/golang/mock v1.3.1
github.com/gorilla/context v0.0.0-20181012153548-51ce91d2eadd // indirect
github.com/gorilla/mux v1.5.0
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
Expand Down