Skip to content
This repository has been archived by the owner on May 14, 2021. It is now read-only.

Commit

Permalink
Public key pinning of API server certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
stenya committed Jan 12, 2021
1 parent 16f084d commit c8dc7aa
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 3 deletions.
4 changes: 4 additions & 0 deletions api/api_cert_public_keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package api

// APIIvpnHashes - base64-encoded SHA256 hashes for api server public keys (in use for certificate key pinning)
var APIIvpnHashes = []string{"g6WEFnt9DyTi70nW/fufsZNw83vFpcmIhMuDPQ1MFcI=", "KCcpK9y22OrlapwO1/oP8q3LrcDM9Jy9lcfngg2r+Pk=", "iRHkSbdOY/YD8EE5fpl8W0P8EqmfkBRTADEegR2/Wnc=", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU="}
72 changes: 69 additions & 3 deletions api/api_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ package api

import (
"bytes"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -70,16 +73,79 @@ func newRequest(urlPath string, method string, contentType string, body io.Reade
return req, nil
}

func findPinnedKey(certHashes []string, certBase64hash256 string) bool {
for _, hash := range certHashes {
if hash == certBase64hash256 {
return true
}
}
return false
}

type dialer func(network, addr string) (net.Conn, error)

func makeDialer(certHashes []string, skipCAVerification bool, serverName string) dialer {
return func(network, addr string) (net.Conn, error) {
defer func() {
if r := recover(); r != nil {
log.Error(fmt.Sprintf("PANIC (API request): "), r)
if err, ok := r.(error); ok {
log.ErrorTrace(err)
}
}
}()

tlsConfig := &tls.Config{
InsecureSkipVerify: skipCAVerification,
ServerName: serverName, // only have sense when skipCAVerification == false
}

c, err := tls.Dial(network, addr, tlsConfig)
if err != nil {
return c, err
}
connstate := c.ConnectionState()
var lastErr error = nil
for _, peercert := range connstate.PeerCertificates {
der, err := x509.MarshalPKIXPublicKey(peercert.PublicKey)
if err != nil {
lastErr = err
continue
}

hash := sha256.Sum256(der)
certBase64hash := base64.StdEncoding.EncodeToString(hash[:])

if err != nil {
log.Error(err)
}

if findPinnedKey(certHashes, certBase64hash) {
return c, nil // Pinned Key found
}

}
if lastErr != nil {
return nil, fmt.Errorf("Certificate check error: pinned certificate key not found: %w", lastErr)
}
return nil, fmt.Errorf("Certificate check error: pinned certificate key not found")
}
}

func (a *API) doRequest(urlPath string, method string, contentType string, request interface{}, timeoutMs int) (resp *http.Response, err error) {
lastIP, ips := a.getAlternateIPs()

// When trying to access API server by alternate IPs (not by DNS name)
// we need to configure TLS to use api.ivpn.net hostname
// (to avoid certificate errors)
transCfg := &http.Transport{
TLSClientConfig: &tls.Config{
ServerName: _apiHost,
},
// NOTE: TLSClientConfig not in use in case of DialTLS defined
//TLSClientConfig: &tls.Config{
// ServerName: _apiHost,
//},

// using certificate key pinning
DialTLS: makeDialer(APIIvpnHashes, false, _apiHost),
}
// configure http-client with preconfigured TLS transport
timeout := _defaultRequestTimeout
Expand Down

0 comments on commit c8dc7aa

Please sign in to comment.