Another extension for go-jwt that allows creating and verifying JWT tokens where the private key is embedded inside PIV-enabled Yubikey.
for reference, see
To Start, you need a special yubikey and a certificte enabled for signing installed. It should be installed in slot 9c
as shown here.
You can generate the key on the Yubikey (preferable) or import it
Either way, it should look like htis:
$ yubico-piv-tool -a status
Version: 5.2.7
Serial Number: 13981219
CHUID: 3019d4e739da739ced39ce739d836858210842108421c84210c3eb3410580d44ea9aa3cb26993084513cb2a39d350832303330303130313e00fe00
CCC: No data available
Slot 9c:
Algorithm: RSA2048
Subject DN: C=US, O=Google, OU=Enterprise, CN=yubikey-svc@domain.com
Issuer DN: C=US, O=Google, OU=Enterprise, CN=Enterprise Subordinate CA
Fingerprint: dc4ea638c324cd259bb20ea42a5e4529aacc11d7f6835bf60cb533b9ba7a2d51
Not Before: Dec 12 16:36:00 2020 GMT
Not After: Dec 12 16:36:00 2022 GMT
PIN tries left: 3
Once you have that, create a test JWT and verify it with an RSA key that is extracted from a TPM and also directly.
# cd examples/
# go run main.go
TOKEN: eyJhbGciOiJSUzI1NiIsImtpZCI6ImFiY2RlZmciLCJ0eXAiOiJKV1QifQ.eyJleHAiOjE2OTMxMzUxMDEsImlzcyI6InRlc3QifQ.m2piHlp09fSbSR3nN0ca2JXu6TmnfDu-WCe9ZwpLlPb9dmKRmThSsbTRD7HL3mhGpez1fImWMCjnOtiIqxNxPe37HyuDnqUKJ3ei2EIGX0bNFzt95fnkNIIWr_4ug09CimmZaQ5P2Bp97-_ghIHVAXmTEtPnhtu8DappFo95mTP4asUhItYTI3elFjVpBzr9eoVr_7-JoI8BLMPIuhdwz3mx0bIjdQ63pYl6QgWq87-PpiTxvycstafLFla0n-uhF3qSjunUSG9N-jjgBmUPS7DL_mB5vygQ0OGxjDIfoNRxBv9nSUqS3LJO9L49be03t--UcSOhY51bTA-Xo7kgew
2023/08/27 07:17:21 verified with YK PublicKey
2023/08/27 07:17:21 verified with exported PubicKey
The JWT is formatted as:
{
"alg": "RS256",
"kid": "abcdefg",
"typ": "JWT"
}
{
"exp": 1693135101,
"iss": "test"
}
to use, just import the library ("github.com/salrashid123/golang-jwt-yubikey"
) configure the Yubikey with the pin. Remember to set the override so that the correct alg
is defined in the JWT header
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/golang-jwt/jwt"
yk "github.com/salrashid123/golang-jwt-yubikey"
)
var ()
func main() {
ctx := context.Background()
var keyctx interface{}
claims := &jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
Issuer: "test",
}
yk.SigningMethodYKRS256.Override()
token := jwt.NewWithClaims(yk.SigningMethodYKRS256, claims)
config := &yk.YKConfig{
Pin: "123456",
}
keyctx, err := yk.NewYKContext(ctx, config)
if err != nil {
log.Fatalf("Unable to initialize tpmJWT: %v", err)
}
token.Header["kid"] = config.GetKeyID()
tokenString, err := token.SignedString(keyctx)
if err != nil {
log.Fatalf("Error signing %v", err)
}
fmt.Printf("TOKEN: %s\n", tokenString)
// verify with TPM based publicKey
keyFunc, err := yk.YKVerfiyKeyfunc(ctx, config)
if err != nil {
log.Fatalf("could not get keyFunc: %v", err)
}
vtoken, err := jwt.Parse(tokenString, keyFunc)
if err != nil {
log.Fatalf("Error verifying token %v", err)
}
if vtoken.Valid {
log.Println(" verified with TPM PublicKey")
}
// verify with provided RSAPublic key
pubKey := config.GetPublicKey()
v, err := jwt.Parse(vtoken.Raw, func(token *jwt.Token) (interface{}, error) {
return pubKey, nil
})
if v.Valid {
log.Println(" verified with exported PubicKey")
}
}