1 package core
2
3 import (
4 "crypto"
5 "crypto/ecdsa"
6 "crypto/rand"
7 "crypto/rsa"
8 "crypto/sha256"
9 "crypto/x509"
10 "encoding/base64"
11 "encoding/hex"
12 "encoding/pem"
13 "errors"
14 "expvar"
15 "fmt"
16 "io"
17 "math/big"
18 mrand "math/rand"
19 "os"
20 "path"
21 "reflect"
22 "regexp"
23 "sort"
24 "strings"
25 "time"
26 "unicode"
27
28 "gopkg.in/go-jose/go-jose.v2"
29 )
30
31 const Unspecified = "Unspecified"
32
33
34
35
36
37 var BuildID string
38
39
40 var BuildHost string
41
42
43 var BuildTime string
44
45 func init() {
46 expvar.NewString("BuildID").Set(BuildID)
47 expvar.NewString("BuildTime").Set(BuildTime)
48 }
49
50
51
52 type randSource interface {
53 Read(p []byte) (n int, err error)
54 }
55
56
57
58 var RandReader randSource = rand.Reader
59
60
61 func RandomString(byteLength int) string {
62 b := make([]byte, byteLength)
63 _, err := io.ReadFull(RandReader, b)
64 if err != nil {
65 panic(fmt.Sprintf("Error reading random bytes: %s", err))
66 }
67 return base64.RawURLEncoding.EncodeToString(b)
68 }
69
70
71 func NewToken() string {
72 return RandomString(32)
73 }
74
75 var tokenFormat = regexp.MustCompile(`^[\w-]{43}$`)
76
77
78
79 func LooksLikeAToken(token string) bool {
80 return tokenFormat.MatchString(token)
81 }
82
83
84
85
86
87 func Fingerprint256(data []byte) string {
88 d := sha256.New()
89 _, _ = d.Write(data)
90 return base64.RawURLEncoding.EncodeToString(d.Sum(nil))
91 }
92
93 type Sha256Digest [sha256.Size]byte
94
95
96
97 func KeyDigest(key crypto.PublicKey) (Sha256Digest, error) {
98 switch t := key.(type) {
99 case *jose.JSONWebKey:
100 if t == nil {
101 return Sha256Digest{}, errors.New("cannot compute digest of nil key")
102 }
103 return KeyDigest(t.Key)
104 case jose.JSONWebKey:
105 return KeyDigest(t.Key)
106 default:
107 keyDER, err := x509.MarshalPKIXPublicKey(key)
108 if err != nil {
109 return Sha256Digest{}, err
110 }
111 return sha256.Sum256(keyDER), nil
112 }
113 }
114
115
116
117 func KeyDigestB64(key crypto.PublicKey) (string, error) {
118 digest, err := KeyDigest(key)
119 if err != nil {
120 return "", err
121 }
122 return base64.StdEncoding.EncodeToString(digest[:]), nil
123 }
124
125
126 func KeyDigestEquals(j, k crypto.PublicKey) bool {
127 digestJ, errJ := KeyDigestB64(j)
128 digestK, errK := KeyDigestB64(k)
129
130
131 if errJ != nil || errK != nil {
132 return false
133 }
134 return digestJ == digestK
135 }
136
137
138 func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) {
139 switch ak := a.(type) {
140 case *rsa.PublicKey:
141 return ak.Equal(b), nil
142 case *ecdsa.PublicKey:
143 return ak.Equal(b), nil
144 default:
145 return false, fmt.Errorf("unsupported public key type %T", ak)
146 }
147 }
148
149
150
151 func SerialToString(serial *big.Int) string {
152 return fmt.Sprintf("%036x", serial)
153 }
154
155
156
157 func StringToSerial(serial string) (*big.Int, error) {
158 var serialNum big.Int
159 if !ValidSerial(serial) {
160 return &serialNum, fmt.Errorf("invalid serial number %q", serial)
161 }
162 _, err := fmt.Sscanf(serial, "%036x", &serialNum)
163 return &serialNum, err
164 }
165
166
167
168
169 func ValidSerial(serial string) bool {
170
171
172
173 if len(serial) != 32 && len(serial) != 36 {
174 return false
175 }
176 _, err := hex.DecodeString(serial)
177 return err == nil
178 }
179
180
181 func GetBuildID() (retID string) {
182 retID = BuildID
183 if retID == "" {
184 retID = Unspecified
185 }
186 return
187 }
188
189
190 func GetBuildTime() (retID string) {
191 retID = BuildTime
192 if retID == "" {
193 retID = Unspecified
194 }
195 return
196 }
197
198
199 func GetBuildHost() (retID string) {
200 retID = BuildHost
201 if retID == "" {
202 retID = Unspecified
203 }
204 return
205 }
206
207
208
209
210 func IsAnyNilOrZero(vals ...interface{}) bool {
211 for _, val := range vals {
212 switch v := val.(type) {
213 case nil:
214 return true
215 case []byte:
216 if len(v) == 0 {
217 return true
218 }
219 default:
220 if reflect.ValueOf(v).IsZero() {
221 return true
222 }
223 }
224 }
225 return false
226 }
227
228
229
230
231 func UniqueLowerNames(names []string) (unique []string) {
232 nameMap := make(map[string]int, len(names))
233 for _, name := range names {
234 nameMap[strings.ToLower(name)] = 1
235 }
236
237 unique = make([]string, 0, len(nameMap))
238 for name := range nameMap {
239 unique = append(unique, name)
240 }
241 sort.Strings(unique)
242 return
243 }
244
245
246
247 func HashNames(names []string) []byte {
248 names = UniqueLowerNames(names)
249 hash := sha256.Sum256([]byte(strings.Join(names, ",")))
250 return hash[:]
251 }
252
253
254 func LoadCert(filename string) (*x509.Certificate, error) {
255 certPEM, err := os.ReadFile(filename)
256 if err != nil {
257 return nil, err
258 }
259 block, _ := pem.Decode(certPEM)
260 if block == nil {
261 return nil, fmt.Errorf("no data in cert PEM file %q", filename)
262 }
263 cert, err := x509.ParseCertificate(block.Bytes)
264 if err != nil {
265 return nil, err
266 }
267 return cert, nil
268 }
269
270
271 const retryJitter = 0.2
272
273
274
275
276
277
278 func RetryBackoff(retries int, base, max time.Duration, factor float64) time.Duration {
279 if retries == 0 {
280 return 0
281 }
282 backoff, fMax := float64(base), float64(max)
283 for backoff < fMax && retries > 1 {
284 backoff *= factor
285 retries--
286 }
287 if backoff > fMax {
288 backoff = fMax
289 }
290
291
292 backoff *= (1 - retryJitter) + 2*retryJitter*mrand.Float64()
293 return time.Duration(backoff)
294 }
295
296
297
298 func IsASCII(str string) bool {
299 for _, r := range str {
300 if r > unicode.MaxASCII {
301 return false
302 }
303 }
304 return true
305 }
306
307 func Command() string {
308 return path.Base(os.Args[0])
309 }
310
View as plain text