package com.idega.core.ldap.client.cbutil; /** * <p>This is a grab bag of useful static functions * related to security - mainly doing conversions * between PEM and DER.</p> * * (nb: PEM = 'Privacy Enhanced Mail' format,<br> * while DER = 'Destinguished Encoding Rules' ASN1 data.<br> * - PEM is usually base64 encoded DER data, with some minor frills.) * */ public class CBSecurity { // XXX WARNING !!! XXX // Many of the following constants have derived from observation, rather than looking // At standards. ('cause I can't find the standards :-( ). - CB /** * Standard header for the base 64 encoded info block of a pem file. */ public static final byte[] PEM_BEGIN = (new String("-----BEGIN")) .getBytes(); /** * Standard footer for the base 64 encoded info block of a pem file. */ public static final byte[] PEM_END = (new String("-----END")) .getBytes(); /** * Standard header for a pem encoded certificate block */ public static final byte[] PEM_CERT_HEADER = new String("-----BEGIN CERTIFICATE-----").getBytes(); /** * Standard footer for a pem encoded certificate block */ public static final byte[] PEM_CERT_FOOTER = new String("-----END CERTIFICATE-----").getBytes(); /** * Standard header for a pem encoded encrypted private key block */ public static final byte[] PEM_ENC_KEY_HEADER = (new String("-----BEGIN ENCRYPTED PRIVATE KEY-----")) .getBytes(); /** * Standard header for a pem unencoded encrypted private key block */ public static final byte[] PEM_KEY_HEADER = (new String("-----BEGIN PRIVATE KEY-----")) .getBytes(); /** * Standard footer for a pem encoded encrypted private key block */ public static final byte[] PEM_ENC_KEY_FOOTER = (new String("-----END ENCRYPTED PRIVATE KEY-----")) .getBytes(); /** * Standard footer for a pem unencoded encrypted private key block */ public static final byte[] PEM_KEY_FOOTER = (new String("-----END PRIVATE KEY-----")) .getBytes(); /** * Standard header for a pem encoded RSA private key block */ public static final byte[] PEM_RSA_KEY_HEADER = new String("-----BEGIN RSA PRIVATE KEY-----").getBytes(); /** * Standard header for a pem encoded RSA private key block */ public static final byte[] PEM_RSA_KEY_FOOTER = new String("-----END RSA PRIVATE KEY-----").getBytes(); /** * Returns the position that a searchByte first appears in * a byte array. * * @param mainArray the byte array to search within * @param searchByte the byte to look for */ public static int indexOf(byte[] mainArray, byte searchByte) { return indexOf(mainArray, searchByte, 0); } /** * Returns the first position, greater than a given index, * that a searchByte first appears at within an array. * * @param mainArray the byte array to search within * @param searchByte the byte to look for */ public static int indexOf(byte[] mainArray, byte searchByte, int fromIndex) { int len = mainArray.length; // Some sanity checks... if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= len) { return -1; } // find the byte! for (int i=fromIndex; i<len; i++) { if (mainArray[i] == searchByte) { return i; } } return -1; // didn't find anything... } /** * <p>Tries to match a byte sequence within a larger * byte array.</p> * * <p>Students of Sun's java.lang.String class may * recognise some of this code :-). </p> * * @param mainArray the base array to search within. * @param searchSequence the short sequence to find the position of * within the main array. * * @return the index of the searchSequence within the main Array, * or -1 if not found. */ public static int indexOf(byte[] mainArray, byte[] searchSequence) { return indexOf(mainArray, searchSequence, 0); } /** * <p>Tries to match a byte sequence within a larger * byte array.</p> * * <p>Students of Sun's java.lang.String class may * recognise some of this code :-). </p> * * @param mainArray the base array to search within. * @param searchSequence the short sequence to find the position of * within the main array. * @param fromIndex the position to start searching from. * * @return the index of the searchSequence within the main Array, * or -1 if not found. */ public static int indexOf(byte[] mainArray, byte[] searchSequence, int fromIndex) { byte v1[] = mainArray; byte v2[] = searchSequence; int max = mainArray.length; // Sanity Check (including empty arrays special condition) if (fromIndex >= max) { if (mainArray.length == 0 && fromIndex == 0 && searchSequence.length == 0) { /* There is an empty array at index 0 in an empty array. */ return 0; } return -1; // from index too large } // Sanity check: set negative fromIndexes to 0. if (fromIndex < 0) { fromIndex = 0; } // Empty search array is matched immediately if (searchSequence.length == 0) { return fromIndex; } byte first = v2[0]; int i = fromIndex; startSearchForFirstChar: while (true) { /* Look for first character. */ while (i < max && v1[i] != first) { i++; } if (i >= max) // didn't find the sequence: return -1 { return -1; } /* Found first character, now look at the rest of v2 */ int j = i + 1; int end = j + searchSequence.length - 1; int k = 1; while (j < end) { if (v1[j++] != v2[k++]) { i++; /* Look for str's first char again. */ continue startSearchForFirstChar; } } return i; // Found whole string!!! } } /** * A simple check to see if a file is a PEM file, by * looking for PEM '------BEGIN...' and PEM '-----END' * tags. Note that this is Not Conclusive! */ public static boolean isPEM(byte[] test) { if (indexOf(test, PEM_BEGIN) == -1) { return false; // no PEM start string } if (indexOf(test, PEM_END) == -1) { return false; // no PEM end string } return true; // has PEM begin and end tags - probably a PEM! } /** * This takes a byte array of PEM (originally rfc 1421-1424, but * has drifted a bit) encoded data, such as might be read as raw * bytes from a text file, and converts it to 'raw' DER binary * data (i.e. a byte array with values from 0x0 to 0xFF). * * @param pem the pem data to convert * @return the converted raw data */ public static byte[] convertFromPEM(byte[] pem) { return convertFromPEM(pem, PEM_BEGIN, PEM_END); } /** * <p>This takes a byte array of PEM (originally rfc 1421-1424, but * has drifted a bit) encoded data, such as might be read as raw * bytes from a text file, and converts it to 'raw' DER binary * data (i.e. a byte array with values from 0x0 to 0xFF).</p> * * <p>In addition, this method allows the start of the PEM header * tag to be explicitly specified. This is useful when * a single file contains multiple data blocks (e.g. a cert *and* * a private key). Only the beginning of the stard header needs to * be specified; e.g. '-----BEGIN RSA PRIVATE' is sufficient, the * full header is not required. (The footer is assumed to be the * first block starting with '-----END...')</p> * * @param pem the pem data to convert * @return the converted raw data */ public static byte[] convertFromPEM(byte[] pem, byte[] header) { return convertFromPEM(pem, header, PEM_END); } /** * <p>This takes a byte array of PEM (originally rfc 1421-1424, but * has drifted a bit) encoded data representing an X509 certificate * and converts it to 'raw' DER binary * data (i.e. a byte array with values from 0x0 to 0xFF).</p> * * @param pem the pem data containing a certificate to convert * @return the converted raw data */ public static byte[] convertFromPEMCertificate(byte[] pem) { return convertFromPEM(pem, PEM_CERT_HEADER, PEM_END); } /** * <p>This takes a byte array of PEM (originally rfc 1421-1424, but * has drifted a bit) encoded data, such as might be read as raw * bytes from a text file, and converts it to 'raw' DER binary * data (i.e. a byte array with values from 0x0 to 0xFF).</p> * * <p>In addition, this method allows the start of the PEM header * and footer tag to be explicitly specified. This is useful when * a single file contains multiple data blocks (e.g. a cert *and* * a private key). Only the beginning of the headers needs to * be specified; e.g. '-----BEGIN RSA PRIVATE' is sufficient, the * full header/footer is not required.</p> * * @param pem the pem data to convert * @return the converted raw data */ public static byte[] convertFromPEM(byte[] pem, byte[] header, byte[] footer) { int start, end; start = indexOf(pem, header); end = indexOf(pem, footer); if (start == -1 || end == -1) { return null; // Something wrong - no headers! } start = indexOf(pem, (byte) '\n', start) + 1; // skip past any more text, by avoiding all lines less than 64 characters long... int next; while ((next = indexOf(pem, (byte) '\n', start)) < start+64) { if (next == -1) { break; // - maybe for a very, very short file? } start = next + 1; // keep looking for short lines... } if (start == -1) { return null; } int len = end - start; byte[] data = new byte[len]; System.arraycopy(pem, start, data, 0, len); // remove the PEM fluff from tbe base 64 data, stick in 'data' return CBBase64.decode(data); // return the raw binary data } /** * This takes an array of raw data representing a DER encoded X509 certificate, * and base64 encodes it, adding PEM style -----BEGIN CERTIFICATE----- * and -----END CERTIFICATE----- tags. * * @param der the DER encoded data */ public static byte[] convertToPEMCertificate(byte[] der) { return convertToPEM(der, PEM_CERT_HEADER, PEM_CERT_FOOTER); } /** * This takes an array of raw data representing a DER encoded RSA private key, * and base64 encodes it, adding PEM style -----BEGIN CERTIFICATE----- * and -----END CERTIFICATE----- tags. * * @param der the DER encoded data */ public static byte[] convertToPEMRSAPrivateKey(byte[] der) { return convertToPEM(der, PEM_RSA_KEY_HEADER, PEM_RSA_KEY_FOOTER); } /** * This takes an array of raw data representing an * Encrypted DER encoded private key (probably pkcs 8), * and base64 encodes it, adding PEM style -----BEGIN CERTIFICATE----- * and -----END CERTIFICATE----- tags. * * @param der the DER encoded data */ public static byte[] convertToPEMEncryptedPrivateKey(byte[] der) { return convertToPEM(der, PEM_ENC_KEY_HEADER, PEM_ENC_KEY_FOOTER); } public static byte[] convertToPEMPrivateKey(byte[] der) { return convertToPEM(der, PEM_KEY_HEADER, PEM_KEY_FOOTER); } protected static byte[] convertToPEM(byte[] der, byte[] header, byte[] footer) { try { byte[] base64Data = CBBase64.encodeFormatted(der, 0, 64); // theoretically may throw exception (should never happen)... int len = header.length + 1 + base64Data.length + footer.length + 1; // '+1's for '\n' chars. byte[] pem = new byte[len]; int pos = 0; System.arraycopy(header, 0, pem, 0, header.length); pos += header.length; pem[pos++] = (byte) '\n'; System.arraycopy(base64Data, 0, pem, pos, base64Data.length); pos += base64Data.length; System.arraycopy(footer, 0, pem, pos, footer.length); pos += footer.length; pem[pos] = (byte) '\n'; base64Data = null; der = null; return pem; } catch (Exception e) { System.err.println("error decoding pem file: " + e); return null; } } }