/* See LICENSE for licensing and NOTICE for copyright. */
package org.cryptacular.util;
import java.nio.CharBuffer;
/**
* Utility class with helper methods for common PEM encoding operations.
*
* @author Middleware Services
*/
public final class PemUtil
{
/** Line length. */
public static final int LINE_LENGTH = 64;
/** PEM encoding header start string. */
public static final String HEADER_BEGIN = "-----BEGIN";
/** PEM encoding footer start string. */
public static final String FOOTER_END = "-----END";
/** Procedure type tag for PEM-encoded private key in OpenSSL format. */
public static final String PROC_TYPE = "Proc-Type:";
/** Decryption infor tag for PEM-encoded private key in OpenSSL format. */
public static final String DEK_INFO = "DEK-Info:";
/** Private constructor of utility class. */
private PemUtil() {}
/**
* Determines whether the data in the given byte array is base64-encoded data of PEM encoding. The determination is
* made using as little data from the given array as necessary to make a reasonable determination about encoding.
*
* @param data Data to test for PEM encoding
*
* @return True if data appears to be PEM encoded, false otherwise.
*/
public static boolean isPem(final byte[] data)
{
final String start = new String(data, 0, 10, ByteUtil.ASCII_CHARSET).trim();
if (!start.startsWith(HEADER_BEGIN) && !start.startsWith(PROC_TYPE)) {
// Check all bytes in first line to make sure they are in the range
// of base64 character set encoding
for (int i = 0; i < LINE_LENGTH; i++) {
if (!isBase64Char(data[i])) {
// Last two bytes may be padding character '=' (61)
if (i > LINE_LENGTH - 3) {
if (data[i] != 61) {
return false;
}
} else {
return false;
}
}
}
}
return true;
}
/**
* Determines whether the given byte represents an ASCII character in the character set for base64 encoding.
*
* @param b Byte to test.
*
* @return True if the byte represents an ASCII character in the set of valid characters for base64 encoding, false
* otherwise. The padding character '=' is not considered valid since it may only appear at the end of a
* base64 encoded value.
*/
public static boolean isBase64Char(final byte b)
{
return !(b < 47 || b > 122 || b > 57 && b < 65 || b > 90 && b < 97) || b == 43;
}
/**
* Decodes a PEM-encoded cryptographic object into the raw bytes of its ASN.1 encoding. Header/footer data and
* metadata info, e.g. Proc-Type, are ignored.
*
* @param pem Bytes of PEM-encoded data to decode.
*
* @return ASN.1 encoded bytes.
*/
public static byte[] decode(final byte[] pem)
{
return decode(new String(pem, ByteUtil.ASCII_CHARSET));
}
/**
* Decodes a PEM-encoded cryptographic object into the raw bytes of its ASN.1 encoding. Header/footer data and
* metadata info, e.g. Proc-Type, are ignored.
*
* @param pem PEM-encoded data to decode.
*
* @return ASN.1 encoded bytes.
*/
public static byte[] decode(final String pem)
{
final CharBuffer line = CharBuffer.allocate(128);
final CharBuffer input = CharBuffer.wrap(pem);
final CharBuffer output = CharBuffer.allocate(pem.length());
char current;
while (input.hasRemaining()) {
current = input.get();
if (current == '\r') {
// Assume CRLF line endings, so discard next char before writing line
input.get();
writeLine(line, output);
} else if (current == '\n') {
writeLine(line, output);
} else {
line.put(current);
}
}
if (line.hasRemaining()) {
writeLine(line, output);
}
output.flip();
return CodecUtil.b64(output);
}
/**
* Copies a non-header line to the output buffer.
*
* @param line Line to consider writing.
* @param output Output buffer.
*/
private static void writeLine(final CharBuffer line, final CharBuffer output)
{
final String s = line.flip().toString();
if (
!(s.startsWith(HEADER_BEGIN) || s.startsWith(FOOTER_END) || s.startsWith(PROC_TYPE) || s.startsWith(DEK_INFO) ||
s.trim().length() == 0)) {
output.put(line);
}
line.clear();
}
}