All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.adorsys.xs2a.adapter.deutschebank.DeutscheBankPsuPasswordEncryptionService Maven / Gradle / Ivy

There is a newer version: 0.1.18-alpha-release-candidate
Show newest version
package de.adorsys.xs2a.adapter.deutschebank;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.util.Base64;
import de.adorsys.xs2a.adapter.api.PropertyUtil;
import de.adorsys.xs2a.adapter.api.PsuPasswordEncryptionService;
import de.adorsys.xs2a.adapter.api.exception.PsuPasswordEncodingException;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/*
 * Deutsche bank password encryption flow is based on JWE (RFC7516 - https://tools.ietf.org/html/rfc7516)
 */
// TODO adjust this logic on the additional information from Deutsche bank about the certificates API
public class DeutscheBankPsuPasswordEncryptionService implements PsuPasswordEncryptionService {
    private static final String URL_TO_CERTIFICATE
        = PropertyUtil.readProperty("deutsche-bank.aspsp.certificate.url", "https://xs2a.db.com/pb/aspsp-certificates/tpp-pb-password_cert.pem");
    private static final String DEFAULT_EXCEPTION_MESSAGE = "Exception during Deutsche bank adapter PSU password encryption";

    private static DeutscheBankPsuPasswordEncryptionService encryptionService;

    private JWEHeader jweHeader;
    private JWEEncrypter jweEncrypter;

    private boolean isInit = false;

    @Override
    public String encrypt(String password) {
        if (!isInit) {
            init();
        }

        JWEObject jweObject = new JWEObject(jweHeader, new Payload(password));

        try {
            jweObject.encrypt(jweEncrypter);
        } catch (JOSEException e) {
            throw new PsuPasswordEncodingException(DEFAULT_EXCEPTION_MESSAGE, e);
        }

        return jweObject.serialize();
    }

    private void init() {
        CertificateFactory certificateFactory = new CertificateFactory();

        try {
            URI certificateUri = new URI(URL_TO_CERTIFICATE);
            InputStream certs = getCertificates(certificateUri);

            // Warning for unchecked assignment can be ignored,
            // as under the hood CertificateFactory#engineGenerateCertificates returns ArrayList,
            // even though java.util.Collection is mentioned as a return type.
            // See org.bouncycastle.jcajce.provider.asymmetric.x509.CertificateFactory#engineGenerateCertificates implementation for further details.
            @SuppressWarnings("unchecked")
            Collection certificates = certificateFactory.engineGenerateCertificates(certs);

            if (certificates.isEmpty()) {
                throw new PsuPasswordEncodingException("No certificates have been provided by bank for PSU password encryption");
            }

            List x509Certificates = toX509Certificates(certificates);

            List x509CertificateChainEncoded = x509Certificates.stream()
                                                           .map(this::toBase64)
                                                           .collect(Collectors.toList());

            jweHeader = new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM)
                            .x509CertURL(certificateUri)
                            .x509CertChain(x509CertificateChainEncoded)
                            .build();

            jweEncrypter = new RSAEncrypter(RSAKey.parse(getBankCertificate(x509Certificates)));

            this.isInit = true;
        } catch (Exception e) {
            throw new PsuPasswordEncodingException(DEFAULT_EXCEPTION_MESSAGE, e);
        }
    }

    private List toX509Certificates(Collection certificates) {
        return certificates.stream()
                   .map(certificate -> {
                       if (!(certificate instanceof X509Certificate)) {
                           throw new PsuPasswordEncodingException("Certificate provided by bank is not a X509 type");
                       }
                       return (X509Certificate) certificate;
                   })
                   .collect(Collectors.toList());
    }

    private Base64 toBase64(X509Certificate certificate) {
        try {
            return Base64.encode(certificate.getEncoded());
        } catch (CertificateEncodingException e) {
            throw new PsuPasswordEncodingException(DEFAULT_EXCEPTION_MESSAGE, e);
        }
    }

    private X509Certificate getBankCertificate(List certificates) {
        return certificates.get(0);
    }

    // Bouncy Castle CertificateFactory#engineGenerateCertificates in version 1.64 and higher will fail
    // if PEM file ends with more that one trailing new line (e.g. "\n\n"). This condition should be checked and fixed.
    // Related issue: https://github.com/adorsys/xs2a-adapter/issues/577
    private InputStream getCertificates(URI certificateUri) throws IOException {
        byte[] pemBytes = IOUtils.toByteArray(certificateUri);

        String pemFile = new String(pemBytes).replace("\n\n", "\n");

        return IOUtils.toInputStream(pemFile, (String) null);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy