/* * 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;
/** * @param filename the filename to mention in error messages, not used for anything else. * @param fileStream the stream containing the pem(s). * @param password password to load the pem objects. * @param passwordFilename the password filename to mention in error messages, not used for anything else. * @param passwordFileStream stream containing the password to load the pem objects. * @param keyAlgorithm the algorithm to help to know how to load the objects (guessed if null). * * @throws IOException if input can't be read. * @throws GeneralSecurityException if input can't be parsed/loaded.
*/ public PEMFile(String filename, InputStream fileStream, String password, String passwordFilename,
InputStream passwordFileStream, String keyAlgorithm) throws IOException, GeneralSecurityException {
List<Part> parts = new ArrayList<>(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(fileStream, StandardCharsets.US_ASCII))) {
Part part = null;
String line; while ((line = reader.readLine()) != null) { if (line.startsWith(Part.BEGIN_BOUNDARY)) {
part = new Part();
part.type =
line.substring(Part.BEGIN_BOUNDARY.length(), line.length() - Part.FINISH_BOUNDARY.length())
.trim();
} elseif (line.startsWith(Part.END_BOUNDARY)) {
parts.add(part);
part = null;
} elseif (part != null && !line.contains(":") && !line.startsWith(" ")) {
part.content += line;
} elseif (part != null && line.contains(":") && !line.startsWith(" ")) { /* Something like DEK-Info: DES-EDE3-CBC,B5A53CB8B7E50064 */ if (line.startsWith("DEK-Info: ")) {
String[] pieces = line.split(" ");
pieces = pieces[1].split(","); if (pieces.length == 2) {
part.algorithm = pieces[0];
part.ivHex = pieces[1];
}
}
}
}
}
/** * Extracts the private key from an unencrypted PEMFile. * * @param keyAlgorithm Key algorithm if known or null if it needs to be obtained from the PEM file * @param format The format used to encode the private key * @param filename The name of the PEM file * * @return The clear text private key extracted from the PEM file * * @throws GeneralSecurityException If there is a cryptographic error processing the PEM file
*/ public PrivateKey toPrivateKey(String keyAlgorithm, Format format, String filename) throws GeneralSecurityException { return toPrivateKey(keyAlgorithm, format, filename, decode());
}
/** * Extracts the private key from an encrypted PEMFile. * * @param password Password to decrypt the private key * @param keyAlgorithm Key algorithm if known or null if it needs to be obtained from the PEM file * @param format The format used to encode the private key * @param filename The name of the PEM file * * @return The clear text private key extracted from the PEM file * * @throws GeneralSecurityException If there is a cryptographic error processing the PEM file * @throws IOException If there is an I/O error reading the PEM file
*/ public PrivateKey toPrivateKey(String password, String keyAlgorithm, Format format, String filename) throws GeneralSecurityException, IOException {
String secretKeyAlgorithm;
String cipherTransformation; int keyLength;
byte[] iv = fromHex(ivHex); // The IV is also used as salt for the password generation byte[] key = deriveKeyPBKDF1(keyLength, password, iv);
SecretKey secretKey = new SecretKeySpec(key, secretKeyAlgorithm);
Cipher cipher = Cipher.getInstance(cipherTransformation);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); byte[] pkcs1 = cipher.doFinal(decode()); return toPrivateKey(keyAlgorithm, format, filename, pkcs1);
} case PKCS8: { // Encrypted PEM file is PKCS8
Asn1Parser p = new Asn1Parser(decode());
//@formatter:off /* * RFC 5208 - PKCS #8 * RFC 8108 - PKCS #5 * * SEQ - PKCS #8 * SEQ - PKCS #8 encryptionAlgorithm * OID - PKCS #5 PBES2 OID * SEQ - PKCS #5 PBES2-params * SEQ - PKCS #5 PBES2 key derivation function * OID - PKCS #5 PBES2 KDF OID - must be PBKDF2 * SEQ - PKCS #5 PBKDF2-params * OCTET STRING - PKCS #5 PBKDF2 salt * INT - PKCS #5 PBKDF2 interationCount * INT - PKCS #5 PBKDF2 key length OPTIONAL * SEQ - PKCS #5 PBKDF2 PRF * OID - PKCS #5 PBKDF2 PRF OID * NULL - PKCS #5 PBKDF2 PRF parameters * SEQ - PKCS #5 PBES2 encryption scheme * OID - PKCS #5 PBES2 algorithm OID * OCTET STRING - PKCS #5 PBES2 algorithm iv * OCTET STRING - PKCS #8 encryptedData
*/ //@formatter:on
// Parse the PKCS #8 outer sequence and validate the length
p.parseTagSequence();
p.parseFullLength();
// Parse the PKCS #8 encryption algorithm
p.parseTagSequence();
p.parseLength();
// PBES2 OID byte[] oidEncryptionAlgorithm = p.parseOIDAsBytes(); /* * Implementation note. If other algorithms are ever supported, the KDF check below is likely to * need to be adjusted.
*/ if (!Arrays.equals(oidEncryptionAlgorithm, OID_PBES2)) { thrownew NoSuchAlgorithmException(sm.getString("pemFile.unknownPkcs8Algorithm",
toDottedOidString(oidEncryptionAlgorithm)));
}
// Encrypted data byte[] encryptedData = p.parseOctetString();
// ASN.1 parsing complete
// Build secret key to decrypt encrypted data byte[] key = deriveKeyPBKDF2("PBKDF2With" + prf, password, salt, iterationCount,
algorithm.getKeyLength());
SecretKey secretKey = new SecretKeySpec(key, algorithm.getSecretKeyAlgorithm());
// Configure algorithm to decrypt encrypted data
Cipher cipher = Cipher.getInstance(algorithm.getTransformation());
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
// Decrypt the encrypted key and call this method again to process the key byte[] decryptedData = cipher.doFinal(encryptedData); return toPrivateKey(keyAlgorithm, format, filename, decryptedData);
} default: { // Expected to be unencrypted thrownew NoSuchAlgorithmException(sm.getString("pemFile.unknownEncryptedFormat", format));
}
}
}
private PrivateKey toPrivateKey(String keyAlgorithm, Format format, String filename, byte[] source) throws GeneralSecurityException {
KeySpec keySpec = null;
switch (format) { case PKCS1: {
keySpec = parsePKCS1(source); break;
} case PKCS8: {
keySpec = new PKCS8EncodedKeySpec(source); break;
} case RFC5915: {
keySpec = new PKCS8EncodedKeySpec(rfc5915ToPkcs8(source)); break;
}
}
//@formatter:off /* * RFC5915: SEQ * INT value = 1 * OCTET STRING len = 32 bytes * [0] * OID named EC * [1] * BIT STRING len = 520 bits * * PKCS8: SEQ * INT value = 0 * SEQ * OID 1.2.840.10045.2.1 (EC public key) * OID named EC * OCTET STRING * SEQ * INT value = 1 * OCTET STRING len = 32 bytes * [1] * BIT STRING len = 520 bits *
*/ //@formatter:on privatebyte[] rfc5915ToPkcs8(byte[] source) { // Parse RFC 5915 format EC private key
Asn1Parser p = new Asn1Parser(source);
// Type (sequence)
p.parseTag(0x30); // Length
p.parseFullLength();
// Version
BigInteger version = p.parseInt(); if (version.intValue() != 1) { thrownew IllegalArgumentException(sm.getString("pemFile.notValidRFC5915"));
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.