cluster-bootstrap: make IsValidBootstrapToken() be in constant-time

The function uses BootstrapTokenRegexp.MatchString(token)
which is not a recommended practice.

Instead, break down the token into its components: ID, secret.
The ID is public thus we can use Regexp matching for it.
The secret needs constant time comparison. Iterate over
every character and make sure it fits the 0-9a-z range and that
it has a length of 16.
This commit is contained in:
Lubomir I. Ivanov 2023-09-04 13:29:40 +03:00
parent 5c80007ecc
commit 1d519f1b08

View File

@ -84,10 +84,36 @@ func TokenFromIDAndSecret(id, secret string) string {
return fmt.Sprintf("%s.%s", id, secret)
}
// IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token and
// in other words satisfies the BootstrapTokenRegexp
// IsValidBootstrapToken returns whether the given string is valid as a Bootstrap Token.
// Avoid using BootstrapTokenRegexp.MatchString(token) and instead perform constant-time
// comparisons on the secret.
func IsValidBootstrapToken(token string) bool {
return BootstrapTokenRegexp.MatchString(token)
// Must be exactly two strings separated by "."
t := strings.Split(token, ".")
if len(t) != 2 {
return false
}
// Validate the ID: t[0]
// Using a Regexp for it is safe because the ID is public already
if !BootstrapTokenIDRegexp.MatchString(t[0]) {
return false
}
// Validate the secret with constant-time: t[1]
secret := t[1]
if len(secret) != api.BootstrapTokenSecretBytes { // Must be an exact size
return false
}
for i := range secret {
c := int(secret[i])
notDigit := (c < 48 || c > 57) // Character is not in the 0-9 range
notLetter := (c < 97 || c > 122) // Character is not in the a-z range
if notDigit && notLetter {
return false
}
}
return true
}
// IsValidBootstrapTokenID returns whether the given string is valid as a Bootstrap Token ID and