package wristband import ( "crypto/rand" "crypto/sha256" "crypto/subtle" "encoding/base64" "encoding/hex" ) // randomBase64URL returns n cryptographically random bytes, base64url // (RawURLEncoding, no padding) encoded, matching PHP's // rtrim(strtr(base64_encode(random_bytes(n)), '+/', '-_'), '=') byte for // byte (D-01/D-04). func randomBase64URL(n int) (string, error) { buf := make([]byte, n) if _, err := rand.Read(buf); err != nil { return "", err } return base64.RawURLEncoding.EncodeToString(buf), nil } // sha256Hex is the fixed transform every opaque secret (client secret, code, // refresh token) is compared and persisted through: never the raw variable- // length secret (D-04). func sha256Hex(raw string) string { sum := sha256.Sum256([]byte(raw)) return hex.EncodeToString(sum[:]) } // constantEqual compares two fixed-transform strings (sha256 hex digests or // PKCE S256 challenges) in constant time (D-04; RFC 7636 verifier compare). func constantEqual(a, b string) bool { return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 } // s256Challenge is the RFC 7636 S256 transform: base64url(sha256(verifier)). func s256Challenge(verifier string) string { sum := sha256.Sum256([]byte(verifier)) return base64.RawURLEncoding.EncodeToString(sum[:]) }