git.iko.soy/typeable/oauth2-proxy
on f820deb help sign in
125 lines 3.6 kB history raw
1package encryption
3import (
4 "crypto/hmac"
5 "crypto/sha256"
6 "encoding/base64"
7 "fmt"
8 "hash"
9 "net/http"
10 "strconv"
11 "strings"
12 "time"
15const (
16 CodeChallengeMethodPlain = "plain"
17 CodeChallengeMethodS256 = "S256"
20// SecretBytes attempts to base64 decode the secret, if that fails it treats the secret as binary
21func SecretBytes(secret string) []byte {
22 b, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(secret, "="))
23 if err == nil {
24 // Only return decoded form if a valid AES length
25 // Don't want unintentional decoding resulting in invalid lengths confusing a user
26 // that thought they used a 16, 24, 32 length string
27 for _, i := range []int{16, 24, 32} {
28 if len(b) == i {
29 return b
30 }
31 }
32 }
33 // If decoding didn't work or resulted in non-AES compliant length,
34 // assume the raw string was the intended secret
35 return []byte(secret)
38// cookies are stored in a 3 part (value + timestamp + signature) to enforce that the values are as originally set.
39// additionally, the 'value' is encrypted so it's opaque to the browser
41// Validate ensures a cookie is properly signed
42func Validate(cookie *http.Cookie, seed string, expiration time.Duration) (value []byte, t time.Time, ok bool) {
43 // value, timestamp, sig
44 parts := strings.Split(cookie.Value, "|")
45 if len(parts) != 3 {
46 return
47 }
48 if checkSignature(parts[2], seed, cookie.Name, parts[0], parts[1]) {
49 ts, err := strconv.Atoi(parts[1])
50 if err != nil {
51 return
52 }
53 // The expiration timestamp set when the cookie was created
54 // isn't sent back by the browser. Hence, we check whether the
55 // creation timestamp stored in the cookie falls within the
56 // window defined by (Now()-expiration, Now()].
57 t = time.Unix(int64(ts), 0)
58 if t.After(time.Now().Add(expiration*-1)) && t.Before(time.Now().Add(time.Minute*5)) {
59 // it's a valid cookie. now get the contents
60 rawValue, err := base64.URLEncoding.DecodeString(parts[0])
61 if err == nil {
62 value = rawValue
63 ok = true
64 return
65 }
66 }
67 }
68 return
71// SignedValue returns a cookie that is signed and can later be checked with Validate
72func SignedValue(seed string, key string, value []byte, now time.Time) (string, error) {
73 encodedValue := base64.URLEncoding.EncodeToString(value)
74 timeStr := fmt.Sprintf("%d", now.Unix())
75 sig, err := cookieSignature(sha256.New, seed, key, encodedValue, timeStr)
76 if err != nil {
77 return "", err
78 }
79 cookieVal := fmt.Sprintf("%s|%s|%s", encodedValue, timeStr, sig)
80 return cookieVal, nil
83func GenerateCodeChallenge(method, codeVerifier string) (string, error) {
84 switch method {
85 case CodeChallengeMethodPlain:
86 return codeVerifier, nil
87 case CodeChallengeMethodS256:
88 shaSum := sha256.Sum256([]byte(codeVerifier))
89 return base64.RawURLEncoding.EncodeToString(shaSum[:]), nil
90 default:
91 return "", fmt.Errorf("unknown challenge method: %v", method)
92 }
95func cookieSignature(signer func() hash.Hash, args ...string) (string, error) {
96 h := hmac.New(signer, []byte(args[0]))
97 for _, arg := range args[1:] {
98 _, err := h.Write([]byte(arg))
99 if err != nil {
100 return "", err
101 }
102 }
103 var b []byte
104 b = h.Sum(b)
105 return base64.URLEncoding.EncodeToString(b), nil
108func checkSignature(signature string, args ...string) bool {
109 checkSig, err := cookieSignature(sha256.New, args...)
110 if err != nil {
111 return false
112 }
113 return checkHmac(signature, checkSig)
116func checkHmac(input, expected string) bool {
117 inputMAC, err1 := base64.URLEncoding.DecodeString(input)
118 if err1 == nil {
119 expectedMAC, err2 := base64.URLEncoding.DecodeString(expected)
120 if err2 == nil {
121 return hmac.Equal(inputMAC, expectedMAC)
122 }
123 }
124 return false