xref: /aosp_15_r20/external/tink/go/jwt/jwt_test.go (revision e7b1675dde1b92d52ec075b0a92829627f2c52a5)
1// Copyright 2022 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15////////////////////////////////////////////////////////////////////////////////
16
17package jwt_test
18
19import (
20	"bytes"
21	"encoding/json"
22	"fmt"
23	"log"
24	"time"
25
26	"github.com/google/tink/go/insecurecleartextkeyset"
27	"github.com/google/tink/go/jwt"
28	"github.com/google/tink/go/keyset"
29)
30
31// [START jwt-signature-example]
32func Example_signAndVerify() {
33	// A private keyset created with
34	// "tinkey create-keyset --key-template=JWT_RS256_2048_F4 --out private_keyset.cfg".
35	// Note that this keyset has the secret key information in cleartext.
36	privateJSONKeyset := `{
37		"primaryKeyId": 185188009,
38		"key": [
39			{
40				"keyData": {
41					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey",
42					"value": "EosCEAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQABGoACT2lWxwySaQbp/N3lBUZ/dJ+AKsiaWWdfNmbTfwpCwbHhwhFKv5lMpynWgCIzS7d0uDpPKhLq20eZMpaVjXRaTn92vzuyB7DbpFiukkvGO839CvS9iueMjDP/weHlwzxtHqKJKVoRg7WAS6Iy7XUngLhT5GKNdbsooJ1GSKXyhbgWyMcspKSQe4lZXUntVMK5z4iLNmcQwsBp8yM55mZra13TXowob/E/wd+tGiABCn6CDt8G1gXzWDaoF2tt6WhSGZbXUVGagmoea/BWeAuJyKSSi5h+uPpc5SPhGvyKfSEVaCs2QeM7/UIXhzAcx2j/VqySb6y9EbSiJfy8vr49QSKBAQD+AbFCGHd9kZ5LIQrfe9caOxS9pQPdFkBJESw0C3x2uBIg8awiQsuVXMeEgyGLyWBZoi2x98OMSR9OzCuSLtb7Nv0Wqn0LUj4WPRdmg//uLeD3O2rcVRIR4db/B8WvXnK2uQsqwGDyh4BepGvprXQPYMX2uwnBGL2ccS2De53HJSqBAQC1QfOi4egjmlmXqJLpISUSN1NixkIi8EJHaZZ0YrbaRrEyiJczthcazNHFt6gzgOcosFaKaZeqps4Tet+5NgS7eh7RzLQ2+cfT4ewpT2ExJ4NsOy8XDqD6GRjliLxjGAoUf24s3B+3LLACPiQjeeZGJP0ivh384WabyXXxRgHFSTKBAQChl7gKIYCbHPHEQAAnzyQ4Js/6GinMFCTPlyI09f23lUDLPpRQs4fKvNydO8Myp+ko/NjvOH1qGPbW7WLmu+++n+wA6HNmqWqgQTtK170Q7JULE/zWsTQutitN0cb82yxFfJFTIFJM2NFc5GNWpSeJxPoMDk+VTcUK6qGW3SSyFTqBAQCeaPFA3SZAV1kNjio2zNzVOr0JijOqzUdfmgv/03Xy9e1POMjMTMuMhIygu42o1XMwwEwh037Vicp4g96aw3cHUgc1XC30DgByUPRQdit/BgV5xY+2GvbdHKoBkKrz/8Jvf58OXaLqN4frrdtvlc2GaDVC89zJcUR3ym3lW0WY4UKBAQD6MCruwXaxXJMxjtlH1YT5ow4R5neeiswNfGj4Ta/WbWyiVA60zpdNbGqH+etmiHY8+aBb/H4O9+JhOcBtlMLN4UlK1jg8wPSemZjsIPiUZXHkeIUa2RTUSz90wgz7aOqC0lYsLLFaJNWs54fC9LpZ0JzoqYDI8iDPnlE7xaag9g==",
43					"keyMaterialType": "ASYMMETRIC_PRIVATE"
44				},
45				"status": "ENABLED",
46				"keyId": 185188009,
47				"outputPrefixType": "TINK"
48			}
49		]
50	}`
51
52	// The corresponding public keyset created with
53	// "tinkey create-public-keyset --in private_keyset.cfg"
54	publicJSONKeyset := `{
55		"primaryKeyId": 185188009,
56		"key": [
57			{
58				"keyData": {
59					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey",
60					"value": "EAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQAB",
61					"keyMaterialType": "ASYMMETRIC_PUBLIC"
62				},
63				"status": "ENABLED",
64				"keyId": 185188009,
65				"outputPrefixType": "TINK"
66			}
67		]
68	}`
69
70	// Create a keyset handle from the cleartext private keyset in the previous
71	// step. The keyset handle provides abstract access to the underlying keyset to
72	// limit the access of the raw key material. WARNING: In practice,
73	// it is unlikely you will want to use a insecurecleartextkeyset, as it implies
74	// that your key material is passed in cleartext, which is a security risk.
75	// Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault.
76	// See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets.
77	privateKeysetHandle, err := insecurecleartextkeyset.Read(
78		keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset)))
79	if err != nil {
80		log.Fatal(err)
81	}
82
83	// Retrieve the JWT Signer primitive from privateKeysetHandle.
84	signer, err := jwt.NewSigner(privateKeysetHandle)
85	if err != nil {
86		log.Fatal(err)
87	}
88
89	// Use the primitive to create and sign a token. In this case, the primary key of the
90	// keyset will be used (which is also the only key in this example).
91	expiresAt := time.Now().Add(time.Hour)
92	audience := "example audience"
93	subject := "example subject"
94	rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
95		Audience:  &audience,
96		Subject:   &subject,
97		ExpiresAt: &expiresAt,
98	})
99	if err != nil {
100		log.Fatal(err)
101	}
102	token, err := signer.SignAndEncode(rawJWT)
103	if err != nil {
104		log.Fatal(err)
105	}
106
107	// Create a keyset handle from the keyset containing the public key. Because the
108	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
109	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
110		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
111	if err != nil {
112		log.Fatal(err)
113	}
114
115	// Retrieve the Verifier primitive from publicKeysetHandle.
116	verifier, err := jwt.NewVerifier(publicKeysetHandle)
117	if err != nil {
118		log.Fatal(err)
119	}
120
121	// Verify the signed token.
122	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
123	if err != nil {
124		log.Fatal(err)
125	}
126	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
127	if err != nil {
128		log.Fatal(err)
129	}
130
131	// Extract subject claim from the token.
132	if !verifiedJWT.HasSubject() {
133		log.Fatal(err)
134	}
135	extractedSubject, err := verifiedJWT.Subject()
136	if err != nil {
137		log.Fatal(err)
138	}
139	fmt.Println(extractedSubject)
140	// Output: example subject
141}
142
143// [END jwt-signature-example]
144
145// [START jwt-generate-jwks-example]
146func Example_generateJWKS() {
147	// A Tink keyset in JSON format with one JWT public key.
148	publicJSONKeyset := `{
149		"primaryKeyId": 185188009,
150		"key": [
151			{
152				"keyData": {
153					"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey",
154					"value": "EAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQAB",
155					"keyMaterialType": "ASYMMETRIC_PUBLIC"
156				},
157				"status": "ENABLED",
158				"keyId": 185188009,
159				"outputPrefixType": "TINK"
160			}
161		]
162	}`
163
164	// Create a keyset handle from the keyset containing the public key. Because the
165	// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
166	publicKeysetHandle, err := keyset.ReadWithNoSecrets(
167		keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
168	if err != nil {
169		log.Fatal(err)
170	}
171
172	// Create a publicJWKset from publicKeysetHandle.
173	publicJWKset, err := jwt.JWKSetFromPublicKeysetHandle(publicKeysetHandle)
174	if err != nil {
175		log.Fatal(err)
176	}
177
178	// Remove whitespace so that we can compare it to the expected string.
179	compactPublicJWKset := &bytes.Buffer{}
180	err = json.Compact(compactPublicJWKset, publicJWKset)
181	if err != nil {
182		log.Fatal(err)
183	}
184	fmt.Println(compactPublicJWKset.String())
185	// Output:
186	// {"keys":[{"alg":"RS256","e":"AQAB","key_ops":["verify"],"kid":"Cwm-qQ","kty":"RSA","n":"ALPYon71jmzS2z_se87qVSGE3Rym2uFRYCRJ5xrSCCNtUfihnXU3bzRgKJHeBj6xZNRgz4rR6wAKFtfjVq2n7BFItifUst7sBMmq7Cg4IHsKAQjYNn3FgImV6ZT41Mk5Yay707sq3C_5hj8vom-23ZqL_g9hD_qylB-v8nbUBosh0Ud7rq01Fn6zA9f-lmgVX54KcuT8bNp7h10ZgR0Clpj37xK4K-Gm3Y4Sg2liLsc0orHtQkgG4lkzXMaSLhWGYD44EM6anofdy1FyF_WCXbP24R7V6kXcZOsk4rJaBzmREyLSv6xwghNuXS5JGidi4uwLZyXQXOrRFDYrhY9iQo0","use":"sig"}]}
187}
188
189// [END jwt-generate-jwks-example]
190
191// [START jwt-verify-with-jwks-example]
192func Example_verifyWithJWKS() {
193	// A signed token with the subject 'example subject', audience 'example audience'.
194	// and expiration on 2023-03-23.
195	token := `eyJhbGciOiJSUzI1NiIsImtpZCI6IkN3bS1xUSJ9.eyJhdWQiOiJleGFtcGxlIGF1ZGllbmNlIiwiZXhwIjoxNjc5NTcyODQzLCJzdWIiOiJleGFtcGxlIHN1YmplY3QifQ.dUPhvdmEnGuyESLBQn5OC3QmnRcJlcMfxDPsZ2wfqBK9poQag94xLxBnkzSZnhPP2gQcIt2aOCFeftL1MK3boI3g887J2hZ6hJmeABVi82YGK16P6LIgZuALdjiUcyexus5sxcEo2iuELzUy0hOzS2dDQWOoWCznltGFuavNQGW8A2365JScCsQeoDLAa-IX89vJww0uQVRZ8AxYigLJ5DhILtu-Lssq5sSpT28XASAMzafuYvAI60Cw8nvxTaheRA8AkTI9DWERV4Z-0UQNV2O61U6_24hkjIYCGpuz8_5vBB-W3jijIdWf8J1BNyBfjNeh9eXgSZh8J3wBCEb98Q`
196
197	// A public keyset in the JWK set format.
198	publicJWKset := `{
199		"keys":[
200			{
201				"alg":"RS256",
202				"e":"AQAB",
203				"key_ops":["verify"],
204				"kid":"Cwm-qQ",
205				"kty":"RSA",
206				"n":"ALPYon71jmzS2z_se87qVSGE3Rym2uFRYCRJ5xrSCCNtUfihnXU3bzRgKJHeBj6xZNRgz4rR6wAKFtfjVq2n7BFItifUst7sBMmq7Cg4IHsKAQjYNn3FgImV6ZT41Mk5Yay707sq3C_5hj8vom-23ZqL_g9hD_qylB-v8nbUBosh0Ud7rq01Fn6zA9f-lmgVX54KcuT8bNp7h10ZgR0Clpj37xK4K-Gm3Y4Sg2liLsc0orHtQkgG4lkzXMaSLhWGYD44EM6anofdy1FyF_WCXbP24R7V6kXcZOsk4rJaBzmREyLSv6xwghNuXS5JGidi4uwLZyXQXOrRFDYrhY9iQo0",
207				"use":"sig"
208			}
209		]
210	}`
211
212	// Create a keyset handle from publicJWKset.
213	publicKeysetHandle, err := jwt.JWKSetToPublicKeysetHandle([]byte(publicJWKset))
214	if err != nil {
215		log.Fatal(err)
216	}
217
218	// Retrieve the Verifier primitive from publicKeysetHandle.
219	verifier, err := jwt.NewVerifier(publicKeysetHandle)
220	if err != nil {
221		log.Fatal(err)
222	}
223
224	// Verify the signed token. For this example, we use a fixed date. Usually, you would
225	// either not set FixedNow, or set it to the current time.
226	audience := "example audience"
227	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{
228		ExpectedAudience: &audience,
229		FixedNow:         time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC),
230	})
231	if err != nil {
232		log.Fatal(err)
233	}
234	verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
235	if err != nil {
236		log.Fatal(err)
237	}
238
239	// Extract subject claim from the token.
240	if !verifiedJWT.HasSubject() {
241		log.Fatal(err)
242	}
243	extractedSubject, err := verifiedJWT.Subject()
244	if err != nil {
245		log.Fatal(err)
246	}
247	fmt.Println(extractedSubject)
248	// Output: example subject
249}
250
251// [END jwt-verify-with-jwks-example]
252
253// [START jwt-mac-example]
254func Example_computeMACAndVerify() {
255	// Generate a keyset handle.
256	handle, err := keyset.NewHandle(jwt.HS256Template())
257	if err != nil {
258		log.Fatal(err)
259	}
260
261	// TODO: Save the keyset to a safe location. DO NOT hardcode it in source
262	// code.  Consider encrypting it with a remote key in a KMS.  See
263	// https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets
264
265	// Create a token and compute a MAC for it.
266	expiresAt := time.Now().Add(time.Hour)
267	audience := "example audience"
268	customClaims := map[string]any{"custom": "my custom claim"}
269	rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
270		Audience:     &audience,
271		CustomClaims: customClaims,
272		ExpiresAt:    &expiresAt,
273	})
274	if err != nil {
275		log.Fatal(err)
276	}
277	mac, err := jwt.NewMAC(handle)
278	if err != nil {
279		log.Fatal(err)
280	}
281	token, err := mac.ComputeMACAndEncode(rawJWT)
282	if err != nil {
283		log.Fatal(err)
284	}
285
286	// Verify the MAC.
287	validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
288	if err != nil {
289		log.Fatal(err)
290	}
291	verifiedJWT, err := mac.VerifyMACAndDecode(token, validator)
292	if err != nil {
293		log.Fatal(err)
294	}
295
296	// Extract a custom claim from the token.
297	if !verifiedJWT.HasStringClaim("custom") {
298		log.Fatal(err)
299	}
300	extractedCustomClaim, err := verifiedJWT.StringClaim("custom")
301	if err != nil {
302		log.Fatal(err)
303	}
304	fmt.Println(extractedCustomClaim)
305	// Output: my custom claim
306}
307
308// [END jwt-mac-example]
309