I have a JWT token generated in nodejs app. It is signed using HS256. I've written the code to validate it in golang. I get an error message of "signature is invalid" even though I verified it in the JWT.io site.
The code validates also Public/Private, but this works. Only the HS256 is not
I've also printed the token and the secret to make sure they are the right values.
Any help will be appreciated.
My golang code:
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Validate the alg is the expected algorithm:
if conf.JwtAlgorithm != token.Header["alg"] {
log.Printf("unexpected signing method: %s, conf algorithm: %s\n", token.Header["alg"], conf.JwtAlgorithm)
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
log.Printf("JWT algo is: %s, Public is %s, secret is %s", token.Header["alg"], publicKey, secret)
if secret != "" {
log.Printf("Returning secret %s", secret)
return []byte(secret), nil
}
if publicKey != "" {
pub, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(publicKey))
fmt.Println("pub is of type RSA:", pub)
return pub, nil
}
return nil, fmt.Errorf("PublicKey and secret are empty")
})
Since you only have a single HMAC key, you'll want something like this:
package main
import (
"log"
"github.com/golang-jwt/jwt/v4"
)
func main() {
const tokenString = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.drt_po6bHhDOF_FJEHTrK-KD8OGjseJZpHwHIgsnoTM"
var keyfunc jwt.Keyfunc = func(token *jwt.Token) (interface{}, error) {
return []byte("mysecret"), nil
}
parsed, err := jwt.Parse(tokenString, keyfunc)
if err != nil {
log.Fatalf("Failed to parse JWT.\nError: %s", err.Error())
}
if !parsed.Valid {
log.Fatalln("Token is not valid.")
}
log.Println("Token is valid.")
}
It's certainly confusing what the return type should be for a jwt.Keyfunc. For an HMAC key, the return type should be []byte.
Please note that HMAC keys do not use public key cryptography and therefore are only a private key that shouldn't be shared.
If the JWTs you need to parse and verify start to become more complex, check out this package: github.com/MicahParks/keyfunc. It has support for multiple given keys like HMAC and remote JWKS resources.
Related
I could use the Azure Key Vault provider for Secrets Store CSI driver (https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-driver)
but I need to have a bit more control over the certificates via a custom k8 management API am developing for my use case. I had some challenges with this.
I think I have a good enough solution but want to put it out in the world for comment from others that may have done this in the past.
here is what I did...
When one creates the certificates (or uploads them) in Azure vault the values are stored in secrets. I used the Azure sdk for Go to get them
I use base64 package to decode what Azure sends me (a PFX containing private and public keys
I use pkcs12 package pkcs12.toPEM function to break it apart into an array of pem.Block.
Each block has a value that contains headers in the value
I need to strip the headers out before I use them in the kubernetes secret
package secretcreator
import (
"context"
"encoding/base64"
"encoding/pem"
"log"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azcertificates"
"github.com/Azure/azure-sdk-for-go/sdk/keyvault/azsecrets"
"golang.org/x/crypto/pkcs12"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)
type MyStruct struct {
kubeClientSet *kubernetes.Clientset
azureVaultSecrets *azsecrets.Client
azureVaultCertificates *azcertificates.Client
beNamespace *v1.Namespace
identitySecrests *v1.Secret
}
func (t *MyStruct) createSecrets() error {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
//tlsSecret.value is a base64 encoded PEM/PFX containing PRIVATE KEY and CERTIFICATES blocks
tlsSecret, err := t.azureVaultSecrets.GetSecret(ctx, `myCertName`, ``, nil)
if err != nil {
log.Printf(`Failed to retrieve cert :%s`, err.Error())
return err
}
//pfxBytes is the Byte representation of the string stored in tlsSecret.Value
pfxBytes, err := base64.StdEncoding.Strict().DecodeString(*tlsSecret.Value)
if err != nil {
log.Printf(`Failed to decode cert PFX:%s`, err.Error())
return err
}
// pemBlocks is a array of *pem.Block - in my case I have 1 PRIVATE KEY Block and three CERTIFICATE Blocks
//the blocks have headers too that look something like this on the PRIVATE KEY, after encoding the encoded string looks like this
//
// -----BEGIN PRIVATE KEY-----
// Microsoft CSP Name: Microsoft Enhanced Cryptographic Provider v1.0
// friendlyName: {D06B3D5A-98D1-45F1-A8C2-DC091232D6A7}
// localKeyId: 01000000
//
// MIIEAI.......ymfjsQ==
// -----END PRIVATE KEY-----
pemBlocks, err := pkcs12.ToPEM(pfxBytes, "")
if err != nil {
log.Printf(`Failed to decode key or cert from PEM Blocks:%s`, err.Error())
return err
}
// k8 needs to store TLS in a secret with key value pairs in the data - one for tls.key and the other for tls.crt
// this code iterates through the blocks, looks at the type of data stored and breaks it up into tls.key and tls.crt
// k8.nginx does not like the headers for some reason - so i strip them out using base64.StdEncoding.Encode to string and
// then adding "-----BEGIN..." and "-----END..." strings"
var tlsKey []byte
var tlsCert []byte
for _, b := range pemBlocks {
if b.Type == "PRIVATE KEY" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsKey = append(tlsKey, "-----BEGIN PRIVATE KEY-----\n"...)
tlsKey = append(tlsKey, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsKey = append(tlsKey, "-----END PRIVATE KEY-----\n"...)
}
if b.Type == "CERTIFICATE" {
block, _ := pem.Decode(pem.EncodeToMemory(b))
tlsCert = append(tlsCert, "-----BEGIN CERTIFICATE-----\n"...)
tlsCert = append(tlsCert, base64.StdEncoding.EncodeToString(block.Bytes)...)
tlsCert = append(tlsCert, "-----END CERTIFICATE-----\n"...)
}
}
// this following code uses client-go package to create a TLS secret in kubernetes
tlsData := make(map[string][]byte)
tlsData[`tls.key`] = tlsKey
tlsData[`tls.crt`] = tlsCert
beTls := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: `mycertname`,
Namespace: t.beNamespace.Name,
},
Data: tlsData,
Type: v1.SecretTypeTLS,
}
_, err = t.kubeClientSet.CoreV1().Secrets(t.beNamespace.Name).Create(ctx, beTls, metav1.CreateOptions{})
if err != nil {
log.Printf(`Failed to create TLS secret :%s`, err.Error())
return err
}
return nil
}
,,,
I am trying to do the modern authentication from azure ad with client certificate.
The process says.
Create an azure application.
Upload the certificate to azure application and get the thumbprint.
Generate the JWT token using that certificate(https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-certificate-credentials)
Then use that jwt-token to get the access-token.
To get the access-token, call an API (https://login.microsoftonline.com//oauth2/token)
The HTTP method is of type POST and post body data will be x-www-form-urlencoded
After doing all the steps when i try to get the token.
I get this error.
{
"error": "invalid_client",
"error_description": "AADSTS700023: Client assertion audience claim does not match Realm issuer. Review the documentation at https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials .\r\nTrace ID: 714b5009-b74d-46b5-bd0e-3bea76272a01\r\nCorrelation ID: 2cb703bb-f325-44b5-a669-605bc7a81ac0\r\nTimestamp: 2020-08-28 07:31:19Z",
"error_codes": [
700023
],
"timestamp": "2020-08-28 07:31:19Z",
"trace_id": "714b5009-b74d-46b5-bd0e-3bea76272a01",
"correlation_id": "2cb703bb-f325-44b5-a669-605bc7a81ac0"
}
I am generating the JWT token using go code and parsing the PFX file.
func getAuthJWTToken() (string, error) {
clientID := "**********************"
_tenantName := "******************"
pfxFilePath := `E:\abcd.pfx`
certPassword := `*********`
authToken := ""
pfxFile, err := os.Open(pfxFilePath)
if err != nil {
return authToken, err
}
pfxfileinfo, _ := pfxFile.Stat()
var size int64 = pfxfileinfo.Size()
pfxbytes := make([]byte, size)
buffer := bufio.NewReader(pfxFile)
_, err = buffer.Read(pfxbytes)
//PFX to PEM for computation of signature
var pembytes []byte
blocks, err := pkcs12.ToPEM(pfxbytes, certPassword)
for _, b := range blocks {
pembytes = append(pembytes, pem.EncodeToMemory(b)...)
}
//Decoding the certificate contents from pfxbytes
pk, cert, err := pkcs12.Decode(pfxbytes, certPassword)
if cert == nil {
fmt.Printf("Bye")
return authToken, nil
}
if pk == nil {
}
pfxFile.Close() // close file
notToBeUsedBefore := time.Now()
expirationTime := time.Now().Add(3000 * time.Minute)
URL := fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/token", _tenantName)
id := guid.New()
claims := &claims{
StandardClaims: jwt.StandardClaims{
// In JWT, the expiry time is expressed as unix milliseconds
ExpiresAt: expirationTime.Unix(),
Audience: URL,
Issuer: clientID, // consumer key of the connected app, hardcoded
NotBefore: notToBeUsedBefore.Unix(),
Subject: clientID,
Id: id.String(),
},
}
//token_header map[string]interface{}
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
sha1Fingerprint := sha1.Sum(cert.Raw)
var slice []byte
slice = sha1Fingerprint[:]
b64FingerPrint := base64.StdEncoding.EncodeToString([]byte(slice))
token.Header["x5t"] = b64FingerPrint
signKey, err := jwt.ParseRSAPrivateKeyFromPEM(pembytes) // parse the RSA key
tokenString, err := token.SignedString(signKey) // sign the claims with private key
fmt.Printf(fmt.Sprintf("JWT token is %s", tokenString))
return tokenString, err
}
I need the help to resolve this issue.
I am generating a JSON web signature in JavaScript using node-jws package (https://www.npmjs.com/package/jws). In Headers, I am giving crit: ["exp"] and exp: someTimeStamp. The snippet given below is used in generating the JWS:
let token = jws.sign({
header: { alg: 'HS256', crit: ["exp"], exp: Math.floor(Date.now() / 1000) + (60 * 60) },
payload: "somestring" ,
privateKey: 'supersecret',
});
I am verifying this token in Golang using the snippet given below:
import (
"github.com/square/go-jose"
)
func main() {
jsonWebSig, err := jose.ParseSigned(token)
if err != nil {
panic(err)
}
payload, err := jsonWebSig.Verify([]byte("supersecret"))
fmt.Println(string(payload))
fmt.Println(err)
}
The above code in GO works if I don't give the crit: ["exp"] in header while generating the token in JS. Otherwise, it gives me the error saying square/go-jose: error in cryptographic primitive.
I have to use crit: ["exp"] in headers at any cost. Is there any way to verify this?
I need to verify ES256 JWT tokens in a Cloudflare Worker. To my understanding they do not run Node.js there and I will have to make everything work with the Web Crypto API (available in browsers, too, as window.crypto.subtle). However, I am expecting to receive the public keys as PEM files and I think I am having problems importing them.
I have been trying to modify an existing open source JWT implementation that only supports HS256 to support ES256.
In addition to trying to use the actual tokens and keys, and keys generated in browsers and OpenSSL, I have tried to use a working example from the JWT.io website (after converting it to JWK format using node-jose), since it should validate correctly. But I am not getting any errors, my code running in browser just tells me the token is not valid.
Here is my Node REPL session I used for converting the key from JWT.io to JWK:
> const jose = require('node-jose')
> const publicKey = `-----BEGIN PUBLIC KEY-----
... MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEEVs/o5+uQbTjL3chynL4wXgUg2R9
... q9UU8I5mEovUf86QZ7kOBIjJwqnzD1omageEHWwHdBO6B+dFabmdT9POxg==
... -----END PUBLIC KEY-----`
> const keyStore = jose.JWK.createKeyStore()
> keyStore.add(publicKey, 'pem')
> keyStore.toJSON()
{
keys: [
{
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
}
]
}
>
Here is the failing validation, based on code from webcrypto-jwt package:
function utf8ToUint8Array(str) {
// Adapted from https://chromium.googlesource.com/chromium/blink/+/master/LayoutTests/crypto/subtle/hmac/sign-verify.html
var Base64URL = {
stringify: function (a) {
var base64string = btoa(String.fromCharCode.apply(0, a));
return base64string.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
},
parse: function (s) {
s = s.replace(/-/g, '+').replace(/_/g, '/').replace(/\s/g, '');
return new Uint8Array(Array.prototype.map.call(atob(s), function (c) { return c.charCodeAt(0); }));
}
};
str = btoa(unescape(encodeURIComponent(str)));
return Base64URL.parse(str);
}
var cryptoSubtle = (crypto && crypto.subtle) ||
(crypto && crypto.webkitSubtle) ||
(window.msCrypto && window.msCrypto.Subtle);
// Token from JWT.io
var tokenParts = [
'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9',
'eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0',
'tyh-VfuzIxCyGYDlkBA7DfyjrqmSHu6pQ2hoZuFqUSLPNY2N0mpHb3nk5K17HWP_3cYHBw7AhHale5wky6-sVA'
];
// Public key from JWT.io converted in Node using node-jose
var publicKey = {
kty: 'EC',
kid: '19J8y7Zprt2-QKLjF2I5pVk0OELX6cY2AfaAv1LC_w8',
crv: 'P-256',
x: 'EVs_o5-uQbTjL3chynL4wXgUg2R9q9UU8I5mEovUf84',
y: 'kGe5DgSIycKp8w9aJmoHhB1sB3QTugfnRWm5nU_TzsY'
};
var importAlgorithm = {
name: 'ECDSA',
namedCurve: 'P-256',
hash: 'SHA-256',
};
cryptoSubtle.importKey(
"jwk",
publicKey,
importAlgorithm,
false,
["verify"]
).then(function (key) {
var partialToken = tokenParts.slice(0,2).join('.');
var signaturePart = tokenParts[2];
cryptoSubtle.verify(
importAlgorithm,
key,
utf8ToUint8Array(signaturePart),
utf8ToUint8Array(partialToken)
).then(function (ok) {
if (ok) {
console.log("I think it's valid");
} else {
console.log("I think it isn't valid");
}
}).catch(function (err) {
console.log("error verifying", err);
});
}).catch(function(err) {
console.log("error importing", err);
});
Since I copied a valid key and a valid token from JWT.io, I am expecting the code to log "I think it's valid" without errors. It does not show any errors, indeed, but it ends up in "I think it isn't valid" branch.
Answered here How to verify a signed JWT with SubtleCrypto of the Web Crypto API?
So, if I understood correctly, the problem was that base64 encoding included in the open source upstream just does not work correctly in one of the directions, since it uses the browser's btoa. Adapting https://github.com/swansontec/rfc4648.js instead works.
In the Fabric-CA Client’s CLI documentation(https://hyperledger-fabric-ca.readthedocs.io/en/release-1.4/clientcli.html) , there are two commands named "enroll" and "reenroll" respectively. Could any expert tell me the difference between them? Thanks.
Enroll command is used to enroll the user for the first time with the CA.
There could be cases when a certificate expires or gets compromised (so it has to be revoked). So this is when re enrollment comes into the picture and you enroll the same identity again with the CA to get new certificates.
Additional Note about Revocation of certificates:
Revocation can be done for a number of reasons (exactly 10), also when revoking please don't forget to update the CRL(Certificate Revocation List).
Please go through these sections of the documentation for a better understanding of the same:
Reenrolling an Identity
Revoking an Identity
Generating a CRL
When in doubt code is the best documentation
// Handle an enroll request, guarded by basic authentication
func enrollHandler(ctx *serverRequestContextImpl) (interface{}, error) {
id, err := ctx.BasicAuthentication()
if err != nil {
return nil, err
}
resp, err := handleEnroll(ctx, id)
if err != nil {
return nil, err
}
err = ctx.ui.LoginComplete()
if err != nil {
return nil, err
}
return resp, nil
}
// Handle a reenroll request, guarded by token authentication
func reenrollHandler(ctx *serverRequestContextImpl) (interface{}, error) {
// Authenticate the caller
id, err := ctx.TokenAuthentication()
if err != nil {
return nil, err
}
return handleEnroll(ctx, id)
}
we can see both call handleEnroll method. The only difference seems to be that enroll uses BasicAuthentication whereas reenroll uses TokenAuthentication.
// BasicAuthentication authenticates the caller's username and
password // found in the authorization header and returns the username
// TokenAuthentication authenticates the caller by token // in the
authorization header. // Returns the enrollment ID or error.
So for security reasons you would call enroll first providing your username and password. And then use the token obtained by calling enroll to reenroll without having to expose your password again. Thus the difference between the two is purely a security measure. And yes, thanks Fabric for making it confusing.