I am trying to verify a JWT token by verifying its signature. But during the verification I get an error
java.security.SignatureException: Signature length not correct: got
342 but was expecting 256
. I assume that signature is the encrypted sha256 hash of the data. In my case data is base64(header)+"."+base64(body). Here is my code:
static String certificate = "MIIDBjCCAe4CCQDmPif23IJerzANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTE1MDkyMjA3MDkwMFoXDTE2MDkyMTA3MDkwMFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYjKc8pLFkAa45j6w6PHsroe7ijOCfhZmVtMCvZ8lINaP9mR8irOJHpLdJs4vpbxEZMqqLMhKjO7iUmXBmml37QRlJXY6f25essPkTdUmhiIrU/rIrZrCanvegXUHkvf4xvOQ1BTx/p5b1iIq3Wrk5Fox3pMigzqYhk4YuiJho8uabC9zyecmS3zIoRgwx+Vacel/ZW6r6YOlB6mblN9IvasvqWgDalegmMKOIZvwkpo/3mfzcGi5haWZZ3ufUqQjb4B7raJmfyrLnwi6XI9UzzGc04pCfIAsxTb5yM8cJQcJ/5VHF3h21eFJdZKyD2210gSq7/Y8Oda0dDXQchmFcCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAmBm84PfwOe5sGemT9D4ImIYWp/1xrV6b0zeaDZ6Yi7sz9R3VKjfnAxUVOHA7GlMi8xHHdDu7VhVhAWyFCvITdG00K18xJuKap1bwzw3kr70lLQaFahV+zaWKfWAfV34tots3G/hMqdOv0a+I/5t/T7oKPCmm/IfCVKdC1tGbTji+hxVLpaAkn60RFNzLKGFwtSxv9ObxR5Hn88+wV48VAcEnwcUk2DjBi1fW6jnMcNJbVd+/oKBOwj7UK2Lk10Qaeet8KKh5fFKEpgx7D4ITwer0G/Je1NMv1/lfNzpKlTKoBureF5C6B+rJIesQ/dAfg6H/ggxbgVMuo6imIPVvrg==";
static String signedData = "Z5MwwjtXdypMQGNwmNNuCVmRcDVT24EgtwoDWalF4icxwz7jyB99Yg3262D7OsERewv4cOfdEz3bbOF-iG7YWXeSC9YZeO1tGapqlc8FRtAergSUZC7BcbFEx75MfSy7qLWYTOfdpJesQ23rOzjF7KdrAMJC_Y0T_r6RuBcZVyfT4P55kICETYyv7bBDXc9V8BJUf-QHDu6DaH7u6PSyeOmdzFInI_LwySnMlr3VahoUfUpJmauU8yHQUnFakJBgrMBe1Au9tS-HxtDVnHmoHQw8xGXsVQnEOa1aPAcVWy0v7hILUSmWNAG3IZ0JwUztQitgtnTTzXDszUxTbJ4YlQ";
static String data = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwczovL2NpdHJpeHAuY29tOjg0NDMvIiwiaXNzIjoiaHR0cHM6Ly9zdHMud2luZG93cy5uZXQvZGQ5YjZhM2UtMjlkMS00MjU0LWE3NDYtZTAyOTQxNDQ0NTE3LyIsImlhdCI6MTQ0MjYyNjA0NSwibmJmIjoxNDQyNjI2MDQ1LCJleHAiOjE0NDI2Mjk5NDUsInZlciI6IjEuMCIsInRpZCI6ImRkOWI2YTNlLTI5ZDEtNDI1NC1hNzQ2LWUwMjk0MTQ0NDUxNyIsIm9pZCI6ImJkZDNmZDAyLTEyMzMtNGMxOC05NTRmLWJkNGFjMWYzOWU5OSIsInVwbiI6InNwQGNpdHJpeHAuY29tIiwic3ViIjoieUFfZTFzMmFOeFdvR3NSNzhfVWNYZkk5dERlYUM0QUozdXFZQXFDdllSbyIsImdpdmVuX25hbWUiOiJzIiwiZmFtaWx5X25hbWUiOiJwIiwibmFtZSI6InNwIiwiYW1yIjpbInB3ZCIsInJzYSJdLCJ1bmlxdWVfbmFtZSI6InNwQGNpdHJpeHAuY29tIiwiYXBwaWQiOiIyOWQ5ZWQ5OC1hNDY5LTQ1MzYtYWRlMi1mOTgxYmMxZDYwNWUiLCJhcHBpZGFjciI6IjAiLCJzY3AiOiJtZG1fZGVsZWdhdGlvbiIsImFjciI6IjEiLCJpcGFkZHIiOiI2My4xMTAuNTEuMTEiLCJkZXZpY2VpZCI6ImQyNzU3NzY0LWFiOTEtNDBiMS05MmM2LTViOWE4MWYxODNiYyJ9";
public static void main(String[] args) throws UnsupportedEncodingException {
// TODO Auto-generated method stub
String certString = "-----BEGIN CERTIFICATE-----\r\n" + certificate + "\r\n-----END CERTIFICATE-----";
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
CertificateFactory cf;
try {
cf = CertificateFactory.getInstance("X.509");
InputStream stream = new ByteArrayInputStream(certString.getBytes()); //StandardCharsets.UTF_8
java.security.cert.Certificate cert = cf.generateCertificate(stream);
PublicKey pk = cert.getPublicKey();
//verifying content with signature and content :
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initVerify( pk );
sig.update(data.getBytes());
Boolean ret = sig.verify(signedData.getBytes());
} catch (CertificateException | SignatureException | NoSuchAlgorithmException | InvalidKeyException e) {
e.printStackTrace();
}
The signature part of a JWT is base64url encoded (see JWS ยง7.1) so you'll need to properly decode it before verification.
Replace the line Boolean ret = sig.verify(signedData.getBytes()); with something like the following (using the Base64 decoder from Apache Commons Codec),
byte[] signature = org.apache.commons.codec.binary.Base64.decodeBase64(signedData);
Boolean ret = sig.verify(signature);
And the signature on that JWT should verify for you.
Having said that, I'd very much recommend using a library for JWT processing. A decent library will handle stuff like that for you and can provide more JWT functionality too.
For example, the following using the jose4j JWT library could replace the stuff from the code in the question after getting the public key from the certificate. This not only verifies the signature per the spec but also validates the claims in the JWT to make sure it's still valid, was issued to you, etc..
JwtConsumer jwtConsumer = new JwtConsumerBuilder()
.setVerificationKey(pk)
.setRequireExpirationTime()
.setExpectedAudience("https://citrixp.com:8443/")
.setExpectedIssuer("https://sts.windows.net/dd9b6a3e-29d1-4254-a746-e02941444517/")
.build();
JwtClaims claims = jwtConsumer.processToClaims(data + "." + signedData);
System.out.println("Subject: " + claims.getSubject());
System.out.println("UPN: " + claims.getStringClaimValue("upn")); // or whatever, etc....
That JWT expired a few days so the processToClaims will throw an exception, which is what you want. You can add a .setEvaluationTime(NumericDate.fromSeconds(1442626055)) when building the JwtConsumer to get it to work for the given JWT though.
Related
Here I am trying to verify the signature against the pass file which I have.
I have created a method to validate the signature as below. Which have context data (.pass file bytes and signature bytes)
Signature hash algorithm is RSA with SHA512 and PKCS1 padding, Do I need to specify anything for PKS1 padding?
public boolean validateSignature(Context context, byte[] data, byte[] signature) {
try {
// Reading public certificate from raw folder
InputStream is = context.getResources().openRawResource(R.raw.public_cer);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
// This line is giving error
X509Certificate crt = (X509Certificate) cf.generateCertificate(is);
PublicKey publicKey = crt.getPublicKey();
Signature sig = Signature.getInstance("SHA512withRSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(Base64.decode(signature, Base64.NO_WRAP));
} catch (Exception ex) {
Log.d(TAG, ex.getLocalizedMessage());
}
return false;
}
I am trying to verifying the signature with below code but getting error.
if (validateSignature(
applicationContext,
bytes, // Byte array of Data file
signatureBytes // Byte array of signature file
)
) {
Log.d(TAG, "checkSignature: true")
} else {
Log.d(TAG, "checkSignature: false")
}
Error
com.android.org.conscrypt.OpenSSLX509CertificateFactory$ParsingException: java.lang.RuntimeException: error:0c0000be:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
I am trying to verify the signature against the given public.cer file
I the signature is verified I can confirm that the file is authenticated to use.
My Targeted sdk is 32
For a payment API I need to hash a string (a request payload) with SHA-512,
sign itwith "RSA" according to the documentation.
Hash the payload string (payout instruction) using SHA512 algorithm.
Sign the hashed payload string using RSA algorithm and the signing certificate private key.
Base64 encode the signed hash
Step 1 and 3 I've got no problems with. Step 2 is the problem. Support sent me a java example but we don't run java and I would like to do this in OpenSSL if at all possible.
private static String createSignature(String stringToBeSigned, PrivateKey privateKey) {
try {
byte[] hashString = hashString(stringToBeSigned);
Signature sign = Signature.getInstance("SHA512withRSA");
sign.initSign(privateKey);
sign.update(hashString);
byte[] signature = sign.sign();
return Base64.getEncoder().encodeToString(signature);
} catch (Exception e) {
throw new RuntimeException("Could not create signed payout request.", e);
}
}
private static byte[] hashString(String stringToBeHashed) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-512");
return digest.digest(stringToBeHashed.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
The Key is provided. How do I find what "SHA512withRSA" corresponds to using OpenSSL?
Now I want to generate an JWT token to request apple server follow this docs, I have the p8 file download from apple, how to get jwt sign key from p8 file? this is my jwt token generate code:
Map<String,Object> jwtHeader = new HashMap<>();
jwtHeader.put("alg","ES256");
jwtHeader.put("kid","YDKL424AF9");
jwtHeader.put("typ","JWT");
Map<String,Object> appleJwtPayload = new HashMap<>();
appleJwtPayload.put("iss","5fb8e836-27d7-4390-8f40-008acd64a29d");
appleJwtPayload.put("iat",System.currentTimeMillis() / 1000L);
appleJwtPayload.put("exp",System.currentTimeMillis() / 1000L + 60 * 15);
appleJwtPayload.put("aud","appstoreconnect-v1");
appleJwtPayload.put("nonce",UUID.randomUUID().toString());
appleJwtPayload.put("bid","com.earth.dolphin");
String appleKey = "<how to get the apple key>";
SecretKey secretKey = new SecretKeySpec(appleKey.getBytes(), SignatureAlgorithm.ES256.getJcaName());
String accessToken = Jwts.builder()
.setClaims(appleJwtPayload)
.setHeader(jwtHeader)
.signWith(secretKey)
.compact();
I read the KeyStore code follow this question, but I still could not figure out how to do it, any one could help me?
get the sign key like this:
byte[] p8der = Files.readAllBytes(Path.of("/opt/apps/dolphin-post/AuthKey_YDKL424AF9.p8"));
PKCS8EncodedKeySpec priPKCS8 = new PKCS8EncodedKeySpec(new org.apache.commons.codec.binary.Base64().decode(p8der));
PrivateKey appleKey = KeyFactory.getInstance("EC").generatePrivate(priPKCS8);
the file AuthKey_YDKL424AF9.p8 was download from apple, you should remove the begin and end header of the file. This is my full function to get private key:
public static PrivateKey getPrivateKey(String filename, String algorithm) throws IOException {
String content = new String(Files.readAllBytes(Paths.get(filename)), "utf-8");
try {
String privateKey = content.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s+", "");
KeyFactory kf = KeyFactory.getInstance(algorithm);
return kf.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Java did not support the algorithm:" + algorithm, e);
} catch (InvalidKeySpecException e) {
throw new RuntimeException("Invalid key format");
}
}
you can use it like this:
PrivateKey appleKey = SecurityUtil.getPrivateKey(APPLE_PRIVATE_KEY_PATH,"EC");
the APPLE_PRIVATE_KEY_PATH in my OS is:
apple.private.key.path=/opt/apps/dolphin-post/AuthKey.p8
changed it to your path.
Below I have included code that takes a Thumbprint of a certificate as an argument and then loads the appropriate cert from the Windows certificate store.
This was done because now I would like to use the found cert to make API calls to an API that requires this certificate- if the certificate is not included in the request the API will reject me as unauthorized.
I have previously done this in C# and it was quite simple as I was able to just create a clientHandler that identified the protocol, callback, and certificate that allowed for creation of a usable HttpClient. Java is significantly more complicated and is giving me issues since it appears I must work with the keymanager(and possibly trustStore and SSLContext). In other words I am having a hard time understanding how to use these objects to send my certificate to the API with my request.
I realize I may not be able to simply build the client with the cert to get the successful connection. But how can I use the x509certificate that I have in memory to successfully send requests to the API.
I am not looking for a solution that will 100% work for my case, I would just like to see a clean example of how to send an API request with the certificate(in whichever way the libraries require) in Java.
public static X509Certificate LoadCert(String inThumbprint) {
X509Certificate ReturnCert = null;
try{
KeyStore keyStore = KeyStore.getInstance("Windows-MY");
keyStore.load(null, null); // Load keystore
String currentAlias;
X509Certificate foundCert;
String calculatedThumbprint;
for (Enumeration<String> oEnum = keyStore.aliases(); oEnum.hasMoreElements();) {
currentAlias = oEnum.nextElement(); System.out.println(currentAlias);
foundCert = (X509Certificate) keyStore.getCertificate(currentAlias);
calculatedThumbprint = getThumbprint(foundCert); System.out.println(calculatedThumbprint);
if (calculatedThumbprint.equals(inThumbprint)) {
ReturnCert = foundCert;
}
}
} catch (Exception ex){
ex.printStackTrace();
}
return ReturnCert;
}
private static String getThumbprint(X509Certificate cert)
throws NoSuchAlgorithmException, CertificateEncodingException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] der = cert.getEncoded();
md.update(der);
byte[] digest = md.digest();
String digestHex = DatatypeConverter.printHexBinary(digest);
return digestHex.toLowerCase();
}
I want to show that if I modify one bit or byte from a given X509 certificate the signature verification results false (because this modification results different hash value from the certificate). I'm stuck in the case that how to do the modification on the certificate using getTBSCertificate() method. My following code does the verification process perfectly BUT I tried to make it fail using bit or byte modification's idea but it doesn't work. Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification
public class VerifyX509 {
private static Certificate getCACert;
private static Certificate[] getCert;
public static void main(String[] args) throws CertificateEncodingException {
setURLConnection("https://www.google.com");
X509Certificate x509cert= (X509Certificate) getCert[0];
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
// HOW TO UPDATE getTBSCertificate() after flipping the b[0] to make Verify() in my method verifySign() return false!
verifySign();
}
public static void setURLConnection(String link){
try{
int i=1;
URL destinationURL = new URL(link);
HttpsURLConnection con = (HttpsURLConnection) destinationURL.openConnection();
con.connect();
getCert = con.getServerCertificates();
for (Certificate c : getCert)
{
if (i==2)
{
getCACert= c;
return;
}
i+=1;
}
}catch (Exception e1) {
JOptionPane.showMessageDialog(null, "Error while connection! Check your Internet Connection.");
e1.printStackTrace();
}
}
public static boolean verifySign()
{
try
{
getCert[0].verify(getCACert.getPublicKey());
return true;
} catch (GeneralSecurityException e2)
{
return false;
}
}
}
How can I setup proof-of-concept code to show that the verification while fail?
Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification.
You can demonstrate this (to a certain probability of correctness) by simply flipping random bits in valid certificates and then attempting to validate them.
However, you cannot prove anything like this. A proper proof requires:
A mathematical proof that a properly implement X509 certificate has the property that changing the certificate renders it invalid (with some probability very close to one1).
A formal-methods proof that the code that is loading the certificate and doing the verification is correctly implemented.
1 - In fact, it is easy to see that the probability cannot be exactly one. Apply the pigeonhole principle.
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
Changing a byte in an array that you have obtained from the certificate doesn't change the certificate.
You would have to reload it from the byte array using a CertificateFactory.
Mr. Mike, all what you have to do is to get the row data DER-encoded certificate information (TBS part) and you can extract it as below
URL url = new URL("https://www.google.com/");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.connect();
Certificate userCert[] = con.getServerCertificates();
X509Certificate x509cert = ((X509Certificate) userCert[0]);
byte[] tbs=x509cert.getTBSCertificate();
Then copy the content of the array b to another array bcopy through a loop and do what ever modifications you want (i.e by using the masking technique Anding with x55) after that you can get the hash value through
String sha1 = "";
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(bcopy);
sha1 = byteToHex(crypt.digest());
private static String byteToHex(final byte[] hash)
{
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
at this point you have the hash value of the modified certificate, you can go now and extract the signature from the original certificate [ byte[] sig= x509cert.getSignature(); ] and decrypt the signature to get the hash value and compare it with the modified hash value, good luck ;)
If you look at RFC 5280, the cert has 3 fields:
tbsCertificate
signatureAlgorithm
signatureValue
The signatureValue is the very last item in the certificate.
I had a similar requirement. These are the steps I followed:
Have 1 certificate in .crt format (base-64 encoded) file. The cert is enclosed between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"
Edit the very last character of the certificate on the line just before the END CERTIFICATE line. Just add 1 to that character or reduce by 1. If the last character is x, make it either y or w.
This will change the signature in the certificate and the signature won't be valid anymore.