Skip to content

Commit 3aa1e7a

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 3aa1e7a

File tree

3 files changed

+57
-43
lines changed

3 files changed

+57
-43
lines changed

libs/pindialer/dialer.go

+10-24
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,15 @@
22
package pindialer
33

44
import (
5-
"context"
65
"crypto/sha256"
76
"crypto/tls"
7+
"crypto/x509"
88
"encoding/base64"
99
"errors"
10-
"fmt"
11-
"net"
1210
)
1311

14-
// ContextDialer is a function connecting to the address on the named network
15-
type ContextDialer func(ctx context.Context, network, addr string) (net.Conn, error)
16-
17-
func validateChain(fingerprint string, connstate tls.ConnectionState) error {
18-
for _, chain := range connstate.VerifiedChains {
12+
func validateChain(fingerprint string, verifiedChains [][]*x509.Certificate) error {
13+
for _, chain := range verifiedChains {
1914
for _, cert := range chain {
2015
hash := sha256.Sum256(cert.RawSubjectPublicKeyInfo)
2116
digest := base64.StdEncoding.EncodeToString(hash[:])
@@ -27,22 +22,13 @@ func validateChain(fingerprint string, connstate tls.ConnectionState) error {
2722
return errors.New("the server certificate was not valid")
2823
}
2924

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
25+
// Get tls.Config that validates the connection certificate chain against the
26+
// given fingerprint.
27+
func GetTLSConfig(fingerprint string) *tls.Config {
28+
return &tls.Config{
29+
VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
30+
return validateChain(fingerprint, verifiedChains)
31+
},
4632
}
4733
}
4834

libs/wallet/provider/uphold/uphold.go

+10-8
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,9 @@ func init() {
123123
proxy = nil
124124
}
125125

126-
fingerprintDialer := pindialer.MakeContextDialer(upholdCertFingerprint)
127-
128126
// Uphold reports HTTP 401 error when connecting with HTTP2, so disable
129127
// HTTP/2 via setting TLSNextProto to an empty map. We do not need to set
130-
// this field on defaultHTTPClient as we set DialTLSContext without setting
128+
// this field on defaultHTTPClient as we set TLSClientConfig without setting
131129
// ForceAttemptHTTP2 and that disables HTTP/2 also. But for clarity we
132130
// always set TLSNextProto.
133131
disableHTTP2 := make(
@@ -138,9 +136,9 @@ func init() {
138136
Timeout: httpTimeout,
139137
Transport: middleware.InstrumentRoundTripper(
140138
&http.Transport{
141-
DialTLSContext: fingerprintDialer,
142-
Proxy: proxy,
143-
TLSNextProto: disableHTTP2,
139+
Proxy: proxy,
140+
TLSClientConfig: pindialer.GetTLSConfig(upholdCertFingerprint),
141+
TLSNextProto: disableHTTP2,
144142
}, "uphold"),
145143
}
146144
httpClientNoFP = &http.Client{
@@ -889,12 +887,16 @@ func (resp upholdTransactionResponse) ToTransactionInfo() *walletutils.Transacti
889887
return &txInfo
890888
}
891889

892-
// SubmitTransaction submits the base64 encoded transaction for verification but
893-
// does not move funds unless confirm is set to true.
890+
// SubmitTransaction creates a transaction and, when `confirm` is true, also
891+
// submits it. If `conform` is false, ConfirmTransaction() should be used later
892+
// to actually submit the transaction.
894893
func (w *Wallet) SubmitTransaction(ctx context.Context, transactionB64 string, confirm bool) (*walletutils.TransactionInfo, error) {
895894
return w.submitTransaction(ctx, defaultHTTPClient, transactionB64, confirm)
896895
}
897896

897+
// The implementation helper for `SubmitTransaction()` that takes an extra
898+
// client argument to use fingeprinting/non-fingerprinting client depending on
899+
// the caller needs.
898900
func (w *Wallet) submitTransaction(
899901
ctx context.Context,
900902
client *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)