-
Notifications
You must be signed in to change notification settings - Fork 25
/
pinner.go
144 lines (118 loc) · 3.93 KB
/
pinner.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
package azuretls
import (
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"errors"
tls "github.com/Noooste/utls"
"net/url"
"sync"
)
// Fingerprint computes the SHA256 Fingerprint of a given certificate's
// RawSubjectPublicKeyInfo. This is useful for obtaining a consistent
// identifier for a certificate's public key. The result is then base64-encoded
// to give a string representation which can be conveniently stored or compared.
func Fingerprint(c *x509.Certificate) string {
digest := sha256.Sum256(c.RawSubjectPublicKeyInfo)
return base64.StdEncoding.EncodeToString(digest[:])
}
// PinManager is a concurrency-safe struct designed to manage
// and verify public key pinning for SSL/TLS certificates. Public key pinning
// is a security feature which can be used to specify a set of valid public keys
// for a particular web service, thus preventing man-in-the-middle attacks
// due to rogue certificates.
type PinManager struct {
redo bool
mu *sync.RWMutex // Read-Write mutex ensuring concurrent access safety.
m map[string]bool // A map representing the certificate pins. If a pin exists and is set to true, it is considered valid.
}
// NewPinManager initializes a new instance of PinManager with
// an empty set of pins. This is the entry point to begin using
// the pinning functionality.
func NewPinManager() *PinManager {
return &PinManager{
mu: new(sync.RWMutex),
m: make(map[string]bool),
}
}
// AddPin safely adds a new pin (Fingerprint) to the PinManager.
// If a service's certificate changes (e.g., due to renewal), new pins
// should be added to continue trusting the service.
func (p *PinManager) AddPin(pin string) {
p.mu.Lock()
p.m[pin] = true
p.mu.Unlock()
}
// Verify checks whether a given certificate's public key is
// currently pinned in the PinManager. This method should be
// used during the SSL/TLS handshake to ensure the remote service's
// certificate matches a previously pinned public key.
func (p *PinManager) Verify(c *x509.Certificate) bool {
fp := Fingerprint(c)
p.mu.RLock()
defer p.mu.RUnlock()
v, ok := p.m[fp]
return ok && v
}
// New establishes a connection to the provided address, retrieves
// its SSL/TLS certificates, and pins their public keys in the
// PinManager. This can be used initially to populate the PinManager
// with pins from a trusted service.
func (p *PinManager) New(addr string) (err error) {
dial, err := tls.Dial("tcp", addr, &tls.Config{InsecureSkipVerify: true})
if err != nil {
return errors.New("failed to generate pins for " + addr + ": " + err.Error())
}
cs := dial.ConnectionState()
_ = dial.Close()
var pins = make([]string, 0, len(cs.PeerCertificates))
for _, c := range cs.PeerCertificates {
pins = append(pins, Fingerprint(c))
}
p.mu.Lock()
for _, c := range pins {
p.m[c] = true
}
p.mu.Unlock()
return nil
}
func (p *PinManager) GetPins() []string {
p.mu.RLock()
defer p.mu.RUnlock()
var pins = make([]string, 0, len(p.m))
for k, v := range p.m {
if !v {
continue
}
pins = append(pins, k)
}
return pins
}
// AddPins associates a set of certificate pins with a given URL within
// a session. This allows for URL-specific pinning, useful in scenarios
// where different services (URLs) are trusted with different certificates.
func (s *Session) AddPins(u *url.URL, pins []string) error {
conn := s.Connections.Get(u)
conn.mu.Lock()
defer conn.mu.Unlock()
if conn.PinManager == nil {
conn.PinManager = NewPinManager()
}
for _, pin := range pins {
conn.PinManager.AddPin(pin)
}
return nil
}
// ClearPins removes all pinned certificates associated
// with a specific URL in the session. This can be used to reset trust
// settings or in scenarios where a service's certificate is no longer deemed trustworthy.
func (s *Session) ClearPins(u *url.URL) error {
conn := s.Connections.Get(u)
conn.mu.Lock()
defer conn.mu.Unlock()
for k := range conn.PinManager.m {
conn.PinManager.m[k] = false
}
conn.PinManager.redo = true
return nil
}