I created a jwt token using java keystore public key an and io.jsonwebtoken library.
After generation i copy pasted the generated token in https://jwt.io website. It decoded my token without using private key.How come this is possible?
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
PrivateKey privateKey = pair.getPrivate();
Claims claims = Jwts.claims().setSubject(userName);
claims.put("scopes", scopes);
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.RS256, privateKey)
.compact();
Your JWT is only signed, not encrypted. The main security feature of the incoming JWT is that it has a checksum/signature at the end. Your Java program has the ability to verify that the checksum matches the actual content of the JWT (e.g. headers and claims). If the checksum does not match, then the server will assume the JWT has been tampered with and reject it. The main use of a JWT is not so much protecting critical information as it is about controlling authorization and authentication in your application.
Related
I would like to authenticate with MSAL4J and the certificate stored in Azure Key Vault (AKV). The certificate is a self-signed Azure Key Vault certificate.
I could find an example based on a certificate and key stored locally (file system) but not a certificate created and stored in AKV. How to use the certificate, key, and secret objects obtained from azure-security-keyvault-* with MSAL4J?
The key from azure-security-keyvault-keys is com.azure.security.keyvault.keys.models.KeyVaultKey, but MSAL4J expects java.security.PrivateKey.
How to apply the secret obtained from azure-security-keyvault-secrets to decrypt the private key?
Are you sure it is supported? As far as I know certificated-based authentication is not supported.
MSAL uses either public clients or confidential clients.
https://learn.microsoft.com/en-us/azure/active-directory/develop/authentication-flows-app-scenarios#daemon-app-that-calls-a-web-api-in-the-daemons-name
https://learn.microsoft.com/en-us/azure/active-directory/develop/msal-client-applications
However, I did find this on their wiki: https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Client-Credentials
There are two types of client secrets in MSAL4J:
Application Secrets
Certificates
You need to instantiate a confidential client application; if you have a certificate:
String PUBLIC_CLIENT_ID;
String AUTHORITY;
PrivateKey PRIVATE_KEY;
X509Certificate PUBLIC_KEY;
IClientCredential credential = ClientCredentialFactory.createFromCertificate(PRIVATE_KEY, PUBLIC_KEY);
ConfidentialClientApplication app =
ConfidentialClientApplication
.builder(PUBLIC_CLIENT_ID, credential)
.authority(AUTHORITY)
.build();
Then acquire a token: https://github.com/AzureAD/microsoft-authentication-library-for-java/wiki/Acquiring-Tokens#confidential-client-applications
https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/src/client/ConfidentialClientApplication.ts
You would need to use: acquireTokenByClientCredential
https://azuread.github.io/microsoft-authentication-library-for-js/ref/classes/_azure_msal_node.confidentialclientapplication.html#acquiretokenbyclientcredential
Also see:
https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/dev/msal4j-sdk/src/integrationtest/java/com.microsoft.aad.msal4j/CertificateHelper.java
https://learn.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow
Connecting to Azure AD v2.0 endpoint I cannot validate my token which jwt.io and jwt.ms declare as valid
A public token is no problem:
IDTokenValidator validator = new IDTokenValidator(issuer, clientId, JWSAlgorithm.RS256, jwkSetUrl);
IDTokenClaimsSet validatedClaimsSet;
try {
validatedClaimsSet = validator.validate(jwt, null);
} catch (BadJOSEException | JOSEException e) {
LOGGER.error(e.getMessage());
return;
}
but validating a token coming from a confidential client will throw the exception
Signed JWT rejected: Another algorithm expected, or no matching key(s) found
with the following code
IDTokenValidator validator = new IDTokenValidator(issuer, clientId, JWSAlgorithm.RS256, new Secret(authMethod.getSecret()));
No matching keys are found because (JWKMatcher.java:1258)
keytype does not match: OCT instead of RSA
use is null
ops is null
It seems an inadequate JWKSet is created from the secret in ImmutableSecret.java:47 but I seem to lack understanding as to how the secret can contain the necessary data found in the jwks_uri from Azure
Any advice would be appreciated
Validating an ID token works the same way for both public and confidential clients. So just get rid of the secret parameter and your code will work - using the token's signing public key.
The secret parameter is for JWTs that are signed with a symmetric algorithm such as HMACSHA256 - which is almost never a good idea. More about this topic in JWT Security Best Practices.
I am developing a backend application using Spring Boot with Java. I have to fulfill this requirement: when a specific controller of my application is called I have to return a JWT (created by me) to the client.
My application has a certificate that I created using an openssl command.
The command I used generated a .pem certificate which I converted to a .crt certificate (still using openssl).
This is the certificate (.pem version):
-----BEGIN CERTIFICATE-----
MIID3zCCAsegAwIBAgIUaQYkHJTPg5Xl29W18fl1FK3F034NFVDWòEMChvcNAQEL
BQAwfjELMAkGA1UEBhMCSVQxEDAOBgNVBAgMB0JvbG9nbmExEDAOBgNVBAcMB0Jv
bG9nbmExEjAQBgNVBAoMCVVuaXNhbHV0ZTEdMBsGA1UECwwUU3ZpbHVwcG8tTXVs
dGljYW5hbGUxGDAWBgNVBAMMD2JlLXByZW5vdGF6aW9uaTAgFw0yMTEwMTgxNjIx
MDJaGA8yMTIxMDkyNDE2MjEwMlowfjELMAkGA1UEBhMCSVQxEDAOBgNVBAgMB0Jv
bG9nbmExEDAOBgN5KLT56L5KJ6L5JK3L434NREDFEFEL3WECN4CXaqre35EdMBsG
A1UECwwUU3ZpbHVwcG8tTXVsdGljYW5hbGUxGDAWBgNVBAMMD2JlLXByZW5vdGF6
aW9uaTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALrM4q5Azkjm32Ep
MvtcxnA0Ri+6m2DOOjNvJE5rn392hfiZFZcf4K/NCR8F1uM4thru3RsU5mAuhSrK
Nn58fP5KFKVJFLFJDEHWEB3R4RRIMom7PBusRXPbWM+xr0mGdP3SdXr2c71SvSBQ
KVCEBWfR86qSDSDSDSDXCXs4534rI95R7PYXSOrrH7z89u6p9unSI0OKlEwoVOhM
90JdwhVkc6TSSuFBcsFoXlUjcBDYIkZVIgXpj2+EqhDxUSKIlQr6WKWb3DYaGLyG
/9UUPAbLpMTd+D/T9d+cmM3X0WtoNSSvB30qthMFPs9Pdv5Keba7jbfYyYhpECVu
HVCujSkCAwEAAaNTMFEwHQYDVR0OBBYEFCh3SmbuLXhoCtKEq4wSqlBsPQPOMB8G
A1UdIwQYMBaAFCh3SmbuLXhoCtKEq4wSqlBsPQPOMA8GA1UdEwEB/wQFMAMBAf8w
DQYJKoZIhvcNAQELBQADggEBAA38HsRF4NPEITje0hzQmZG0Zhz2C+2EWH9Z+3ow
JfGc/y40I1WYja23zCwyUndQ5M8QXo4OYt8/yNiuGDPQf/n0QqDQ6ynwfkLkmr6m
3a7ocDT8eUFbisEZGWuYqInAi6oiRNc//NYEQOvapNkasu01p/mLhlrINoRIMwX0
JC5EovSivp05xSB6LHuT454545wddsfcwejh23jkashaksaslasLAF9348D8FlXU
X+cD5wvsGQcwmEu64BxEauDGOSybRDD6ppfSPtTivhR0UyLK9hAQB76Ns6oRbBCc
aLEzzgYhOTKHjkmI27qqUa16Jf6TFJe22Q6wbtOrEOnxDiw=
-----END CERTIFICATE-----
I need to create a JWT token (to be returned to the client) starting from this specific certificate that I have saved within my application. What is the easiest way to do this using Java (possibly with standard Java libraries)?
Unfortunately I am not an expert on tokens, public keys, private keys etc ... I just know that the JWT token I create must allow the client to authenticate to me when it calls new endpoints (managed by other controllers in my application, which will validate the token!)
I will suggest the following way to proceed further:
Follow the blog and resurrect a minimalistic running version that has JWT authentication integrated.
https://medium.com/wolox/securing-applications-with-jwt-spring-boot-da24d3d98f83
Now change AuthenticationFilter class and instead of using the hardcoded key in successfulAuthentication method use the private key of your certificate. You can retrieve the private key using bouncy castle libraries and the method defined in blog section 4.2: https://www.baeldung.com/java-read-pem-file-keys
public RSAPrivateKey readPrivateKey(File file) throws Exception {
KeyFactory factory = KeyFactory.getInstance("RSA");
try (FileReader keyReader = new FileReader(file);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
return (RSAPrivateKey) factory.generatePrivate(privKeySpec);
}
}
Once you retrieve the RSAKey use the get the getEncoded() (https://docs.oracle.com/javase/8/docs/api/java/security/Key.html#getEncoded--)
method of retrieved RSAPRivateKey instead of KEY.getBytes() in line 54 of AuthenticationFilter class
I have generated a private key using Elliptic Curve cryptography:
openssl ecparam -genkey -name secp521r1 -noout | openssl pkcs8 -topk8 -nocrypt
I have used the following Java code to sign a JWT:
String privateKeyPEM = "MIHtAgEAMBAGByqGSM49AgEGBSuBBAAjBIHVMIHSAgEBBEEmSOGpmkjzKM+uWhya"
+ "Cl6sbSsmROUol4HaDbORnOI6klbEjbCkPEyxKRnrrtrGFShhu7TPPlGDK39f+K3G"
+ "IZhbYKGBiQOBhgAEAJQiOIKV7YmIVI30Y3y1UZIvgZFRviHFWvSiTXEG4IqzHKpF"
+ "jOIYs0rzn1F2zrFHKpmMtZ0Kh5OzyfJsGeu1GZPzANYLZQ9m13Joi3fhGFUgHLNL"
+ "0hsz/HQP89aTa9Qr8QqEP7r/vCvrcoKn9cKPGwRxOFkRgG4FWGv76F/hv+1Cj2Z7";
byte[] encoded = Base64.decodeBase64(privateKeyPEM);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
ECPrivateKey privateKey = (ECPrivateKey) keyFactory.generatePrivate(keySpec);
final JwtBuilder jwtBuilder = Jwts.builder()
.setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
.signWith(SignatureAlgorithm.ES512, privateKey)
.setHeaderParam("typ", "JWT");
System.out.println(jwtBuilder.compact());
However, the resulting JWT always have an invalid signature:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh
I can't figure out what's wrong with my code.
The posted private key is a PKCS#8 key. From this the following public X.509 key can be derived:
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAlCI4gpXtiYhUjfRjfLVRki+BkVG+
IcVa9KJNcQbgirMcqkWM4hizSvOfUXbOsUcqmYy1nQqHk7PJ8mwZ67UZk/MA1gtl
D2bXcmiLd+EYVSAcs0vSGzP8dA/z1pNr1CvxCoQ/uv+8K+tygqf1wo8bBHE4WRGA
bgVYa/voX+G/7UKPZns=
-----END PUBLIC KEY-----
If a JWT is created with the posted code, e.g.:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.AE0sx6wHk2xBPkbam24n8NE39qkB0YX4j65DhrWyBKtaQXRMZjuzV78vFir3scfXVolFOf2gpo2K6x_hu0jPz-0IAIMbYQsglePQHQ9OZMSb2XAxKCVXccdvW27QeBov-VGUxxlL-CFNviaPaAGbNny_sc8cRjIF97pDD4KjOPBKkZzt
then this can be verified without problems with this public key, check it e.g. here, i.e. the posted code produces a valid signature.
On the other hand, the JWT posted in the question:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.MIGHAkFvCPq_BeXvATTN1duKjEf3K_Fja0ueoTuPQHC9kBc828wem7YO0vnlK6PVYXSkBk4gBaD0-OIMY_r-HS7-4-HaBwJCAMbj0k5YsBywMzme_adKTQq7YUsVvyZwGp8aVgX7vxsMhf-WNvQJSg7AG_zQiUaQ4jqtT9ZKzNoU4P5NZIGMDRCh
can indeed not be verified. The signature of this JWT is Base64url decoded:
30818702416f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07024200c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1
and thus ASN.1 encoded, s. here and here. However, JWTs use a signature encoded as r|s, see e.g. here. If the signature is converted to this encoding, the result is:
6f08fabf05e5ef0134cdd5db8a8c47f72bf1636b4b9ea13b8f4070bd90173cdbcc1e9bb60ed2f9e52ba3d56174a4064e2005a0f4f8e20c63fafe1d2efee3e1da07c6e3d24e58b01cb033399efda74a4d0abb614b15bf26701a9f1a5605fbbf1b0c85ff9636f4094a0ec01bfcd0894690e23aad4fd64accda14e0fe4d64818c0d10a1
If this is Base64url encoded and used in the posted JWT (instead of the old signature), that is:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.bwj6vwXl7wE0zdXbioxH9yvxY2tLnqE7j0BwvZAXPNvMHpu2DtL55Suj1WF0pAZOIAWg9PjiDGP6_h0u_uPh2gfG49JOWLAcsDM5nv2nSk0Ku2FLFb8mcBqfGlYF-78bDIX_ljb0CUoOwBv80IlGkOI6rU_WSszaFOD-TWSBjA0QoQ
the JWT can be successfully validated.
Since the posted code generates an RFC compliant JWT (with r|s signature), the JWT posted in the question was probably not generated with the posted code (because of the ASN.1 signature).
Update: According to the jjwt bugtracker there is a bug (#125) that causes the signature to be signed incorrectly with ASN.1. This bug should be fixed with jjwt 0.7 and would be a plausible explanation for your issue, provided you are working with an affected version (the bug is from 05.2016).I have tested your code with jjwt 0.9.1 (from 07.2018), which generates a valid signature, meaning it works.The current version is jjwt 0.11.2 (from 06.2020), which also works according to the other answer.So if you are working with an affected version, it would be best to use a newer / the current jjwt version. If this is not possible for some reason you can of course convert the signature manually from ASN.1 to r|s encoding.
My IntelliJ is claiming that the "signWith"-line is deprecated.
So changing your code
final JwtBuilder jwtBuilder = Jwts.builder()
.setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
.signWith(SignatureAlgorithm.ES512, privateKey)
.setHeaderParam("typ", "JWT");
to
final JwtBuilder jwtBuilder = Jwts.builder()
.setSubject("713f42c9-7df5-4271-8b53-112f30936c56")
.signWith(privateKey, SignatureAlgorithm.ES512)
.setHeaderParam("typ", "JWT");
is giving this JWT:
eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJzdWIiOiI3MTNmNDJjOS03ZGY1LTQyNzEtOGI1My0xMTJmMzA5MzZjNTYifQ.ALFk_BGerAstughF4ssl5eGQmx0mu5jvWb13QB228hAD5g8dwM-NvBsyevCuYUXLBJKzIUdPL-LVwQoPkwIbYrKhACzKwUwRN_v3IX2GIPW2ctTcRGPwA7gUaDWrOtwqcHALSfk20QZXT2TQfOnXX8tv0vhXLK_SnnHH5o1b96sa_HSR
As you only provided the ec private key I used OpenSSL to generate the EC Public key and passed the JWT and public key to the Online JWT verifier https://jwt.io/
and got the result "Signature verified".
I'm using Firebase with a Email & Password authentication. Once the user has been signed in successfully I'll receive an AuthData object which contains a token.
I wanna send this token to my backend, verify it and extract the uid from it - unfortunately I don't know how to do this.
I'm aware of the Firebase secret and if I go to jwt.io, enter the token and the secret the signature is verified and I see the correct payload - so this actually works.
Since I'm running Java on my backend I've been using jjwt for the decryption process unfortunately it always throws a SignatureException:
io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
Following the code I've been using:
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
byte[] encodedKey = Base64.decode("my-firebase-secret", Base64.DEFAULT);
Key k = new SecretKeySpec(encodedKey, signatureAlgorithm.getJcaName());
Claims claims = Jwts.parser()
.setSigningKey(k)
.parseClaimsJws("the-token").getBody();
I've also tried it with the following snippet:
Claims claims = Jwts.parser()
.setSigningKey(DatatypeConverter.parseBase64Binary("my-firebase-secret"))
.parseClaimsJws(jwt).getBody();
But got the same exception. So what am I doing wrong? Thanks in advance.
Have you tried to use getBytes instead of parseBase64Binary? Here is the example:
Claims claims = Jwts.parser().setSigningKey("my-firebase-secret".getBytes("UTF-8")).parseClaimsJws(jwt).getBody();