Java twitter OAuth, Failed to validate oauth signature - java

I am writing a code for oauth with twitter, and i have 401 error code and "Failed to validate oauth signature and token" response from twitter when i fetch post request to https://api.twitter.com/oauth/request_token. This is my data which i have:
Consumer/api key - ffo9MLdRF8XOd9DKB0HeA
Consumer/api secret - fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs
Url callback - https://oauth.vk.com/blank.html
My steps:
1.Prepared string for making signature
POST&https%3A%2F%2Fapi.twitter.com%2Foauth%2Frequest_token&oauth_callback%3Dhttps%3A%2F%2Foauth.vk.com%2Fblank.html%26oauth_consumer_key%3Dffo9MLdRF8XOd9DKB0HeA%26oauth_nonce%3Dfb0e9383f0c84326a124dd4ccfddd2d2%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1393951866%26oauth_version%3D1.0
2.Created signature qQwIvFao9yeIQpi9ouz0oFi7/v8= by code:
public String calculateSign(String stringToEncode, String secret) throws Exception{
byte[] keyBytes = secret.getBytes();
byte[] text = stringToEncode.getBytes();
SecretKey secretKey = new SecretKeySpec(keyBytes, "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKey);
byte[] encoded = mac.doFinal(text);
return new String(Base64.encode(encoded, Base64.DEFAULT)).trim();
}
3.Final Authorization header (with escaped quotes):
OAuth oauth_nonce="fb0e9383f0c84326a124dd4ccfddd2d2", oauth_callback="https%3A%2F%2Foauth.vk.com%2Fblank.html", oauth_signature_method="HMAC-SHA1", oauth_timestamp="1393951866", oauth_consumer_key="ffo9MLdRF8XOd9DKB0HeA", oauth_signature="qQwIvFao9yeIQpi9ouz0oFi7%2Fv8%3D", oauth_version="1.0"
Question to people who maybe knows how to work with twitter. What is wrong with my data?
I can provide more code, but i think it is not useful.
P.S. - i provided working credentials, i will change them after 3 - 4 hours.
UPD: This is my key which used in calculateSign function
fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs%26

There is an error in your signature base string. The callback url should be double encoded so it should be:
... &oauth_callback%3Dhttps%253A%252F%252Foauth.vk.com%252Fblank.html ...
Also you do not need to percent encode the ampersand ('&') character in the signing key, so you should use:
fUJtvIpujTslQOlVbZY6QU8cNEMdwoxzTG1gh93SUgs&
You can check your signature using the online OAuth Test Console.

Related

AWS S3 Java SDK - Downloading an encrypted GetObject by Stream of a Selling Partner (SP) API url

I started using SellingPartner (SP) recently and I am kind confused how they provide us S3 reports to download.
When I fetch a Report Document from SP API I get this return (omitted):
GetReportDocumentResponse class:
{
"payload": {
"reportDocumentId": "amzn1.tortuga.3.OMITTED.OMITTED",
"url": "https://tortuga-prod-na.s3-external-1.amazonaws.com/%2FOMITED/amzn1.tortuga.3.OMITTED.OMITTED?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20201025T163212Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=OMITED%2F20201025%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=OMITED",
"encryptionDetails": {
"standard": "AES",
"initializationVector": "OMITED==",
"key": "+OMITED="
},
"compressionAlgorithm": null
},
"errors": null
}
If I copy/paste the payload.url directly in my browser, it downloads an encrypted document, which looks fine (I couldn't decrypt it though, snippet in the end).
I am trying to download using the AWS S3 Java SDK and I keep getting software.amazon.awssdk.services.s3.model.S3Exception: Access Denied
I have this snippet:
public String getReportFile(String reportDocumentId) throws IOException {
GetReportDocumentResponse response = getReport(reportDocumentId);
ReportDocumentEncryptionDetails encryptionDetails = response.getPayload().getEncryptionDetails();
GetObjectRequest request =
GetObjectRequest.builder()
.key(reportDocumentId)
.bucket("tortuga-prod-na") //hardcoding here, thats the bucket on the URL, right?
.sseCustomerAlgorithm(encryptionDetails.getStandard())
.sseCustomerKey(encryptionDetails.getKey())
// .sseCustomerKeyMD5() should I apply it? Is that the Initialization Vector field?
.build();
//I tried both without Credentials, and using accessKey and secretKey from my personal account, not sure if should be another one related to the URL, what should I use for credentials if the URL works fine in my browser?
StaticCredentialsProvider credentialsProvider =
StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey));
BufferedReader br =
new BufferedReader(
new InputStreamReader(
S3Client.builder()
.credentialsProvider(credentialsProvider)
.region(Region.US_EAST_1)
.build()
.getObject(request)));
My end goal is to download this file in chunks (as it may have over 500mb) and process a few hundred lines at a time. Would that be possible if it's encrypted? I would like to download it already decrypted and be able to process it in chunks.
I wonder how to make the same request using S3Client like the URL coming from the JSON. Do we have a way to just paste a URL on S3Client, include the encryption settings and make a call?
About the downloaded file from the browser, I tried to decrypt it doing this:
byte[] bytes = FileUtils.readFileToByteArray(new File("encrypted_file"));
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5PADDING");
SecretKeySpec secretKey = new SecretKeySpec(Base64.getDecoder().decode(<payload.encryptionDetails.key String value>), "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
System.out.println(new String(cipher.doFinal(bytes)));
which throws exception:
Exception in thread "main" javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
Thanks in advance.
If I copy/paste the payload.url directly in my browser, it downloads an encrypted document, which looks fine (I couldn't decrypt it though, snippet in the end).
It means the object is not using SSE-C, otherwise you wouldn't be able to download it. Seems the content is encrypted in the client side (outside s3 api) and the encrypted content is uploaded as a normal object. So please check in your code, where the content is uploaded, for encryption parameters .
If you are able to directly download the object from the browser, then just download the content as a normal object.
Note: proper use of the AWS S3 Client Side Encryption
About the downloaded file from the browser, I tried to decrypt it doing this
wherever you got this code, please do not use it. Just using the AES/ECB/PKCS5PADDING mode is not safe.
Using the IV (initializationVector) implies using different encryption mode of operation. You have to find out which is it is from the code or service which encrypts the content.
private static final String SYMMETRIC_KEY_ALG = "AES";
// find out the correct value, could be AES/CBC/PKCS5Padding
private static final String SYMMETRIC_CIPHER_NAME = "???";
IvParameterSpec ivParamSpec = new IvParameterSpec(encryptionParams.getIv());
SecretKey symmetricKey = new SecretKeySpec(encryptionParams.getKey(), SYMMETRIC_KEY_ALG);
Cipher cipher = Cipher.getInstance(SYMMETRIC_CIPHER_NAME);
cipher.init(Cipher.DECRYPT_MODE, symmetricKey, ivParamSpec);
byte[] decrypted = cipher.doFinal(encryptionParams.getCiphertext());
However - you really need to find out how the content is encrypted. It could be possible the aes-gcm mode is used and then part of the ciphertext can be an authentication hash. So here you should not make assumptions and find the real data.

JWT decrypts, but throws a mac check failed error

I have a jhipster spring boot application which accepts a token generated by a third party which has been encrypted with our public key. I have a JWTFilter which decrypts the token using our private key and creates an authentication object which is stored in the security context. Once execution reaches the controller, I intend to pull the username and password from the security context so I can make API calls back to the third party application.
This is working to some degree in our integration environment where the third-party has a link to a running instance of our application. To test locally, I am hitting the link in the integration environment, and copying the token. Then I make a request from Postman to an instance of our application I'm running locally with the token added in the headers, the same as our js client would.
I am using "com.nimbusds:nimbus-jose-jwt:4.23" for decryption, and I am getting a 'MAC check failed' error. I can change the value of macCheckPassed to true in the debugger, and the decryption will complete, allowing me to see the claims and load them into the security context. However, some other filter is catching my hack, and the request gets rejected with an authorization error.
public static byte[] decryptAuthenticated(final SecretKey secretKey,
final byte[] iv,
final byte[] cipherText,
final byte[] aad,
final byte[] authTag,
final Provider ceProvider,
final Provider macProvider)
throws JOSEException {
// Extract MAC + AES/CBC keys from input secret key
CompositeKey compositeKey = new CompositeKey(secretKey);
// AAD length to 8 byte array
byte[] al = AAD.computeLength(aad);
// Check MAC
int hmacInputLength = aad.length + iv.length + cipherText.length + al.length;
byte[] hmacInput = ByteBuffer.allocate(hmacInputLength).
put(aad).
put(iv).
put(cipherText).
put(al).
array();
byte[] hmac = HMAC.compute(compositeKey.getMACKey(), hmacInput, macProvider);
byte[] expectedAuthTag = Arrays.copyOf(hmac, compositeKey.getTruncatedMACByteLength());
boolean macCheckPassed = true;
if (! ConstantTimeUtils.areEqual(expectedAuthTag, authTag)) {
// Thwart timing attacks by delaying exception until after decryption
macCheckPassed = false;
}
byte[] plainText = decrypt(compositeKey.getAESKey(), iv, cipherText, ceProvider);
if (! macCheckPassed) {
throw new JOSEException("MAC check failed");
}
return plainText;
}
What is this MAC check? I thought it had to do with the origin of the token. Something along the lines of the token being encrypted with the MAC id of the source system, which throws an error when it doesn't synch up with my current host.
What other filter would be rejecting the request if the decryption passed? Is there some other flag I'm supposed to be setting so the framework will honor the request?
JWE spec mandates authenticated encryption, to ensure the plain text is not just encrypted, but also protected against tampering. To ensure that an HMAC is applied after the content encryption.
The "Mac check failed" error can mean two things - the library that produced the original JWE / JWT has applied the HMAC incorrectly, or, the JWE / JWT was modified in transit.

Verify Signature of SAML Response

I'm trying to follow the following spec to verify the signature of the SAML response: https://www.w3.org/TR/xmldsig-core/#sec-PKCS1
Here is my workflow: I get the SAML Response. I get rid of the signature envelope, I canonicalize it, I check the digest and then I check the signature. I was able to successfully calculate SHA1 digest of the transformed SAML response and verify it. However, RSA-SHA1 signature checking still eludes me.
SAML Response included signature method algorithm: http://www.w3.org/2000/09/xmldsig#rsa-sha1
I have this method to check the signature:
public static boolean verifySignature(String signatureType, PublicKey publicKey, byte[] contentBytes, byte[] sigBytes) {
try {
Signature sig = Signature.getInstance(signatureType);
sig.initVerify(publicKey);
sig.update(contentBytes);
return sig.verify(sigBytes);
} catch (Exception e) {
Logger.log(e);
return false;
}
}
I do something like this to call it:
String publicKeyStr = "MIIDNDCCAhwCCQCEk14scLMSGjANBgkqhkiG9w0BAQsFADBcMR8wHQYDVQQDDBZhdXRoLnJldHJvZmljaWVuY3kuY29tMQ4wDAYDVQQKDAVSZXRybzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24wHhcNMTYwMzAyMTU1NTQ4WhcNMTcwMzAyMTU1NTQ4WjBcMR8wHQYDVQQDDBZhdXRoLnJldHJvZmljaWVuY3kuY29tMQ4wDAYDVQQKDAVSZXRybzELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAk1BMQ8wDQYDVQQHDAZCb3N0b24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDwUlZ+qOA61kYh0MGHKTe4JtSL1qJVT/8/i6sWFqbAiGdV33pxSm8iBpqLNPG+fr8aZKugik8mN3y0kZEDwD+EATXGUpqT1v1R6Wr4rRUwsC0wl6d6EiWfdSsu66T11hdrugXF/psjyy4mmvIMMU4RlSaZmT2+gpe57IWPO522Y1HqbuvSvKJgjdyrlhIKLmw7HvZ1ZrQ1j01Hd3/rdK41+zWpSCLuSellHowiDmOzNnyFpTw+SZA0GKE+cm3BOTj/cM36GR2XJd0kIRqj1qqu29cGMlBjQ3cMleG6HoHR9oPNFwYtW1fDTHG6MnxRXYCfZiPeBdX3eBDSuz7GQAv5AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEuQEPhpWdAGg46PSbE20bNt4YH+PjiIzPaXzN5UmpJjyl2kbR18HYXZHEtAXc+ItrYnK+oe+6FhwJ9aiwlrf1/p+U61iyvA9/HWzyW4vflaLWRQ8FxNhAJiVu57IQObjZ13EQfu1O8RK4wzNUVJhOz+pp9toqTHn8wmUPApzqMIl0KpDCPy1TNrwItaiy/gzWQngBMgIcrDeycR8wK2EH6txT8BzI+aqdIc3gBXsn/kS90lelbvYreXqF7VHoMs8fVEPN5fTRjqT9oZb60b1DxcniiTEknZtqprYuMpgEzpOHrzmlbCoNU2yBHuNTQWBz3lG+XtSMtgh16v5Vm48ag=";
PublicKey publicKey = Security.getPublicKey(publicKeyStr); // uses Certificate class
String sig = "QfAcRJM2P65JucyBpqn6j48/zd1oSLPBJ0lYI1grH5/xenwBEh0D4Eh0S1J3535OUmldpW7D+G7DW6eAT5N8TdoqUvMXuIAUpFHHDR45KZykPXJPUtli+z2rwlCCHypZWnniT/wrcQYdpp1zzNJBBtKdkaqQg0NMktPSQ/0ti+ruMI3qwfTaL9kDQ3Zyi/a2J3RCAPA0RfviLnDLlid0PthiV1NEbs0AvnguDi57fWXAILk0L1Cx20sliQckxlFQ9u4OaHeMscXdjh3SfESkM9m0Y9PppisZWTrCYzGmvDwsZTCBPD3f/ZFIit+Smgh2fi1u8/gZq0yizPyacR3Y/A==";
String newXMLToOperateOn = "<saml2:Assertion xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema" ID="_420cc1a25a3890bd5df1f4c04bd7e986" ...";
boolean sign = Security.verifySignature("SHA1withRSA", pk, newXMLToOperateOn.getBytes(), Util.base64DecodeAsBytes(sig));
// SIGN IS FALSE!!!!
newXMLToOperateOn is the correct canonicalized string XML of the SAMLResponse. This is what I use to generate the correct SHA1 digest. Security.verify() works correctly since I use to for OpenID SSO checking and that works.
I also tried to substitute the data to check instead of the entire XML to the bytes of the SHA1 digest, but didn't work either. E.G. verify(algo, pk, sha1Digest.getBytes(), sig.getBytes().
Can someone tell me if there is something that I'm doing horribly wrong when checking the signature? The spec linked above tells me the signing method does this:
CRYPT (PAD (ASN.1 (OID, DIGEST (data))))
Which doesn't help me a ton when I'm verifying the signature. I cannot recreate the signature since I can't find what private key they use in their examples.
Thanks for any help.
You only need to verify the 'SignedInfo' portion of the XML

Parser exception in JWT when encryption and signing is enabled

I'm new to JWT, learning through standalone code to understand JWT API's. Below code sign and encrypt JWT token from sender's end and it get validated at receiver's end.
Library: JOSE 0.4.1
package com.one00bytes.jwt;
public class JWTSignEncryption {
public static void main(String[] args) throws Exception {
/***************************SENDER'S END ***********************************/
JwtClaims claims = new JwtClaims();
claims.setAudience("Admins");
claims.setIssuer("CA");
claims.setSubject("users");
claims.setClaim("email", "users#test.com");
claims.setClaim("Country", "Antartica");
System.out.println(claims.toJson());
//SIGNING
RsaJsonWebKey jsonSignKey = RsaJwkGenerator.generateJwk(2048);
JsonWebSignature jws = new JsonWebSignature();
jws.setKey(jsonSignKey.getPrivateKey());
jws.setPayload(claims.toJson());
jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA512);
String signedJwt = jws.getCompactSerialization();
System.out.println("Signed ::" + signedJwt);
//ENCRYPTING
RsaJsonWebKey keyEncrypt = RsaJwkGenerator.generateJwk(2048);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey contentEncryptKey = keyGen.generateKey();
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setContentEncryptionKey(contentEncryptKey.getEncoded());
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
SecureRandom iv = SecureRandom.getInstance("SHA1PRNG");
jwe.setIv(iv.generateSeed(32));
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);
/***************************RECEIVER'S END ***********************************/
JwtConsumer consumer = new JwtConsumerBuilder()
.setExpectedAudience("Admins")
.setExpectedIssuer("CA")
.setRequireSubject()
.setDecryptionKey(keyEncrypt.getPrivateKey())
.setVerificationKey(jsonSignKey.getPublicKey())
.build();
JwtClaims receivedClaims = consumer.processToClaims(encryptedJwt);
System.out.println("SUCESS :: JWT Validation :: " + receivedClaims);
}
}
Observing below exception when running this program:
Exception in thread "main" org.jose4j.jwt.consumer.InvalidJwtException: Unable to parse JWT Claim Set JSON: eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiaWF0IjoxNDM0NTM0MDgxLCJleHAiOjE0MzQ1MzQ2ODEsImp0aSI6IjJxUUpuMDVGY3RrLWF1VG1vVktuWXciLCJuYmYiOjE0MzQ1MzM5NjEsImVtYWlsIjoidXNlcnNAMTAwYnl0ZXMuY29tIiwiQ291bnRyeSI6IkFudGFydGljYSIsImhvYmJpZXMiOlsiQmxvZ2dpbmciLCJQbGF5aW5nIGNhcmRzIiwiR2FtZXMiXX0.soY_5Hbam569I-CnUW1F4GWdaqprh-XAOtAMOcb7zZSiRcIhXYUdJjEslrDbwphAP135SvmoXO4nVaVmo-d8oWREFYUeXEDzHbrqHNp7pp5pH6hGTJ5C4uE1UVzZ4bis3g_KEgZvEn31NnV4RcU_oRn2Q4inkrTlYKY-juEtCmpPQ0sSP4GiDbwVIfCj-kxZsKh_i9n28SSK890K3DIGiFWOUDwrnY4Yfr1UffsUS9ovyhtqrOcN4YsJR4XzGPaLehlR-qD7eOdAdmVb8RDtGKufNuCd7Q9OFfeKzBmGITHsvd6IPVYLLCfSCzO6PqQSIzkupl5D6HqoOqID8JZLxA
at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:50)
at org.jose4j.jwt.JwtClaims.parse(JwtClaims.java:56)
at org.jose4j.jwt.consumer.JwtConsumer.process(JwtConsumer.java:267)
at org.jose4j.jwt.consumer.JwtConsumer.processToClaims(JwtConsumer.java:115)
at com.one00bytes.jwt.JWTSignEncryption.main(JWTSignEncryption.java:76)
Caused by: org.jose4j.lang.JoseException: Parsing error: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:66)
at org.jose4j.jwt.JwtClaims.<init>(JwtClaims.java:45)
... 4 more
Caused by: org.jose4j.json.internal.json_simple.parser.ParseException: Unexpected character (e) at position 0.
at org.jose4j.json.internal.json_simple.parser.Yylex.yylex(Yylex.java:612)
at org.jose4j.json.internal.json_simple.parser.JSONParser.nextToken(JSONParser.java:269)
at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:118)
at org.jose4j.json.internal.json_simple.parser.JSONParser.parse(JSONParser.java:81)
at org.jose4j.json.JsonUtil.parseJson(JsonUtil.java:62)
... 5 more
Signed JWT
eyJhbGciOiJSUzUxMiJ9.eyJhdWQiOiJBZG1pbnMiLCJpc3MiOiJDQSIsInN1YiI6InVzZXJzIiwiZW1haWwiOiJ1c2Vyc0B0ZXN0LmNvbSIsIkNvdW50cnkiOiJBbnRhcnRpY2EifQ.5Xu7v2MosIQmtAOlqfM2PE9eJeT0iZzL9x6RIvqx_PAHKer0ylo-0wT9eON_qX1H_QZekTWMf8ok4fxdZNv2KP_AkNqSKLXYJ65TjPnfcX8-dooDJM9txfRWOFqJWx4yj4CTMPNR6rNhizkC9jUaLisPIjogc_a_61qTSnvHXFnuaYmkovN2Y3WfuXjhUZCH98hodRL_ATg1_SpO0bPb7_N1Z76yrcv0RYQan0Y5kICWYdhHlk8Dw6I2fLMVsl3HiYiRq4XBJE8AY_g742Uq5kTS62PKohg3IjfRa-g2rjgKo1XW2sRLVc7vnns2L3TqESo5vgvorTjKnCTQKuHpIg
Encrypted JWT
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIn0.lZ2nqCeiPzsPmJShsrDD3uA55-06A649CMtwOyuY9nNzMtUGyzV-G8qc4w4ui1uWrtzypBs5Eyq4GfjnTtVHbcDVkS1HVc3tfxNAPY8dfjVrWNz59HyKt4bCjBdqqhBOdZezLtWB9aoWIwZoHLf4D8aUcVUtDsFELVcScmiQNtzHwvpDHZb4oxRfPl-OuOTkKA23C8lnnDMO1KUy8ZXHD4p0jQKAcaV877gYm8NbHDwOBEf-ItWJOGx2jV60apWd0hKqwfFR2QKD9wmGgXpbFZ08ro7X2fj8rTgKWhDgoBT_JVZdVFhVI4T4RLRDrCJqkyeciXhLm7W_xNhWBXAMrA.94SuB596ZLuUtw53wrofwN5jZXfT5f-ZarJhQc9Mj0M.0Ow5DXfilYX3ty49H4lNMNPljlWAFASc49zljhRSIIUSlmUHLZo0SAezn-n_FdxexAIYLk_FtRgnkMHDEyxJ1V1yHhqa1Jvdb36lTYyptqCJhMkOV1XGn58L4Z9QQmdrIZnn5iHxZ9-N1Jfjs0eoKiLBgR9O7ZEcs7QrWZVT6n_HrGrIloYQu_lFgmk5O7k47_15CVXaFqIohpHXETejoHEwjQj-iTToNRaHWNFAKvlpUBz4mUgk9RSIQCxK1GxxS8wxP44w5G4HdOIjFNwTsRDXeSZy0mU9zTNUCmDEUT9MFESfmVU1nPurdT-VoiPvVklbJZW8Sas0hWgqQkdQdP35nFY1sjCgfMB9iYUeEU-TCE219wkm1XXrLJwLEYZclL_4ckl4zExo2wb3Czwd8f5iO9fBQQWZ4mdwThK4VtZaPs1JEkxwGLI0SHA8Jr-e2PsDrkGEnxs74FsJ5MKluU2ZKvKcGXyQPaaTRa0ecJLD5-YYBuTtxOnU3gM_5aZm97pd_wiPk_h81r5aiwjSfRF3Ihxp37KNPfNOMJoA9xe2F51m1AvmjrOUgSM156LwmFyJFebVfarb9NPtJ_q1wU891sCu2Vmv520BR4QfIc-ayIwTVxLgZSN-BP7PhEJb_x8.XhZpINBxRdFFEgwPTcAgJg
Same code runs seperately for signing and encryption, but didn't run, if I include both.
Please help me to understand what I'm doing wrong.
Thanks In Advance
For a nested JWT (i.e. JWE[JWS[JSON Claims]] which is what you're dong), the "cty" (content type) header of the the JWE is supposed to have a value of "JWT" to indicate that the payload is itself a JWT. The definition of"cty" in the JWT spec, RFC 7519, talks about that a bit more. It helps the consumer/receiver know how to process things.
The exception you're seeing is the result of the library trying to parse the JWS compact serialization, which is the payload of the JWE, as JSON.
According to spec, you really should set the cty header to "JWT" on the JWE, which indicates that the JWE payload is itself a JWT. That can be done with jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT"); or jwe.setContentTypeHeaderValue("JWT") as of v0.4.2.
You can also tell the JwtConsumer to be a bit more liberal in processing and make a best effort when the cty header isn’t present and the payload doesn’t parse as JSON but can be parsed into a JOSE object. This can be done with .setEnableLiberalContentTypeHandling() on the JwtConsumerBuilder.
Couple more observations:
You don't need to set the content encryption key or the IV on the JWE. The library uses a secure random to generate those for you with the appropriate lengths. So the following should be sufficent,
JsonWebEncryption jwe = new JsonWebEncryption();
jwe.setHeader(HeaderParameterNames.CONTENT_TYPE, "JWT");
jwe.setKey(keyEncrypt.getPublicKey());
jwe.setPayload(signedJwt);
jwe.setAlgorithmHeaderValue(KeyManagementAlgorithmIdentifiers.RSA_OAEP_256);
jwe.setEncryptionMethodHeaderParameter(ContentEncryptionAlgorithmIdentifiers.AES_256_GCM);
String encryptedJwt = jwe.getCompactSerialization();
System.out.println("Encrypted ::" + encryptedJwt);
Also, I'm guessing from the use of RSA_OAEP_256 and AES_256_GCM that you're using Bouncy Castle. I'd strongly recommend upgrading to jose4j 0.4.4 due to a security vulnerability that was identified when using the library with the Bouncy Castle security provider. See the Release Notes on v 0.4.4 for more info https://bitbucket.org/b_c/jose4j/wiki/Release%20Notes#!jose4j-044-july-24-2015
A JWT has as its payload, or Message, the UTF-8 representation of the Claims Set. From RFC 7519:
Let the Message be the octets of the UTF-8 representation of the
JWT Claims Set.
This is the case for both signed JWTs (which are JWS objects), and encrypted JWTs (using JWE):
if the JWT is a JWE, create a JWE using the Message as
the plaintext for the JWE; all steps specified in JWE for
creating a JWE MUST be followed.
Accordingly, for verification of an encrypted JWT, the payload is interpreted as a Claims Set:
Else, if the JWT is a JWE, follow the steps specified in
JWE for validating a JWE. Let the Message be the resulting
plaintext.
The mistake you have made in your program is using the serialization of the signed JWT as the payload of a JWE, but then attempting to process the resulting object as n encrypted JWE. Accordingly, the library attempts to interpret a serialized signed JWT (the JWS Flattened Serialization) as a serialized JWT Claims Set (a JSON object). This explains the exception you are getting:
Caused by: org.jose4j.lang.JoseException: Parsing error:
org.jose4j.json.internal.json_simple.parser.ParseException:
Unexpected character (e) at position 0.
It appears that you are attempting to produce a JWT that is both encrypted and authenticated. All JWE algorithms are authenticated encryption algorithms, so there is no need to do anything with JWS to achieve this - an encrypted JWT is sufficient.

Time-sensitive encrypt / decrypt bug from PHP to Java, via URL encoding

I have an access-controlled PHP system that HTTP redirects clients to a Java-based reporting system with a unique security model (it's awful). To get around the report security model I'm using a Tomcat filter to validate all requests before they reach the reporting system. I'm using an encrypted token passed from PHP to Java which tells the reporting system who the client is. The filter checks the requested report name against a restricted list and returns a 403 if the client's role is insufficient.
The encrypted token stores a timestamp and the user's role, e.g.
1365549482|SysAdmin
When encrypted it looks something like this
vSEFgBYd30Ik5p4PZlG968cvdg==
The PHP system acts as a proxy for all reporting requests. When a user requests a report the request goes to PHP, which generates an encrypted token, URL encodes it, then appends it to the report URL and makes the GET request to the reporting system. My Java filter decrypts the token, pulls it apart, and figures out what to do.
9 times out of 10 this is fine, but occasionally the token cannot be properly decrypted. The above (unencrypted) example is converted to something like this
1365549482q??YZ7
And everything goes wrong.
I'm a bit out of my depth with encryption, decryption, and the particulars of character encoding, but unfortunately I'm the only developer available to work on this. Any thoughts at all on what might be going wrong here would be hugely appreciated. I don't expect any big code changes as it works most of the time, but there is clearly a time-sensitive component in the mix that I don't understand. Code snippets below
EDIT
I've spent a while debugging this now and it just got stranger. I wrote a small Java program to request a token from PHP via HTTP GET. The PHP script returns the same (URL-encoded) value that is passed to Java via a URL parameter in the normal workflow. The Java program decodes and decrypts this in the same way as the code snippet below and checks the result. Over thousands of iterations (so far, and counting) it is working as expected. However, while this test is going on I can see the same failures happening in the filter's log file.
Whatever's causing this intermittent problem is seemingly related the Java class being a Tomcat filter or the data being passed by URL via Tomcat. Does this give anyone a hint as to what could be going on here? I'm so very confused right now.
PHP
$presentAsSeconds = time();
$message = strval($presentAsSeconds + Configure::read('Reporting.Authentication.ExpireInSeconds')) . '|' . $userDetails['role'];
return base64_encode(
mcrypt_encrypt(
MCRYPT_RIJNDAEL_128,
md5(Configure::read('Reporting.Authentication.Key')), // matches "the key" in Java function
$message,
MCRYPT_MODE_CFB,
Configure::read('Reporting.Authentication.IVector') // matches "the vector" in Java function
)
);
Java
private String decrypt(String initial) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(md5("the key").getBytes("UTF-8"), "AES");
IvParameterSpec initialVector = new IvParameterSpec("the vector".getBytes("UTF-8"));
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, initialVector);
byte[] encryptedByteArray = (new org.apache.commons.codec.binary.Base64()).decode(initial.getBytes("UTF-8"));
byte[] decryptedByteArray = cipher.doFinal(encryptedByteArray);
return (new String(decryptedByteArray, "UTF8"));
}
private String md5(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(input.getBytes("UTF-8"));
BigInteger number = new BigInteger(1, messageDigest);
return number.toString(16);
}
The problem may be with your getBytes() methods - these use the default system character encoding, which is NOT the same across every JVM. Use getBytes("UTF-8") instead.
I think the issue is in how you've used the mcrypt library in PHP and then you're base64 encoding the encrypted data? We've definitily had some issues doing something similar and dropped the base64 encode and it worked after that.
Can you use another method to pass the token instead of the url? Such as a cookie or auth header?
Here's a snippet of my encryption/decryption in PHP (I'm not great with Java) because I think your mcrypt isn't right.
ENCODING:
$userObjectJson = json_encode($this);
//encrypt the user session object
$mcrypt = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CFB, '');
$_SESSION['iv'] = mcrypt_create_iv(mcrypt_enc_get_iv_size($mcrypt), MCRYPT_RAND);
$keySize = mcrypt_enc_get_key_size($mcrypt);
$key = substr(MYAWESOME_KEY, 0, $keySize);
mcrypt_generic_init($mcrypt, $key, $_SESSION['iv']);
$_SESSION['user'] = mcrypt_generic($mcrypt, $userObjectJson);
mcrypt_generic_deinit($mcrypt);
mcrypt_module_close($mcrypt);
DECODING:
//decrypt the user session object
$mcrypt = mcrypt_module_open(MCRYPT_3DES, '', MCRYPT_MODE_CFB, '');
$keySize = mcrypt_enc_get_key_size($mcrypt);
$key = substr(MYAWESOME_KEY, 0, $keySize);
mcrypt_generic_init($mcrypt, $key, $_SESSION['iv']);
$userObjectJson = mdecrypt_generic($mcrypt, $_SESSION['user']);
mcrypt_generic_deinit($mcrypt);
mcrypt_module_close($mcrypt);

Categories

Resources