How to setup public key for verifying JWT tokens from Keycloak? - jwt

I'm writing backend microservice that receives requests from front-end which have Authorisation: Bearer ... header, with token obtained from keycloak (which is inside docker container).
I got the RSA public key to verify the signature of that token from Keys section of realm settings, but it seems that when container with keycloak restarts, it regenerates pair of keys, and my public key set in service config becomes invalid.
What is the proper way to work with RSA public key from keycloak? Is there some way to configure it to use a fixed pair of keys for realm? Are keys exported when realm exports? Or I have to get the public key from keycloak using url like http://keycloak:8080/auth/realms/:realm_name:, which I rather not to do because this adds a dependency between keycloak and backend.

You should verify the JWT token's signature based on the issuer identity server's /.well-known/jwks endpoint.
1) Query the issuer identity server's /.well-known/jwks endpoint (JWKS stands for JSON Web Key Set)
2) From the JWKS, get the JWK (JSON Web Key) with the same kid (Key ID) as the Bearer token we are verifying. To get the kid from your JWT token, first decode it using jwt.io's Debugger tool.
3) As long as identity server-issued tokens are verified with an asymmetric cryptography algorithm (e.g.: RS256), we can verify the signature with the Public Key only (so you won't need the Private Key)
4) The Public Key can be retrieved from the JWK (it is the x5c entry in the JWK JSON )
5) Verify the JWT Bearer token's signature with this Public Key.
For example, in Java you can verify it like this:
// verify JWT signature based on Access Identity's JWKS RSA public key (RS256)
try {
Jwk jwk = new UrlJwkProvider(new URL(issuer + Constants.JWKS_ENDPOINT)).get(decodedJWT.getKeyId());
final PublicKey publicKey = jwk.getPublicKey();
if (!(publicKey instanceof RSAPublicKey)) {
throw new IllegalArgumentException("Key with ID " + decodedJWT.getKeyId() + " was found in JWKS but is not a RSA-key.");
}
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(issuer)
.build(); //Reusable verifier instance
verifier.verify(bearerToken);
LOGGER.info("Token verified!");
} catch (Exception e) {
LOGGER.error(e.getMessage());
throw new InvalidAccessTokenException("JWTVerificationException - Invalid token signature.");
}

Related

Keycloak public key is not base64url encoded

I copied public key from my realm settings and tried to validate in https://jwt.io. It says "Error: Looks like your JWT header is not encoded correctly using base64url". I am using the key to authenticate by sending it has client_assertion. Any help on how to get or generate correct JWT in Keycloak is so much appreciated
Header and Payload section is blank in jwt.io
This is steps to create Public Key and create a JWT
Switching Algorithm is "RS256" in JWT.io
the public key and private key will generate by JWO.io
Update header and payload section
the token will be generate in Encoded section
if you want to generate public key from Keycloak.
call cert API and copy n value
paste this node java-script and run it by "node get-publick-key.js"
//getPem = function(modulus_base);
var getPem = require('rsa-pem-from-mod-exp');
//modulus should be a base64/base64Url string
var modulus = "388m-vc59JVeHP6kbHRtykkky41sby3gldYs8JY3I6xrI_cUs7dbCLDeVtPq78359sGujTtVrT6-P_VOPQqwJEWQQJ3Yvw5zpX510o5UBHob-qmbTopvB0tiJPBi-GXU_Vwx84unR6udVaXXg_FODxqP-vQS7wPwt_omdPW8a5gp8H3uhqEckNxGd2Cp7mFwC2rcwEjBpZBFGoQo8lvGpYBCzzPfSEkET-CvAnrMM8AhqtC0qRhBBfOdzYDclxJYmXb2wz8OhIflgySOMrhdG-oqugp4wxLk2fE528Sz6tdhFwRgWwcOKFxvPNW6-0puRCbW3vKV89jhXMuwtE6aXQ";
//exponent should be base64/base64url
var exponent = "AQAB";
var pem = getPem(modulus, exponent);
console.log(pem);
it will be print public key
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA388m+vc59JVeHP6kbHRtykkky41sby3gldYs8JY3I6xrI/cUs7db
CLDeVtPq78359sGujTtVrT6+P/VOPQqwJEWQQJ3Yvw5zpX510o5UBHob+qmbTopv
B0tiJPBi+GXU/Vwx84unR6udVaXXg/FODxqP+vQS7wPwt/omdPW8a5gp8H3uhqEc
kNxGd2Cp7mFwC2rcwEjBpZBFGoQo8lvGpYBCzzPfSEkET+CvAnrMM8AhqtC0qRhB
BfOdzYDclxJYmXb2wz8OhIflgySOMrhdG+oqugp4wxLk2fE528Sz6tdhFwRgWwcO
KFxvPNW6+0puRCbW3vKV89jhXMuwtE6aXQIDAQAB
-----END RSA PUBLIC KEY-----
I have written a Java program using JJWT library to generate JWT from the JKS file that is generated from Client-Keys-Generate new keys and certificate section and was able to validate JWT and signature in jwt.io

