package cologne.eck.all_peas.control;
/*
* Peafactory - Production of Password Encryption Archives
* Copyright (C) 2015 Axel von dem Bruch
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* See: http://www.gnu.org/licenses/gpl-2.0.html
* You should have received a copy of the GNU General Public License
* along with this library.
*/
/**
* This is the base class for all Password Encrypted Archives (peas).
* This is the parent class of all PswDialog* classes.
*/
//import java.awt.Window;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import cologne.eck.all_peas.data.Attachments;
import cologne.eck.all_peas.data.PeaProperties;
import cologne.eck.all_peas.files.FileComposer;
import cologne.eck.all_peas.files.FileModel;
import cologne.eck.all_peas.files.FileTypePanel;
import cologne.eck.all_peas.gui.PeaDialog;
import cologne.eck.all_peas.gui.PswDialogView;
import cologne.eck.peafactory.crypto.CipherStuff;
import cologne.eck.peafactory.crypto.HashStuff;
import cologne.eck.peafactory.crypto.RandomStuff;
import cologne.eck.peafactory.crypto.kdf.KeyDerivation;
import cologne.eck.tools.Comparator;
import cologne.eck.tools.Converter;
import cologne.eck.tools.EntropyPool;
import cologne.eck.tools.UnexpectedValueException;
import cologne.eck.tools.Zeroizer;
import settings.*;
public abstract class PeaControl {
// for Image Lock PEA and Notebook PEA: the chosen file names to decrypt
private static ArrayList<String> availableFileNames = new ArrayList<String>();
private static PeaControl dialog;
private static char[] initializedPassword = null;
private static String errorMessage = null;
// the panel to display encrypted files
private static FileTypePanel encryptedFileTypePanel;
//============================================================
// --- abstract Methods:
//
// pre computation while windowOpening
// this is nowhere used yet, but maybe used later
/**
* Used for pre-computations while the user types the password
*/
public abstract void preComputeInThread();
//
// before exit: clear secret values
/**
* Overwriting secret value before closing the program
*/
public abstract void clearSecretValues();
/**
* Start the decryption
*/
public abstract void startDecryption();
// used in menu of PswDialogView:
/**
* Display a file chooser to initialize (encrypt)
* new files
*/
public abstract void initializeNewFiles();
// used in menu of PswDialogView:
/**
* Display a file chooser to open
* previously encrypted files
*/
public abstract void openEncryptedFiles();
// currently only used in PeaProperties.getFileType():
/**
* Get the chosen file names to decrypt
*
* @return an array of Strings of the selected file names
*/
public abstract String[] getSelectedFileNames();
/**
* Get the version of the used PEA as String
*
* @return the version as String
*/
public abstract String getVersion();
/**
* Get the year of the publication of the used PEA
*
* @return year of publication as String
*/
public abstract String getYearOfPublication();
/**
* Get the specific website of the used PEA, that is the
* web address following the language, for example "/html/file_pea.html"
*
* @return the website as String
*/
public abstract String getWebsite();
/**
* Get the link with the source code of the used PEA
*
* @return the link as String
*/
public abstract String getSourceLink();
/**
* Get the frame with the decrypted content
*
* @return the frame of the opened PEA
*/
public abstract Object getLockFrame();
/**
* Check if the given file is in the expected format
* for the specific PEA: For Notebook PEA: txt or rtf,
* for Image Lock PEA: png, jpg, bmp,gif,
* for File Lock PEA: nothing to check
*
* @param file
* @param easyCheck
* @param middleCheck
* @param expensiveCheck
* @return
*/
public abstract boolean checkFormat(File file, boolean easyCheck, boolean middleCheck, boolean expensiveCheck);
// only used in Notebook PEA:
/**
* Get currently used plain text
*
* @return the current plain text
*/
protected abstract byte[] getPlainBytes();
//============================================================
/**
* Start the execution of the PEA: stop the collection of entropy,
* add file to path file, start decryption process
*
* @param rememberPath remember the selected file names or not
*/
public final void startExecution(boolean rememberPath) {
PswDialogView.setStarted(true);
EntropyPool.getInstance().stopCollection();
if (PswDialogView.isInitializing() == false) {
// remember this file?
if (rememberPath == true ) {
String[] newNames;
// if (PeaProperties.getFileType().equals("file")) {
// set encrypted file names:
newNames = PeaControl.getDialog().getSelectedFileNames();
/* } else if (PeaProperties.getFileType().equals("text file")
|| PeaProperties.getFileType().equals("image")) {
int size = PeaControl.getAvailableFileNames().size();
ArrayList<String> list = PeaControl.getAvailableFileNames();
newNames = new String [size];
for (int i = 0; i < size; i++) {
newNames[i] = list.get(i);
}
} else {
newNames = new String[1];
//newNames[0] = PeaControl.getEncryptedFileName();
newNames[0] = PeaControl.getAvailableFileNames().get(0);
}
*/
PathFileManager.addFilesToPathFile( newNames );
}
/* if (PeaControl.getAvailableFileNames().get(0) == null
&& (PeaProperties.getFileType().equals("text") )) { // only Notes
return;
} else { */
PeaControl.getDialog().startDecryption();
// }
} else { // initializing
PeaControl.getDialog().startDecryption();
// TODO for all PEAs??? Always ask to remember? Or only check box?
// but all files from FileTypePanel
// remember this file?
/* if (PeaProperties.isInsideJar() == false) {
if (rememberPath == true ) {
String[] newName = { PeaControl.getAvailableFileNames().get(0) };
PathFileManager.addFilesToPathFile( newName);
}
} */
}
PswDialogView.setStarted(false);
}
public void setSaltForNewFiles() {
String[] shownFileNames = PeaControl.getEncryptedFileTypePanel().getValidFileNames(false);
if (shownFileNames != null && shownFileNames.length > 0){
// ask to take salt from files
//String questionMessage = "Do you want to open the new files together with the previous files?";
int result = PeaDialog.showQuestion(
//FileControl.dialogView,
PswDialogView.getView(),
PeaProperties.getBundle().getString("add_new_files_when_decrypted") + "\n"
+ PeaProperties.getBundle().getString("open_files_together"),
PeaProperties.getBundle().getString("init_new_file"),
0, 1);
if (result == 0){ // yes ok
boolean saltSuccess = setSaltForEncryptedFiles(shownFileNames);
if (saltSuccess == false) {
// reset salt if password failed before:
// set new salt to attach to file and xor with programRandomBytes
KeyDerivation.setSalt(
new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize()));
PeaDialog.showMessage(
PswDialogView.getView(),
PeaProperties.getBundle().getString("operation_failed")
+ "\n" + PeaProperties.getBundle().getString("open_files_separately"));
System.err.println("New files can not be opened together with previous files");
}
} else {
// reset salt if password failed before:
// set new salt to attach to file and xor with programRandomBytes
KeyDerivation.setSalt(
new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize()));
}
} else {
// reset salt if password failed before:
// set new salt to attach to file and xor with programRandomBytes
KeyDerivation.setSalt(
new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize()));
}
}
/**
* Derive the key from the password. Performs an initial
* hash and derives the key using the selected key derivation function
*
* @param pswInputChars the password
* @return the derived key in required size
*/
public final static byte[] deriveKeyFromPsw(char[] pswInputChars) {
if (pswInputChars == null) {
pswInputChars = "no password".toCharArray();
} else if (pswInputChars.length == 0) {
pswInputChars = "no password".toCharArray();
}
//System.out.println("PeaControl deriveKeyFromPsw\n"
// + " pswInputChars: " + new String(pswInputChars));
byte[] pswInputBytes = Converter.chars2bytes(pswInputChars);
//Help.printBytes("PeaControl deriveKeyFromPsw \n pswInputBytes", pswInputBytes);
//Help.printBytes("PeaControl deriveKeyFromPsw \n programRandomBytes", Attachments.getProgramRandomBytes());
if (pswInputBytes.length > 0) {
Zeroizer.zero(pswInputChars);
}
// prevent using a zero password:
pswInputChars = null;
//=============
// derive key:
//
// 1. initial hash, reasons:
// - Bcrypt limited password length,
// - password remains n RAM in Bcrypt and Scrypt
// - key file property
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(pswInputBytes);
}
byte[] pswHash = HashStuff.generateHKDFBytes(pswInputBytes,
Attachments.getProgramRandomBytes(), // if no key file property: this is a fixed string
64); // size of pswHash: 64 bytes
Zeroizer.zero(pswInputBytes);
//Help.printBytes("PeaControl deriveKeyFromPsw \n pswHash", pswHash);
if (PeaProperties.getWorkingMode().equals("-t")){
Comparator.checkNullVector(pswHash);
}
// 2. derive key from selected KDF:
byte[] keyMaterial = KeyDerivation.getKdf().deriveKey(pswHash);
Zeroizer.zero(pswHash);
return keyMaterial;
}
protected final byte[] getKeyMaterial() {
//-------------------------------------------------------------------------------
// Check if t is alive, if so: wait...
if (PswDialogView.getThread() != null) {
if (PswDialogView.getThread().isAlive()) { // this should never happen...
System.err.println("PeaControl: WhileTypingThread still alive!");
try {
PswDialogView.getThread().join();
} catch (InterruptedException e) {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("PeaControl: Joining thread interrupted");
e.printStackTrace();
}
}
}
}
//---------------------------------------------------------------------
// get password
char[] pswInputChars = null;
if (PswDialogView.isInitializing() == false){
pswInputChars = PswDialogView.getPassword();
} else {
if (PeaProperties.getFileType().equals("text file") || PeaProperties.getFileType().equals("image")){
pswInputChars = Arrays.copyOf(initializedPassword, initializedPassword.length);
} else {
pswInputChars = initializedPassword;
}
}
PswDialogView.clearPassword();
//System.out.println("PeaControl getKeyMaterial, pswInputChars: " + new String(pswInputChars) );
byte[] keyMaterial = deriveKeyFromPsw(pswInputChars);
if (PeaProperties.getFileType().equals("image") || PeaProperties.getFileType().equals("text file")){
// do not zeroize
// the password is needed to decrypt the image or
// to decrypt the file (text file)
} else {
Zeroizer.zero(initializedPassword);
initializedPassword = null;
}
//System.out.println("initialized Password: " + initializedPassword);
if (keyMaterial == null) {
PswDialogView.getView().displayErrorMessages("Key derivation failed.\n"
+ "Program bug.", true);
PswDialogView.clearPassword();
}
return keyMaterial;
}
/**
* Initialize required values for execution: hash, cipher,
* KDF, KDF parameters, programRandomBytes, salt, file identifier
*/
public final static void initializeVariables() {
HashStuff.setHashAlgo(PeaSettings.getHashAlgo() );
HashStuff.setHMacAlgo(PeaSettings.getHashAlgo() );
CipherStuff.setCipherAlgo(PeaSettings.getCipherAlgo() );
KeyDerivation.setKdf( PeaSettings.getKdfScheme() );
KeyDerivation.settCost(PeaSettings.getIterations() );
KeyDerivation.setmCost(PeaSettings.getMemory() );
KeyDerivation.setArg3(PeaSettings.getParallelization());
KeyDerivation.setVersionString(PeaSettings.getVersionString() );
Attachments.setProgramRandomBytes(PeaSettings.getProgramRandomBytes() );
Attachments.setFileIdentifier(PeaSettings.getFileIdentifier() );
// create model, composer and view of files:
FileModel model = new FileModel();
FileComposer fc = new FileComposer(model);
fc.setPlainModus(false);// encrypted files
/* FileTypePanel ftp = new FileTypePanel(350, 250, false, false,
fc, PswDialogView.getView());
PeaProperties.setTypePanel(ftp);
encryptedFileTypePanel = ftp;
*/
encryptedFileTypePanel = new FileTypePanel(350, 250, false, false,
fc, PswDialogView.getView());
PeaProperties.setTypePanel(encryptedFileTypePanel);
}
/* public static final void initializePassword() {
NewPasswordDialog.setRandomCollector(true);
NewPasswordDialog newPswDialog = NewPasswordDialog.getInstance(
PswDialogView.getView(), PeaProperties.getBundle().getString("initialization"), null,
PswDialogView.getView().getLocation());
dialog.setInitializedPassword( newPswDialog.getDialogInput()
);
NewPasswordDialog.setRandomCollector(false);
if (newPswDialog.getDialogInput() == null){
System.exit(0);
} else {
if (PeaProperties.getFileType().equals("image")) {
PswDialogView.setMessage("select_image", true);
}
dialog.startExecution(false);
}
}*/
// If a blank PEA is started first:
public final byte[] initializeCiphertext(byte[] ciphertext){
// salt must be set manually and will be attached to the file later
// in encryption step
byte[] attachedSalt = new RandomStuff().createRandomBytes(KeyDerivation.getSaltSize());
if ( ! PeaProperties.getFileType().equals("file") ){//PeaSettings.getBound() == false) {
KeyDerivation.setSalt(attachedSalt);
}
// attach salt:
ciphertext = Attachments.attachBytes(ciphertext, attachedSalt);
// attach fileIdentifier
ciphertext = Attachments.attachBytes(ciphertext, Attachments.getFileIdentifier());
return ciphertext;
}
// if the PAE is not bonded to a content: the salt is attached
public final void handleAttachedSalt(byte[] ciphertext) {
byte[] tmp = new byte[KeyDerivation.getSaltSize()];
System.arraycopy(ciphertext,
ciphertext.length
- Attachments.getFileIdentifierSize()
- KeyDerivation.getSaltSize(),
tmp, 0, tmp.length);
KeyDerivation.setSalt(tmp);
}
//== file functions: ===============================================================
// TODO replace with FileModel and remove
/**
* Set the salt from previous files from FileTypePanel
*
* @param fileNames file names from panel
*
* @return true: successfully set the salt
* false: salt setting failed
*/
public boolean setSaltForEncryptedFiles(String[] fileNames){
if (fileNames == null || fileNames.length == 0){
return false;
}
String saltContainingFile = null;
// look for the first file with salt (no directory)
for (int i = 0; i < fileNames.length; i++) {
if (new File(fileNames[i]).isFile() ){
saltContainingFile = fileNames[i];
break;
} else {
continue;
}
}
if (saltContainingFile == null) { // no file found
System.err.println("missing salt containing file");
PswDialogView.setMessage(PeaProperties.getBundle().getString("no_valid_file_selected" + " (salt missing)"), true);
return false;
} else {
if (PeaProperties.getWorkingMode().equals("-t")){
System.out.println("Get salt from file " + saltContainingFile );
}
}
// get and set the attached salt:
int saltLen = KeyDerivation.getSaltSize();
byte[] end = null;
try {
end = Attachments.getEndBytesOfFile(
saltContainingFile,//chosenFileNames[0],
Attachments.getFileIdentifierSize()
+ saltLen);
byte[] attachedSalt = new byte[saltLen];
System.arraycopy(end, 0, attachedSalt, 0, saltLen);
// reset salt if password failed before:
KeyDerivation.setSalt(attachedSalt);
} catch (IOException e) {
System.err.println(
"###" + // translate here, not in PswDialogView
PeaProperties.getBundle().getString("file_inappropriate")
+ saltContainingFile//chosenFileNames[0]
+ PeaProperties.getBundle().getString("caused_error")
+ e.toString() + ", " + e.getMessage() + "\n"
+ PeaProperties.getBundle().getString("attached_salt_not_found"));
return false;
} catch (Exception e) {
PswDialogView.setMessage("unexpected_error", true);
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println(e.toString() + ", " + e.getMessage());
e.printStackTrace();
}
return false;
}
return true;
}
/**
* Check a password for a cipher text: check if the pswIdentifier
* is the encrypted String "_PEA_FACTORY_ID_".
* The nonce is taken from the cipher text,
* the cipher text must in the original state,
* (not modified: (fileIdentifier, salt and nonce not cut)
*
* @param cipherText the original cipher text to check
* @param key the key to use for decryption
*
* @return null if the password does not fail,
* an error message otherwise
*/
public final static String checkPassword(byte[] cipherText, byte[] key) {
if (cipherText == null) {
if (PeaProperties.getWorkingMode().equals("-t")) {
new UnexpectedValueException("cipherText", "byte[]", "is null").printDescription();
}
}
if (key == null) {
if (PeaProperties.getWorkingMode().equals("-t")) {
new UnexpectedValueException("key", "byte[]", "is null").printDescription();
}
}
// check the size of the cipher text:
if (cipherText.length < (Attachments.getFileIdentifierSize()
+ KeyDerivation.getSaltSize()
+ Attachments.getNonceSize()
+ Attachments.getPswIdentifierSize() ) ){
return PeaProperties.getBundle().getString("unsuitable_ciphertext_len");
}
// get pswIdentifier from cipher text:
byte[] pswIdentifier = new byte[Attachments.getPswIdentifierSize()];
System.arraycopy(cipherText, cipherText.length
- Attachments.getFileIdentifierSize()
- KeyDerivation.getSaltSize()
- Attachments.getNonceSize()
- Attachments.getPswIdentifierSize(),
pswIdentifier, 0, pswIdentifier.length);
// get nonce from cipher text:
byte[] nonce = new byte[ Attachments.getNonceSize()];
System.arraycopy(cipherText, cipherText.length
- Attachments.getFileIdentifierSize()
- KeyDerivation.getSaltSize()
- Attachments.getNonceSize(),
nonce, 0, nonce.length);
boolean result = Attachments.checkPswIdentifier(pswIdentifier, key, nonce);
if (result == true) {
return null;
} else {
return PeaProperties.getBundle().getString("password_failed");
}
}
// TODO replace with FileModel and remove
/**
* Checks access to a file and the file identifier
*
* @param fileName the file to check
* @param addFileIfValid add file to the list of available files if it is valid or not,
* this list is for EditorPEA, ImagePEA, NotesPEA - FilePea manages
* its own list of files in FileComposer
* @return null if the file is valid, otherwise an error message
*/
/* public final static String checkFile(String fileName, boolean addFileIfValid) {
//System.out.println("PeaControl check: " + fileName);
fileName = fileName.trim(); // ignore whitespace from manually editing the path file
File file = new File( fileName );
if (file.exists() && file.isFile() && file.canRead() && file.canWrite() ) {
RandomAccessFile f = null;
try {
f = new RandomAccessFile(file, "rwd");
//System.out.println("PeaControl checkFile: " + fileName);
if ( Attachments.checkFileIdentifier(f, false) == true) {
f.close();
//============================
// - - - Success: - - - - - -
//============================
if (addFileIfValid == true) {
//System.out.println("PeaControl check file added: " + fileName);
PeaControl.addAvailableFileName(fileName);
}
return null;
} else {
f.close();
//System.out.println("file identifier failed : " + fileName );
return PeaProperties.getBundle().getString("not_encrypted_with_this_archive")
+ " " + fileName;
}
} catch (FileNotFoundException e) {
System.err.println(e.toString());
return fileName + ": " + e;
} catch (IOException e) {
System.err.println(e.toString());
return fileName + ": " + e;
} catch (Exception e) {
System.err.println(e.toString());
try {
f.close();
} catch (IOException e1) {
System.err.println(e1.toString());
}
return PeaProperties.getBundle().getString("unexpected_error") + fileName + ": " + e;
}
} else {
return PeaProperties.getBundle().getString("no_access") + ": " + fileName;
}
}
*/
// TODO delete and replace single file with FileModel
/**
* Used for PEAs which can open only one file at one time (simple notebook, image)
* and for editor (create CheckBoxes for valid files).
* Searches the previously encrypted files. Checks the default location,
* the external file name (set in PeaFactory) and the path file.
*
* @return String "PeaProperties.isInsideJar()" if file is inside the PEA or null
*/
/* public final static String searchSingleFile() {
boolean addFileIfValid = true; */
/* if (PeaProperties.getFileType().equals("text file")){
// do not add the file instead select by check boxes
addFileIfValid = false;
}*/
/* PeaControl.setErrorMessage("");
if (PeaProperties.getFileType().equals("image")){
URL url = ReadResources.class.getClassLoader().getResource("resources/text.lock");
try {
//InputStream ios = ReadResources.class.getClass().getClassLoader().getResourceAsStream("resources/text.lock");
InputStream ios = url.openStream();
ios.close();
PeaControl.addAvailableFileName(PeaProperties.getBundle().getString("image_inside_jar_archive"));
} catch (NullPointerException npe) {
//System.err.println("No image inside the jar archive: " + npe.toString());
} catch (FileNotFoundException fnfe) {
//System.out.println("No image inside the jar archive: " + fnfe.toString());
} catch (IOException ioe) {
System.err.println("Image inside the PEA: access failed: " + ioe.toString());
} catch (Exception e) {
System.err.println("Image inside the PEA: access failed: " + e.toString());
}
}
// 0. check default location
// is automatically added in checkFile if valid
checkFile("resources" + File.separator + "text.lock", addFileIfValid);
// 1. check externalFilePath:
if ( PeaSettings.getExternalFilePath() != null) {
String externalFileName = PeaSettings.getExternalFilePath();
if ( checkFile(externalFileName, addFileIfValid) == null ) { // access and fileIdentifier
// everything handled in checkFile
} else {
PswDialogView.setMessage("###" + externalFileName + " - " + PeaProperties.getBundle().getString("invalid_file"), true);
}
}
// 2. check path file
String[] pathNames = PathFileManager.accessPathFile();
//if (pathNames != null) for(int i=0;i<pathNames.length;i++) System.out.println("path file: " + pathNames[i]);
if (pathNames == null) {
//System.out.println("No result from previously saved file names in path file.");
} else {
for (int i = 0; i < pathNames.length; i++) {
if (checkFile(pathNames[i], addFileIfValid) == null ) {
// everything handled in checkFile
} else {
if (PeaProperties.getWorkingMode().equals("-t")){
System.err.println("invalid file in path file: " + pathNames[i]);
}
}
}
}
// check if at least one valid file was found:
if (availableFileNames.size() == 0) {
// no file found from standard location, external file name and path file:
System.err.println("No valid file found.");
PswDialogView.setMessage("###" + PeaProperties.getBundle().getString("no_valid_file_found")
+ " -> " + PeaProperties.getBundle().getString("open_or_initialize"), true);
}
return null;
} */
//==========================================
// Getter & Setter
public final static void setDialog(PeaControl _dialog) {
dialog = _dialog;
}
public final static PeaControl getDialog() {
return dialog;
}
public final static String getErrorMessage() {
return errorMessage;
}
public final static void setErrorMessage(String newErrorMessage) {
errorMessage = newErrorMessage;
}
/**
* Set the password from initialization process
* to use in key derivation
*
* @param psw the password to set
*/
public void setInitializedPassword(char[] psw){
initializedPassword = psw;
}
/**
* @return the encryptedFileTypePanel
*/
public static FileTypePanel getEncryptedFileTypePanel() {
return encryptedFileTypePanel;
}
/**
* @param encryptedFileTypePanel the encryptedFileTypePanel to set
*/
public static void setEncryptedFileTypePanel(FileTypePanel encryptedFileTypePanel) {
PeaControl.encryptedFileTypePanel = encryptedFileTypePanel;
}
/**
* Return the file name which can be decrypted with his PEA
*
* @return the availableFileNames
*/
public static ArrayList<String> getAvailableFileNames() {
return availableFileNames;
}
/**
* Set the available file names
*
* @param newAvailableFileNames the file names to set
*/
public static void setAvailableFileNames(ArrayList<String> newAvailableFileNames) {
availableFileNames = newAvailableFileNames;
}
/**
* @param availableFileNames the availableFileNames to set
*/
public static void addAvailableFileName(String availableFileName) {
if (PeaProperties.getFileType().equals("file")){
return;
}
if (PeaControl.availableFileNames.contains(availableFileName)){
// System.out.println("file already available: " + availableFileName);
} else {
PeaControl.availableFileNames.add(availableFileName);
// PswDialogView.addAvailableFile(availableFileName, false);// false: not new selected
//Collections.sort(PeaControl.availableFileNames);
}
}
}