package com.puppetlabs.puppetdb.javaclient.ssl;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.security.KeyException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* Utility class that creates a KeySpecs from PEM files or DER encodings.
*/
public class KeySpecFactory {
/**
* Key types known to this factory
*/
public static enum KeyType {
/** PKCS#1 RSA private key */
PKCS1,
/** PCKS#8 private key */
PKCS8,
/** X509 public key */
X509
}
static class PKCS1Parser {
private int pos;
private final byte[] bytes;
private final BigInteger[] bis = new BigInteger[8];
PKCS1Parser(byte[] code) throws KeyException {
this.bytes = code;
pos = 0;
readTag(0x30);
readLength(); // Read and skip total key length
skipBI(); // Skip version
for(int i = 0; i < 8; ++i)
bis[i] = readBI();
}
private RSAPrivateCrtKeySpec keySpec() throws KeyException {
return new RSAPrivateCrtKeySpec(//
bis[0], // modulus
bis[1], // public exponent
bis[2], // private exponent
bis[3], // prime P
bis[4], // prime Q
bis[5], // prime exponent P
bis[6], // prime exponent Q
bis[7] // crt coefficient
);
}
private BigInteger readBI() throws KeyException {
int len = readBILength();
byte[] x = new byte[len];
System.arraycopy(bytes, pos, x, 0, len);
pos += len;
return new BigInteger(x);
}
private int readBILength() throws KeyException {
readTag(2);
return readLength();
}
private int readLength() {
int len = (bytes[pos++] & 0xff);
if((len & 0x80) == 0x80) {
int n = len & 0x7f;
len = 0;
for(int i = 0; i < n; ++i, ++pos)
len = (len << 8) | (bytes[pos] & 0xff);
}
return len;
}
private void skipBI() throws KeyException {
int len = readBILength();
pos += len;
}
private void readTag(int tag) throws KeyException {
if(bytes[pos] != tag)
throw new KeyException("Invalid tag: Expected " + Integer.toHexString(tag) + " but got " +
Integer.toHexString(bytes[pos] & 0xff) + " at offset " + pos);
++pos;
}
}
private static final String PKCS8_MARKER = "-----BEGIN PRIVATE KEY-----";
private static final String PKCS1_MARKER = "-----BEGIN RSA PRIVATE KEY-----";
private static final String X509_MARKER = "-----BEGIN PUBLIC KEY-----";
private static final String BEGIN_MARKER = "-----BEGIN ";
private static KeyType getKeyType(String marker, String inputName) throws KeyException {
if(PKCS1_MARKER.equals(marker))
return KeyType.PKCS1;
if(PKCS8_MARKER.equals(marker))
return KeyType.PKCS8;
if(X509_MARKER.equals(marker))
return KeyType.X509;
throw new KeyException("Marker not recognized as a key marker: " + marker + " in file: " + inputName);
}
private static byte[] readBytes(BufferedReader reader, String endMarker, String inputName) throws IOException {
String line = null;
StringBuilder bld = new StringBuilder();
while((line = reader.readLine()) != null) {
line = line.trim();
if(endMarker.equals(line))
return Base64.decodeBase64(bld.toString());
bld.append(line);
}
throw new IOException("No END marker found in: " + inputName);
}
/**
* Reads a key spec in PEM format from a reader.
*
* @param reader
* The reader from where the PEM contents is read
* @param inputName
* Name of input. Typically a file name (used in exceptions only).
* @return The created spec.
* @throws IOException
* @throws KeyException
*/
public static KeySpec readKeySpec(BufferedReader reader, String inputName) throws IOException, KeyException {
String line;
while((line = reader.readLine()) != null) {
if(line.startsWith(BEGIN_MARKER)) {
String marker = line.trim();
return readKeySpec(getKeyType(marker, inputName), readBytes(reader, marker.replace("BEGIN", "END"), inputName));
}
}
throw new IOException("No BEGIN marker found in: " + inputName);
}
/**
* Reads a key spec from a PEM file.
*
* @param file
* The PEM file to read from
* @return The created key spec
* @throws IOException
* @throws KeyException
*/
public static KeySpec readKeySpec(File file) throws IOException, KeyException {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), "ASCII"));
try {
return readKeySpec(reader, file.getAbsolutePath());
}
finally {
reader.close();
}
}
/**
* Reads a key spec in from bytes.
*
* @param keyType
* The type to use when decoding the bytes.
* @param keyData
* The reader from where the PEM contents is read
* @return The created spec.
* @throws IOException
* @throws KeyException
*/
public static KeySpec readKeySpec(KeyType keyType, byte[] keyData) throws IOException, KeyException {
switch(keyType) {
case PKCS1:
return new PKCS1Parser(keyData).keySpec();
case PKCS8:
return new PKCS8EncodedKeySpec(keyData);
default:
return new X509EncodedKeySpec(keyData);
}
}
}