88 "errors"
99 "io/ioutil"
1010 "net/http"
11+ "os"
1112 "path/filepath"
1213 "strings"
1314 "time"
@@ -16,37 +17,89 @@ import (
1617// New creates a new http.Client with a custom certificate, which can be loaded from the passed CA Bundle file and/or
1718// directory. If both CABundleFile and CABundleDir are empty arguments, it creates an unsecure HTTP client.
1819func New (CABundleFile , CABundleDir string , httpTimeout time.Duration ) (* http.Client , error ) {
20+ return _new (CABundleFile , CABundleDir , httpTimeout , "" )
21+ }
22+
23+ // NewAcceptInvalidHostname new http.Client with ability to accept HTTPS certificates that don't
24+ // match the hostname of the server they are connecting to.
25+ func NewAcceptInvalidHostname (CABundleFile , CABundleDir string , httpTimeout time.Duration , hostname string ) (* http.Client , error ) {
26+ return _new (CABundleFile , CABundleDir , httpTimeout , hostname )
27+ }
28+
29+ func _new (CABundleFile , CABundleDir string , httpTimeout time.Duration , acceptInvalidHostname string ) (* http.Client , error ) {
1930 // go default http transport settings
20- transport := & http.Transport {}
31+ t := & http.Transport {}
2132
2233 if CABundleFile != "" || CABundleDir != "" {
2334 certs , err := getCertPool (CABundleFile , CABundleDir )
2435 if err != nil {
2536 return nil , err
2637 }
27- transport .TLSClientConfig = & tls.Config {RootCAs : certs }
38+
39+ t .TLSClientConfig = & tls.Config {
40+ RootCAs : certs ,
41+ }
42+
43+ if acceptInvalidHostname != "" {
44+ // Default validation is replaced with VerifyPeerCertificate.
45+ // Note that when InsecureSkipVerify and VerifyPeerCertificate are in use,
46+ // ConnectionState.VerifiedChains will be nil.
47+ t .TLSClientConfig .InsecureSkipVerify = true
48+ // While packages like net/http will implicitly set ServerName, the
49+ // VerifyPeerCertificate callback can't access that value, so it has to be set
50+ // explicitly here or in VerifyPeerCertificate on the client side. If in
51+ // an http.Transport DialTLS callback, this can be obtained by passing
52+ // the addr argument to net.SplitHostPort.
53+ t .TLSClientConfig .ServerName = acceptInvalidHostname
54+ // Approximately equivalent to what crypto/tls does normally:
55+ // https://github.com/golang/go/commit/29cfb4d3c3a97b6f426d1b899234da905be699aa
56+ t .TLSClientConfig .VerifyPeerCertificate = func (certificates [][]byte , _ [][]* x509.Certificate ) error {
57+ certs := make ([]* x509.Certificate , len (certificates ))
58+ for i , asn1Data := range certificates {
59+ cert , err := x509 .ParseCertificate (asn1Data )
60+ if err != nil {
61+ return errors .New ("tls: failed to parse certificate from server: " + err .Error ())
62+ }
63+ certs [i ] = cert
64+ }
65+
66+ opts := x509.VerifyOptions {
67+ Roots : t .TLSClientConfig .RootCAs , // On the server side, use config.ClientCAs.
68+ DNSName : acceptInvalidHostname ,
69+ Intermediates : x509 .NewCertPool (),
70+ // On the server side, set KeyUsages to ExtKeyUsageClientAuth. The
71+ // default value is appropriate for clients side verification.
72+ // KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
73+ }
74+ for _ , cert := range certs [1 :] {
75+ opts .Intermediates .AddCert (cert )
76+ }
77+ _ , err := certs [0 ].Verify (opts )
78+ return err
79+ }
80+
81+ }
2882 }
2983
3084 return & http.Client {
3185 Timeout : httpTimeout ,
32- Transport : transport ,
86+ Transport : t ,
3387 }, nil
3488}
3589
3690func getCertPool (certFile string , certDirectory string ) (* x509.CertPool , error ) {
3791 caCertPool := x509 .NewCertPool ()
38- if certFile != "" {
39- caCert , err := ioutil .ReadFile (certFile )
40- if err != nil {
41- return nil , err
42- }
4392
44- ok := caCertPool .AppendCertsFromPEM (caCert )
45- if ! ok {
46- return nil , errors .New ("can't parse certificate" )
93+ if certFile != "" {
94+ if err := addCert (filepath .Join (certDirectory , certFile ), caCertPool ); err != nil {
95+ if os .IsNotExist (err ) {
96+ if err = addCert (certFile , caCertPool ); err != nil {
97+ return nil , err
98+ }
99+ }
47100 }
48-
49101 }
102+
50103 if certDirectory != "" {
51104 files , err := ioutil .ReadDir (certDirectory )
52105 if err != nil {
@@ -55,17 +108,25 @@ func getCertPool(certFile string, certDirectory string) (*x509.CertPool, error)
55108
56109 for _ , f := range files {
57110 if strings .Contains (f .Name (), ".pem" ) {
58- caCertFilePath := filepath .Join (certDirectory + "/" + f .Name ())
59- caCert , err := ioutil .ReadFile (caCertFilePath )
60- if err != nil {
111+ caCertFilePath := filepath .Join (certDirectory , f .Name ())
112+ if err := addCert (caCertFilePath , caCertPool ); err != nil {
61113 return nil , err
62114 }
63- ok := caCertPool .AppendCertsFromPEM (caCert )
64- if ! ok {
65- return nil , errors .New ("can't parse certificate" )
66- }
67115 }
68116 }
69117 }
70118 return caCertPool , nil
71119}
120+
121+ func addCert (certFile string , caCertPool * x509.CertPool ) error {
122+ caCert , err := ioutil .ReadFile (certFile )
123+ if err != nil {
124+ return err
125+ }
126+
127+ ok := caCertPool .AppendCertsFromPEM (caCert )
128+ if ! ok {
129+ return errors .New ("can't parse certificate" )
130+ }
131+ return nil
132+ }
0 commit comments