dvsekhvalnov / jose-jwt

Ultimate Javascript Object Signing and Encryption (JOSE), JSON Web Token (JWT) and Json Web Keys (JWK) Implementation for .NET and .NET Core
MIT License
936 stars 184 forks source link

AES/GCM/NoPadding decryption failed: Tag mismatch! #179

Closed eirikmorken closed 2 years ago

eirikmorken commented 2 years ago

Attempting to sign and encrypt JWT token. When decrypting I get com.nimbusds.jose.JOSEException: AES/GCM/NoPadding decryption failed: Tag mismatch! ... Caused by javax.crypto.AEADBadTagException: Tag mismatch!

Decrytion is done outside my control, but decrytion has worked in the past with similar requests.

Using following code:

public string EncryptPayloadByKey(RequestDto requestDto)
    {
        var signCertificate = CertificateLocator.Find(_Konfig.RequestSignThumbprint);
        var rsaSignCertificateKeyPrivate = signCertificate.GetRSAPrivateKey();
        string req = "{\"nin\": \"01010000110\",\"atc\": [\"ABC01DE\",\"XYZ02AB\"]}";
        string token = JWT.Encode(req, rsaSignCertificateKeyPrivate, JwsAlgorithm.RS256);

        var encryptCertificate = CertificateLocator.Find(_Konfig.RequestEncryptThumbprint);
        var rsaEncryptCertificateKeyPublic = encryptCertificate.GetRSAPublicKey();
        var signThumbprintAsBytes = StringToByteArray(_Konfig.RequestSignThumbprint);
        var encryptThumbprint = StringToByteArray(_Konfig.RequestEncryptThumbprint256);
        var headers = new Dictionary<string, object>()
        {
            { "x5t#S256", Base64UrlEncoder.Encode(encryptThumbprint) }
        };
        return JWT.Encode(token, rsaEncryptCertificateKeyPublic, JweAlgorithm.RSA_OAEP_256, JweEncryption.A256GCM, extraHeaders: headers);
    }
dvsekhvalnov commented 2 years ago

Hi, @eirikmorken

sounds like key mismatch at first glance.

Library is cross-tested against nimbusds, should be no compatibility issues.

Also, latest v4 release supports JWK and you can use it for cert thumbprints too: https://github.com/dvsekhvalnov/jose-jwt#working-with-certificate-chains

eirikmorken commented 2 years ago

Thank you for your reply @dvsekhvalnov . Could you provide an example of how one would apply the key chain to sign and encrypt the payload? Do you still need to "JWT.Encode..." twice?

dvsekhvalnov commented 2 years ago

