org.wildfly.security.keystore.KeyStoreUtil Maven / Gradle / Ivy
/*
* JBoss, Home of Professional Open Source.
* Copyright 2018 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* Licensed 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.wildfly.security.keystore;
import static org.wildfly.security.keystore.ElytronMessages.log;
import static org.wildfly.security.provider.util.ProviderUtil.findProvider;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.channels.FileChannel;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Supplier;
import org.wildfly.common.iteration.CodePointIterator;
import org.wildfly.security.pem.Pem;
import org.wildfly.security.pem.PemEntry;
/**
* Utility functions for manipulating KeyStores.
*
* @author Martin Mazanek
*/
public class KeyStoreUtil {
private static final String BCFKS = "BCFKS";
private static final String BKS = "BKS";
private static final String JCEKS = "JCEKS";
private static final String JKS = "JKS";
private static final String PKCS12 = "PKCS12";
private static final String UBER = "UBER";
private static final int VERSION_0 = 0;
private static final int VERSION_1 = 1;
private static final int VERSION_2 = 2;
private static final int JCEKS_MAGIC = 0xcececece;
private static final int JKS_MAGIC = 0xfeedfeed;
private static final int SEQUENCE = 0x30000000;
private static final int PEM_MAGIC = 0x2d2d2d2d;
/**
* Tries to parse a keystore based on known recognizable patterns.
*
* This method can parse JKS, JCEKS, PKCS12, BKS, BCFKS and UBER key stores as well as PEM files. At first the
* method looks for recognizable patterns of JKS, JCEKS, PKCS12 and BKS key store types and tries to parse them if
* found. If the pattern recognition fails, brute force is used to load the key store.
*
* The provider supplier is used for loading the key stores.
*
* @param providers provider supplier for loading the keystore (must not be {@code null})
* @param providerName if specified only providers with this name will be used
* @param is the key store file input stream (must not be {@code null})
* @param filename the filename for prioritizing brute force checks using the file extension
* @param password password of the key store. Should be the empty string for PEM files.
* @return loaded key store if recognized
* @throws IOException
*/
public static KeyStore loadKeyStore(final Supplier providers, final String providerName, FileInputStream is, String filename, char[] password) throws IOException, KeyStoreException {
DataInputStream dis = new ResettableDataFileInputStream(is);
int firstInt = dis.readInt();
dis.reset();
KeyStore result = null;
if (firstInt == JKS_MAGIC) {
result = tryLoadKeystore(providers, providerName, dis, password, JKS);
} else if (firstInt == JCEKS_MAGIC) {
result = tryLoadKeystore(providers, providerName, dis, password, JCEKS);
} else if (firstInt == VERSION_1 || firstInt == VERSION_2) {
dis.reset();
dis.skip(32);
byte firstElementType = dis.readByte();
dis.reset();
if (firstElementType <= 5) {
result = tryLoadKeystore(providers, providerName, dis, password, BKS, UBER);
} else {
result = tryLoadKeystore(providers, providerName, dis, password, UBER, BKS);
}
} else if (firstInt == VERSION_0) {
result = tryLoadKeystore(providers, providerName, dis, password, UBER);
} else if ((firstInt & 0xff000000) == SEQUENCE) {
String[] parts = filename.split("\\.");
String extension = parts[parts.length - 1];
if (extension.startsWith("b") || extension.startsWith("B")) {
result = tryLoadKeystore(providers, providerName, dis, password, BCFKS, PKCS12);
} else {
result = tryLoadKeystore(providers, providerName, dis, password, PKCS12, BCFKS);
}
} else if (firstInt == PEM_MAGIC) {
result = loadPemAsKeyStore(is, password);
}
if (result == null) {
throw log.keyStoreTypeNotDetected();
}
return result;
}
private static KeyStore tryLoadKeystore(final Supplier providers, final String providerName, InputStream is, char[] password, String... types) {
for (String type : types) {
try {
log.debug("Searching provider for: " + type);
Provider provider = findProvider(providers, providerName, KeyStore.class, type);
if (provider == null) {
log.debug("Provider not found");
continue;
}
log.debug("Provider found: " + provider.getName());
KeyStore keystore = KeyStore.getInstance(type, provider);
is.reset();
keystore.load(is, password);
return keystore;
} catch (Exception e) {
log.debug("KeyStore is not of type " + type);
continue;
}
}
return null;
}
private static KeyStore loadPemAsKeyStore(FileInputStream is, char[] password) throws KeyStoreException, IOException {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
try {
keyStore.load(null);
} catch (Exception e) {
// won't happen
}
// try to load it as a PEM
PrivateKey pk = null;
List certificates = new ArrayList<>();
// Reading all of the file should not be an issue
byte[] pem = new byte[(int) is.getChannel().size()];
is.read(pem);
for (Iterator> it = Pem.parsePemContent(CodePointIterator.ofUtf8Bytes(pem)); it.hasNext(); ) {
Object entry = it.next().getEntry();
if (entry instanceof PrivateKey) {
// Private key
pk = (PrivateKey) entry;
} else if (entry instanceof Certificate) {
// Certificate
Certificate certificate = (Certificate) entry;
certificates.add(certificate);
}
}
if (pk != null) {
// A keystore
Certificate certificate = certificates.get(0);
String alias = certificate instanceof X509Certificate ? ((X509Certificate) certificate).getSubjectX500Principal().getName() : "key";
keyStore.setKeyEntry(alias, pk, password, certificates.toArray(new Certificate[0]));
} else {
// A truststore
int i = 1;
for (Certificate certificate : certificates) {
String alias = certificate instanceof X509Certificate ? ((X509Certificate)certificate).getSubjectX500Principal().getName() : Integer.toString(i++);
keyStore.setCertificateEntry(alias, certificate);
}
}
return keyStore;
}
//FileInputStream does not support marking by default and buffering unknown sized file doesn't seem right
private static class ResettableDataFileInputStream extends DataInputStream {
private FileChannel fc;
private long startingPosition = 0;
public ResettableDataFileInputStream(FileInputStream is) {
super(is);
this.fc = is.getChannel();
try {
this.startingPosition = fc.position();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void reset() throws IOException {
fc.position(startingPosition);
}
@Override
public long skip(long bytes) throws IOException {
fc.position(fc.position() + bytes);
return 0;
}
}
}