used bouncycastle for parsing cert pem credentials
This commit is contained in:
		
							parent
							
								
									ee4fdde56f
								
							
						
					
					
						commit
						9d19a15413
					
				@ -20,68 +20,57 @@ import io.netty.handler.ssl.SslContext;
 | 
			
		||||
import io.netty.handler.ssl.SslContextBuilder;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.apache.commons.codec.binary.Base64;
 | 
			
		||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
 | 
			
		||||
import org.bouncycastle.cert.X509CertificateHolder;
 | 
			
		||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
 | 
			
		||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
 | 
			
		||||
import org.bouncycastle.openssl.PEMDecryptorProvider;
 | 
			
		||||
import org.bouncycastle.openssl.PEMEncryptedKeyPair;
 | 
			
		||||
import org.bouncycastle.openssl.PEMKeyPair;
 | 
			
		||||
import org.bouncycastle.openssl.PEMParser;
 | 
			
		||||
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
 | 
			
		||||
import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
 | 
			
		||||
import org.bouncycastle.util.encoders.Hex;
 | 
			
		||||
import org.bouncycastle.operator.InputDecryptorProvider;
 | 
			
		||||
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;
 | 
			
		||||
import org.bouncycastle.pkcs.PKCSException;
 | 
			
		||||
import org.bouncycastle.pkcs.jcajce.JcePKCSPBEInputDecryptorProviderBuilder;
 | 
			
		||||
import org.thingsboard.server.common.data.StringUtils;
 | 
			
		||||
 | 
			
		||||
import javax.crypto.Cipher;
 | 
			
		||||
import javax.crypto.EncryptedPrivateKeyInfo;
 | 
			
		||||
import javax.crypto.SecretKey;
 | 
			
		||||
import javax.crypto.SecretKeyFactory;
 | 
			
		||||
import javax.crypto.spec.IvParameterSpec;
 | 
			
		||||
import javax.crypto.spec.PBEKeySpec;
 | 
			
		||||
import javax.crypto.spec.SecretKeySpec;
 | 
			
		||||
import javax.net.ssl.KeyManagerFactory;
 | 
			
		||||
import javax.net.ssl.TrustManagerFactory;
 | 
			
		||||
import java.io.ByteArrayInputStream;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.math.BigInteger;
 | 
			
		||||
import java.nio.ByteBuffer;
 | 
			
		||||
import java.security.AlgorithmParameters;
 | 
			
		||||
import java.security.Key;
 | 
			
		||||
import java.security.KeyFactory;
 | 
			
		||||
import java.security.KeyPair;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.StringReader;
 | 
			
		||||
import java.security.GeneralSecurityException;
 | 
			
		||||
import java.security.KeyStore;
 | 
			
		||||
import java.security.MessageDigest;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.Security;
 | 
			
		||||
import java.security.cert.CertPath;
 | 
			
		||||
import java.security.cert.Certificate;
 | 
			
		||||
import java.security.cert.CertificateFactory;
 | 
			
		||||
import java.security.cert.X509Certificate;
 | 
			
		||||
import java.security.spec.KeySpec;
 | 
			
		||||
import java.security.spec.PKCS8EncodedKeySpec;
 | 
			
		||||
import java.security.spec.RSAPrivateCrtKeySpec;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
@Data
 | 
			
		||||
@Slf4j
 | 
			
		||||
@JsonIgnoreProperties(ignoreUnknown = true)
 | 
			
		||||
public class CertPemCredentials implements ClientCredentials {
 | 
			
		||||
    private static final String TLS_VERSION = "TLSv1.2";
 | 
			
		||||
 | 
			
		||||
    public static final String PRIVATE_KEY_ALIAS = "private-key";
 | 
			
		||||
    public static final String X_509 = "X.509";
 | 
			
		||||
    public static final String CERT_ALIAS_PREFIX = "cert-";
 | 
			
