org.opcfoundation.ua.transport.security.BcCryptoProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.opcfoundation.ua.transport.security.BcCryptoProvider.java

Source

/* Copyright (c) 1996-2015, OPC Foundation. All rights reserved.
   The source code in this file is covered under a dual-license scenario:
 - RCL: for OPC Foundation members in good-standing
 - GPL V2: everybody else
   RCL license terms accompanied with this source code. See http://opcfoundation.org/License/RCL/1.00/
   GNU General Public License as published by the Free Software Foundation;
   version 2 of the License are accompanied with this source code. See http://opcfoundation.org/License/GPLv2
   This source code is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
package org.opcfoundation.ua.transport.security;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.PrivateKey;
import java.security.PublicKey;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.asn1.pkcs.RSAPublicKey;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.CryptoException;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.Signer;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.engines.RijndaelEngine;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.params.RSAKeyParameters;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.crypto.signers.RSADigestSigner;
import org.bouncycastle.util.encoders.Base64;
import org.opcfoundation.ua.common.ServiceResultException;
import org.opcfoundation.ua.core.StatusCodes;
import org.opcfoundation.ua.transport.tcp.impl.SecurityToken;
import org.opcfoundation.ua.utils.CryptoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BcCryptoProvider implements CryptoProvider {

    static Logger logger = LoggerFactory.getLogger(BcCryptoProvider.class);

    public BcCryptoProvider() {

    }

    @Override
    public byte[] base64Decode(String string) {
        return Base64.decode(string);
    }

    @Override
    public String base64Encode(byte[] bytes) {
        try {
            return new String(Base64.encode(bytes), "UTF-8");
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Mac createMac(SecurityAlgorithm algorithm, byte[] secret) throws ServiceResultException {
        SecretKeySpec keySpec = new SecretKeySpec(secret, algorithm.getStandardName());
        Mac hmac;
        try {
            hmac = Mac.getInstance(algorithm.getStandardName());
            hmac.init(keySpec);
        } catch (InvalidKeyException e) {
            throw new ServiceResultException(StatusCodes.Bad_SecurityChecksFailed, e);
        } catch (GeneralSecurityException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }
        return hmac;
    }

    /*
     * @Override public void encryptAsymm(SecurityConfiguration profile, byte[]
     * dataToEncrypt, java.security.interfaces.RSAPublicKey
     * encryptingCertificate, byte[] output, int outputOffset) throws
     * ServiceResultException {
     * 
     * SecurityPolicy policy = profile.getSecurityPolicy(); SecurityAlgorithm
     * algorithm = policy.getAsymmetricEncryptionAlgorithm();
     * 
     * try { encryptAsymm(algorithm, dataToEncrypt,
     * getCertificate(encryptingCertificate), output, outputOffset); } catch
     * (CertificateEncodingException e) { throw new
     * ServiceResultException(StatusCodes.Bad_InternalError, e); }
     * 
     * }
     */

    @Override
    public int decryptAsymm(PrivateKey decryptingKey, SecurityAlgorithm algorithm, byte[] dataToDecrypt,
            byte[] output, int outputOffset) throws ServiceResultException {

        java.security.interfaces.RSAPrivateCrtKey rsaPrivateKey = (java.security.interfaces.RSAPrivateCrtKey) decryptingKey;
        RSAPrivateKey privateKey = new RSAPrivateKey(rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent(),
                rsaPrivateKey.getPrivateExponent(), rsaPrivateKey.getPrimeP(), rsaPrivateKey.getPrimeQ(),
                rsaPrivateKey.getPrimeExponentP(), rsaPrivateKey.getPrimeExponentQ(),
                rsaPrivateKey.getCrtCoefficient());

        AsymmetricBlockCipher cipher = getAsymmetricCipher(algorithm, privateKey);

        try {

            int len = 0;
            int inputBlockSize = cipher.getInputBlockSize();
            int outputBlockSize = cipher.getOutputBlockSize();
            logger.debug("Decrypt: inputBlockSize={}, outputBlockSize={}, dataToDecrypt.length={}", inputBlockSize,
                    outputBlockSize, dataToDecrypt.length);
            for (int i = 0; i < dataToDecrypt.length; i += inputBlockSize) {
                int size = Math.min(dataToDecrypt.length - i, inputBlockSize);
                byte[] tmp = cipher.processBlock(dataToDecrypt, i, size);
                System.arraycopy(tmp, 0, output, outputOffset + len, tmp.length);
                len += tmp.length;
            }
            return len;

        } catch (CryptoException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }

    }

    /*
     * private RSAPublicKey getRSAPublicKey(Certificate cert) {
     * SubjectPublicKeyInfo subjectPublicKeyInfo = cert
     * .getSubjectPublicKeyInfo(); DERBitString publicKeyData =
     * subjectPublicKeyInfo.getPublicKeyData(); return
     * RSAPublicKey.getInstance(publicKeyData.getBytes()); }
     */

    /*
     * @Override public int decryptAsymm(SecurityConfiguration profile, byte[]
     * dataToDecrypt, java.security.cert.Certificate decryptingCertificate,
     * byte[] output, int outputOffset) throws ServiceResultException {
     * 
     * java.security.interfaces.RSAPrivateCrtKey rsaPrivateKey =
     * (java.security.interfaces.RSAPrivateCrtKey) profile
     * .getLocalPrivateKey(); RSAPrivateKey privateKey = new RSAPrivateKey(
     * rsaPrivateKey.getModulus(), rsaPrivateKey.getPublicExponent(),
     * rsaPrivateKey.getPrivateExponent(), rsaPrivateKey.getPrimeP(),
     * rsaPrivateKey.getPrimeQ(), rsaPrivateKey.getPrimeExponentP(),
     * rsaPrivateKey.getPrimeExponentQ(), rsaPrivateKey.getCrtCoefficient());
     * return decryptAsymm(profile, dataToDecrypt, privateKey, output,
     * outputOffset);
     * 
     * }
     */

    @Override
    public int decryptSymm(SecurityToken token, byte[] dataToDecrypt, int inputOffset, int inputLength,
            byte[] output, int outputOffset) throws ServiceResultException {

        BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new AESEngine()));

        cipher.init(false, new ParametersWithIV(new KeyParameter(token.getRemoteEncryptingKey()),
                token.getRemoteInitializationVector()));

        int decryptedBytes = cipher.processBytes(dataToDecrypt, inputOffset, inputLength, output, outputOffset);

        try {

            decryptedBytes += cipher.doFinal(output, outputOffset + decryptedBytes);
            return decryptedBytes;

        } catch (DataLengthException e) {
            logger.error("Input data is not an even number of encryption blocks.");
            throw new ServiceResultException(StatusCodes.Bad_InternalError,
                    "Error in symmetric decrypt: Input data is not an even number of encryption blocks.");
        } catch (CryptoException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }

    }

    /*
     * public int decryptAsymm(SecurityConfiguration profile, byte[]
     * dataToDecrypt, RSAPrivateKey privateKey, byte[] output, int outputOffset)
     * throws ServiceResultException {
     * 
     * SecurityPolicy policy = profile.getSecurityPolicy(); SecurityAlgorithm
     * algorithm = policy.getAsymmetricEncryptionAlgorithm();
     * AsymmetricBlockCipher cipher = getAsymmetricCipher(algorithm,
     * privateKey);
     * 
     * try {
     * 
     * int len = 0; int inputBlockSize = cipher.getInputBlockSize(); int
     * outputBlockSize = cipher.getOutputBlockSize(); logger.info(
     * "Decrypt: inputBlockSize={}, outputBlockSize={}, dataToDecrypt.length={}"
     * , inputBlockSize, outputBlockSize, dataToDecrypt.length); for (int i = 0;
     * i < dataToDecrypt.length; i += inputBlockSize) { int size =
     * Math.min(dataToDecrypt.length - i, inputBlockSize); byte[] tmp =
     * cipher.processBlock(dataToDecrypt, i, size); System.arraycopy(tmp, 0,
     * output, outputOffset + len, tmp.length); len += tmp.length; } return len;
     * 
     * } catch (CryptoException e) { throw new
     * ServiceResultException(StatusCodes.Bad_InternalError, e); }
     * 
     * }
     */

    public void encryptAsymm(PublicKey encryptingCertificate, SecurityAlgorithm algorithm, byte[] dataToEncrypt,
            byte[] output, int outputOffset) throws ServiceResultException {

        try {
            java.security.interfaces.RSAPublicKey encryptingCertificateRSA = (java.security.interfaces.RSAPublicKey) encryptingCertificate;
            RSAPublicKey publicKey = new RSAPublicKey(encryptingCertificateRSA.getModulus(),
                    encryptingCertificateRSA.getPublicExponent());
            AsymmetricBlockCipher cipher = getAsymmetricCipher(algorithm, publicKey);

            int len = 0;
            int inputBlockSize = cipher.getInputBlockSize();
            int outputBlockSize = cipher.getOutputBlockSize();
            logger.debug("Encrypt: inputBlockSize={}, outputBlockSize={}, dataToEncrypt.length={}", inputBlockSize,
                    outputBlockSize, dataToEncrypt.length);
            for (int i = 0; i < dataToEncrypt.length; i += inputBlockSize) {
                int size = Math.min(dataToEncrypt.length - i, inputBlockSize);
                byte[] tmp = cipher.processBlock(dataToEncrypt, i, size);
                System.arraycopy(tmp, 0, output, outputOffset + len, tmp.length);
                len += tmp.length;
            }

        } catch (InvalidCipherTextException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }

    }

    @Override
    public int encryptSymm(SecurityToken token, byte[] dataToEncrypt, int inputOffset, int inputLength,
            byte[] output, int outputOffset) throws ServiceResultException {

        BufferedBlockCipher cipher = new BufferedBlockCipher(new CBCBlockCipher(new RijndaelEngine()));

        cipher.init(true, new ParametersWithIV(new KeyParameter(token.getLocalEncryptingKey()),
                token.getLocalInitializationVector()));

        int encryptedBytes = cipher.processBytes(dataToEncrypt, inputOffset, inputLength, output, outputOffset);

        try {

            encryptedBytes += cipher.doFinal(output, outputOffset + encryptedBytes);
            return encryptedBytes;

        } catch (DataLengthException e) {
            logger.error("Input data is not an even number of encryption blocks.");
            throw new ServiceResultException(StatusCodes.Bad_InternalError,
                    "Error in symmetric decrypt: Input data is not an even number of encryption blocks.");
        } catch (CryptoException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }

    }

    @Override
    public byte[] signAsymm(PrivateKey senderPrivate, SecurityAlgorithm algorithm, byte[] dataToSign)
            throws ServiceResultException {
        if (algorithm == null)
            return null;

        if (dataToSign == null || senderPrivate == null)
            throw new IllegalArgumentException("null arg");

        java.security.interfaces.RSAPrivateCrtKey privateKey = (java.security.interfaces.RSAPrivateCrtKey) senderPrivate;
        RSAPrivateKey privKey = new RSAPrivateKey(privateKey.getModulus(), privateKey.getPublicExponent(),
                privateKey.getPrivateExponent(), privateKey.getPrimeP(), privateKey.getPrimeQ(),
                privateKey.getPrimeExponentP(), privateKey.getPrimeExponentQ(), privateKey.getCrtCoefficient());

        Signer signer = getAsymmetricSigner(true, algorithm, privKey);
        signer.update(dataToSign, 0, dataToSign.length);

        try {
            return signer.generateSignature();
        } catch (DataLengthException e) {
            logger.error("Input data is not an even number of encryption blocks.");
            throw new ServiceResultException(StatusCodes.Bad_InternalError,
                    "Error in symmetric decrypt: Input data is not an even number of encryption blocks.");
        } catch (CryptoException e) {
            throw new ServiceResultException(StatusCodes.Bad_InternalError, e);
        }

    }

    @Override
    public void signSymm(SecurityToken token, byte[] input, int verifyLen, byte[] output)
            throws ServiceResultException {

        SecurityAlgorithm algorithm = token.getSecurityPolicy().getSymmetricSignatureAlgorithm();
        HMac hmac = createMac(algorithm, new KeyParameter(token.getLocalSigningKey()));
        hmac.update(input, 0, verifyLen);
        hmac.doFinal(output, 0);

    }

    @Override
    public boolean verifyAsymm(PublicKey signingCertificate, SecurityAlgorithm algorithm, byte[] dataToVerify,
            byte[] signature) throws ServiceResultException {
        if (algorithm == null)
            return true;
        if (signingCertificate == null || dataToVerify == null || signature == null)
            throw new IllegalArgumentException("null arg");

        java.security.interfaces.RSAPublicKey signingCertificateRSA = (java.security.interfaces.RSAPublicKey) signingCertificate;
        RSAPublicKey publicKey = new RSAPublicKey(signingCertificateRSA.getModulus(),
                signingCertificateRSA.getPublicExponent());
        Signer signer = getAsymmetricSigner(false, algorithm, publicKey);
        signer.update(dataToVerify, 0, dataToVerify.length);
        return signer.verifySignature(signature);

    }

    @Override
    public void verifySymm(SecurityToken token, byte[] dataToVerify, byte[] signature)
            throws ServiceResultException {

        SecurityAlgorithm algorithm = token.getSecurityPolicy().getSymmetricSignatureAlgorithm();
        HMac hmac = createMac(algorithm, new KeyParameter(token.getRemoteSigningKey()));
        byte[] computedSignature = new byte[hmac.getMacSize()];
        hmac.update(dataToVerify, 0, dataToVerify.length);
        hmac.doFinal(computedSignature, 0);

        // Compare signatures
        // First test that sizes are the same
        if (signature.length != computedSignature.length) {
            logger.warn("Signature lengths do not match: \n" + CryptoUtil.toHex(signature) + " vs. \n"
                    + CryptoUtil.toHex(computedSignature));
            throw new ServiceResultException(StatusCodes.Bad_SecurityChecksFailed,
                    "Invalid signature: lengths do not match");
        }
        // Compare byte by byte
        for (int index = 0; index < signature.length; index++) {
            if (signature[index] != computedSignature[index]) {
                logger.warn("Signatures do not match: \n" + CryptoUtil.toHex(signature) + " vs. \n"
                        + CryptoUtil.toHex(computedSignature));
                throw new ServiceResultException(StatusCodes.Bad_SecurityChecksFailed,
                        "Invalid signature: signatures do not match");
            }
        }
        // Everything went fine, signatures matched
    }

    private HMac createMac(SecurityAlgorithm algorithm, KeyParameter param) throws ServiceResultException {

        HMac hmac = null;
        if (algorithm.equals(SecurityAlgorithm.HmacSha1)) {
            hmac = new HMac(new SHA1Digest());
        } else if (algorithm.equals(SecurityAlgorithm.HmacSha256)) {
            hmac = new HMac(new SHA256Digest());
        } else {
            throw new ServiceResultException(StatusCodes.Bad_SecurityPolicyRejected,
                    "Unsupported symmetric signature algorithm: " + algorithm);
        }
        hmac.init(param);
        return hmac;

    }

    private AsymmetricBlockCipher getAsymmetricCipher(boolean forEncryption, SecurityAlgorithm algorithm,
            CipherParameters params) throws ServiceResultException {
        AsymmetricBlockCipher cipher = null;
        if (algorithm.equals(SecurityAlgorithm.Rsa15)) {
            cipher = new PKCS1Encoding(new RSAEngine());
        } else if (algorithm.equals(SecurityAlgorithm.RsaOaep)) {
            cipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest());
        } else {
            throw new ServiceResultException(StatusCodes.Bad_SecurityPolicyRejected,
                    "Unsupported asymmetric encryption algorithm: " + algorithm);
        }
        cipher.init(forEncryption, params);
        return cipher;
    }

    private AsymmetricBlockCipher getAsymmetricCipher(SecurityAlgorithm algorithm, RSAPrivateKey privateKey)
            throws ServiceResultException {
        CipherParameters params = new RSAPrivateCrtKeyParameters(privateKey.getModulus(),
                privateKey.getPublicExponent(), privateKey.getPrivateExponent(), privateKey.getPrime1(),
                privateKey.getPrime2(), privateKey.getExponent1(), privateKey.getExponent2(),
                privateKey.getCoefficient());
        return getAsymmetricCipher(false, algorithm, params);
    }

    private AsymmetricBlockCipher getAsymmetricCipher(SecurityAlgorithm algorithm, RSAPublicKey publicKey)
            throws ServiceResultException {
        CipherParameters params = new RSAKeyParameters(false, publicKey.getModulus(),
                publicKey.getPublicExponent());
        // logger.info("Cipher: \nmodulus={}, \npublicExponent={}\n",
        // publicKey.getModulus(), publicKey.getPublicExponent());
        return getAsymmetricCipher(true, algorithm, params);
    }

    private Signer getAsymmetricSigner(boolean forSigning, SecurityAlgorithm algorithm, CipherParameters params)
            throws ServiceResultException {

        Signer signer = null;
        if (algorithm.equals(SecurityAlgorithm.RsaSha1)) {
            signer = new RSADigestSigner(new SHA1Digest());
        } else if (algorithm.equals(SecurityAlgorithm.RsaSha256)) {
            signer = new RSADigestSigner(new SHA256Digest());
        } else {
            throw new ServiceResultException(StatusCodes.Bad_SecurityPolicyRejected,
                    "Unsupported asymmetric signature algorithm: " + algorithm);
        }
        signer.init(forSigning, params);
        return signer;

    }

    private Signer getAsymmetricSigner(boolean forSigning, SecurityAlgorithm algorithm, RSAPrivateKey privateKey)
            throws ServiceResultException {

        CipherParameters params = new RSAPrivateCrtKeyParameters(privateKey.getModulus(),
                privateKey.getPublicExponent(), privateKey.getPrivateExponent(), privateKey.getPrime1(),
                privateKey.getPrime2(), privateKey.getExponent1(), privateKey.getExponent2(),
                privateKey.getCoefficient());
        return getAsymmetricSigner(forSigning, algorithm, params);

    }

    private Signer getAsymmetricSigner(boolean forSigning, SecurityAlgorithm algorithm, RSAPublicKey publicKey)
            throws ServiceResultException {

        CipherParameters params = new RSAKeyParameters(false, publicKey.getModulus(),
                publicKey.getPublicExponent());
        return getAsymmetricSigner(forSigning, algorithm, params);

    }

}