Go · 1400 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package runnertoken mints and hashes long-lived runner registration tokens.
4 //
5 // Tokens are 32 random bytes rendered as hex for operator copy/paste. Only the
6 // SHA-256 hash is stored in runner_tokens; the plaintext is printed once by
7 // `shithubd admin actions runner register` and then lost.
8 package runnertoken
9
10 import (
11 "crypto/rand"
12 "crypto/sha256"
13 "crypto/subtle"
14 "encoding/hex"
15 "errors"
16 "strings"
17 )
18
19 const SizeBytes = 32
20
21 var (
22 ErrMalformed = errors.New("runnertoken: malformed token")
23 ErrWrongSize = errors.New("runnertoken: wrong token length")
24 )
25
26 // New mints a token and returns the hex encoding plus its SHA-256 hash.
27 func New() (encoded string, hash []byte, err error) {
28 raw := make([]byte, SizeBytes)
29 if _, err := rand.Read(raw); err != nil {
30 return "", nil, err
31 }
32 encoded = hex.EncodeToString(raw)
33 sum := sha256.Sum256(raw)
34 return encoded, sum[:], nil
35 }
36
37 // HashOf decodes a hex registration token and returns the stored hash.
38 func HashOf(encoded string) ([]byte, error) {
39 raw, err := hex.DecodeString(strings.TrimSpace(encoded))
40 if err != nil {
41 return nil, ErrMalformed
42 }
43 if len(raw) != SizeBytes {
44 return nil, ErrWrongSize
45 }
46 sum := sha256.Sum256(raw)
47 return sum[:], nil
48 }
49
50 // Equal compares two token hashes in constant time.
51 func Equal(a, b []byte) bool {
52 return subtle.ConstantTimeCompare(a, b) == 1
53 }
54