Hi @eirikmorken ,

  1. if you want to separately sign token and then encrypt it - you'd call JWT.Encode twice:

    var signed = Jose.JWT.Encode(payload, privateKey, JwsAlgorithm.ES256);
    var encrypted = Jose.JWT.Encode(signed, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
  2. What is key chain? Did you mean cert chain instead?

eirikmorken commented 2 years ago

Apologies, yes I meant certificate chain. How I would access my private key and public key after adding them to the key.SetX509Chain.

After simply adding both sertificates to the keychain and using them:

var signCertificate = CertificateLocator.Find(_Konfig.RequestSignThumbprint);
var encryptCertificate = CertificateLocator.Find(_Konfig.RequestEncryptThumbprint);
Jwk key = new Jwk();
key.SetX509Chain(new List<X509Certificate2> { encryptCertificate, signCertificate });

(RsaUsingSha alg expects key to be of RSA type or Jwk type with kty='RSA') ---> System.ArgumentException: RsaUsingSha alg expects key to be of RSA type or Jwk type with kty='RSA' at Jose.RsaUsingSha.Sign(Byte[] securedInput, Object key)

dvsekhvalnov commented 2 years ago

I think you trying to misuse x5c param. It is basically not a way to hold or transfer key material, it's just a "hint" to provide trust chain to receiver to let him ensure he can trust your key:

  1. your key is encoded to JWK normal way via (x,y or n,p,d or other params)
  2. your first value in x5c array is PKIX certificate containing the key from (1)
  3. all next values in array - are direct issuer (or signer) or previous value - cert chain up to root

When receiver wants to use JWK - it will:

  1. use key material directly to decode token
  2. can optionally use x5c chain to verify it trusts the key

In your example i doubt signCertificate and encryptCertificate are forming a chain, unlikely you sign one with another, more likely they are 2 independent certificates?

To load a chain from .p12 you can do something like:

X509Certificate2Collection collection = new X509Certificate2Collection();
collection.Import("chain.p12", "1", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.MachineKeySet);

// iterate over collection to get individual X509Certificate2 objects

Is it making it more clear?

dvsekhvalnov commented 2 years ago

Do you have example of what you trying to achieve? May be there is easier way.

eirikmorken commented 2 years ago

I assume the code in my initial post is rather close to what i want to achieve.

I want to sign a json string, with one certificate. Then encrypt the payload using another certificate. I wish to add an extra header with the thumbprint of the encryption certificate, to show which encryption certificate I used to the recepient.

Would be great to know whether the code I published would do what I just explained, or if I am missing something, or doing it in the wrong order. Still receiving Tag missmatch error.

dvsekhvalnov commented 2 years ago

Here you go, sender side, c#:

X509Certificate2 signingCert = SigningCert();
X509Certificate2 encryptionCert = EncryptionCert();

string req = "{\"nin\": \"01010000110\",\"atc\": [\"ABC01DE\",\"XYZ02AB\"]}";

string signed = JWT.Encode(req, signingCert.GetRSAPrivateKey(), JwsAlgorithm.RS256);

// eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuaW4iOiAiMDEwMTAwMDAxMTAiLCJhdGMiOiBbIkFCQzAxREUiLCJYWVowMkFCIl19.bdJe8Qq01ctbd4p_7DXIG-IWQ7cWuKqylv4GPv5e9Xr1NVBVSWy4v-54NfARfqT5gB8CTWhxP5a3CDqawdD_6LwjmVy_gbnQ2EPaAlrPOWXwTnj9A5indUcwH6XqH_iDd01aThsNgZoPhkzhZBxQoBodLYsV2GcqUOMW4mZLJOtyMMsOitVZDzVEL3jkarAn7ZKp-znxMtE5aRI3Dzh1p_42V9-xsloMwkk5RjRMyoQN0ZZg0YcjIdlQ-tEtnJBDOBwVaolLA5oSCrx4x324Icb49mGCOe4tVPuc7K4qSVYcS0yh4ES65LpmKSr6weZOA0POplN5oq-Ng6SN4S_n7g

Jwk encryptionKey = new Jwk(encryptionCert.GetRSAPublicKey(), isPrivate: false);
encryptionKey.SetX5TSha256(encryptionCert);

string encrypted = JWT.Encode(signed, encryptionKey, JweAlgorithm.RSA_OAEP_256, JweEncryption.A256GCM, extraHeaders: new Dictionary<string, object>{ { "x5t#S256", encryptionKey.X5TSha256 } });

// eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwieDV0I1MyNTYiOiJ1eUl1dlJyQ3FEQll6NVhJRE1rNXoxQ1Q1X0dwZWxfOEd5bElBRlp4UlZjIn0.aHzA5vITVFpV4RxBydx-zWFSdMc-piKkHBv-k7W6HJYf0U3Vr6clXvFiAqr-EKzlJHsNw5gOb4bL8IXuBh0ucrsVKDsYBKjlLdUeUcNO40Vb5fqjW16MI0OjiO2UUh6x0iIGyj_R6UjXJEwwbXIZUM2N2KnDMtKCGCrgOrr4-WX8hMMnDLQwYULrpKVxAgLXavterQSMHK_yvL6IWHInjHI2jPsQnZbBIQtlvVzj5G106iKNkM1Pkh6mtrwU_BvGsWr99Ew_qFyUumiUsSujBIkhfCOM9c8X7oUiNPsEJrTeVdQaUfeh4PA_Ru2ZeRWtUKDtMQ6aGmXoSTMG1BxORA.GOju4q62FC4hP1FA.era4U1vKPCZcBYW3QjPnbY4AoPA7nsCeQv-EnYwdgAt5KHpcnd2wbqVMZ5rDfTWp6_W3TlhnLSWV_F5SoBwrQqqfK_nDm-9xwNU-XTqyuI_imsn_dlctMaiJfObkgxMJBV8cNMOGW4_lqHF35AUq8JDbk4mTWXRjbFyiiX5RwArPNMRBG7ffsEoIkprnSf_7VYjID_kz8n9LR7Z1BW0Wmj8UysJC6AKRakzOZqvyOorN6POuqqt0Ejgn5_JI7eRxKtq90aT7sUhYl57GrEKPWKTykg1H6Lud4KXlKYmPLBLSwjQCXGLfRlXbyZtfUTjl0MMV48OdrzGV6NaI4Xdq7cEUEHHLUl3M__V75oHv3fAy-lbkg-mLaixbZT4NfQw4YMLK-PYc8-i6MBmDxrcGPivhfqO5cBscea-go6UCy3wiHTUnmLqwCCItJCxkPxsGldya0gjMDZywnKGw6_qH1hVPMXnPX7l9fN_E28CD0Zlpbxtg8FzEiHFfZ_iPXpUnM9CehGsmqJHSht9lXw7_gKz77mZR6X_jghhk3TzgAoMnn5BYN-CsyyEuS9LITjTOB-OLX762aWkxTv5Bsvkg-g.OwxDrYf36fox0556IOwxgQ

Receiver side, java, nimbusds library:

KeyStore ks = KeyStore.getInstance("pkcs12");
FileInputStream fis = new FileInputStream("encryption.p12");
ks.load(fis, "somepassword".toCharArray());
fis.close();
RSAPrivateKey privateKey = (RSAPrivateKey) ks.getKey("jwt", "somepassword".toCharArray());

String token = "eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJlbmMiOiJBMjU2R0NNIiwieDV0I1MyNTYiOiJ1eUl1dlJyQ3FEQll6NVhJRE1rNXoxQ1Q1X0dwZWxfOEd5bElBRlp4UlZjIn0.aHzA5vITVFpV4RxBydx-zWFSdMc-piKkHBv-k7W6HJYf0U3Vr6clXvFiAqr-EKzlJHsNw5gOb4bL8IXuBh0ucrsVKDsYBKjlLdUeUcNO40Vb5fqjW16MI0OjiO2UUh6x0iIGyj_R6UjXJEwwbXIZUM2N2KnDMtKCGCrgOrr4-WX8hMMnDLQwYULrpKVxAgLXavterQSMHK_yvL6IWHInjHI2jPsQnZbBIQtlvVzj5G106iKNkM1Pkh6mtrwU_BvGsWr99Ew_qFyUumiUsSujBIkhfCOM9c8X7oUiNPsEJrTeVdQaUfeh4PA_Ru2ZeRWtUKDtMQ6aGmXoSTMG1BxORA.GOju4q62FC4hP1FA.era4U1vKPCZcBYW3QjPnbY4AoPA7nsCeQv-EnYwdgAt5KHpcnd2wbqVMZ5rDfTWp6_W3TlhnLSWV_F5SoBwrQqqfK_nDm-9xwNU-XTqyuI_imsn_dlctMaiJfObkgxMJBV8cNMOGW4_lqHF35AUq8JDbk4mTWXRjbFyiiX5RwArPNMRBG7ffsEoIkprnSf_7VYjID_kz8n9LR7Z1BW0Wmj8UysJC6AKRakzOZqvyOorN6POuqqt0Ejgn5_JI7eRxKtq90aT7sUhYl57GrEKPWKTykg1H6Lud4KXlKYmPLBLSwjQCXGLfRlXbyZtfUTjl0MMV48OdrzGV6NaI4Xdq7cEUEHHLUl3M__V75oHv3fAy-lbkg-mLaixbZT4NfQw4YMLK-PYc8-i6MBmDxrcGPivhfqO5cBscea-go6UCy3wiHTUnmLqwCCItJCxkPxsGldya0gjMDZywnKGw6_qH1hVPMXnPX7l9fN_E28CD0Zlpbxtg8FzEiHFfZ_iPXpUnM9CehGsmqJHSht9lXw7_gKz77mZR6X_jghhk3TzgAoMnn5BYN-CsyyEuS9LITjTOB-OLX762aWkxTv5Bsvkg-g.OwxDrYf36fox0556IOwxgQ";

EncryptedJWT jwt = EncryptedJWT.parse(token);

jwt.decrypt(new RSADecrypter(privateKey));

JWEHeader header = jwt.getHeader();

String x5t = header.getX509CertSHA256Thumbprint().toString();
String signedPayload = jwt.getPayload().toString();

Working just fine, you can run yourself.

eirikmorken commented 2 years ago

Thank you! You have been truuly helpful.