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