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