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

Commit

Permalink
Merge pull request #1181 from YanzheL/feature-https-mitm
Browse files Browse the repository at this point in the history
feature: Hijack HTTPS by generating leaf TLS certs on the fly issued by user provided CA.
  • Loading branch information
inoc603 committed Feb 5, 2020
2 parents 6745cc8 + 96c3c82 commit e1ae4b8
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 2 deletions.
78 changes: 78 additions & 0 deletions dfdaemon/proxy/cert.go
@@ -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
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
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
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

0 comments on commit e1ae4b8

Please sign in to comment.