-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathtoken.go
180 lines (159 loc) · 5.64 KB
/
token.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package siwago
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/json"
"fmt"
"strings"
"time"
)
//struct for JWT Header
type JWTTokenHeader struct {
Alg string `json:"alg"`
Kid string `json:"kid"`
}
//struct for JWT Body
type JWTTokenBody struct {
Iss string `json:"iss"`
Iat int64 `json:"iat"`
Exp int64 `json:"exp"`
Aud string `json:"aud"`
Sub string `json:"sub"`
AtHash string `json:"at_hash"`
Email string `json:"email"`
EmailVerified string `json:"email_verified"`
IsPrivateEmail string `json:"is_private_email"`
RealUserStatus int64 `json:"real_user_status"`
AuthTime int64 `json:"auth_time"`
Nonce string `json:"nonce"`
}
//struct to hold the decoded idtoken
type SiwaIdToken struct {
Header *JWTTokenHeader
Body *JWTTokenBody
Signature []byte
Valid bool
}
//struct for token returned from apple
type Token struct {
//(Reserved for future use) A token used to access allowed data. Currently, no data set has been defined for access.
AccessToken string `json:"access_token"`
//The type of access token. It will always be bearer.
TokenType string `json:"token_type"`
//The amount of time, in seconds, before the access token expires.
ExpiresIn int64 `json:"expires_in"`
//The refresh token used to regenerate new access tokens. Store this token securely on your server.
RefreshToken string `json:"refresh_token"`
//A JSON Web Token that contains the user’s identity information.
IdToken string `json:"id_token"`
//Set if ErrorResponse is recieved
//A string that describes the reason for the unsuccessful request. The string consists of a single allowed value.
//Possible values: invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope
Error string `json:"error"`
//After the token is fetched from apple, id token is validated
//this field stores the result of the validation check
Valid bool `json:"_"`
//The decoded Id token
//Holds the decoded JWT Header, Body, Signature and result of validity check
DecodedIdToken *SiwaIdToken `json:"_"`
}
func (self Token) String() string {
return fmt.Sprintf("AccessToken: %v, TokenType: %v, ExpiresIn:%v, RefreshToken:%v, IdToken:%v, Error:%v, Valid:%v",
self.AccessToken, self.TokenType, self.ExpiresIn, self.RefreshToken, self.IdToken, self.Error, self.Valid)
}
//function to verify idtoken signature for apple published public key
func verifyAppleRSA256(message string, signature []byte, kid string) bool {
var rsaPublicKey *rsa.PublicKey
var err error
var hashed [32]byte
//get the public key
rsaPublicKey = getApplePublicKeyObject(kid, "RS256")
//if key found, validate
if rsaPublicKey != nil {
bytesToHash := []byte(message)
//get hash
hashed = sha256.Sum256(bytesToHash)
err = rsa.VerifyPKCS1v15(rsaPublicKey, crypto.SHA256, hashed[:], signature)
if err != nil {
return false
}
}
return true
}
//validates idToken without nonce check
func ValidateIdToken(aud string, idToken string) (*SiwaIdToken, string) {
return ValidateIdTokenWithNonce(aud, idToken, "")
}
//validates idtoken
//more info: https://developer.apple.com/documentation/signinwithapplerestapi/verifying_a_user
func ValidateIdTokenWithNonce(aud string, idToken string, nonce string) (*SiwaIdToken, string) {
//initialize the token object
var siwaIdToken *SiwaIdToken = &SiwaIdToken{Valid: false}
if idToken == "" {
return siwaIdToken, "empty_token"
}
//split and decode token
parts := strings.Split(idToken, ".")
if len(parts) != 3 {
return siwaIdToken, "invalid_format_missing_parts"
}
jsonHeaderB, err := base64UrlDecode(parts[0])
if err != nil {
return siwaIdToken, "invalid_format_header_base64_decode_failed error:" + err.Error()
}
var jwtHeader JWTTokenHeader
err = json.Unmarshal(jsonHeaderB, &jwtHeader)
if err != nil {
return siwaIdToken, "invalid_format_header_json_decode_failed error:" + err.Error()
}
jsonBodyB, err := base64UrlDecode(parts[1])
if err != nil {
return siwaIdToken, "invalid_format_body_base64_decode_failed error:" + err.Error()
}
var jwtBody JWTTokenBody
err = json.Unmarshal(jsonBodyB, &jwtBody)
if err != nil {
return siwaIdToken, "invalid_format_body_json_decode_failed error:" + err.Error()
}
//the basic validation tests pass. Now check if the contents of token are valid
var reason string
var valid bool = true
//Verify the nonce for the authentication
//if idtoken had nonce, the check will fail
if jwtBody.Nonce != "" && jwtBody.Nonce != nonce {
reason = reason + "nonce_check_failed"
valid = false
}
//Verify that the iss field contains https://appleid.apple.com
if jwtBody.Iss != "https://appleid.apple.com" {
reason = reason + " iss_check_failed"
valid = false
}
//Verify that the aud field is the developer’s client_id
if jwtBody.Aud != aud {
reason = reason + " aud_check_failed"
valid = false
}
//Verify that the time is earlier than the exp value of the token
if jwtBody.Exp < time.Now().Unix() {
reason = reason + " expiry_in_past"
valid = false
}
//Verify the JWS E256 signature using the server’s public key
var decodedSignature []byte
decodedSignature, err = base64UrlDecode(parts[2])
if err != nil {
reason = reason + " signature_base64_decode_failed error:" + err.Error()
valid = false
} else if !verifyAppleRSA256(parts[0]+"."+parts[1], decodedSignature, jwtHeader.Kid) {
reason = reason + " signature_verification_failed"
valid = false
}
//set the values of parsed token into the id token object
siwaIdToken.Header = &jwtHeader
siwaIdToken.Body = &jwtBody
siwaIdToken.Valid = valid
siwaIdToken.Signature = decodedSignature
return siwaIdToken, reason
}