/
pkce.go
151 lines (130 loc) · 4.68 KB
/
pkce.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// Package PKCE implements Proof Key for Code Exchange by OAuth Public Clients.
//
// See also: https://datatracker.ietf.org/doc/html/rfc7636.
package pkce
import (
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/binary"
"fmt"
"golang.org/x/oauth2"
)
//go:generate go run golang.org/x/tools/cmd/stringer -type=Method -linecomment -output=pkce_string.go
var encoding = base64.URLEncoding.WithPadding(base64.NoPadding)
// Method used to create the PKCE Code [Challenge].
type Method uint8
// Available methods for creating the PKCE Code [Challenge].
//
// If the client is capable of using [MethodS256], it MUST use [MethodS256], as
// [MethodS256] is Mandatory To Implement (MTI) on the server. Clients are
// permitted to use [MethodPlain] only if they cannot support [MethodS256] for
// some technical reason and know via out-of-band configuration that the server
// supports [MethodPlain].
//
// The [MethodPlain] transformation is for compatibility with existing
// deployments and for constrained environments that can't use the [MethodS256]
// transformation.
//
// See also: https://datatracker.ietf.org/doc/html/rfc7636#section-4.2.
const (
MethodPlain Method = iota + 1 // plain
MethodS256 // S256
)
// MethodFromString returns a method from its string representation.
func MethodFromString(s string) (Method, error) {
switch s {
case MethodPlain.String():
return MethodPlain, nil
case MethodS256.String():
return MethodS256, nil
}
return 0, fmt.Errorf("invalid method %q", s)
}
// AuthCodeOption returns an option compatible with [oauth2.Config.Exchange].
func (m Method) AuthCodeOption() oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("code_challenge_method", m.String())
}
// Verifier is a 43-octet URL safe PKCE Code Verifier.
//
// Use [Verifier.String] to get a string representation of the verifier or
// [Verifier.AuthCodeOption] to get an option compatible with
// [oauth2.Config.AuthCodeURL].
type Verifier [43]byte
// New creates a new PKCE Code Verifier.
func New() (v Verifier, err error) {
b := make([]byte, 32)
if err := binary.Read(rand.Reader, binary.LittleEndian, b); err != nil {
return v, err
}
encoding.Encode(v[:], b)
return v, nil
}
// VerifierFromString returns a verifier from its string representation.
func VerifierFromString(s string) (v Verifier) {
copy(v[:], []byte(s))
return
}
// Challenge creates the PKCE Code Challenge for the PKCE Code Verifier using
// the given method.
//
// If the client is capable of using [MethodS256], it MUST use [MethodS256], as
// [MethodS256] is Mandatory To Implement (MTI) on the server. Clients are
// permitted to use [MethodPlain] only if they cannot support [MethodS256] for
// some technical reason and know via out-of-band configuration that the server
// supports [MethodPlain].
//
// The [MethodPlain] transformation is for compatibility with existing
// deployments and for constrained environments that can't use the [MethodS256]
// transformation.
//
// See also: https://datatracker.ietf.org/doc/html/rfc7636#section-4.2.
func (v Verifier) Challenge(method Method) (c Challenge) {
switch method {
case MethodPlain:
copy(c[:], v[:])
case MethodS256:
sum := sha256.Sum256(v[:])
encoding.Encode(c[:], sum[:])
default:
panic(fmt.Errorf("unknown code challenge method %q", method))
}
return
}
// AuthCodeOption returns an option compatible with [oauth2.Config.AuthCodeURL].
func (v Verifier) AuthCodeOption() oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("code_verifier", v.String())
}
// String returns the string representation of the PKCE Code Verifier.
//
// It implements [fmt.Stringer].
func (v Verifier) String() string {
return string(v[:])
}
// Challenge is a 43-octet URL safe PKCE Code Challenge.
//
// Use [Challenge.String] to get a string representation of the challenge or
// [Challenge.AuthCodeOption] to get an option compatible with
// [oauth2.Config.Exchange].
type Challenge [43]byte
// ChallengeFromString returns a challenge from its string representation.
func ChallengeFromString(s string) (c Challenge) {
copy(c[:], []byte(s))
return
}
// AuthCodeOption returns an option compatible with [oauth2.Config.Exchange].
func (c Challenge) AuthCodeOption() oauth2.AuthCodeOption {
return oauth2.SetAuthURLParam("code_challenge", c.String())
}
// Verify the PKCE Code Challenge using the given PKCE Code Verifier and method.
func (c Challenge) Verify(verifier Verifier, method Method) bool {
challenge := verifier.Challenge(method)
return subtle.ConstantTimeCompare(c[:], challenge[:]) == 1
}
// String returns the string representation of the PKCE Code Challenge.
//
// It implements [fmt.Stringer].
func (c Challenge) String() string {
return string(c[:])
}