ACM PCA Issue certificate for client authentication and signing data

I'm trying to implement MTLS client authentication using AWS ACM Private CA to issue X.509 client certificates.
The certificate and the correlating private key is supposed to be stored in a password protected PKCS#12 file.
The private key will also be used by the client to sign data.
If I request a new certificate using aws-acm-sdk:
RequestCertificateResult response = acm.requestCertificate(new RequestCertificateRequest()
.withCertificateAuthorityArn(CA_ARN)
.withIdempotencyToken("1234")
.withDomainName("localhost.com"));
return response.getCertificateArn();
And then export the it using the arn, I get a certificate, certificateChain and a privateKey as strings.
ExportCertificateResult response = acm.exportCertificate(new ExportCertificateRequest()
.withCertificateArn(certificateArn)
.withPassphrase(ByteBuffer.wrap(password.getBytes())));
String certificate = response.getCertificate();
String certificateChain = response.getCertificateChain();
String privateKey = response.getPrivateKey();
But I'm not able to add any type of identifier that let's me tie the certificate to a user during authentication (I'm using Java and Spring security x509 authentication, which extracts e.g. the subject CN (Common Name) from the certificate which then can be used to identify a user).
If I want to add custom attributes to the certificate, I need to issue a certificate through the aws-acm-pca-sdk:
IssueCertificateRequest request = new IssueCertificateRequest()
.withCertificateAuthorityArn(CA_ARN)
.withCsr(stringToByteBuffer(getCSR()))
.withTemplateArn("arn:aws:acm-pca:::template/EndEntityClientAuthCertificate_APIPassthrough/V1")
.withSigningAlgorithm(SigningAlgorithm.SHA256WITHRSA)
.withValidity(new Validity()
.withValue(365L)
.withType(ValidityPeriodType.DAYS))
.withIdempotencyToken(userId)
.withApiPassthrough(new ApiPassthrough()
.withSubject(new ASN1Subject()
.withCustomAttributes(List.of(
new CustomAttribute()
.withObjectIdentifier("1.3.6.1.4.1") // CustomOID
.withValue("userId")))));
return acmPca.issueCertificate(request).getCertificateArn();
But if I use the sdk to get the certificate, it doesn't contain any private key.
GetCertificateResult response = acmPca.getCertificate(new GetCertificateRequest()
.withCertificateAuthorityArn(CA_ARN)
.withCertificateArn(certificateArn));
String certificate = response.getCertificate();
String certificateChain = response.getCertificateChain();
So, when I read documentation I found that I need to import it to ACM in order to export it and get the private key.
But in order to import it to ACM I also need to provide the private key..
But as I understand it, the private key should be used when issuing the certificate in the first place?
Should I create a new public/private key pair using KMS or what am I supposed to do?
Im confused.. Please help!
I had misinterpreted the responsibility of the PCA.
The PCA is only responsible for issuing certificates, not keys or even CSR (Certificate Signing Requests).
I assumed that the CSR were created by PCA, so the getCSR() method actually fetched the CA's CSR from PCA, while the actual CSR should be generated internally using a private key which has been generated either programmatically or using KMS.

Verifying Hydra-generated JWT access_token signature in jwt.io?

I configured Hydra to return JWT in access_token (STRATEGIES_ACCESS_TOKEN=jwt). Pasting the resulting token[1] in jwt.io works—it's decoded successfully and the data looks right. Now I want to verify the signature.
So I open http://localhost:9000/.well-known/jwks.json and extract the public RSA keys[2][3], but none of them works—jwt.io gives me "Invalid Signature".
FYI the same error is also thrown by FusionAuth JWT library. My Scala code to get the keys:
private val hydraVerifiers = {
val keys = retrieveKeysFromJWKS("http://localhost:9000/.well-known/jwks.json")
keys.asScala.map(JSONWebKey.parse).map(_.asInstanceOf[RSAPublicKey]).map(RSAVerifier.newVerifier)
}.toSeq
(If you're not familiar with Scala/FusionAuth, the code above retrieves keys from a URL, parses them to java.security.PublicKey object, and creates JWT verifiers based on the public keys)
Any suggestions?
[1]
eyJhbGciOiJSUzI1NiIsImtpZCI6InB1YmxpYzpjMzQ5NWJkMC0wZDFhLTRiOWMtOWZhNy1hZjE5ZWNlODMxMTEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOltdLCJjbGllbnRfaWQiOiJpZC5ldmVyeS5leHQuc3RlYW0iLCJleHAiOjE2Mjc3NTI0ODEsImV4dCI6e30sImlhdCI6MTYyNzc0ODg4MCwiaXNzIjoiaHR0cDovLzEyNy4wLjAuMTo5MDAwLyIsImp0aSI6IjAxNjRkMzk4LTY4OWQtNDUzZi05YmMxLWVjYTgyMGJkOWY1OSIsIm5iZiI6MTYyNzc0ODg4MCwic2NwIjpbImVpZC5leHRlbmQiLCJvZmZsaW5lX2FjY2VzcyJdLCJzdWIiOiIweDMyY2ZkMSJ9.h2XUovsF7e5OI60BnKakbHVQJoNJmxQSsmLMy2MxyxFHQ4VMDdISPbkJVD5fsPCdLVw3RcggSid8S7fHJpDWqgJD0UP1WZBqlYN6E2FYs25QRsn8tvOlb9RqDAq1sqhSA1DhPCSsg0OB37gqQq4M8TjYfH0gqrj6oiU9YvgvlKU2Q7uaXfUjxWNkW_6KCZsdUSAGsHtIT18BWudcry7xafVsbrSdp7HkdRhbbdGCh9-q7lTWx6dJB5gOUTI4TYWCzOZnos48NilIhrk_sZ6V7qk6cxkmGVQflQZ9sLmHGewY6IF7j5OKBDcl-fNGfOyvhn2HkTGzTb_H1uB4dxm1NkopPf4dQHU4A63CnoJLqS_Rhg7aYvdcSyJTtiEGXOQjApP9QkawePsd7JYlSHE7XvYxW7frM0VVO04Fu8HjftMliYKXHct0w9bVeykwRGF_m63VGaain0DjE6H5UKLNdNQvg5r4Azu-hBiR5sH5cL6j26ffEx_P-XnIK5cHyX2A9GHk9V_p6_SiLtKpEuKO4sZPBU4zc6z0J1_aFn-AAtFRpKvHKAfUNCnVacEkXO4k6EfJ3qLkrF2KNIfYCqcTTFl8tPdmbb_n020xdCZNpVKPW_G9qRqpf7okwx9ppGO6OD__uIXNEOcuKD-9pVk2qsdl4RqHAvVO6GNurbcAXQU
[2]
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx33RsetBbPDnYSTJh6Y8esAxiqkWV5pawNUXoDvqZ46lPm1B2tTC0JDlpF5YmuoMEPIqFJJ5BiJpo8CN+5CGDnxSFfiamRomCa2ij6s+XmcPAC41POwpQn0+ijHd/wjh/lEFl+w6xhNhDoLTjxDYIDS8OQ5EhYH8vnHrNnOeh3X7f9kLC00M1S8f0uxbEzjqsfRjSUghIAn8NB3u/wj5/1G++guXDv8WRjadAWjzIhUy9MERIdhK6Mitp9xwPyXLFCQViYLeq9gTGYUqh4G9LzHPAlJW9qWwNM5ij3K+X4Qeu9jJaHCL2ZPywkSkoa6veDjnFs7uS695FqM+5NGyEWPeriCJ1zkL2n8PiGRDSk9jFlQi93+BFyIfUkTPD32CfLV5dYs+a3vV5Uy3cxC+JgxnqVlXY7k3MoaxU08oOMOuGNdCG9RoTtWuERtLa4wkGCKwfdnMzwhWQzKhQGPGRUTq+CxI4WCghfBfqBef74bldmmH/t42FfuCjWvVi6PlDi0YJtixrayJeVBy89dp0Xh1O+g4S9KRx5O1hiJPSNz6Q5O8lHSp4naQhsCGA53JoFofZSj9nBrL6o/Vlh0RPX0c9I2rpPr8YPGC3pxPvbMeP34hbW15F6JSSA9gACc+A2RYWhgZbR2QojJLhhQggSaWZ0g9TF+XG8WQsLISHDUCAwEAAQ==
[3]
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0+Xpe52O+qFGT4WHoJ2bsIC7/Vb51RulDKx/kNNchoERh1pg4TRL2WjXW7CK72CMz7cpNfsuxeC0RXLRMU+aAXyrsWkVm9u0IZsruOHOFjOeWFlqI5ZdnnkHYaH3GQhfXUcnJFQGHpzTn1hWlGT2UWuuSxqYcQ/SUeD7r5rvzOSAPAztLTAzsMmMrDPRz9KWDpmhLPh32WE903eooLAYsneNkGBvaWbc3PF+39P+5z730pWjgaeDsFHJug9c34AxmWP3NfudguW6/Ud9GR250iBHqPBcInafry9orWb+qndWXZLv74gCxovxdA0xuJnO+P/KeYZ5fCI4Lv8VT8H8ascy4LenUjyoR9dUsLpvOyYjNg7qPfZFyi0fN/feyh5M4MAkqPi3MmLzYWI9vlzmUBSzJ8TxHoonBHYIHwjif3ovRavmDSsuEQ2iZcz7P27hx569B50a66VUcXJVxoxSOgsjBD/WG6d97ACftZUaZxYk1SUrp5KGL1qoOlKGTDuIlJjP87/0Rg2TBMR8a6EdofSpAVC1RvB1AXupMK6xfMCRJzMAv4O54Y7esCT6u1hE/z9toQ5ArUPTg+rdskXg9eEQUaIykjJaxaFtzayCyhWFHBqdusZIDcU5e8lRaCzBXfMiXrlGj1E0wviynwfVf+Q8X/HZBXhEtlCfOm6ESe8CAwEAAQ==

How to encrypt JWT secret code for protect it in case of APK decompilation?

I have a problem with JWT. If someone decompiles my APK, they can see the secret code I used to create my token. Example code, the code is secret:
String originalInput = "secret";
String encodedString =Base64Utils.encode(originalInput.getBytes());
String jwt = Jwts.builder().claim("emailId","test123#gmail.com").claim("user", "123456")
.claim("phoneNo", "1111111111")
.signWith(SignatureAlgorithm.HS256, encodedString)
.compact();
As there are a client (the APK) and a backend, you should use an asymmetric algorithm such as RS256, PS256 or ES256, not a symmetric one (HS256).
If the issuer of the token is your backend, you only need the public key on client side (your APK). This key can safely be shipped as it is public.
If the client is the issuer, key should not be shipped with your application but generated on the device and securely stored using the Keystore API (https://developer.android.com/training/articles/keystore). The associated public key should be sent to the backend. This means that each client has a uniquely generated private key.

Getting Keycloak's public key

I realized there are many iterations of this questions. But I can't seem to understand the answer correctly.
We have secured our rabbitmq and rest endpoints with a oauth2 spring server similar to this post. But it doesn't have all of the features we need and want. So we would like to use Keycloak. I have been successful with securing the rest endpoint by just going to the new version of spring security 5.1 and specifing the security.oauth2.resource.jwk.key-set-uri and setting the necessary dependencies and configuration.
While trying to secure the RabbitMQ, I have been running into problems checking the bearer token from the message header because the keycloak jwks endpoint isn't returning the true RSA public key.
RabbitMQ uses the CustomMessageListenerContainer to get the token from the message header and uses the DefaultTokenServices to check the token.
From my understanding, the endpoint that responds with the key is https://keycloak-server/auth/realms/my-realm/protocol/openid-connect/certs
Doing a HttpGet on this endpoint, I get a response that looks like the following
{
"keys": [{
"kid": "7JUbcl_96GNk2zNh4MAORuEz3YBuprXilmTXjm0gmRE",
"kty": "RSA",
"alg": "RS256",
"use": "sig",
"n": "nE9gEtzZvV_XisnAY8Hung399hwBM_eykZ9J57euboEsKra8JvDmE6w7SSrk-aTVjdNpjdzOyrFd4V7tFqev1vVJu8MJGIyQlbPv07MTsgYE5EPM4DxdQ7H6_f3vQjq0hznkFvC-hyCqUhxPTXM5NgvH86OekL2C170xnd50RLWw8FbrprP2oRjgBnXMAif1Dd8kwbKKgf5m3Ou0yTVGfsCRG1_LSj6gIEFglxNHvGz0RejoQql0rGMxcW3MzCvc-inF3FCafQTrG5eWHqp5xXEeMHz0JosQ7BcT8MVp9lHT_utiazhQ1uKZEb4uoYOyy6mDDkx-wExpZkOx76bk_Yu-N25ljY18hNllnV_8gVMkX46_vcc-eN3DRZGNJ-Asd_sZrjbXbAvBbKwVxZeOTaXiUdvl8O0G5xX2xPnS_WA_1U4b_V1t28WtnX4bqGlOejW2kkjLvNrpfQ5fnvLjkl9I2B16Mbh9nS0LJD0RR-AkBsv3rKEnMyEkW9UsfgYKLFKuH32x_CXi9uyvNDas_q8WS3QvYwAGEMRO_4uICDAqupCVb1Jcs9dvd1w-tUfj5MQOXB-srnQYf5DbFENTNM1PK390dIjdLJh4k2efCJ21I1kYw2Qr9lHI4X2peTinViaoOykykJiol6LMujUcfqaZ1qPKDy_UnpAwGg9NyFU",
"e": "AQAB"
}
]
}
From my understanding, the field with key "n" is supposed to be an RSA256 key. Adding it to a RSAVerifier eventually gets an error of "Caused by: org.springframework.security.jwt.codec.InvalidBase64CharacterException: Bad Base64 input character decimal 95 in array position 2."
However, if I login to keycloak admin page and go into the realm settings-> keys and click the public key, a popup shows the public key minus the "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" headers and footers. Hard coding this enables everything to work.
Is the key encoded?
I've tried doing a Base64Utils.decodeFromUrlSafeString and a Base64Utils.decodeFromString. The first returning something smaller and doesn't lool like the key and the later creating an Illegal argument exception Illegal base64 character 5f.
Update:
The n being returned is the modulous and e is the public exponent of the public key. But how does one get the actual key string?
The keys are also directly on https://keycloak-server/auth/realms/my-realm, in a format directly exploitable with your code:
{
"realm": "my-realm",
"public_key": "MIIBI...",
"token-service": "https://keycloak-server/auth/realms/my-realm/protocol/openid-connect",
"account-service": "https://keycloak-server/auth/realms/my-realm/account",
"tokens-not-before": 0
}
I'll found it also on:
open admin console
choose realm
choose Realm Settings
open tab 'Keys'
open tab 'active'
in the column 'Public keys' press 'Public Key
a popup with the public key appears.
There is toIntegerBytes before base64 encode, so it is not just base64 decode. Try:
BigInteger modulus = new BigInteger(1, Base64.decodeBase64("n-value-here"));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64("e-value-here"));
JWKS endpoints are designed to have their keys changed over time (in a process called key rotation), so retrieving the public key as per the accepted answer is not a good idea. What you should opt for instead is to use a JWKS client. I use node-jwks-rsa, but the same creators also have a java implementation (jwks-rsa-java).