Skip to content

Commit f55b2be

Browse files
committed
Store the temporarily generated certificates to the file system.
1 parent 5cd67d5 commit f55b2be

3 files changed

Lines changed: 269 additions & 17 deletions

File tree

certcache.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/*
2+
* Copyright (c) 2016 Moriyoshi Koizumi
3+
*
4+
* All rights reserved.
5+
*
6+
* Redistribution and use in source and binary forms, with or without
7+
* modification, are permitted provided that the following conditions are
8+
* met:
9+
*
10+
* * Redistributions of source code must retain the above copyright
11+
* notice, this list of conditions and the following disclaimer.
12+
* * Redistributions in binary form must reproduce the above
13+
* copyright notice, this list of conditions and the following disclaimer
14+
* in the documentation and/or other materials provided with the
15+
* distribution.
16+
* * Neither the name of Elazar Leibovich. nor the names of its
17+
* contributors may be used to endorse or promote products derived from
18+
* this software without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package main
33+
34+
import (
35+
"crypto"
36+
"crypto/tls"
37+
"crypto/x509"
38+
"encoding/pem"
39+
"fmt"
40+
"github.com/Sirupsen/logrus"
41+
"io/ioutil"
42+
"os"
43+
"path/filepath"
44+
"strings"
45+
"time"
46+
)
47+
48+
type CertCache struct {
49+
CacheDir string
50+
Logger *logrus.Logger
51+
certs map[string]*tls.Certificate
52+
issuerCert *x509.Certificate
53+
privateKey crypto.PrivateKey
54+
}
55+
56+
const certificateFileName = "cert.pem"
57+
const certificateBlockName = "CERTIFICATE"
58+
59+
func buildKeyString(hosts []string) string {
60+
key := strings.Join(hosts, ";")
61+
return key
62+
}
63+
64+
func (c *CertCache) writeCertificate(key string, cert *tls.Certificate) (err error) {
65+
leadingDirs, ok := c.buildPathToCachedCert(key)
66+
if !ok {
67+
return
68+
}
69+
err = os.MkdirAll(leadingDirs, os.FileMode(0777))
70+
if err != nil {
71+
return
72+
}
73+
path := filepath.Join(leadingDirs, certificateFileName)
74+
w, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, os.FileMode(0666))
75+
if err != nil {
76+
return
77+
}
78+
defer w.Close()
79+
err = pem.Encode(w, &pem.Block{Type: certificateBlockName, Bytes: cert.Certificate[0]})
80+
if err != nil {
81+
return
82+
}
83+
return nil
84+
}
85+
86+
func (c *CertCache) buildPathToCachedCert(key string) (string, bool) {
87+
if c.CacheDir == "" {
88+
return "", false
89+
}
90+
return filepath.Join(c.CacheDir, key), true
91+
}
92+
93+
func (c *CertCache) readAndValidateCertificate(key string, hosts []string, now time.Time) (*tls.Certificate, error) {
94+
leadingDirs, ok := c.buildPathToCachedCert(key)
95+
if !ok {
96+
return nil, nil
97+
}
98+
path := filepath.Join(leadingDirs, certificateFileName)
99+
pemBytes, err := ioutil.ReadFile(path)
100+
if err != nil {
101+
return nil, err
102+
}
103+
certDerBytes := []byte(nil)
104+
for {
105+
var pemBlock *pem.Block
106+
pemBlock, pemBytes = pem.Decode(pemBytes)
107+
if pemBlock == nil {
108+
break
109+
}
110+
if pemBlock.Type == certificateBlockName {
111+
certDerBytes = pemBlock.Bytes
112+
break
113+
}
114+
}
115+
if certDerBytes == nil {
116+
return nil, fmt.Errorf("No valid certificate contained in %s", path)
117+
}
118+
x509Cert, err := x509.ParseCertificate(certDerBytes)
119+
if err != nil {
120+
return nil, fmt.Errorf("Invalid certificate found in %s (%s)", path, err.Error())
121+
}
122+
x509Cert.RawIssuer = c.issuerCert.Raw
123+
err = x509Cert.CheckSignatureFrom(c.issuerCert)
124+
if err != nil {
125+
return nil, fmt.Errorf("Invalid certificate found in %s (%s)", path, err.Error())
126+
}
127+
if !now.Before(x509Cert.NotAfter) {
128+
return nil, fmt.Errorf("Ceritificate no longer valid (not after: %s, now: %s)", x509Cert.NotAfter.Format(time.RFC1123), now.Format(time.RFC1123))
129+
}
130+
131+
outer:
132+
for _, a := range hosts {
133+
for _, b := range x509Cert.DNSNames {
134+
if a == b {
135+
break outer
136+
}
137+
}
138+
return nil, fmt.Errorf("Certificate does not cover the host name %s", a)
139+
}
140+
141+
return &tls.Certificate{
142+
Certificate: [][]byte{certDerBytes, c.issuerCert.Raw},
143+
PrivateKey: c.privateKey,
144+
}, nil
145+
}
146+
147+
func (c *CertCache) readCertificate(key string, hosts []string, now time.Time) (cert *tls.Certificate, err error) {
148+
cert, err = c.readAndValidateCertificate(
149+
key,
150+
hosts,
151+
now,
152+
)
153+
if err != nil {
154+
c.Logger.Warn(err.Error())
155+
err = nil
156+
}
157+
return
158+
}
159+
160+
func (c *CertCache) Put(hosts []string, cert *tls.Certificate) error {
161+
key := buildKeyString(hosts)
162+
c.certs[key] = cert
163+
return c.writeCertificate(key, cert)
164+
}
165+
166+
func (c *CertCache) Get(hosts []string, now time.Time) (cert *tls.Certificate, err error) {
167+
key := buildKeyString(hosts)
168+
cert, ok := c.certs[key]
169+
if !ok {
170+
c.Logger.Debug("Certificate not found in in-process cache")
171+
cert, err = c.readCertificate(key, hosts, now)
172+
if err != nil {
173+
return
174+
}
175+
if cert != nil {
176+
c.certs[key] = cert
177+
}
178+
}
179+
return
180+
}
181+
182+
func NewCertCache(cacheDir string, logger *logrus.Logger, issuerCert *x509.Certificate, privateKey crypto.PrivateKey) *CertCache {
183+
return &CertCache{
184+
CacheDir: cacheDir,
185+
Logger: logger,
186+
certs: make(map[string]*tls.Certificate),
187+
issuerCert: issuerCert,
188+
privateKey: privateKey,
189+
}
190+
}

config.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import (
5151
"path/filepath"
5252
"regexp"
5353
"strings"
54+
"time"
5455
)
5556

5657
type ResponseFilterFactory func(*ConfigReaderContext, map[interface{}]interface{}) (ResponseFilter, error)
@@ -73,6 +74,8 @@ type MITMConfig struct {
7374
Certificate *x509.Certificate
7475
PrivateKey crypto.PrivateKey
7576
}
77+
CacheDirectory string
78+
DisableCache bool
7679
}
7780

7881
type ProxyConfig struct {
@@ -88,6 +91,7 @@ type Config struct {
8891
Proxy ProxyConfig
8992
MITM MITMConfig
9093
ResponseFilters []ResponseFilter
94+
NowGetter func() (time.Time, error)
9195
}
9296

9397
var clientAuthTypeValues = map[string]tls.ClientAuthType{
@@ -908,6 +912,24 @@ func (ctx *ConfigReaderContext) extractMITMConfig(configMap map[string]interface
908912
return
909913
}
910914
retval.SigningCertificateKeyPair.PrivateKey = tlsCert.PrivateKey
915+
__cache_directory, ok := _tls["cache_directory"]
916+
if ok {
917+
_cache_directory, ok := __cache_directory.(string)
918+
if !ok {
919+
err = fmt.Errorf("%s: invalid structure under tls; cache_directory must be a string", ctx.Filename)
920+
return
921+
}
922+
retval.CacheDirectory = _cache_directory
923+
}
924+
__disable_cache, ok := _tls["disable_cache"]
925+
if ok {
926+
_disable_cache, ok := __disable_cache.(bool)
927+
if !ok {
928+
err = fmt.Errorf("%s: invalid structure under tls; disable_cache must be a boolean", ctx.Filename)
929+
return
930+
}
931+
retval.DisableCache = _disable_cache
932+
}
911933
}
912934
return
913935
}
@@ -961,6 +983,10 @@ func (ctx *ConfigReaderContext) extractResponseFilters(configMap map[string]inte
961983
return
962984
}
963985

986+
func defaultNow() (time.Time, error) {
987+
return time.Now(), nil
988+
}
989+
964990
func loadConfig(yamlFile string, progname string) (*Config, error) {
965991
f, err := os.Open(yamlFile)
966992
if err != nil {
@@ -998,5 +1024,6 @@ func loadConfig(yamlFile string, progname string) (*Config, error) {
9981024
Proxy: proxy,
9991025
MITM: mitm,
10001026
ResponseFilters: responseFilters,
1027+
NowGetter: defaultNow,
10011028
}, nil
10021029
}

0 commit comments

Comments
 (0)