@@ -8,11 +8,13 @@ package tlsconfig
88import (
99 "crypto/tls"
1010 "crypto/x509"
11+ "encoding/pem"
1112 "fmt"
1213 "io/ioutil"
1314 "os"
1415
1516 "github.com/Sirupsen/logrus"
17+ "github.com/pkg/errors"
1618)
1719
1820// Options represents the information needed to create client and server TLS configurations.
@@ -29,6 +31,10 @@ type Options struct {
2931 InsecureSkipVerify bool
3032 // server-only option
3133 ClientAuth tls.ClientAuthType
34+
35+ // If Passphrase is set, it will be used to decrypt a TLS private key
36+ // if the key is encrypted
37+ Passphrase string
3238}
3339
3440// Extra (server-side) accepted CBC cipher suites - will phase out in the future
@@ -80,6 +86,67 @@ func certPool(caFile string) (*x509.CertPool, error) {
8086 return certPool , nil
8187}
8288
89+ // IsErrEncryptedKey returns true if the 'err' is an error of incorrect
90+ // password when tryin to decrypt a TLS private key
91+ func IsErrEncryptedKey (err error ) bool {
92+ return errors .Cause (err ) == x509 .IncorrectPasswordError
93+ }
94+
95+ // getCert returns a Certificate from the CertFile and KeyFile in 'options',
96+ // if the key is encrypted, the Passphrase in 'options' will be used to
97+ // decrypt it.
98+ func getCert (options Options ) ([]tls.Certificate , error ) {
99+ if options .CertFile == "" && options .KeyFile == "" {
100+ return nil , nil
101+ }
102+
103+ errMessage := "Could not load X509 key pair"
104+
105+ cert , err := ioutil .ReadFile (options .CertFile )
106+ if err != nil {
107+ return nil , errors .Wrap (err , errMessage )
108+ }
109+
110+ prKeyBytes , err := ioutil .ReadFile (options .KeyFile )
111+ if err != nil {
112+ return nil , errors .Wrap (err , errMessage )
113+ }
114+
115+ prKeyBytes , err = getPrivateKey (prKeyBytes , options .Passphrase )
116+ if err != nil {
117+ return nil , errors .Wrap (err , errMessage )
118+ }
119+
120+ tlsCert , err := tls .X509KeyPair (cert , prKeyBytes )
121+ if err != nil {
122+ return nil , errors .Wrap (err , errMessage )
123+ }
124+
125+ return []tls.Certificate {tlsCert }, nil
126+ }
127+
128+ // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
129+ // If the private key is encrypted, 'passphrase' is used to decrypted the
130+ // private key.
131+ func getPrivateKey (keyBytes []byte , passphrase string ) ([]byte , error ) {
132+ // this section makes some small changes to code from notary/tuf/utils/x509.go
133+ pemBlock , _ := pem .Decode (keyBytes )
134+ if pemBlock == nil {
135+ return nil , fmt .Errorf ("no valid private key found" )
136+ }
137+
138+ var err error
139+ if x509 .IsEncryptedPEMBlock (pemBlock ) {
140+ keyBytes , err = x509 .DecryptPEMBlock (pemBlock , []byte (passphrase ))
141+ if err != nil {
142+ return nil , errors .Wrap (err , "private key is encrypted, but could not decrypt it" )
143+ }
144+ keyBytes = pem .EncodeToMemory (& pem.Block {Type : pemBlock .Type , Bytes : keyBytes })
145+ }
146+
147+ return keyBytes , nil
148+ }
149+
83150// Client returns a TLS configuration meant to be used by a client.
84151func Client (options Options ) (* tls.Config , error ) {
85152 tlsConfig := ClientDefault
@@ -99,6 +166,11 @@ func Client(options Options) (*tls.Config, error) {
99166 }
100167 tlsConfig .Certificates = []tls.Certificate {tlsCert }
101168 }
169+ tlsCerts , err := getCert (options )
170+ if err != nil {
171+ return nil , err
172+ }
173+ tlsConfig .Certificates = tlsCerts
102174
103175 return & tlsConfig , nil
104176}
0 commit comments