2018-11-26 11:23:27 +01:00
|
|
|
// Copyright (c) 2018 Shivaram Lingamneni <slingamn@cs.stanford.edu>
|
|
|
|
// released under the MIT license
|
|
|
|
|
|
|
|
package utils
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/rand"
|
|
|
|
"crypto/subtle"
|
2018-12-28 19:45:55 +01:00
|
|
|
"encoding/base32"
|
2019-05-12 22:26:23 +02:00
|
|
|
"encoding/base64"
|
2019-12-18 21:44:06 +01:00
|
|
|
"strings"
|
2018-12-28 19:45:55 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2019-02-08 02:41:25 +01:00
|
|
|
// slingamn's own private b32 alphabet, removing 1, l, o, and 0
|
2019-05-12 08:17:57 +02:00
|
|
|
B32Encoder = base32.NewEncoding("abcdefghijkmnpqrstuvwxyz23456789").WithPadding(base32.NoPadding)
|
2018-11-26 11:23:27 +01:00
|
|
|
)
|
|
|
|
|
2019-02-12 06:27:57 +01:00
|
|
|
const (
|
|
|
|
SecretTokenLength = 26
|
|
|
|
)
|
|
|
|
|
2018-11-26 11:23:27 +01:00
|
|
|
// generate a secret token that cannot be brute-forced via online attacks
|
|
|
|
func GenerateSecretToken() string {
|
|
|
|
// 128 bits of entropy are enough to resist any online attack:
|
|
|
|
var buf [16]byte
|
|
|
|
rand.Read(buf[:])
|
2018-12-28 19:45:55 +01:00
|
|
|
// 26 ASCII characters, should be fine for most purposes
|
2019-05-12 08:17:57 +02:00
|
|
|
return B32Encoder.EncodeToString(buf[:])
|
2018-11-26 11:23:27 +01:00
|
|
|
}
|
|
|
|
|
2019-05-15 11:00:55 +02:00
|
|
|
// "munge" a secret token to a new value. requirements:
|
|
|
|
// 1. MUST be roughly as unlikely to collide with `GenerateSecretToken` outputs
|
|
|
|
// as those outputs are with each other
|
|
|
|
// 2. SHOULD be deterministic (motivation: if a JOIN line has msgid x,
|
|
|
|
// create a deterministic msgid y for the fake HistServ PRIVMSG that "replays" it)
|
|
|
|
// 3. SHOULD be in the same "namespace" as `GenerateSecretToken` outputs
|
|
|
|
// (same length and character set)
|
|
|
|
func MungeSecretToken(token string) (result string) {
|
|
|
|
bytes, err := B32Encoder.DecodeString(token)
|
|
|
|
if err != nil {
|
|
|
|
// this should never happen
|
|
|
|
return GenerateSecretToken()
|
|
|
|
}
|
|
|
|
// add 1 with carrying
|
|
|
|
for i := len(bytes) - 1; 0 <= i; i -= 1 {
|
|
|
|
bytes[i] += 1
|
|
|
|
if bytes[i] != 0 {
|
|
|
|
break
|
|
|
|
} // else: overflow, carry to the next place
|
|
|
|
}
|
|
|
|
return B32Encoder.EncodeToString(bytes)
|
|
|
|
}
|
|
|
|
|
2018-11-26 11:23:27 +01:00
|
|
|
// securely check if a supplied token matches a stored token
|
|
|
|
func SecretTokensMatch(storedToken string, suppliedToken string) bool {
|
|
|
|
// XXX fix a potential gotcha: if the stored token is uninitialized,
|
|
|
|
// then nothing should match it, not even supplying an empty token.
|
|
|
|
if len(storedToken) == 0 {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return subtle.ConstantTimeCompare([]byte(storedToken), []byte(suppliedToken)) == 1
|
|
|
|
}
|
2019-05-12 22:26:23 +02:00
|
|
|
|
|
|
|
// generate a 256-bit secret key that can be written into a config file
|
|
|
|
func GenerateSecretKey() string {
|
|
|
|
var buf [32]byte
|
|
|
|
rand.Read(buf[:])
|
|
|
|
return base64.RawURLEncoding.EncodeToString(buf[:])
|
|
|
|
}
|
2019-12-18 21:44:06 +01:00
|
|
|
|
|
|
|
func normalizeCertfp(certfp string) string {
|
|
|
|
return strings.ToLower(strings.Replace(certfp, ":", "", -1))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convenience to compare certfps as returned by different tools, e.g., openssl vs. oragono
|
|
|
|
func CertfpsMatch(storedCertfp, suppliedCertfp string) bool {
|
|
|
|
if storedCertfp == "" {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return normalizeCertfp(storedCertfp) == normalizeCertfp(suppliedCertfp)
|
|
|
|
}
|