github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/auth/token.go (about) 1 // Copyright 2015 Keybase, Inc. All rights reserved. Use of 2 // this source code is governed by the included BSD license. 3 4 // Code used to support authentication tokens for arbitrary purposes. 5 package auth 6 7 import ( 8 "bytes" 9 "crypto/rand" 10 "encoding/hex" 11 "encoding/json" 12 "math" 13 "time" 14 15 "github.com/keybase/client/go/kbcrypto" 16 libkb "github.com/keybase/client/go/libkb" 17 keybase1 "github.com/keybase/client/go/protocol/keybase1" 18 ) 19 20 const ( 21 TokenType = "auth" 22 CurrentTokenVersion = 2 23 ChallengeLengthBytes = 32 24 ChallengeLengthString = ChallengeLengthBytes * 2 // we use hex encoding 25 ) 26 27 type TokenAuth struct { 28 Server string `json:"server"` 29 Challenge string `json:"session"` 30 } 31 32 type TokenKey struct { 33 UID keybase1.UID `json:"uid"` 34 Username libkb.NormalizedUsername `json:"username"` 35 KID keybase1.KID `json:"kid"` 36 } 37 38 type TokenBody struct { 39 Auth TokenAuth `json:"auth"` 40 Key TokenKey `json:"key"` 41 Type string `json:"type"` 42 Version int `json:"version"` 43 } 44 45 type TokenClient struct { 46 Name string `json:"name"` 47 Version string `json:"version"` 48 } 49 50 type Token struct { 51 Body TokenBody `json:"body"` 52 Client TokenClient `json:"client"` 53 CreationTime int64 `json:"ctime"` 54 ExpireIn int `json:"expire_in"` 55 Tag string `json:"tag"` 56 } 57 58 func NewToken(uid keybase1.UID, username libkb.NormalizedUsername, kid keybase1.KID, 59 server, challenge string, now int64, expireIn int, 60 clientName, clientVersion string) *Token { 61 return &Token{ 62 Body: TokenBody{ 63 Auth: TokenAuth{ 64 Server: server, 65 Challenge: challenge, 66 }, 67 Key: TokenKey{ 68 UID: uid, 69 Username: username, 70 KID: kid, 71 }, 72 Type: TokenType, 73 Version: CurrentTokenVersion, 74 }, 75 Client: TokenClient{ 76 Name: clientName, 77 Version: clientVersion, 78 }, 79 CreationTime: now, 80 ExpireIn: expireIn, 81 Tag: "signature", 82 } 83 } 84 85 func (t Token) Bytes() []byte { 86 bytes, err := json.Marshal(&t) 87 if err != nil { 88 return []byte{} 89 } 90 return bytes 91 } 92 93 func (t Token) String() string { 94 return string(t.Bytes()) 95 } 96 97 func VerifyToken(signature, server, challenge string, maxExpireIn int) (*Token, error) { 98 var t *Token 99 key, token, _, err := kbcrypto.NaclVerifyAndExtract(signature) 100 if err != nil { 101 return nil, err 102 } 103 if t, err = parseToken(token); err != nil { 104 return nil, err 105 } 106 if key.GetKID() != t.KID() { 107 return nil, InvalidTokenKeyError{ 108 expected: key.GetKID().String(), 109 received: t.KID().String(), 110 } 111 } 112 if TokenType != t.Type() { 113 return nil, InvalidTokenTypeError{ 114 expected: TokenType, 115 received: t.Type(), 116 } 117 } 118 if server != t.Server() { 119 return nil, InvalidTokenServerError{ 120 expected: server, 121 received: t.Server(), 122 } 123 } 124 if challenge != t.Challenge() { 125 return nil, InvalidTokenChallengeError{ 126 expected: challenge, 127 received: t.Challenge(), 128 } 129 } 130 remaining := t.TimeRemaining() 131 if remaining > maxExpireIn { 132 return nil, MaxTokenExpiresError{ 133 creationTime: t.CreationTime, 134 expireIn: t.ExpireIn, 135 now: time.Now().Unix(), 136 maxExpireIn: maxExpireIn, 137 remaining: remaining, 138 } 139 } 140 if remaining <= 0 { 141 return nil, TokenExpiredError{ 142 creationTime: t.CreationTime, 143 expireIn: t.ExpireIn, 144 now: time.Now().Unix(), 145 } 146 } 147 return t, nil 148 } 149 150 func (t Token) TimeRemaining() int { 151 ctime := time.Unix(t.CreationTime, 0) 152 expires := ctime.Add(time.Duration(t.ExpireIn) * time.Second) 153 return int(math.Ceil(time.Until(expires).Seconds())) 154 } 155 156 func (t Token) Server() string { 157 return t.Body.Auth.Server 158 } 159 160 func (t Token) Challenge() string { 161 return t.Body.Auth.Challenge 162 } 163 164 func (t Token) UID() keybase1.UID { 165 return t.Body.Key.UID 166 } 167 168 func (t Token) KID() keybase1.KID { 169 return t.Body.Key.KID 170 } 171 172 func (t Token) Username() libkb.NormalizedUsername { 173 return t.Body.Key.Username 174 } 175 176 func (t Token) Type() string { 177 return t.Body.Type 178 } 179 180 func (t Token) Version() int { 181 return t.Body.Version 182 } 183 184 func (t Token) ClientName() string { 185 return t.Client.Name 186 } 187 188 func (t Token) ClientVersion() string { 189 return t.Client.Version 190 } 191 192 func parseToken(token []byte) (*Token, error) { 193 decoder := json.NewDecoder(bytes.NewReader(token)) 194 decoder.UseNumber() 195 var t Token 196 if err := decoder.Decode(&t); err != nil { 197 return nil, err 198 } 199 return &t, nil 200 } 201 202 // GenerateChallenge returns a cryptographically secure random challenge string. 203 func GenerateChallenge() (string, error) { 204 buf := make([]byte, ChallengeLengthBytes) 205 if _, err := rand.Read(buf); err != nil { 206 return "", err 207 } 208 return hex.EncodeToString(buf), nil 209 } 210 211 // IsValidChallenge returns true if the passed challenge is validly formed. 212 func IsValidChallenge(challenge string) bool { 213 if len(challenge) != ChallengeLengthString { 214 return false 215 } 216 if _, err := hex.DecodeString(challenge); err != nil { 217 return false 218 } 219 return true 220 }