package ch.ge.ve.offlineadmin.controller; /*- * #%L * Admin offline * %% * Copyright (C) 2015 - 2016 République et Canton de Genève * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import ch.ge.ve.commons.fileutils.OutputFilesPattern; import ch.ge.ve.commons.fileutils.StreamHasher; import ch.ge.ve.commons.properties.PropertyConfigurationException; import ch.ge.ve.commons.properties.PropertyConfigurationService; import ch.ge.ve.offlineadmin.exception.KeyGenerationRuntimeException; import ch.ge.ve.offlineadmin.exception.ProcessInterruptedException; import ch.ge.ve.offlineadmin.services.KeyGenerator; import ch.ge.ve.offlineadmin.util.FileUtils; import ch.ge.ve.offlineadmin.util.LogLevel; import ch.ge.ve.offlineadmin.util.PropertyConfigurationServiceFactory; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import org.apache.log4j.Logger; import javax.crypto.SecretKey; import javax.xml.bind.DatatypeConverter; import java.io.*; import java.nio.file.Path; import java.nio.file.Paths; import java.security.*; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ResourceBundle; import static ch.ge.ve.offlineadmin.util.SecurityConstants.*; /** * This controller manages the display of the KeyGeneration tab */ public class KeyGenerationController extends InterruptibleProcessController { private static final Logger LOGGER = Logger.getLogger(KeyGenerationController.class); @FXML private ResourceBundle resources; /* * By appending <tt>Controller</tt> to the <tt>fx:id</tt> of the node, its controller is resolved automatically */ @FXML private ConsoleOutputControl consoleOutputController; private StringProperty password1 = new SimpleStringProperty(); private StringProperty password2 = new SimpleStringProperty(); private String keySavedMessage; private FileUtils fileUtils; private PropertyConfigurationService propertyConfigurationService; private StreamHasher streamHasher; private PasswordDialogController passwordDialogController; private KeyGenerator keyGenerator; private ZoneId chZoneId = ZoneId.of("Europe/Zurich"); @FXML private void initialize() { fileUtils = new FileUtils(resources); propertyConfigurationService = new PropertyConfigurationServiceFactory().propertyConfigurationService(); streamHasher = new StreamHasher(propertyConfigurationService); passwordDialogController = new PasswordDialogController(resources, consoleOutputController); keyGenerator = new KeyGenerator(propertyConfigurationService); } /** * Generate the encryption keys. The steps are * <ol> * <li>password definition for group 1</li> * <li>password definition for group 2</li> * <li>generation of a symmetric key (for integrity verification)</li> * <li>generation of an asymmetric key pair</li> * <li>wrapping of the public key in a certificate signed with the private key</li> * <li>wrapping of the private key in a password protected keystore (using the concatenation of both passwords)</li> * <li>saving of the keys at a user defined location</li> * </ol> */ public void startKeyGeneration() { consoleOutputController.logOnScreen(resources.getString("key_generation.started")); try { passwordDialogController.promptForPasswords(password1, password2, true); SecretKey secretKey = keyGenerator.generateSecretKey(); KeyPair keyPair = keyGenerator.generateKeyPair(); X509Certificate certificate = keyGenerator.generateCertificate(keyPair); char[] password = (password1.getValue() + password2.getValue()).toCharArray(); KeyStore store = keyGenerator.createKeyStore(keyPair.getPrivate(), certificate, password); consoleOutputController.logOnScreen(resources.getString("key_generation.keys_generated")); saveKeys(secretKey, certificate, store, password); } catch (ProcessInterruptedException e) { consoleOutputController.logOnScreen(resources.getString("key_generation.process_interrupted"), LogLevel.WARN); LOGGER.warn(PROCESS_INTERRUPTED_MESSAGE, e); } catch (PropertyConfigurationException e) { handleInterruption(new ProcessInterruptedException(resources.getString("key_generation.exception_occurred"), e)); LOGGER.error(e); } } @Override protected ResourceBundle getResourceBundle() { return resources; } private void saveKeys(SecretKey secretKey, X509Certificate certificate, KeyStore store, char[] password) throws ProcessInterruptedException, PropertyConfigurationException { File selectedDirectory = fileUtils.getDirectory(resources.getString("key_generation.dir_chooser.title"), fileUtils.getUserHome()); if (selectedDirectory == null) { throw new ProcessInterruptedException("Directory selection cancelled"); } OutputFilesPattern outputFilesPattern = new OutputFilesPattern(); final ZonedDateTime now = ZonedDateTime.now(chZoneId); final String keysFolder = outputFilesPattern.injectParams(propertyConfigurationService.getConfigValue("keys_folder"), now); Path keyFolderPath = Paths.get(selectedDirectory.toString(), keysFolder); if (!keyFolderPath.toFile().mkdir()) { consoleOutputController.logOnScreen(String.format(resources.getString("already.existing_dir"), keyFolderPath), LogLevel.WARN); } String ctrlP12Filename = keyFolderPath + "/" + outputFilesPattern.injectParams(propertyConfigurationService.getConfigValue(CERT_PRIVATE_KEY_FILENAME), now); String ctrlDerFilename = keyFolderPath + "/" + outputFilesPattern.injectParams(propertyConfigurationService.getConfigValue(CERT_PUBLIC_KEY_FILENAME), now); String integrityKeyFilename = keyFolderPath + "/" + outputFilesPattern.injectParams(propertyConfigurationService.getConfigValue(INTEGRITY_KEY_FILENAME), now); keySavedMessage = resources.getString("key_generation.key_saved"); storePrivateKey(store, password, ctrlP12Filename); storeCertificate(certificate, ctrlDerFilename); computePublicKeyHash(propertyConfigurationService, ctrlDerFilename); saveIntegrityKey(secretKey, integrityKeyFilename); } private void storePrivateKey(KeyStore store, char[] password, String ctrlP12Filename) { try { FileOutputStream ctrlP12OutputStream = new FileOutputStream(ctrlP12Filename); store.store(ctrlP12OutputStream, password); ctrlP12OutputStream.close(); consoleOutputController.logOnScreen(String.format(keySavedMessage, ctrlP12Filename)); } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { throw new KeyGenerationRuntimeException("error while storing the private key", e); } } private void storeCertificate(X509Certificate certificate, String ctrlDerFilename) { try { FileOutputStream ctrlDerOutputStream = new FileOutputStream(ctrlDerFilename); ctrlDerOutputStream.write(certificate.getEncoded()); ctrlDerOutputStream.close(); consoleOutputController.logOnScreen(String.format(keySavedMessage, ctrlDerFilename)); } catch (IOException | CertificateEncodingException e) { throw new KeyGenerationRuntimeException("error while storing the certificate", e); } } private void computePublicKeyHash(PropertyConfigurationService propertyConfigurationService, String ctrlDerFilename) { try { FileInputStream ctrlDerInputStream = new FileInputStream(ctrlDerFilename); // needs to be SHA-1, since windows only displays the sha1 hash when viewing a certificate's details MessageDigest digest = MessageDigest.getInstance("SHA-1"); byte[] keyHash = streamHasher.computeHash(ctrlDerInputStream, digest); ctrlDerInputStream.close(); String hashString = DatatypeConverter.printHexBinary(keyHash); consoleOutputController.logOnScreen(String.format(resources.getString("key_generation.public_key_hash"), hashString)); } catch (NoSuchAlgorithmException | IOException e) { throw new KeyGenerationRuntimeException("error while generating the public key hash", e); } } private void saveIntegrityKey(SecretKey secretKey, String integrityKeyFilename) { try { ObjectOutputStream integrityKeyOutputStream = new ObjectOutputStream(new FileOutputStream(integrityKeyFilename)); integrityKeyOutputStream.writeObject(secretKey); integrityKeyOutputStream.close(); consoleOutputController.logOnScreen(String.format(keySavedMessage, integrityKeyFilename)); } catch (IOException e) { throw new KeyGenerationRuntimeException("error while saving the integrity key", e); } } protected void setFileUtils(FileUtils fileUtils) { this.fileUtils = fileUtils; } protected void setStreamHasher(StreamHasher streamHasher) { this.streamHasher = streamHasher; } protected void setPasswordDialogController(PasswordDialogController passwordDialogController) { this.passwordDialogController = passwordDialogController; } protected void setKeyGenerator(KeyGenerator keyGenerator) { this.keyGenerator = keyGenerator; } }