package mykeynote.keynote;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import mykeynote.exceptions.ExceptionString;
import mykeynote.exceptions.FileCreateException;
import mykeynote.exceptions.FileDeleteException;
import mykeynote.exceptions.FileNotWritableException;
import mykeynote.exceptions.FileWriteException;
import mykeynote.exceptions.keynote.MalformedKeyException;
import mykeynote.exceptions.keynote.NoAuthorizerFoundException;
import mykeynote.exceptions.keynote.RetValStringNotParsableException;
import mykeynote.exceptions.keynote.SignatureVerificationException;
import mykeynote.exceptions.keynote.SyntaxError;
import mykeynote.exceptions.keynote.cl.KeyNoteCLException;
import mykeynote.exceptions.keynote.cl.KeyNoteCLProcessCreationException;
import mykeynote.exceptions.keynote.cl.UnknownCLException;
import mykeynote.misc.CommonFunctions;
import mykeynote.misc.FormatConstants;
import mykeynote.server.Report;
import mykeynote.server.configuration.Configuration;
public class KeyNoteCL {
private CommonFunctions common = new CommonFunctions();
private static Configuration config = null;
private static Report report = null;
private static final String badend = "did not verify!";
private static final String sigverstart = "Signature on assertion ";
private static final String okend = "verified.";
private static final String auth = "authorizer:";
private static final String signature = "signature:\n";
private static final String syntaxerror = "Syntax error while parsing assertion ";
private static final KeyNoteCommon knc = new KeyNoteCommon();
//defining the process variables
private InputStream stdin;
private InputStreamReader isr;
private InputStream stderr;
private InputStreamReader esr;
private BufferedReader ibr;
private BufferedReader ebr;
private Process process;
private KeyNoteCommon keynoteCommon = new KeyNoteCommon();
/**
* The default constructor. Note that if you don't use multiple
* configurations and/or reports, you need only one instance as
* it is thread save.
* @param config The configuration
* @param report The report
*/
public KeyNoteCL(Configuration config,Report report){
KeyNoteCL.config = config;
KeyNoteCL.report = report;
}
/**
* This method is used to start the keynote program and check what the permissions
* for the given set of credentials and resource is. Note that some configuration
* options are missing, which are automatically inserted: <br>
* The file where the <b>return values</b> reside:
* @param unique The unique id
* @param report The report
* @param resource The resource, which we want to use.
* @param key The file, where the public key resides.
* @return Returns the keynote permission string.
* @throws IOException
* @throws RetValStringNotParsableException
* @throws KeyNoteCLProcessCreationException
* @throws KeyNoteCLException
* @throws InterruptedException
*/
public String verify(String unique, Report report, File resource, File key) throws IOException, RetValStringNotParsableException, KeyNoteCLProcessCreationException, KeyNoteCLException, InterruptedException{
File env = keynoteCommon.getEnvironmentFile(unique, report, resource, false);
String retvalString = keynoteCommon.getReturnValueString(unique, report, resource);
File assertion = keynoteCommon.getAssertionFile(unique, report, resource, false);
return verify(unique, report, env, retvalString, assertion , key);
}
/**
* This method is used to run the keynote program and get the answer.
* @param unique The unique identifier.
* @param environmentFile The environment file.
* @param returnValueFile The file, where the possible answers, separated by a comma and no space, are stored.
* @param assertionFile The assertion file.
* @param key The public key, given as a String.
* @return Return the keynote answer.
* @throws KeyNoteCLProcessCreationException This error reports that the keynote process could not be started.
* @throws IOException An IOException is thrown only if a line could not be read correctly.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws RetValStringNotParsableException This exception is thrown if the return value String is not parsable
*/
public String verify(String unique, File environmentFile, File returnValueFile,
File assertionFile, File key) throws KeyNoteCLProcessCreationException, KeyNoteCLException, InterruptedException, IOException, RetValStringNotParsableException{
//read the return value file
ArrayList<String> returnValuesArray = common.readFile(returnValueFile);
//get the 1st line as it holds the return values
String returnValues = returnValuesArray.get(0);
//let the other method do the work
String answer = verify(unique, report, environmentFile, returnValues, assertionFile, key);
//return the answer
return answer;
}
/**
* This method is used to run the keynote program and get the answer.
* @param unique The unique identifier.
* @param environment The environment file.
* @param returnValues The possible answers, separated by a comma and no space.
* @param assertion The assertion file.
* @param key The public key, given as a file.
* @return Return the keynote answer.
* @throws KeyNoteCLProcessCreationException This error reports that the keynote process could not be started.
* @throws IOException An IOException is thrown only if a line could not be read correctly.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws RetValStringNotParsableException This exception is thrown if the return value String is not parsable
*/
public String verify(String unique, Report report, File environment, String returnValues,
File assertion, File key) throws KeyNoteCLProcessCreationException, KeyNoteCLException, InterruptedException, IOException, RetValStringNotParsableException{
if(!keynoteCommon.isRetValString(returnValues)){
throw new RetValStringNotParsableException(unique, report, String.format(ExceptionString.RETVALSTRINGPARSEEXCEPTION.getExceptionString(), returnValues));
}
if(!environment.exists()){
report.reportErrorLog(unique, String.format(ExceptionString.FILENOTFOUND.getExceptionString(), environment.getAbsolutePath()));
throw new FileNotFoundException();
}
if(!assertion.exists()){
report.reportErrorLog(unique, String.format(ExceptionString.FILENOTFOUND.getExceptionString(), assertion.getAbsolutePath()));
throw new FileNotFoundException();
}
//create the process and execute the keynote command
if(key == null){
createVerifyProcess(unique, environment.getAbsolutePath() ,returnValues, assertion.getAbsolutePath());
} else {
createVerifyProcess(unique, environment.getAbsolutePath() ,returnValues, assertion.getAbsolutePath(), key.getAbsolutePath());
}
//open the IOs to read the answer
openIO(unique);
//get the answer
String answer = getAnswer(unique, KeyNoteString.VERIFYM.getString());
/* The answer has the following format: Query result = <answer> that
* means the real answer starts at the 15char */
answer = answer.substring(15);
report.reportDebugLog(unique, "KeyNote answer: " + answer);
//Close the IOs
closeIO(unique);
//and return it
return answer;
}
/**
* This method is used to run the keynote program and get the answer.
* @param unique The unique identifier.
* @param environment The environment file.
* @param returnValues The possible answers, separated by a comma and no space.
* @param assertion The assertion file.
* @param key The public key, given as a file.
* @return Return the keynote answer.
* @throws KeyNoteCLProcessCreationException This error reports that the keynote process could not be started.
* @throws IOException An IOException is thrown only if a line could not be read correctly.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws RetValStringNotParsableException This exception is thrown if the return value String is not parsable
*/
public String verify(String unique, Report report, File environment,
File assertion, File key, File resource) throws KeyNoteCLProcessCreationException, KeyNoteCLException, InterruptedException, IOException, RetValStringNotParsableException{
String returnValues = keynoteCommon.getReturnValueString(unique, report, resource);
if(!keynoteCommon.isRetValString(returnValues)){
throw new RetValStringNotParsableException(unique, report, String.format(ExceptionString.RETVALSTRINGPARSEEXCEPTION.getExceptionString(), returnValues));
}
if(!environment.exists()){
report.reportErrorLog(unique, String.format(ExceptionString.FILENOTFOUND.getExceptionString(), environment.getAbsolutePath()));
throw new FileNotFoundException();
}
if(!assertion.exists()){
report.reportErrorLog(unique, String.format(ExceptionString.FILENOTFOUND.getExceptionString(), assertion.getAbsolutePath()));
throw new FileNotFoundException();
}
//create the process and execute the keynote command
if(key == null){
createVerifyProcess(unique, environment.getAbsolutePath() ,returnValues, assertion.getAbsolutePath());
} else {
createVerifyProcess(unique, environment.getAbsolutePath() ,returnValues, assertion.getAbsolutePath(), key.getAbsolutePath());
}
//open the IOs to read the answer
openIO(unique);
//get the answer
String answer = getAnswer(unique, KeyNoteString.VERIFYM.getString());
/* The answer has the following format: Query result = <answer> that
* means the real answer starts at the 15char */
answer = answer.substring(15);
report.reportDebugLog(unique, "KeyNote answer: " + answer);
//Close the IOs
closeIO(unique);
//and return it
return answer;
}
private String getAnswer(String unique, String module) throws InterruptedException, IOException, KeyNoteCLException{
String answer = "", temp;
int exitval;
try {
exitval = process.waitFor();
} catch (InterruptedException e){
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLINTERRUPTEDEXCEPTION.getExceptionString(), module));
throw e;
}
if (exitval == 0) {
try{
if((temp =ibr.readLine()) != null){
answer = temp;
while((temp =ibr.readLine()) != null){
answer += "\n" + temp;
}
}
} catch (IOException e){
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), module));
throw e;
}
//closeIO(unique);
return answer;
} else {
//that means that keynote exited with error.
String message = "";
try{
if((message = ebr.readLine()) != null){
answer = message;
while ((message = ebr.readLine()) != null){
answer += "\n" + message;
}
}
} catch (IOException e){
report.reportErrorLog(unique, String.format( ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), module));
}
//closeIO(unique);
if(answer.length() < 1){//no message at all
throw new KeyNoteCLException(unique, report, String.format(ExceptionString.KEYNOTECLEXCEPTIONNM.getExceptionString(), exitval));
}
throw new KeyNoteCLException(unique, report, String.format(ExceptionString.KEYNOTECLEXCEPTION.getExceptionString(), exitval, answer));
}
}
private void createSigVerifyProcess(String unique, String filename){
String command = String.format("%s sigver %s", config.getPathToKeynote(), filename);
report.reportDebugLog(unique, command);
try{
process = Runtime.getRuntime().exec(command);
} catch (IOException e){
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), KeyNoteString.SIGVERIFYM.getString()));
}
}
/**
* This method verifies if a file is signed correctly. If the method executes with no
* exception throw, that means that all signatures were verified.
* @param unique The unique identifier
* @param filename The file name denoted as string
* @throws InterruptedException This exception is thrown if the keynote CL
* process is terminated.
* @throws IOException An IOException is thrown when there was a problem reading
* the output of the stdin/stderr.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws SignatureVerificationException This exception is thrown, when some of the certificate signatures
* were not verified.
* @throws SyntaxError
* @throws UnknownCLException
*/
public void sigVerify(String unique, String filename) throws InterruptedException, IOException, KeyNoteCLException, SignatureVerificationException, UnknownCLException, SyntaxError{
//create the process
createSigVerifyProcess(unique, filename);
//open the IO
openIO(unique);
//verify
getSigVerifyAnswer(unique, KeyNoteString.SIGVERIFYM.getString());
}
/**
* This method verifies if a file is signed correctly. If the method executes with no
* exception throw, that means that all signatures were verified. This method uses
* {@link #sign(String, String)}, by getting the absolute path of the file.
* @param unique The unique identifier
* @param file The file, which has to be checked.
* @throws InterruptedException This exception is thrown if the keynote CL
* process is terminated.
* @throws IOException An IOException is thrown when there was a problem reading
* the output of the stdin/stderr.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws SignatureVerificationException This exception is thrown, when some of the certificate signatures
* were not verified.
* @throws SyntaxError
* @throws UnknownCLException
*/
public void sigVerify(String unique, File file) throws InterruptedException, IOException, KeyNoteCLException, SignatureVerificationException, UnknownCLException, SyntaxError{
sigVerify(unique, file.getAbsolutePath());
}
private void getSigVerifyAnswer(String unique, String module) throws IOException, InterruptedException, SignatureVerificationException, KeyNoteCLException, UnknownCLException, SyntaxError{
String answer = "";
int exitval;
try {
exitval = process.waitFor();
} catch (InterruptedException e){
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLINTERRUPTEDEXCEPTION.getExceptionString(), module));
closeIO(unique);
throw e;
}
String errors = "";
if(exitval == 0) {
try {
while ((answer = ibr.readLine()) != null){
report.reportDebugLog(unique, answer);
if(answer.startsWith(sigverstart)){
if(answer.endsWith(badend)){
errors += (String) answer.subSequence(sigverstart.length(), answer.lastIndexOf(badend)-1) + " ";
} else if(!answer.endsWith(okend)){
throw new UnknownCLException(unique, String.format(ExceptionString.UNKNOWNCLEXCEPTION.getExceptionString(), answer), report);
}
} else {
throw new UnknownCLException(unique, String.format(ExceptionString.UNKNOWNCLEXCEPTION.getExceptionString(), answer), report);
}
}
while((answer = ebr.readLine()) !=null){
report.reportDebugLog(unique, answer);
if(answer.startsWith(syntaxerror)){
throw new SyntaxError(unique, answer, report);
}
if(answer.startsWith(sigverstart) && answer.endsWith(badend)){
errors += (String) answer.subSequence(sigverstart.length(), answer.lastIndexOf(badend)-1);
errors.concat(" ");
} else {
throw new UnknownCLException(unique, String.format(ExceptionString.UNKNOWNCLEXCEPTION.getExceptionString(), answer), report);
}
}
} catch (IOException e) {
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), module));
closeIO(unique);
throw e;
}
if(errors.length() > 0) {
//errors = errors.substring(0, errors.length() - 2); //remove the last space bar
closeIO(unique);
throw new SignatureVerificationException(unique, String.format(ExceptionString.SIGNATUREVERIFICATIONEXCEPTION.getExceptionString(), errors), report);
}
closeIO(unique);
return;
} else {
//that means that keynote exited with error.
String message = "";
try{
while ((message = ibr.readLine()) != null)
answer += message + "\n";
} catch (IOException e){
report.reportErrorLog(unique, String.format( ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), module));
}
closeIO(unique);
if(answer.length() < 1){//no message at all
throw new KeyNoteCLException(unique, report, String.format(ExceptionString.KEYNOTECLEXCEPTIONNM.getExceptionString(), exitval));
}
throw new KeyNoteCLException(unique, report, String.format(ExceptionString.KEYNOTECLEXCEPTION.getExceptionString(), exitval, answer));
}
}
private void normalizeCertificate(String unique, File file) throws FileNotFoundException, IOException, FileDeleteException, FileCreateException, FileNotWritableException, FileWriteException, MalformedKeyException{
//get the usable key
String usablekey = keynoteCommon.getNormalizedKey(unique, report, file);
//write the new key to a file
common.writeFile(new File(file.getAbsoluteFile() + FormatConstants.ASSERTIONFORMAT), usablekey, true);
}
/**
* This method is the general way to go and sign a file.
* @param unique The unique id
* @param report The report
* @param fileToSign The assertion, which needs to be signed
* @param keyFile The file, where the private key resides
* @param outputFile The file, where the signed assertion is to be saved
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws IOException An IOException is thrown only if a line could not be read correctly.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws FileDeleteException This exception is thrown if the file could not be deleted.
* @throws FileCreateException This exception is thrown if the file could not be created.
* @throws FileNotWritableException This exception is not writable due to write permeations.
* @throws FileWriteException This exception indicates, that during the writing to a file
* an error has occurred.
* @throws MalformedKeyException The supplied key is not in a supported format
*/
public void sign(String unique, Report report, File fileToSign, File keyFile, File outputFile) throws InterruptedException, IOException, KeyNoteCLException, FileDeleteException, FileCreateException, FileNotWritableException, FileWriteException, MalformedKeyException{
ArrayList<String> answer = common.readFile(fileToSign);
answer.add(signature);
common.writeFile(outputFile, answer, true);
String signature = sign(unique, report, outputFile, keyFile);
common.writeFile(outputFile, signature, false);
}
/**
* This method signs an assertion. It can be used for advanced signing. When unsure use
* if you need this, use {@link #sign(String, Report, File, File, File)}.
* @param unique The unique identifier.
* @param report The current report.
* @param file The file, which has to be signed.
* @param key The file, where the key resides and should be used to
* sign the assertion.
* @return Returns the signature, without the starting <b>signature:\n</b>, which should be in the file to sign
* @throws IOException An IOException is thrown only if a line could not be read correctly.
* @throws InterruptedException This exception indicates that the keynote program was terminated before it ended.
* @throws KeyNoteCLException This error indicates that the keynote program exited with a value, different then 0,
* which means that an error has occurred.
* @throws MalformedKeyException The supplied key is not in a supported format
*/
public String sign(String unique, Report report, File fileToSign, File keyFile) throws InterruptedException, IOException, KeyNoteCLException, MalformedKeyException{
common.checkFile(fileToSign);
common.checkFile(keyFile);
//String hashAlgo = knc.getKNHashAlgorithmIdentifier(unique, report, keyFile);;
String hashAlgo = knc.getKeyPem(unique, report, keyFile);
hashAlgo = knc.getKNSignAlgorithmIdentifierHelper(hashAlgo);
String command = String.format("%s sign %s %s %s",
config.getPathToKeynote(), hashAlgo,
fileToSign.getAbsoluteFile(), keyFile.getAbsolutePath());
report.reportDebugLog(unique, command);
try{
process = Runtime.getRuntime().exec(command);
} catch (IOException e){
report.reportErrorLog(unique, String.format(ExceptionString.KEYNOTECLIOEXCEPTION.getExceptionString(), KeyNoteString.SIGVERIFYM.getString()));
}
String answer = "";
openIO(unique);
answer += getAnswer(unique, KeyNoteString.SIGNFYM.getString());
closeIO(unique);
return answer;
}
/**
* This method is depreciated in favor of {@link KeyNoteCL#sign(String, Report, File, File)
* @deprecated
*/
public String sign(String unique, String input) throws InterruptedException, IOException,
NoAuthorizerFoundException, FileDeleteException, FileCreateException, FileNotWritableException,
FileWriteException, MalformedKeyException{
File output = new File(config.getTempDir(), unique + FormatConstants.CREDFORMAT);
File in = new File(input);
String privateKey = copyAdd(unique, in, output);
String execute = String.format("%s sign \"%s\" %s %s",
config.getPathToKeynote(), "sig-x509-sha1-base64:",
output.getAbsoluteFile(), privateKey);
Process p = Runtime.getRuntime().exec(execute);
InputStream stdin = p.getInputStream();
InputStreamReader isr = new InputStreamReader(stdin);
InputStream stderr = p.getErrorStream();
InputStreamReader esr = new InputStreamReader(stderr);
BufferedReader ibr = new BufferedReader(isr);
BufferedReader ebr = new BufferedReader(esr);
int exitval;
String ans = "", temp;
if ((exitval = p.waitFor()) == 0){
while((temp = ibr.readLine()) != null){
ans += temp + "\n";
}
closeIO(unique);
common.writeFile(output, ans, false);
return output.getAbsolutePath();
} else{
while((temp = ebr.readLine()) != null){
ans += temp + "\n";
}
System.err.println(ans);
throw new RuntimeException("The program ended with an error code of " + exitval);
}
}
@Deprecated
private String copyAdd(String unique,File src, File dst) throws IOException, NoAuthorizerFoundException, FileDeleteException, FileCreateException, FileNotWritableException, FileWriteException, MalformedKeyException {
ArrayList<String> temp = common.readFile(src);
int i = 0, end = temp.size();
String s, name = "";
while(i < end){
if((s = temp.get(i)).startsWith(auth)){
// lets take the name of the key and change the key with the name
// needed to be able to start the keynote
if(s.indexOf("\"") > 0){
name = s.substring(s.indexOf("\"")+1, s.length()-1);}
else{
name = s.substring(s.lastIndexOf(' ')+1);
}
// now name is the private key, lets change it with a real key
// because the cert should be only 1 line (and start with
// authorizer:) there is
// no need to do something else here
File key = new File(config.getPrivateKeyDir(), name + FormatConstants.KEYPEM);
File cert = new File(config.getPublicKeyDir(), name + FormatConstants.CERTPEM);
File normalized = new File(config.getPublicKeyDir(), name + FormatConstants.ASSERTIONFORMAT);
normalizeCertificate(unique, cert);
s = common.readFile(normalized).get(0);
temp.set(i, s);
// lets add the signature and save
temp.add("signature:");
// save to dest
end = temp.size();
i = 0;
s = "";
while (i < end)
s += temp.get(i++) +"\n";
common.writeFile(dst, s, true);
// we are ready :)
return key.getAbsolutePath();
}
i++;
}
throw new NoAuthorizerFoundException(unique, report, "The authorizer " + name + " was not found");
}
private void createVerifyProcess(String unique, String enviroment,
String returnValues, String assertion, String key) throws KeyNoteCLProcessCreationException {
String command = String.format("%s verify -e %s -r %s -l %s -k %s",
config.getPathToKeynote(), enviroment, returnValues, assertion, key);
report.reportDebugLog(unique, command);
try{
process = Runtime.getRuntime().exec(command);
} catch (IOException e){
throw new KeyNoteCLProcessCreationException(unique, report, ExceptionString.KEYNOTECLPROCESSCREATIONEXCEPTION.getExceptionString());
}
}
private void createVerifyProcess(String unique, String enviroment,
String returnValues, String assertion) throws KeyNoteCLProcessCreationException {
String command = String.format("%s verify -e %s -r %s -l %s",
config.getPathToKeynote(), enviroment, returnValues, assertion);
report.reportDebugLog(unique, command);
try{
process = Runtime.getRuntime().exec(command);
} catch (IOException e){
throw new KeyNoteCLProcessCreationException(unique, report, ExceptionString.KEYNOTECLPROCESSCREATIONEXCEPTION.getExceptionString());
}
}
private void openIO(String unique){
stdin = process.getInputStream();
isr = new InputStreamReader(stdin);
stderr = process.getErrorStream();
esr = new InputStreamReader(stderr);
ibr = new BufferedReader(isr);
ebr = new BufferedReader(esr);
}
private void closeIO(String unique){
int end = 6; //we have only six
for(int i = 0; i < end; i ++ ){
try{
closeIOHelper(i);
} catch (IOException e) {
report.reportErrorLog(unique, ExceptionString.KEYNOTECLIOCLOSEEXCEPTION.getExceptionString());
//try to close the rest
}
}
}
/**
* This is a helper method, which just closes the different readers/writers.
* @param i The number of the reader/writer to be closed.
* @throws IOException An IOException can occur when closing the reader/writer.
*/
private void closeIOHelper(int i) throws IOException{
switch (i){
case 0: ebr.close(); break;
case 1: ibr.close(); break;
case 2: esr.close(); break;
case 3: isr.close(); break;
case 4: stderr.close(); break;
case 5: stdin.close(); break;
default: break;
}
}
}