blob: 8a4d0557fb4ef1e8b6bf83eb6c2b2ba9838a71ce [file] [log] [blame]
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
package jwt_test
import (
"bytes"
"encoding/json"
"fmt"
"log"
"time"
"github.com/google/tink/go/insecurecleartextkeyset"
"github.com/google/tink/go/jwt"
"github.com/google/tink/go/keyset"
)
// [START jwt-signature-example]
func Example_signAndVerify() {
// A private keyset created with
// "tinkey create-keyset --key-template=JWT_RS256_2048_F4 --out private_keyset.cfg".
// Note that this keyset has the secret key information in cleartext.
privateJSONKeyset := `{
"primaryKeyId": 185188009,
"key": [
{
"keyData": {
"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PrivateKey",
"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==",
"keyMaterialType": "ASYMMETRIC_PRIVATE"
},
"status": "ENABLED",
"keyId": 185188009,
"outputPrefixType": "TINK"
}
]
}`
// The corresponding public keyset created with
// "tinkey create-public-keyset --in private_keyset.cfg"
publicJSONKeyset := `{
"primaryKeyId": 185188009,
"key": [
{
"keyData": {
"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey",
"value": "EAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQAB",
"keyMaterialType": "ASYMMETRIC_PUBLIC"
},
"status": "ENABLED",
"keyId": 185188009,
"outputPrefixType": "TINK"
}
]
}`
// Create a keyset handle from the cleartext private keyset in the previous
// step. The keyset handle provides abstract access to the underlying keyset to
// limit the access of the raw key material. WARNING: In practice,
// it is unlikely you will want to use a insecurecleartextkeyset, as it implies
// that your key material is passed in cleartext, which is a security risk.
// Consider encrypting it with a remote key in Cloud KMS, AWS KMS or HashiCorp Vault.
// See https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets.
privateKeysetHandle, err := insecurecleartextkeyset.Read(
keyset.NewJSONReader(bytes.NewBufferString(privateJSONKeyset)))
if err != nil {
log.Fatal(err)
}
// Retrieve the JWT Signer primitive from privateKeysetHandle.
signer, err := jwt.NewSigner(privateKeysetHandle)
if err != nil {
log.Fatal(err)
}
// Use the primitive to create and sign a token. In this case, the primary key of the
// keyset will be used (which is also the only key in this example).
expiresAt := time.Now().Add(time.Hour)
audience := "example audience"
subject := "example subject"
rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
Audience: &audience,
Subject: &subject,
ExpiresAt: &expiresAt,
})
if err != nil {
log.Fatal(err)
}
token, err := signer.SignAndEncode(rawJWT)
if err != nil {
log.Fatal(err)
}
// Create a keyset handle from the keyset containing the public key. Because the
// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
publicKeysetHandle, err := keyset.ReadWithNoSecrets(
keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
if err != nil {
log.Fatal(err)
}
// Retrieve the Verifier primitive from publicKeysetHandle.
verifier, err := jwt.NewVerifier(publicKeysetHandle)
if err != nil {
log.Fatal(err)
}
// Verify the signed token.
validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
if err != nil {
log.Fatal(err)
}
verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
if err != nil {
log.Fatal(err)
}
// Extract subject claim from the token.
if !verifiedJWT.HasSubject() {
log.Fatal(err)
}
extractedSubject, err := verifiedJWT.Subject()
if err != nil {
log.Fatal(err)
}
fmt.Println(extractedSubject)
// Output: example subject
}
// [END jwt-signature-example]
// [START jwt-generate-jwks-example]
func Example_generateJWKS() {
// A Tink keyset in JSON format with one JWT public key.
publicJSONKeyset := `{
"primaryKeyId": 185188009,
"key": [
{
"keyData": {
"typeUrl": "type.googleapis.com/google.crypto.tink.JwtRsaSsaPkcs1PublicKey",
"value": "EAEagQIAs9iifvWObNLbP+x7zupVIYTdHKba4VFgJEnnGtIII21R+KGddTdvNGAokd4GPrFk1GDPitHrAAoW1+NWrafsEUi2J9Sy3uwEyarsKDggewoBCNg2fcWAiZXplPjUyTlhrLvTuyrcL/mGPy+ib7bdmov+D2EP+rKUH6/ydtQGiyHRR3uurTUWfrMD1/6WaBVfngpy5Pxs2nuHXRmBHQKWmPfvErgr4abdjhKDaWIuxzSise1CSAbiWTNcxpIuFYZgPjgQzpqeh93LUXIX9YJds/bhHtXqRdxk6yTisloHOZETItK/rHCCE25dLkkaJ2Li7AtnJdBc6tEUNiuFj2JCjSIDAQAB",
"keyMaterialType": "ASYMMETRIC_PUBLIC"
},
"status": "ENABLED",
"keyId": 185188009,
"outputPrefixType": "TINK"
}
]
}`
// Create a keyset handle from the keyset containing the public key. Because the
// public keyset does not contain any secrets, we can use [keyset.ReadWithNoSecrets].
publicKeysetHandle, err := keyset.ReadWithNoSecrets(
keyset.NewJSONReader(bytes.NewBufferString(publicJSONKeyset)))
if err != nil {
log.Fatal(err)
}
// Create a publicJWKset from publicKeysetHandle.
publicJWKset, err := jwt.JWKSetFromPublicKeysetHandle(publicKeysetHandle)
if err != nil {
log.Fatal(err)
}
// Remove whitespace so that we can compare it to the expected string.
compactPublicJWKset := &bytes.Buffer{}
err = json.Compact(compactPublicJWKset, publicJWKset)
if err != nil {
log.Fatal(err)
}
fmt.Println(compactPublicJWKset.String())
// Output:
// {"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"}]}
}
// [END jwt-generate-jwks-example]
// [START jwt-verify-with-jwks-example]
func Example_verifyWithJWKS() {
// A signed token with the subject 'example subject', audience 'example audience'.
// and expiration on 2023-03-23.
token := `eyJhbGciOiJSUzI1NiIsImtpZCI6IkN3bS1xUSJ9.eyJhdWQiOiJleGFtcGxlIGF1ZGllbmNlIiwiZXhwIjoxNjc5NTcyODQzLCJzdWIiOiJleGFtcGxlIHN1YmplY3QifQ.dUPhvdmEnGuyESLBQn5OC3QmnRcJlcMfxDPsZ2wfqBK9poQag94xLxBnkzSZnhPP2gQcIt2aOCFeftL1MK3boI3g887J2hZ6hJmeABVi82YGK16P6LIgZuALdjiUcyexus5sxcEo2iuELzUy0hOzS2dDQWOoWCznltGFuavNQGW8A2365JScCsQeoDLAa-IX89vJww0uQVRZ8AxYigLJ5DhILtu-Lssq5sSpT28XASAMzafuYvAI60Cw8nvxTaheRA8AkTI9DWERV4Z-0UQNV2O61U6_24hkjIYCGpuz8_5vBB-W3jijIdWf8J1BNyBfjNeh9eXgSZh8J3wBCEb98Q`
// A public keyset in the JWK set format.
publicJWKset := `{
"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"
}
]
}`
// Create a keyset handle from publicJWKset.
publicKeysetHandle, err := jwt.JWKSetToPublicKeysetHandle([]byte(publicJWKset))
if err != nil {
log.Fatal(err)
}
// Retrieve the Verifier primitive from publicKeysetHandle.
verifier, err := jwt.NewVerifier(publicKeysetHandle)
if err != nil {
log.Fatal(err)
}
// Verify the signed token. For this example, we use a fixed date. Usually, you would
// either not set FixedNow, or set it to the current time.
audience := "example audience"
validator, err := jwt.NewValidator(&jwt.ValidatorOpts{
ExpectedAudience: &audience,
FixedNow: time.Date(2023, 3, 23, 0, 0, 0, 0, time.UTC),
})
if err != nil {
log.Fatal(err)
}
verifiedJWT, err := verifier.VerifyAndDecode(token, validator)
if err != nil {
log.Fatal(err)
}
// Extract subject claim from the token.
if !verifiedJWT.HasSubject() {
log.Fatal(err)
}
extractedSubject, err := verifiedJWT.Subject()
if err != nil {
log.Fatal(err)
}
fmt.Println(extractedSubject)
// Output: example subject
}
// [END jwt-verify-with-jwks-example]
// [START jwt-mac-example]
func Example_computeMACAndVerify() {
// Generate a keyset handle.
handle, err := keyset.NewHandle(jwt.HS256Template())
if err != nil {
log.Fatal(err)
}
// TODO: Save the keyset to a safe location. DO NOT hardcode it in source
// code. Consider encrypting it with a remote key in a KMS. See
// https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#storing-and-loading-existing-keysets
// Create a token and compute a MAC for it.
expiresAt := time.Now().Add(time.Hour)
audience := "example audience"
customClaims := map[string]any{"custom": "my custom claim"}
rawJWT, err := jwt.NewRawJWT(&jwt.RawJWTOptions{
Audience: &audience,
CustomClaims: customClaims,
ExpiresAt: &expiresAt,
})
if err != nil {
log.Fatal(err)
}
mac, err := jwt.NewMAC(handle)
if err != nil {
log.Fatal(err)
}
token, err := mac.ComputeMACAndEncode(rawJWT)
if err != nil {
log.Fatal(err)
}
// Verify the MAC.
validator, err := jwt.NewValidator(&jwt.ValidatorOpts{ExpectedAudience: &audience})
if err != nil {
log.Fatal(err)
}
verifiedJWT, err := mac.VerifyMACAndDecode(token, validator)
if err != nil {
log.Fatal(err)
}
// Extract a custom claim from the token.
if !verifiedJWT.HasStringClaim("custom") {
log.Fatal(err)
}
extractedCustomClaim, err := verifiedJWT.StringClaim("custom")
if err != nil {
log.Fatal(err)
}
fmt.Println(extractedCustomClaim)
// Output: my custom claim
}
// [END jwt-mac-example]