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
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
I have an integration where I validate a JSON created by another service. They provide a public endpoint to fetch the public certificates to validate against.
But I am setting up a test for this and would like to create the same JWT with Nimbus to sign it with my own private key. So I do this like this (it's a nested and encrypted JWT):
https://connect2id.com/products/nimbus-jose-jwt/examples/signed-and-encrypted-jwt
Then I would like to simulate the public endpoint with a MockServer (https://www.mock-server.com/) endpoint in tests. The problem is that I try to create a PEM certificate from the public key from the senderJWK from the example like this:
var encoded = senderJWK.toPublicKey().getEncoded();
var base64Encoded = Base64.getEncoder().encode(encoded);
return new String(base64Encoded, StandardCharsets.UTF_8);
(I have also tested senderJWK.toRSAPublicKey().getEncoded().)
The code that works with the real certificate does not work to parse it. The code to parse it look like this:
private static RSAPublicKey readPublicKey(String publicKey) throws CertificateException {
var bytes = Base64.getDecoder().decode(publicKey);
var inStream = new ByteArrayInputStream(bytes);
var certificateFactory = CertificateFactory.getInstance(X_509_CERTIFICATE_FACTORY);
var certificate = (X509Certificate) certificateFactory.generateCertificate(inStream);
return (RSAPublicKey) certificate.getPublicKey();
}
The error I am getting is:
java.io.IOException: Too short
at java.base/sun.security.util.DerValue.<init>(DerValue.java:333)
at java.base/sun.security.util.DerInputStream.getDerValue(DerInputStream.java:109)
at java.base/sun.security.x509.X509CertImpl.parse(X509CertImpl.java:1771)
at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:183)
... 100 common frames omitted
Wrapped by: java.security.cert.CertificateException: Unable to initialize, java.io.IOException: Too short
at java.base/sun.security.x509.X509CertImpl.<init>(X509CertImpl.java:186)
at java.base/sun.security.provider.X509Factory.engineGenerateCertificate(X509Factory.java:105)
at java.base/java.security.cert.CertificateFactory.generateCertificate(CertificateFactory.java:355)
... 95 common frames omitted
Ok, I think what I need to do is create a X509 certificate from java, and then use the private and public keys from that in the signing and verification.
Found these resources on how to use bouncy castle to do that:
Self signed X509 Certificate with Bouncy Castle in Java
How to create a X509 certificate using Java?
Edit: I got it working fine with that.
I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.
We've generated a Bearer JWT token in .NET Core and have signed the token using the pfx of a X509 self signed certificate generated using Powershell.
We need to verify the token signature in a Springboot Java application.
To achieve this we have imported the pfx into a java keystore (jks) using the key tool in Java.
keytool -importkeystore -srckeystore "certificate.pfx" -srcstoretype pkcs12 -destkeystore "clientcert.jks" -deststoretype JKS
We're doing the verification of the token signature in a Resource Server in which we have a JwtAccessTokenConverter configured as below.
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
InputStream inJksStream = null;
InputStream inputJksStream = null;
inJksStream = new FileInputStream("clientcert.jks");
inputJksStream = new BufferedInputStream(inJksStream);
KeyStore trustStore = KeyStore.getInstance("jks");
trustStore.load(inputJksStream, "password".toCharArray());
Key trustStorePrivateKey = trustStore.getKey("alias-value", "password".toCharArray());
converter.setSigningKey(Base64.getEncoder().encodeToString(trustStorePrivateKey.getEncoded()));
The Controller in the application is decorated with the following element.
#PreAuthorize("isAuthenticated()")
However we encounter a signature invalid exception when a valid JWT token is passed along with a request to the Springboot application.
Is this the appropriate way to verify a Bearer JWT token in Springboot?
I just dealt a similar issue when issuing a JWT using IdentiyServer4. I may be able to help if I can see the output of your error.
May I suggest using the auth0 library to verify your JWT. I just implemented this in a project and it was a lot easier and involved a lot less code than other libraries.
maven: com.auth0 / java-jwt / 3.0.1
A simple method to verify your JWT can be found below...
public static DecodedJWT decodeJWT(String jwtString) {
try {
String secret = //get your secret key
return JWT.require(Algorithm.HMAC256(secret))
.withIssuer("foo")
.build()
.verify(jwtString);
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
I use a third party tool to verify signature and to get certificate detail(like serial number, CA etc..) from signature. The problem with this utility is it is licensed and works on certain machines only.
Can i validate the signature against the data using simple java or .net code?(instead of using paid application). I dont have private key to extract certificate information from signed data.
Or if someone can suggest sample code in java or .net to extract certificate detail if i have pfx file. Of from signed data.
Data is signed with asymmetric encryption.
To extract detail from certificate:
Make a string which keeps certificate data. Just ensure it has -----BEGIN CERTIFICATE----- in starting and -----END CERTIFICATE----- in end.
Now use the following code in Java to extract certificate detail.
InputStream inStream = new ByteArrayInputStream(certString.toString().getBytes("UTF-8"));
BufferedInputStream bis = new BufferedInputStream(inStream);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(bis);
X509Certificate xCert = (X509Certificate)cert;
System.out.println("Certificate Type: "+cert.getType());
System.out.println("Public Key: \n"+cert.getPublicKey());
try{
System.out.println("Signature Algorithm"+xCert.getSigAlgName());
System.out.println("IssuerDN : "+xCert.getIssuerDN());
System.out.println("Serial Number : "+xCert.getSerialNumber());
System.out.println("SubjectDN : "+xCert.getSubjectDN());
}catch(Exception exp){
:
}
If you are having the PFX file, then that may contain the public key certificate which will be required to verify the signature.
Alternatively, if your signature is a PKCS#7 signature, then the signature itself will hold the data, signature and the certificate. Assuming PKCS#7 is not detached.
You need to ask your signer, how is he transferring his certificate for validation.