| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.tomcat.util.net.jsse; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.security.GeneralSecurityException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyFactory; |
| import java.security.PrivateKey; |
| import java.security.cert.CertificateException; |
| import java.security.cert.CertificateFactory; |
| import java.security.cert.X509Certificate; |
| import java.security.spec.InvalidKeySpecException; |
| import java.security.spec.KeySpec; |
| import java.security.spec.PKCS8EncodedKeySpec; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.EncryptedPrivateKeyInfo; |
| import javax.crypto.SecretKey; |
| import javax.crypto.SecretKeyFactory; |
| import javax.crypto.spec.PBEKeySpec; |
| |
| import org.apache.tomcat.util.codec.binary.Base64; |
| import org.apache.tomcat.util.res.StringManager; |
| |
| /** |
| * RFC 1421 PEM file containing X509 certificates or private keys (PKCS#8 only, |
| * i.e. with boundaries containing "BEGIN PRIVATE KEY" or "BEGIN ENCRYPTED PRIVATE KEY", |
| * not "BEGIN RSA PRIVATE KEY" or other variations). |
| */ |
| class PEMFile { |
| |
| private static final StringManager sm = StringManager.getManager(PEMFile.class); |
| |
| private String filename; |
| private List<X509Certificate> certificates = new ArrayList<>(); |
| private PrivateKey privateKey; |
| |
| public List<X509Certificate> getCertificates() { |
| return certificates; |
| } |
| |
| public PrivateKey getPrivateKey() { |
| return privateKey; |
| } |
| |
| public PEMFile(String filename) throws IOException, GeneralSecurityException { |
| this(filename, null); |
| } |
| |
| public PEMFile(String filename, String password) throws IOException, GeneralSecurityException { |
| this.filename = filename; |
| |
| List<Part> parts = new ArrayList<>(); |
| try (BufferedReader in = new BufferedReader(new FileReader(filename))) { |
| Part part = null; |
| String line; |
| while ((line = in.readLine()) != null) { |
| if (line.startsWith(Part.BEGIN_BOUNDARY)) { |
| part = new Part(); |
| part.type = line.substring(Part.BEGIN_BOUNDARY.length(), line.length() - 5).trim(); |
| } else if (line.startsWith(Part.END_BOUNDARY)) { |
| parts.add(part); |
| part = null; |
| } else if (part != null && !line.contains(":") && !line.startsWith(" ")) { |
| part.content += line; |
| } |
| } |
| } |
| |
| for (Part part : parts) { |
| switch (part.type) { |
| case "PRIVATE KEY": |
| privateKey = part.toPrivateKey(null); |
| break; |
| case "ENCRYPTED PRIVATE KEY": |
| privateKey = part.toPrivateKey(password); |
| break; |
| case "CERTIFICATE": |
| case "X509 CERTIFICATE": |
| certificates.add(part.toCertificate()); |
| break; |
| } |
| } |
| } |
| |
| private class Part { |
| public static final String BEGIN_BOUNDARY = "-----BEGIN "; |
| public static final String END_BOUNDARY = "-----END "; |
| |
| public String type; |
| public String content = ""; |
| |
| private byte[] decode() { |
| return Base64.decodeBase64(content); |
| } |
| |
| public X509Certificate toCertificate() throws CertificateException { |
| CertificateFactory factory = CertificateFactory.getInstance("X.509"); |
| return (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(decode())); |
| } |
| |
| public PrivateKey toPrivateKey(String password) throws GeneralSecurityException, IOException { |
| KeySpec keySpec; |
| |
| if (password == null) { |
| keySpec = new PKCS8EncodedKeySpec(decode()); |
| } else { |
| EncryptedPrivateKeyInfo privateKeyInfo = new EncryptedPrivateKeyInfo(decode()); |
| SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(privateKeyInfo.getAlgName()); |
| SecretKey secretKey = secretKeyFactory.generateSecret(new PBEKeySpec(password.toCharArray())); |
| |
| Cipher cipher = Cipher.getInstance(privateKeyInfo.getAlgName()); |
| cipher.init(Cipher.DECRYPT_MODE, secretKey, privateKeyInfo.getAlgParameters()); |
| |
| keySpec = privateKeyInfo.getKeySpec(cipher); |
| } |
| |
| InvalidKeyException exception = new InvalidKeyException(sm.getString("jsse.pemParseError", filename)); |
| for (String algorithm : new String[] {"RSA", "DSA", "EC"}) { |
| try { |
| return KeyFactory.getInstance(algorithm).generatePrivate(keySpec); |
| } catch (InvalidKeySpecException e) { |
| exception.addSuppressed(e); |
| } |
| } |
| |
| throw exception; |
| } |
| } |
| } |