		||||
    public static final String CA_CERT_CERT_ALIAS_PREFIX = "caCert-cert-";
 | 
			
		||||
    protected String caCert;
 | 
			
		||||
    private String cert;
 | 
			
		||||
    private String privateKey;
 | 
			
		||||
    private String password;
 | 
			
		||||
 | 
			
		||||
    static final String OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX = "\\s*"
 | 
			
		||||
            + "-----BEGIN RSA PRIVATE KEY-----" + "\\s*"
 | 
			
		||||
            + "Proc-Type: 4,ENCRYPTED" + "\\s*"
 | 
			
		||||
            + "DEK-Info:" + "\\s*([^\\s]+)" + "\\s+"
 | 
			
		||||
            + "([\\s\\S]*)"
 | 
			
		||||
            + "-----END RSA PRIVATE KEY-----" + "\\s*";
 | 
			
		||||
 | 
			
		||||
    static final Pattern OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN = Pattern.compile(OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_REGEX);
 | 
			
		||||
    public CertPemCredentials() {
 | 
			
		||||
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
 | 
			
		||||
            Security.addProvider(new BouncyCastleProvider());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public CredentialsType getType() {
 | 
			
		||||
@ -91,7 +80,6 @@ public class CertPemCredentials implements ClientCredentials {
 | 
			
		||||
    @Override
 | 
			
		||||
    public SslContext initSslContext() {
 | 
			
		||||
        try {
 | 
			
		||||
            Security.addProvider(new BouncyCastleProvider());
 | 
			
		||||
            SslContextBuilder builder = SslContextBuilder.forClient();
 | 
			
		||||
            if (StringUtils.hasLength(caCert)) {
 | 
			
		||||
                builder.trustManager(createAndInitTrustManagerFactory());
 | 
			
		||||
@ -106,51 +94,13 @@ public class CertPemCredentials implements ClientCredentials {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception {
 | 
			
		||||
        List<X509Certificate> certHolders = readCertFile(cert);
 | 
			
		||||
        Object keyObject = readPrivateKeyFile(privateKey);
 | 
			
		||||
        char[] passwordCharArray = "".toCharArray();
 | 
			
		||||
        if (!StringUtils.isEmpty(password)) {
 | 
			
		||||
            passwordCharArray = password.toCharArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC");
 | 
			
		||||
 | 
			
		||||
        PrivateKey privateKey;
 | 
			
		||||
        if (keyObject instanceof PEMEncryptedKeyPair) {
 | 
			
		||||
            PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder().build(passwordCharArray);
 | 
			
		||||
            KeyPair key = keyConverter.getKeyPair(((PEMEncryptedKeyPair) keyObject).decryptKeyPair(provider));
 | 
			
		||||
            privateKey = key.getPrivate();
 | 
			
		||||
        } else if (keyObject instanceof PEMKeyPair) {
 | 
			
		||||
            KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject);
 | 
			
		||||
            privateKey = key.getPrivate();
 | 
			
		||||
        } else if (keyObject instanceof PrivateKey) {
 | 
			
		||||
            privateKey = (PrivateKey) keyObject;
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
 | 
			
		||||
        clientKeyStore.load(null, null);
 | 
			
		||||
        for (X509Certificate certHolder : certHolders) {
 | 
			
		||||
            clientKeyStore.setCertificateEntry("cert-" + certHolder.getSubjectDN().getName(), certHolder);
 | 
			
		||||
        }
 | 
			
		||||
        clientKeyStore.setKeyEntry("private-key",
 | 
			
		||||
                privateKey,
 | 
			
		||||
                passwordCharArray,
 | 
			
		||||
                certHolders.toArray(new Certificate[]{}));
 | 
			
		||||
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
 | 
			
		||||
        keyManagerFactory.init(clientKeyStore, passwordCharArray);
 | 
			
		||||
        return keyManagerFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected TrustManagerFactory createAndInitTrustManagerFactory() throws Exception {
 | 
			
		||||
        List<X509Certificate> caCertHolders = readCertFile(caCert);
 | 
			
		||||
        List<X509Certificate> caCerts = readCertFile(caCert);
 | 
			
		||||
 | 
			
		||||
        KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
 | 
			
		||||
        caKeyStore.load(null, null);
 | 
			
		||||
        for (X509Certificate caCertHolder : caCertHolders) {
 | 
			
		||||
            caKeyStore.setCertificateEntry("caCert-cert-" + caCertHolder.getSubjectDN().getName(), caCertHolder);
 | 
			
		||||
        for (X509Certificate caCert : caCerts) {
 | 
			
		||||
            caKeyStore.setCertificateEntry(CA_CERT_CERT_ALIAS_PREFIX + caCert.getSubjectDN().getName(), caCert);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
 | 
			
		||||
@ -158,170 +108,74 @@ public class CertPemCredentials implements ClientCredentials {
 | 
			
		||||
        return trustManagerFactory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    List<X509Certificate> readCertFile(String fileContent) throws Exception {
 | 
			
		||||
        if (fileContent == null || fileContent.trim().isEmpty()) {
 | 
			
		||||
            return Collections.emptyList();
 | 
			
		||||
    protected KeyManagerFactory createAndInitKeyManagerFactory() throws Exception {
 | 
			
		||||
        KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
 | 
			
		||||
        kmf.init(loadKeyStore(), password.toCharArray());
 | 
			
		||||
        return kmf;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private KeyStore loadKeyStore() throws Exception {
 | 
			
		||||
        List<X509Certificate> certificates = readCertFile(this.cert);
 | 
			
		||||
        PrivateKey privateKey = readPrivateKey(this.privateKey, this.password);
 | 
			
		||||
 | 
			
		||||
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
 | 
			
		||||
        keyStore.load(null);
 | 
			
		||||
        List<X509Certificate> unique = certificates.stream().distinct().collect(Collectors.toList());
 | 
			
		||||
        for (X509Certificate cert : unique) {
 | 
			
		||||
            keyStore.setCertificateEntry(CERT_ALIAS_PREFIX + cert.getSubjectDN().getName(), cert);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (privateKey != null) {
 | 
			
		||||
            CertificateFactory factory = CertificateFactory.getInstance(X_509);
 | 
			
		||||
            CertPath certPath = factory.generateCertPath(certificates);
 | 
			
		||||
            List<? extends Certificate> path = certPath.getCertificates();
 | 
			
		||||
            Certificate[] x509Certificates = path.toArray(new Certificate[0]);
 | 
			
		||||
            keyStore.setKeyEntry(PRIVATE_KEY_ALIAS, privateKey, password.toCharArray(), x509Certificates);
 | 
			
		||||
        }
 | 
			
		||||
        return keyStore;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected List<X509Certificate> readCertFile(String fileContent) throws IOException, GeneralSecurityException {
 | 
			
		||||
        List<X509Certificate> certificates = new ArrayList<>();
 | 
			
		||||
        String[] pems = fileContent.trim().split("-----END CERTIFICATE-----");
 | 
			
		||||
        for (String pem : pems) {
 | 
			
		||||
            if (pem.trim().isEmpty()) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            pem = pem.replace("-----BEGIN CERTIFICATE-----", "")
 | 
			
		||||
                    .replace("-----END CERTIFICATE-----", "")
 | 
			
		||||
                    .replaceAll("\\s", "");
 | 
			
		||||
            byte[] decoded = Base64.decodeBase64(pem);
 | 
			
		||||
            CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
 | 
			
		||||
            try (InputStream inStream = new ByteArrayInputStream(decoded)) {
 | 
			
		||||
                certificates.add((X509Certificate) certFactory.generateCertificate(inStream));
 | 
			
		||||
        JcaX509CertificateConverter certConverter = new JcaX509CertificateConverter();
 | 
			
		||||
        try (PEMParser pemParser = new PEMParser(new StringReader(fileContent))) {
 | 
			
		||||
            Object object;
 | 
			
		||||
            while ((object = pemParser.readObject()) != null) {
 | 
			
		||||
                if (object instanceof X509CertificateHolder) {
 | 
			
		||||
                    X509Certificate x509Cert = certConverter.getCertificate((X509CertificateHolder) object);
 | 
			
		||||
                    certificates.add(x509Cert);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return certificates;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private PrivateKey readPrivateKeyFile(String fileContent) throws Exception {
 | 
			
		||||
    protected PrivateKey readPrivateKey(String fileContent, String password) throws IOException, PKCSException {
 | 
			
		||||
        PrivateKey privateKey = null;
 | 
			
		||||
        if (fileContent != null && !fileContent.isEmpty()) {
 | 
			
		||||
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
 | 
			
		||||
            KeySpec keySpec = getKeySpec(fileContent);
 | 
			
		||||
            privateKey = keyFactory.generatePrivate(keySpec);
 | 
			
		||||
        JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter();
 | 
			
		||||
 | 
			
		||||
        if (StringUtils.isNotEmpty(fileContent)) {
 | 
			
		||||
            try (PEMParser pemParser = new PEMParser(new StringReader(fileContent))) {
 | 
			
		||||
                Object object;
 | 
			
		||||
                while ((object = pemParser.readObject()) != null) {
 | 
			
		||||
                    if (object instanceof PEMEncryptedKeyPair) {
 | 
			
		||||
                        PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(password.toCharArray());
 | 
			
		||||
                        privateKey = keyConverter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv)).getPrivate();
 | 
			
		||||
                        break;
 | 
			
		||||
                    } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) {
 | 
			
		||||
                        InputDecryptorProvider decProv =
 | 
			
		||||
                                new JcePKCSPBEInputDecryptorProviderBuilder().setProvider(new BouncyCastleProvider()).build(password.toCharArray());
 | 
			
		||||
                        privateKey = keyConverter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(decProv));
 | 
			
		||||
                        break;
 | 
			
		||||
                    } else if (object instanceof PEMKeyPair) {
 | 
			
		||||
                        privateKey = keyConverter.getKeyPair((PEMKeyPair) object).getPrivate();
 | 
			
		||||
                        break;
 | 
			
		||||
                    } else if (object instanceof PrivateKeyInfo) {
 | 
			
		||||
                        privateKey = keyConverter.getPrivateKey((PrivateKeyInfo) object);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return privateKey;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private KeySpec getKeySpec(String encodedKey) throws Exception {
 | 
			
		||||
        KeySpec keySpec = null;
 | 
			
		||||
        Matcher matcher = OPENSSL_ENCRYPTED_RSA_PRIVATEKEY_PATTERN.matcher(encodedKey);
 | 
			
		||||
        if (matcher.matches()) {
 | 
			
		||||
            String encryptionDetails = matcher.group(1).trim();
 | 
			
		||||
            String encryptedKey = matcher.group(2).replaceAll("\\s", "");
 | 
			
		||||
            byte[] encryptedBinaryKey = java.util.Base64.getDecoder().decode(encryptedKey);
 | 
			
		||||
            String[] encryptionDetailsParts = encryptionDetails.split(",");
 | 
			
		||||
            if (encryptionDetailsParts.length == 2) {
 | 
			
		||||
                String encryptionAlgorithm = encryptionDetailsParts[0];
 | 
			
		||||
                String encryptedAlgorithmParams = encryptionDetailsParts[1];
 | 
			
		||||
                byte[] pw = password.getBytes();
 | 
			
		||||
                byte[] iv = Hex.decode(encryptedAlgorithmParams);
 | 
			
		||||
 | 
			
		||||
                MessageDigest digest = MessageDigest.getInstance("MD5");
 | 
			
		||||
                digest.update(pw);
 | 
			
		||||
                digest.update(iv, 0, 8);
 | 
			
		||||
 | 
			
		||||
                byte[] round1Digest = digest.digest();
 | 
			
		||||
                digest.update(round1Digest);
 | 
			
		||||
                digest.update(pw);
 | 
			
		||||
                digest.update(iv, 0, 8);
 | 
			
		||||
 | 
			
		||||
                byte[] round2Digest = digest.digest();
 | 
			
		||||
                Cipher cipher = null;
 | 
			
		||||
                SecretKey secretKey = null;
 | 
			
		||||
                byte[] key = null;
 | 
			
		||||
 | 
			
		||||
                switch(encryptionAlgorithm) {
 | 
			
		||||
                    case "AES-256-CBC":
 | 
			
		||||
                        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 | 
			
		||||
                        key = new byte[32];
 | 
			
		||||
                        System.arraycopy(round1Digest, 0, key, 0, 16);
 | 
			
		||||
                        System.arraycopy(round2Digest, 0, key, 16, 16);
 | 
			
		||||
                        secretKey = new SecretKeySpec(key, "AES");
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "AES-192-CBC":
 | 
			
		||||
                        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 | 
			
		||||
                        key = new byte[24];
 | 
			
		||||
                        System.arraycopy(round1Digest, 0, key, 0, 16);
 | 
			
		||||
                        System.arraycopy(round2Digest, 0, key, 16, 8);
 | 
			
		||||
                        secretKey = new SecretKeySpec(key, "AES");
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "AES-128-CBC":
 | 
			
		||||
                        cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
 | 
			
		||||
                        key = new byte[16];
 | 
			
		||||
                        System.arraycopy(round1Digest, 0, key, 0, 16);
 | 
			
		||||
                        secretKey = new SecretKeySpec(key, "AES");
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "DES-EDE3-CBC":
 | 
			
		||||
                        cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
 | 
			
		||||
                        key = new byte[24];
 | 
			
		||||
                        System.arraycopy(round1Digest, 0, key, 0, 16);
 | 
			
		||||
                        System.arraycopy(round2Digest, 0, key, 16, 8);
 | 
			
		||||
                        secretKey = new SecretKeySpec(key, "DESede");
 | 
			
		||||
                        break;
 | 
			
		||||
                    case "DES-CBC":
 | 
			
		||||
                        cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
 | 
			
		||||
                        key = new byte[8];
 | 
			
		||||
                        System.arraycopy(round1Digest, 0, key, 0, 8);
 | 
			
		||||
                        secretKey = new SecretKeySpec(key, "DES");
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                if (cipher != null) {
 | 
			
		||||
                    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
 | 
			
		||||
                    byte[] pkcs1 = cipher.doFinal(encryptedBinaryKey);
 | 
			
		||||
                    keySpec = decodeRSAPrivatePKCS1(pkcs1);
 | 
			
		||||
                } else {
 | 
			
		||||
                    throw new RuntimeException("Unknown Encryption algorithm!");
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                throw new RuntimeException("Wrong encryption details!");
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            encodedKey = encodedKey.replaceAll(".*BEGIN.*PRIVATE KEY.*", "")
 | 
			
		||||
                    .replaceAll(".*END.*PRIVATE KEY.*", "")
 | 
			
		||||
                    .replaceAll("\\s", "");
 | 
			
		||||
            byte[] decoded = Base64.decodeBase64(encodedKey);
 | 
			
		||||
            if (password == null || password.isEmpty()) {
 | 
			
		||||
                keySpec = new PKCS8EncodedKeySpec(decoded);
 | 
			
		||||
            } else {
 | 
			
		||||
                PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
 | 
			
		||||
 | 
			
		||||
                EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decoded);
 | 
			
		||||
                String algorithmName = privateKeyInfo.getAlgName();
 | 
			
		||||
                Cipher cipher = Cipher.getInstance(algorithmName);
 | 
			
		||||
                SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithmName);
 | 
			
		||||
 | 
			
		||||
                Key pbeKey = secretKeyFactory.generateSecret(pbeKeySpec);
 | 
			
		||||
                AlgorithmParameters algParams = privateKeyInfo.getAlgParameters();
 | 
			
		||||
                cipher.init(Cipher.DECRYPT_MODE, pbeKey, algParams);
 | 
			
		||||
                keySpec = privateKeyInfo.getKeySpec(cipher);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return keySpec;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static BigInteger derint(ByteBuffer input) {
 | 
			
		||||
        int len = der(input, 0x02);
 | 
			
		||||
        byte[] value = new byte[len];
 | 
			
		||||
        input.get(value);
 | 
			
		||||
        return new BigInteger(+1, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static int der(ByteBuffer input, int exp) {
 | 
			
		||||
        int tag = input.get() & 0xFF;
 | 
			
		||||
        if (tag != exp) throw new IllegalArgumentException("Unexpected tag");
 | 
			
		||||
        int n = input.get() & 0xFF;
 | 
			
		||||
        if (n < 128) return n;
 | 
			
		||||
        n &= 0x7F;
 | 
			
		||||
        if ((n < 1) || (n > 2)) throw new IllegalArgumentException("Invalid length");
 | 
			
		||||
        int len = 0;
 | 
			
		||||
        while (n-- > 0) {
 | 
			
		||||
            len <<= 8;
 | 
			
		||||
            len |= input.get() & 0xFF;
 | 
			
		||||
        }
 | 
			
		||||
        return len;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static RSAPrivateCrtKeySpec decodeRSAPrivatePKCS1(byte[] encoded) {
 | 
			
		||||
        ByteBuffer input = ByteBuffer.wrap(encoded);
 | 
			
		||||
        if (der(input, 0x30) != input.remaining()) throw new IllegalArgumentException("Excess data");
 | 
			
		||||
        if (!BigInteger.ZERO.equals(derint(input))) throw new IllegalArgumentException("Unsupported version");
 | 
			
		||||
        BigInteger n = derint(input);
 | 
			
		||||
        BigInteger e = derint(input);
 | 
			
		||||
        BigInteger d = derint(input);
 | 
			
		||||
        BigInteger p = derint(input);
 | 
			
		||||
        BigInteger q = derint(input);
 | 
			
		||||
        BigInteger ep = derint(input);
 | 
			
		||||
        BigInteger eq = derint(input);
 | 
			
		||||
        BigInteger c = derint(input);
 | 
			
		||||
        return new RSAPrivateCrtKeySpec(n, e, d, p, q, ep, eq, c);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,16 +18,27 @@ package org.thingsboard.rule.engine.credentials;
 | 
			
		||||
import org.apache.commons.io.FileUtils;
 | 
			
		||||
import org.junit.Assert;
 | 
			
		||||
import org.junit.Test;
 | 
			
		||||
import org.junit.jupiter.api.Assertions;
 | 
			
		||||
import org.junit.jupiter.params.ParameterizedTest;
 | 
			
		||||
import org.junit.jupiter.params.provider.Arguments;
 | 
			
		||||
import org.junit.jupiter.params.provider.MethodSource;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.security.PrivateKey;
 | 
			
		||||
import java.security.cert.X509Certificate;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
public class CertPemCredentialsTest {
 | 
			
		||||
 | 
			
		||||
    private final CertPemCredentials credentials = new CertPemCredentials();
 | 
			
		||||
 | 
			
		||||
    private static final String PASS = "test";
 | 
			
		||||
    private static final String EMPTY_PASS = "";
 | 
			
		||||
    private static final String RSA = "RSA";
 | 
			
		||||
    private static final String ECDSA = "ECDSA";
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    public void testChainOfCertificates() throws Exception {
 | 
			
		||||
        String fileContent = fileContent("pem/tb-cloud-chain.pem");
 | 
			
		||||
@ -65,6 +76,23 @@ public class CertPemCredentialsTest {
 | 
			
		||||
        Assert.assertEquals(0, x509Certificates.size());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Stream<Arguments> testReadPrivateKey() {
 | 
			
		||||
        return Stream.of(
 | 
			
		||||
                Arguments.of("pem/rsa_key.pem", EMPTY_PASS, RSA),
 | 
			
		||||
                Arguments.of("pem/rsa_encrypted_key.pem", PASS, RSA),
 | 
			
		||||
                Arguments.of("pem/rsa_encrypted_traditional_key.pem", PASS, RSA),
 | 
			
		||||
                Arguments.of("pem/ec_key.pem", EMPTY_PASS, ECDSA)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @ParameterizedTest
 | 
			
		||||
    @MethodSource
 | 
			
		||||
    public void testReadPrivateKey(String keyPath, String password, String algorithm) throws Exception {
 | 
			
		||||
        PrivateKey privateKey = credentials.readPrivateKey(fileContent(keyPath), password);
 | 
			
		||||
        Assertions.assertNotNull(privateKey);
 | 
			
		||||
        Assertions.assertEquals(algorithm, privateKey.getAlgorithm());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private String fileContent(String fileName) throws IOException {
 | 
			
		||||
        ClassLoader classLoader = getClass().getClassLoader();
 | 
			
		||||
        File file = new File(classLoader.getResource(fileName).getFile());
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,8 @@
 | 
			
		||||
-----BEGIN EC PARAMETERS-----
 | 
			
		||||
BggqhkjOPQMBBw==
 | 
			
		||||
-----END EC PARAMETERS-----
 | 
			
		||||
-----BEGIN EC PRIVATE KEY-----
 | 
			
		||||
MHcCAQEEIIEd0mMh0EEy3fMbOpbUY6kW0oAYcaYoTvoVpZxDr5qZoAoGCCqGSM49
 | 
			
		||||
AwEHoUQDQgAEz4MgawieJfVc5zUOPiw5WFxfHGJf7dOMsHvudDxdOs27PXPbJfi0
 | 
			
		||||
9BVJ3+JjNxA2wQz9KUk877oWRYrN/e+MbA==
 | 
			
		||||
-----END EC PRIVATE KEY-----
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
 | 
			
		||||
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
 | 
			
		||||
-----END ENCRYPTED PRIVATE KEY-----
 | 
			
		||||
@ -0,0 +1,30 @@
 | 
			
		||||
-----BEGIN RSA PRIVATE KEY-----
 | 
			
		||||
Proc-Type: 4,ENCRYPTED
 | 
			
		||||
DEK-Info: DES-EDE3-CBC,FB5DE36A7A8B25DA
 | 
			
		||||
 | 
			
		||||
skR15rUvZmdLzGqU5/BF4Yc3E6dxtXTvlOhuGnqH/idItMKUMWIIlQ7ZfWYF/CrC
 | 
			
		||||
CkeAUqhF4y+y+2eR4ejUzZKs6bYjTtkXAXAqQvCsTrTBdQSCcLwbHWLWMro0UG24
 | 
			
		||||
e23Rx8kD0YC7VHqyr08NlLh94wR7kanhEeRUbmKBonZT/I9AZ5ntiBxq9QVBtc8M
 | 
			
		||||
f7LKIsnQDcd39cVXSo3LOJ/x7YVB//Ln1R1dexwxE0sXOyLq2hhrxzfHGuGXXW41
 | 
			
		||||
/3+CeTgmX9Rzpawrq9vbVabPUgFcJlrogNRSnUAm9kz4b2zadCxEaCejVmhBy4wx
 | 
			
		||||
z2AJGcmE+D4VkQK1AAj5+AQQrPOIIFQnyGjwHkJSTGVTcKmttRYZBvUjdfQfj1fK
 | 
			
		||||
NsKOSZLzZGknM8Pz5MHgHqk70C7f+nm0uVhVuAiykA4PY4JdCAuTWJxMM2LWM3/q
 | 
			
		||||
rYCEMwxCGa6U92dakfC+W+d9pAbeN+xYOWkDrqG7BdAYg1P70cuMRPdd5bsq3YPp
 | 
			
		||||
G4n5NVyQvLlocGhZgC7NVzUtc18+rGblV4D657+GZwJnBZJN4TYey4+r2D5fv9rO
 | 
			
		||||
kcRVwfR704BbUBkjIzVzD7nXtBbr3ni8HSqde3g4aVL4WyV9XNvjUsYyrZ0u9Mt1
 | 
			
		||||
IccAsa1xBquUNMxwO1H1mFLtzPKmFzKtlzqiiDsRQoRylwUa1k03sHKUflZRa8Sf
 | 
			
		||||
g4MpTRzK+vo1opMemlonty5YbvWXKlH68ioo49L8N457Y0hIUJOQgywg80NxT33t
 | 
			
		||||
x8y66lawd1Iv+Q7pptVxJtA4JmcdPvGwBLJZY4DacMyp/JqchAQfSQfmQ0tC+RfJ
 | 
			
		||||
z2By/s5wOEuVDksgp8RF1gn+VmvLyOoLK7tq+zpMO1mhfYTCMgSiz2GkNdiW6i25
 | 
			
		||||
gjNWN/F62YL+9VJo4+olrcsYDFiiJq+deQk3H1tQJzu6qECfDqKDyw7IunvTwFil
 | 
			
		||||
5/d43LvLbRj75Kf/++xwTjfHudeTgw02/yPyELnURkUazvkOFsn7n8tU46Qm5TWh
 | 
			
		||||
fPFXSYxRf3m9rkKZB98YOJo595RuZyiYg9dEQX8Gybl1/7H7l4Cvw6yp71kgLrrI
 | 
			
		||||
JRYEt9pmWbQX97UFC3WTVMdKWakFziYVGPvFKkIzrHgcutbQVNsZ7GbO+rdWMIxr
 | 
			
		||||
SfUe6jCEclzGQjI9Ep4PTLjZvbusUMkoUjGasAluXFXDC4RKtpuXd4RmbOLdVuyN
 | 
			
		||||
OnZ5KZHFjrv4ch5PakRTViWFWSddV5CJ4fMkCG9qUHKrUWGjOvzu5rbcUzq3xJZG
 | 
			
		||||
9loIvlA4ekEAhQPHwx69uBmUwnCgyB9CosQGmUlwmC3KALA/EiXklTA6w0fGiiPk
 | 
			
		||||
uLA9oBGrVcD8Peug9Owfmj4fWbxJGP7x1UR/nZWpynIfzME0AD8MK5uqoWmQTG8I
 | 
			
		||||
cLSjVAB1CO//AZe4LpYQulMPq4dipmE+YnKLi0WPXuZVARDciAGGV2BfH6Iv8j9k
 | 
			
		||||
o9IoklcpGg22zXLoGn4tu+7Y5GcoV/mx68Gun1E/QFuY59damAqoc82EEsxr+UX0
 | 
			
		||||
4vGX+KZn283xSYiilE3qpCZOER0ZUFInphUwJzzYfW3mW/AWR78tFQIQiuVKV2/a
 | 
			
		||||
-----END RSA PRIVATE KEY-----
 | 
			
		||||
@ -0,0 +1,52 @@
 | 
			
		||||
-----BEGIN PRIVATE KEY-----
 | 
			
		||||
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
 | 
			
		||||
wOqb/axEbvcj5sV6/4p2zz6AzFPEmQ==
 | 
			
		||||
-----END PRIVATE KEY-----
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user