3
0
mirror of https://github.com/ergochat/ergo.git synced 2024-12-23 11:12:44 +01:00
ergo/irc/utils/crypto.go

84 lines
2.5 KiB
Go
Raw Normal View History

// 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 (
// 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)
)
const (
SecretTokenLength = 26
)
// 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[:])
}
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)
}
// 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)
}