ebics-java / ebics-java-client

Java open source EBICS client - Support for French, German and Swiss banks
GNU Lesser General Public License v2.1
36 stars 35 forks source link

"org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher." when trying to upload file (Possible bug in saving Bank public keys) #16

Open gnikolaus opened 3 years ago

gnikolaus commented 3 years ago

Hi,

I am currently using this client to implement ebics functionality with Taunussparkasse in our application.

The Initialization worked without a problem, but after my account was activated and I tried to test it with some simple download operations no download request worked and the server always responded with the "Invalid Bank Key" error message.

After some searching I found this (supposed) solution, which changes the way the key digests were calculated:

https://sourceforge.net/p/ebics/discussion/general/thread/d4b38e20/#f11d/78fb/abb1/7de4/049d

This change in the getKeyDigest method seems to have fixed some issues for me, as with that change all download operations are working (HAC, STA etc).

However when I try to upload a file to the server I receive a "org.bouncycastle.crypto.DataLengthException: input too large for RSA cipher." Exception.

The Exception happens in the method "InitializationRequestElement.generateTransactionKey" when it tries to encrypt the nonce with the E002 key.

I am still trying to figure out what exactly is going on here, but my suspicion is that the Bank key is simply not saved correct and that the changes made in the getKeyDigest method only masked the problem.

gnikolaus commented 3 years ago

And as murpheys law dictates a short while after I decided to ask for help here I now found the solution myself:

The Problem is that the E002 public key modulus that Taunussparkasse returned in the response as a byte array started with a non-zero value, which was then interpreted by the BigInteger constructor as meaning the number has a negative signum and I created a key with a negative modulus.

A RSA key with a negativ modulo obviously cannot encrypt anything at all.

I fixed it by reverting the getKeyDigest method to its old form (where now removing the first byte of the modulo makes a lot more sense because it should always be 0) and used the BigInteger constructor when converting from bytearray to BigInteger that always treats the byte array as representing a positive number:

new BigInteger(1, orderData.getBankE002PublicKeyModulus())

edit: While i can only test this with my own Tanussparkasse bank, this bug should be fixed by changing the part where it saves the bank keys in KeyManagement.sendHPB to:

 else
    {
        e002PubKey = keystoreManager.getPublicKey(new BigInteger(orderData.getBankE002PublicKeyExponent()), new BigInteger(1, orderData.getBankE002PublicKeyModulus()));
        x002PubKey = keystoreManager.getPublicKey(new BigInteger(orderData.getBankX002PublicKeyExponent()), new BigInteger(1, orderData.getBankX002PublicKeyModulus()));
        session.getUser().getPartner().getBank().setBankKeys(e002PubKey, x002PubKey);
        session.getUser().getPartner().getBank().setDigests(KeyUtil.getKeyDigest(e002PubKey), KeyUtil.getKeyDigest(x002PubKey));
        //keystoreManager.setCertificateEntry(session.getBankID() + "-E002", new ByteArrayInputStream(orderData.getBankE002Certificate()));
        //keystoreManager.setCertificateEntry(session.getBankID() + "-X002", new ByteArrayInputStream(orderData.getBankX002Certificate()));
        keystoreManager.save(new FileOutputStream(path + File.separator + session.getBankID() + ".p12"));
    }
markkko commented 2 years ago

Thanks alot.

Some changes so code can work with other banks, too:

BigInteger bankE002PublicKeyModulus = new BigInteger(orderData.getBankE002PublicKeyModulus());
        if (bankE002PublicKeyModulus.compareTo(BigInteger.ZERO) < 0) {
          bankE002PublicKeyModulus = new BigInteger(1, orderData.getBankE002PublicKeyModulus());
        }
        BigInteger bankX002PublicKeyModulus = new BigInteger(orderData.getBankX002PublicKeyModulus());
        if (bankX002PublicKeyModulus.compareTo(BigInteger.ZERO) < 0) {
          bankX002PublicKeyModulus = new BigInteger(1, orderData.getBankX002PublicKeyModulus());
        }

        e002PubKey = keystoreManager.getPublicKey(new BigInteger(orderData.getBankE002PublicKeyExponent()), bankE002PublicKeyModulus);
        x002PubKey = keystoreManager.getPublicKey(new BigInteger(orderData.getBankX002PublicKeyExponent()), bankX002PublicKeyModulus);

There is probably some better way to do it, but it will work for now.

michaz commented 2 months ago

This problem is more general, because depending on how our banks implement their servers, they will currently get a similar exception when eating the public keys we send them in INI / HIA.

We send them an extra byte. In every public key. How did this ever work for anyone?

(I noticed this while looking at the base64 encoded public keys that other programs produce, and they always end in == while ours just end in =.)

Are all of your bank servers just very lenient? I mean, sure, the extra byte is just a leading zero, so if you interpret it as a number, it's equivalent, but if you interpret it as a byte[], then it's just an array that is too long.

I'm currently trying to find out why I cannot INI with my bank, and so far this is my best candidate.

Maybe #9 and #36 as well? I would guess bank servers usually don't know very well why they error, so anything vaguely related to INI not working could be because of this?

michaz commented 2 months ago

~I mean, in theory the key length we are supposed to send isn't specified, so we could say our key length is 2056 bit and since the math checks out the same, it should technically work.~

~But, Googling this, it looks like at least some libraries don't even support unusual key lengths, so~ we should expect at least some bank servers to error on this.

michaz commented 2 months ago

I think when dealing with those XML-encoded keys, wherever we convert a byte[] to a BigInteger, we are supposed to use e.g.

new BigInteger(1, orderData.getBankE002PublicKeyModulus());

and wherever we convert a BigInteger to a byte[], we should use

org.bouncycastle.util.BigIntegers.asUnsignedByteArray(publicKeyModulus);

I think this is always the right thing, only sometimes it makes a difference and sometimes it doesn't.

The BigInteger(byte[]) constructor, and BigInteger.toByteArray(), are never what we actually mean.

michaz commented 2 months ago

26 is the same thing, too.

michaz commented 2 months ago

Is it possible that the entire support for "German-style" EBICS, which doesn't use Certificates but "raw" numbers, never actually worked without fixing something locally? :-)

uwemaurer commented 2 months ago

I personally only used it with a Swiss Bank (ZKB) and I didn't run into this issue.

If we have a fix I am happy to merge it

michaz commented 2 months ago

Cool! I'll try and separate it from all the other stuff I tried, which probably isn't relevant.