Skip to content

Commit 9521b88

Browse files
committed
fix: fingerprint and proxy
Use tls.Config.VerifyPeerCertificate, not the dialer, to verify the Uphold TLS certificate fingerprint. This ensures that the fingerprint checks are done even if a proxy is used.
1 parent 57c94a7 commit 9521b88

File tree

3 files changed

+50
-33
lines changed

3 files changed

+50
-33
lines changed

libs/pindialer/dialer.go

+10-19
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@ import (
55
"context"
66
"crypto/sha256"
77
"crypto/tls"
8+
"crypto/x509"
89
"encoding/base64"
910
"errors"
10-
"fmt"
1111
"net"
1212
)
1313

1414
// ContextDialer is a function connecting to the address on the named network
1515
type ContextDialer func(ctx context.Context, network, addr string) (net.Conn, error)
1616

17-
func validateChain(fingerprint string, connstate tls.ConnectionState) error {
18-
for _, chain := range connstate.VerifiedChains {
17+
func validateChain(fingerprint string, verifiedChains [][]*x509.Certificate) error {
18+
for _, chain := range verifiedChains {
1919
for _, cert := range chain {
2020
hash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
2121
digest := base64.StdEncoding.EncodeToString(hash[:])
@@ -27,22 +27,13 @@ func validateChain(fingerprint string, connstate tls.ConnectionState) error {
2727
return errors.New("the server certificate was not valid")
2828
}
2929

30-
// MakeContextDialer returns a ContextDialer that only succeeds on connection to a TLS secured address with the pinned fingerprint
31-
func MakeContextDialer(fingerprint string) ContextDialer {
32-
return func(ctx context.Context, network, addr string) (net.Conn, error) {
33-
c, err := tls.Dial(network, addr, nil)
34-
if err != nil {
35-
return c, err
36-
}
37-
select {
38-
case <-ctx.Done():
39-
return nil, fmt.Errorf("context completed")
40-
default:
41-
if err := validateChain(fingerprint, c.ConnectionState()); err != nil {
42-
return nil, fmt.Errorf("failed to validate certificate chain: %w", err)
43-
}
44-
}
45-
return c, nil
30+
// Get tls.Config that validates the connection certificate chain against the
31+
// given fingerprint.
32+
func GetTLSConfig(fingerprint string) *tls.Config {
33+
return &tls.Config{
34+
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
35+
return validateChain(fingerprint, verifiedChains)
36+
},
4637
}
4738
}
4839

libs/wallet/provider/uphold/uphold.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ func init() {
138138
Timeout: httpTimeout,
139139
Transport: middleware.InstrumentRoundTripper(
140140
&http.Transport{
141-
DialTLSContext: fingerprintDialer,
142-
Proxy: proxy,
143-
TLSNextProto: disableHTTP2,
141+
Proxy: proxy,
142+
TLSClientConfig: pindialer.GetTLSConfig(upholdCertFingerprint),
143+
TLSNextProto: disableHTTP2,
144144
}, "uphold"),
145145
}
146146
httpClientNoFP = &http.Client{

libs/wallet/provider/uphold/uphold_test.go

+37-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package uphold
22

33
import (
44
"context"
5+
"crypto/tls"
56
"encoding/hex"
67
"errors"
78
"net/http"
@@ -307,28 +308,53 @@ func TestFingerprintCheck(t *testing.T) {
307308
var proxy func(*http.Request) (*url.URL, error)
308309
wrongFingerprint := "IYSLsapSKlkofKfi6M2hmS4gzXbQKGIX/DHBWIgstw3="
309310

311+
w := requireDonorWallet(t)
312+
313+
req, err := w.signRegistration("randomlabel")
314+
if err != nil {
315+
t.Error(err)
316+
}
317+
318+
// Check fingerprint error case
310319
client := &http.Client{
311320
Timeout: time.Second * 60,
312321
// remove middleware calls
313322
Transport: &http.Transport{
314-
Proxy: proxy,
315-
DialTLSContext: pindialer.MakeContextDialer(wrongFingerprint),
323+
Proxy: proxy,
324+
TLSClientConfig: pindialer.GetTLSConfig(wrongFingerprint),
316325
},
317326
}
318327

319-
w := requireDonorWallet(t)
328+
_, err = client.Do(req)
329+
assert.ErrorContains(t, err, "the server certificate was not valid")
320330

321-
req, err := w.signRegistration("randomlabel")
322-
if err != nil {
323-
t.Error(err)
331+
// Check the fingerprint success case.
332+
tlsConfig := pindialer.GetTLSConfig(upholdCertFingerprint)
333+
334+
// VerifyConnection callback is only called after
335+
// tlsConfig.VerifyPeerCertificate returns success.
336+
verifyConnectionCalled := false
337+
if tlsConfig.VerifyConnection != nil {
338+
t.Fatalf("tlsConfig.VerifyConnection must be unset")
339+
}
340+
tlsConfig.VerifyConnection = func(tls.ConnectionState) error {
341+
if verifyConnectionCalled {
342+
t.Fatalf("Unexpected extra call to VerifyConnection")
343+
}
344+
verifyConnectionCalled = true
345+
return nil
324346
}
325347

326-
_, err = client.Do(req)
327-
// should fail here
328-
if err == nil {
329-
t.Error("unable to fail with bad cert")
348+
client = &http.Client{
349+
Timeout: time.Second * 60,
350+
Transport: &http.Transport{
351+
Proxy: proxy,
352+
TLSClientConfig: tlsConfig,
353+
},
330354
}
331-
assert.Equal(t, errors.Unwrap(err).Error(), "failed to validate certificate chain: the server certificate was not valid")
355+
356+
_, _ = client.Do(req)
357+
assert.Equal(t, true, verifyConnectionCalled)
332358
}
333359

334360
func requireDonorWallet(t *testing.T) *Wallet {

0 commit comments

Comments
 (0)