package mykeynote.keynote;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import mykeynote.exceptions.ExceptionString;
import mykeynote.exceptions.keynote.MalformedKeyException;
import mykeynote.exceptions.keynote.RetValStringNotParsableException;
import mykeynote.misc.CommonFunctions;
import mykeynote.misc.FormatConstants;
import mykeynote.server.Report;
public class KeyNoteCommon {
private CommonFunctions common = new CommonFunctions();
private boolean checkKey(String unique, Report report, ArrayList<String> array) throws MalformedKeyException {
int i = 0;
if (array.size() == 1) {
String check = array.get(0);
while (!check.startsWith(getKNAlgorithmIdentifierHelper(i))) {
i++;
if (getKNAlgorithmIdentifierHelper(i) == null) {
throw new MalformedKeyException(unique, report, String.format(ExceptionString.MALFORMEDKEYEXCEPTION.getExceptionString(),"only one line and no known algorithm identifier"));
}
}
// it seems that we have the key in right format
return true;
}
return false;
}
/**
* This method checks if size of the String array is capable
* of holding a key (array.size() > 0), and checks, if the
* size equals 2, if the last line is empty. If so, remove it.
*
* @param unique The unique id
* @param report The report
* @param array The array, which is to be checked.
* @throws MalformedKeyException This exception is thrown if the
* array size == 0.
*/
private void arraySize(String unique, Report report, ArrayList<String> array)
throws MalformedKeyException {
if (array.size() == 0) {
throw new MalformedKeyException(unique, report, String.format(ExceptionString.MALFORMEDKEYEXCEPTION.getExceptionString(),"the key file is empty"));
}
if (array.size() == 2) { // check if the last line is not just empty
if (array.get(1).length() < 1) {
array.remove(1); // if so, remove it
}
}
}
/**
* This method reads an array and returns the algorithm identifier
* for the corresponding PEM.
* @param unique The unique id
* @param report The report
* @param array The array, which is to be read.
* @return The PEM identifier as a String, represented in the algorithm identifier
* form: <b><algorithm>-<encoding>:</b>, example: <b>x509-base64:</b>.
* @throws MalformedKeyException This exception indicates that the PEM format is not supported.
*/
public String getPEMString(String unique, Report report, ArrayList<String> array) throws MalformedKeyException{
int i = getPEM(unique, report, array);
String pemString = getKNAlgorithmIdentifierHelper(i);
if(pemString != null){
return pemString;
}
throw new MalformedKeyException(unique, report, String.format(
ExceptionString.MALFORMEDKEYEXCEPTION
.getExceptionString(), "unknown pem type"));
}
/**
* Returns the PEM as an int.
* @param unique The unique
* @param report The report
* @param array The array, which is to be read.
* @return Returns the PEM as an int.
* @throws MalformedKeyException This exception indicates that the pem format is not supported.
*/
private int getPEM(String unique, Report report, ArrayList<String> array)
throws MalformedKeyException {
int i = 0;
while (!array.get(0).equalsIgnoreCase(getBeginingCertificate(i))) {
i++;
if (getBeginingCertificate(i) == null) {
throw new MalformedKeyException(unique, report, String.format(
ExceptionString.MALFORMEDKEYEXCEPTION
.getExceptionString(), "unknown key type"));
}
}
array.remove(0); // remote the first line, no use for it any more
return i;
}
/**
* This method takes an array and tries to remove the last line of the array,
* until it removes the PEM ending.
* @param unique The unique id
* @param report The report
* @param array The array, which is to be checked.
* @param i The integer id of the PEM.
* @throws MalformedKeyException This exception is thrown when the array does not
* hold the corresponding PEM ending.
*/
private void removePEMEnding(String unique, Report report, ArrayList<String> array, int i)
throws MalformedKeyException {
int i2 = array.size() - 1;
while (!array.get(i2).equalsIgnoreCase(getEndCertificate(i))) {
if (array.get(i2).length() > 1 || i2 <= 1) {
// i2 <=1, because if we remove the last element, that means
// there was no ending to the certificat
throw new MalformedKeyException(unique, report, String.format(
ExceptionString.MALFORMEDKEYEXCEPTION
.getExceptionString(),
"no appropriate ending for the key"));
}
array.remove(i2);
i2--;
}
// we remove the real last end of the certificate
array.remove(i2);
}
/**
* The normalized key starts with the algorithm identifier and then has all the
* key given as a one line String. This method helps us build the normalized
* key given the certificate form (without the starting/ending of the certificate).
* @param unique The unique id
* @param array The array, where the key resides, this array should exclude the beginning/ending
* of the certificate.
* @param i The id of the algorithm.
* @return Returns the key in an normalized form.
*/
private String normalizeKey(String unique, ArrayList<String> array, int i) {
String answer = getKNAlgorithmIdentifierHelper(i);
for (String s : array) {
answer += s;
}
return answer;
}
/**
* This method gets a key and transforms it from a authority format into a pem
* format
* @param unique The unique id
* @param report The report
* @param array The array, where the key is saved
* @return Returns the key in a pem format
* @throws MalformedKeyException The supplied key is not in a supported format
*/
public String getNormalizedKey(String unique, Report report, ArrayList<String> array)
throws MalformedKeyException {
// check if the array size is ok
arraySize(unique, report, array);
// maybe the key is already in the right type?
if (checkKey(unique, report, array)) {
return array.get(0); // if so, then we are finished
}
// lets see what kind of pem we have
int i = getPEM(unique, report, array);
// remove the last line
removePEMEnding(unique, report, array, i);
// and make the key only 1 line long and have the appropriate begining
return normalizeKey(unique, array, i);
}
/**
* This method gets a key and transforms it from a authority format into a pem
* format. It reads the given file, stores it into an ArrayList of Strings and
* gives it to {@link #getNormalizedKey(String, Report, ArrayList)}.
* @param unique The unique id
* @param report The report
* @param file The file, where the key is saved
* @return Returns the key in a pem format
* @throws MalformedKeyException The supplied key is not in a supported format
*/
public String getNormalizedKey(String unique, Report report, File file)
throws FileNotFoundException, IOException, MalformedKeyException {
// read the file
ArrayList<String> array = common.readFile(file);
// get the usable key
return getNormalizedKey(unique, report, array);
}
private String getBeginingCertificate(int i) {
switch (i) {
case 0:
return "-----BEGIN CERTIFICATE-----";
case 1:
return "-----BEGIN RSA PUBLIC KEY-----";
case 2:
return "-----BEGIN DSA PUBLIC KEY-----";
case 3:
return "-----BEGIN RSA PRIVATE KEY-----";
case 4:
return "-----BEGIN DSA PRIVATE KEY-----";
default:
return null;
}
}
private String getEndCertificate(int i) {
switch (i) {
case 0:
return "-----END CERTIFICATE-----";
case 1:
return "-----END RSA PUBLIC KEY-----";
case 2:
return "-----END DSA PUBLIC KEY-----";
case 3:
return "-----END RSA PUBLIC KEY-----";
case 4:
return "-----END DSA PUBLIC KEY-----";
default:
return null;
}
}
/**
* This method reads a file and checks if the algorithm identifier is a known one.
* @param unique The unique id
* @param report The report
* @param keyFile The file, where the key resides
* @return Returns the algorithm identifier.
* @throws FileNotFoundException The given file could not be found.
* @throws IOException Indicates that there was an IOException while reading the file.
* @throws MalformedKeyException This exception is thrown if after searching the full file, there was no known algorithm identifier.
*/
public String getKNAlgorithmIdentifier(String unique, Report report, File keyFile) throws FileNotFoundException, IOException, MalformedKeyException{
ArrayList<String> array = common.readFile(keyFile);
int i = 0;
if(array.size() < 1){
throw new MalformedKeyException(unique, report, String.format(ExceptionString.MALFORMEDKEYEXCEPTIONZEROLINES.getExceptionString()));
}
boolean iterate;
for(String line : array){
line = line.trim();
if(line.startsWith("\"")){
line = (String) line.subSequence(1, line.length());
}
iterate = true;
i = 0;
while(iterate){
if (!line.startsWith(getKNAlgorithmIdentifierHelper(i))) {
i++;
if (getKNAlgorithmIdentifierHelper(i) == null) {
iterate = false;
}
} else {
return getKNAlgorithmIdentifierHelper(i);
}
}
}
throw new MalformedKeyException(unique, report, String.format(ExceptionString.MALFORMEDKEYEXCEPTIONNOKEY.getExceptionString()));
}
public String getKNHashAlgorithmIdentifier(String unique, Report report, File keyFile) throws FileNotFoundException, IOException, MalformedKeyException{
String knAlgoUdentifier = getKNAlgorithmIdentifier(unique, report, keyFile);
return getKNSignAlgorithmIdentifierHelper(knAlgoUdentifier);
}
/**
* This method reads a file, which holds a certificate, and returns the type of the PEM certificate as a String.
* @param unique The unique ID
* @param report The report
* @param keyFile The certificate file
* @return Returns the type of PEM, which is hold there as a String
* @throws FileNotFoundException This exception is thrown if the supplied keyFile does not exist
* @throws MalformedKeyException This exception indicates that the supplied String in the keyFile could not
* be parsed as a PEM certificate.
* @throws IOException IOException while reading the file.
*/
public String getKeyPem(String unique, Report report, File keyFile) throws FileNotFoundException, MalformedKeyException, IOException{
int i = getPEM(unique, report, common.readFile(keyFile));
String keyPem;
//this is a workaround which forces the signature to be base64, not hex
if(i >= 3 && i <=5){
i = i-3;
}
keyPem = getKNAlgorithmIdentifierHelper(i);
if(keyPem == null){
throw new MalformedKeyException(unique, report, String.format(ExceptionString.MALFORMEDKEYEXCEPTIONZEROLINES.getExceptionString()));
}
return keyPem;
}
/**
* This is a comfort method, which uses {@link #getFileHelper(String, Report, File, String, boolean)}
* to locate the environment file.
* @param unique The unique id
* @param report The report
* @param resource The resource file
* @param checkFatherDir Should we check the father directory?
* @return Returns the environment file.
* @throws FileNotFoundException The file could not be found
*/
public File getEnvironmentFile(String unique, Report report, File resource, boolean checkFatherDir) throws FileNotFoundException{
return getFileHelper(unique, report, resource, FormatConstants.ENVFORMAT, checkFatherDir);
}
/**
* This method checks if for a given resource there is a return value file,
* then parses it and checks if there is a line, which resembles a return
* value String. <br>
* To determine the file, where it is supposed that the return value String
* resides, it uses {@link #getReturnValueFile(String, Report, File)}.
* @param unique The unique id
* @param report The report
* @param resource The resource
* @return Returns the return value String of a given resource.
* @throws RetValStringNotParsableException This exception indicates,
* that not a single line in the return value file, could be identified
* as a return value String.
* @throws FileNotFoundException The return value File, could not be found.
* @throws IOException IOException while reading the file.
*/
public String getReturnValueString(String unique, Report report, File resource) throws RetValStringNotParsableException, IOException{
File retval = getReturnValueFile(unique, report, resource, false);
ArrayList<String> strings = common.readFile(retval);
for(String string : strings){
if(isRetValString(string))
return string;
}
throw new RetValStringNotParsableException(unique, report, String.format(ExceptionString.RETVALSTRINGPARSEEXCEPTION.getExceptionString(), "in file " + retval.getAbsolutePath()));
}
/**
* This is a comfort method, which uses {@link #getFileHelper(String, Report, File, String, boolean)}
* to locate the return value file.
* @param unique The unique id
* @param report The report
* @param resource The resource file
* @param checkFatherDir Should we check the father directory?
* @return Returns the file, where we expect that the return value String resides.
* @throws FileNotFoundException The file could not be found
*/
public File getReturnValueFile(String unique, Report report, File resource, boolean checkFatherDir) throws FileNotFoundException{
return getFileHelper(unique, report, resource, FormatConstants.RETFORMAT, checkFatherDir);
}
/**
* This is a comfort method, which uses {@link #getFileHelper(String, Report, File, String, boolean)}
* to locate the assertion file.
* @param unique The unique id
* @param report The report
* @param resource The resource file
* @param checkFatherDir Should we check the father directory?
* @return Returns the assertion file.
* @throws FileNotFoundException The file could not be found
*/
public File getAssertionFile(String unique, Report report, File resource, boolean checkFatherDir) throws FileNotFoundException{
return getFileHelper(unique, report, resource, FormatConstants.ASSERTIONFORMAT, checkFatherDir);
}
/**
* This method gets a resource and tries to find the file with the corresponding suffix.
* In details, this method looks in the directory, where the resource resides, and finds
* the file, which starts with <b>"."</b> (only when the resource file itself did not start with ".")
* and ends with <b>"." + suffix</b>.
*
* @param unique The unique id
* @param report The report
* @param resource The resource file
* @param suffix The suffix
* @param checkFatherDir Should we check the father directory?
* @return Returns the file with the corresponding suffix for a given resource.
* @throws FileNotFoundException This exception is thrown if no file could
* be found, given the suffix
*
*/
public File getFileHelper(String unique, Report report, File resource, String suffix, boolean checkFatherDir) throws FileNotFoundException{
File fileHelper = null;
if(!resource.getName().startsWith(".")){
fileHelper = new File(resource.getParent(), "." + resource.getName()
+ suffix);
} else {
fileHelper = new File(resource.getParent(), resource.getName() +
suffix);
}
if(fileHelper.exists()){
return fileHelper;
}
if(checkFatherDir){
fileHelper = new File(resource.getParent(), "." + FormatConstants.DIRECTORY +
suffix);
}
if(!fileHelper.exists()){
report.reportErrorLog(unique, String.format(ExceptionString.RESOURCETYPENOTFOUNDEXCEPTION.getExceptionString(), suffix, resource.getName()));
throw new FileNotFoundException();
}
return fileHelper;
}
private String getKNAlgorithmIdentifierHelper(int i) {
switch (i) {
case 0:
return "x509-base64:";
case 1:
return "rsa-base64:";
case 2:
return "dsa-base64:";
case 3:
return "x509-hex:";
case 4:
return "rsa-hex:";
case 5:
return "dsa-hex:";
default:
return null;
}
}
/**
* This method takes an algorithm identifier and returns the appropriate
* sign algorithm identifier.
* @param algorithm The algorithm identifier
* @return Returns the sign algorithm identifier.
*/
public String getKNSignAlgorithmIdentifierHelper(String algorithm){
//we get for example "x509-base64:" and need to make it in the form
//of "sig-x509-sha1-base64:"
String begin = algorithm.substring(0, algorithm.indexOf("-"));
String end = algorithm.substring(algorithm.indexOf("-"));
return "sig-" + begin+ "-sha1" + end;
}
/* The chars definition, needed for {@link #isRetValString(String)}*/
private static final String CHARS = "[0-9a-zA-Z]+";
private static final String REGEX = String.format("^%s(,%s)*$", CHARS, CHARS);
/**
* This method is used to check if a given string is a return value String,
* this means if it is from the form <b>String,String,String...</b>
* @param retvalString The String that needs to be checked
* @return Returns <b>true</b> when it is from the given form, else returns
* <b>false</b>.
*/
public boolean isRetValString(String retvalString){
return Pattern.matches(REGEX, retvalString);
}
}