From ed3c4236cfb886c8b592c2040cd8db214465328e Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 21 Jun 2018 17:45:22 +0200 Subject: [PATCH] Move backup related methods to Helper class --- .../AbstractPainlessOpenPgpStore.java | 1 - .../bouncycastle/PainlessOpenPgpProvider.java | 15 +++- .../ox/bouncycastle/BackupRestoreTest.java | 70 +++++++++++++++ .../smackx/ox/OpenPgpManager.java | 72 +++------------ .../smackx/ox/OpenPgpProvider.java | 3 + .../smackx/ox/SecretKeyBackupHelper.java | 89 +++++++++++++++++++ .../exception/InvalidBackupCodeException.java | 4 + .../ox/exception/NoBackupFoundException.java | 4 +- 8 files changed, 192 insertions(+), 66 deletions(-) create mode 100644 smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BackupRestoreTest.java create mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelper.java diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/AbstractPainlessOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/AbstractPainlessOpenPgpStore.java index 2f05d9999..7fce4c010 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/AbstractPainlessOpenPgpStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/AbstractPainlessOpenPgpStore.java @@ -38,7 +38,6 @@ public abstract class AbstractPainlessOpenPgpStore implements PainlessOpenPgpSto private OpenPgpV4Fingerprint primaryKeyFingerprint = null; private final SecretKeyRingProtector secretKeyRingProtector; - @Override public OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint() { return primaryKeyFingerprint; diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/PainlessOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/PainlessOpenPgpProvider.java index 95c14c740..88b6fb02d 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/PainlessOpenPgpProvider.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/PainlessOpenPgpProvider.java @@ -373,7 +373,12 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { if (publicKeyRings == null) { publicKeyRings = new PGPPublicKeyRingCollection(Collections.singleton(ring)); } else { - publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, ring); + try { + publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(publicKeyRings, ring); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.INFO, "Skip key " + Long.toHexString(ring.getPublicKey().getKeyID()) + + " as it is already in the public key ring."); + } } getStore().storePublicKeyRing(owner, publicKeyRings); } catch (PGPException e) { @@ -392,8 +397,6 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { throw new SmackOpenPgpException("Could not deserialize PGP secret key of " + owner.toString(), e); } - store.setPrimaryOpenPgpKeyPairFingerprint(getFingerprint(importSecretKeys.getPublicKey())); - if (!new BareJidUserId.SecRingSelectionStrategy().accept(owner, importSecretKeys)) { throw new MissingUserIdOnKeyException(owner, importSecretKeys.getPublicKey().getKeyID()); } @@ -426,6 +429,12 @@ public class PainlessOpenPgpProvider implements OpenPgpProvider { return getFingerprint(publicKeys.getPublicKey()); } + @Override + public OpenPgpV4Fingerprint importSecretKey(byte[] bytes) + throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException { + return importSecretKey(owner, bytes); + } + public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) { byte[] hex = Hex.encode(publicKey.getFingerprint()); return new OpenPgpV4Fingerprint(hex); diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BackupRestoreTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BackupRestoreTest.java new file mode 100644 index 000000000..d2031418b --- /dev/null +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BackupRestoreTest.java @@ -0,0 +1,70 @@ +package org.jivesoftware.smackx.ox.bouncycastle; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Arrays; +import java.util.Collections; + +import org.jivesoftware.smack.util.FileUtils; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import org.jivesoftware.smackx.ox.SecretKeyBackupHelper; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; +import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; +import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; +import org.jivesoftware.smackx.ox.util.KeyBytesAndFingerprint; + +import de.vanitasvitae.crypto.pgpainless.key.UnprotectedKeysProtector; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.jxmpp.jid.BareJid; +import org.jxmpp.jid.JidTestUtil; + +public class BackupRestoreTest extends OxTestSuite { + + private static final File backupPath = FileUtils.getTempDir("ox-backup"); + private static final File restorePath = FileUtils.getTempDir("ox-restore"); + private static final BareJid owner = JidTestUtil.BARE_JID_1; + + + @BeforeClass + @AfterClass + public static void deletePaths() { + FileUtils.deleteDirectory(backupPath); + FileUtils.deleteDirectory(restorePath); + } + + @Test + public void test() + throws NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException, + SmackOpenPgpException, MissingUserIdOnKeyException, InvalidBackupCodeException, + MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException { + FileBasedPainlessOpenPgpStore backupStore = new FileBasedPainlessOpenPgpStore(backupPath, new UnprotectedKeysProtector()); + FileBasedPainlessOpenPgpStore restoreStore = new FileBasedPainlessOpenPgpStore(restorePath, new UnprotectedKeysProtector()); + + PainlessOpenPgpProvider backupProvider = new PainlessOpenPgpProvider(owner, backupStore); + PainlessOpenPgpProvider restoreProvider = new PainlessOpenPgpProvider(owner, restoreStore); + + KeyBytesAndFingerprint key = backupProvider.generateOpenPgpKeyPair(owner); + backupProvider.importSecretKey(key.getBytes()); + + final String backupCode = SecretKeyBackupHelper.generateBackupPassword(); + SecretkeyElement backup = SecretKeyBackupHelper.createSecretkeyElement(backupProvider, owner, Collections.singleton(key.getFingerprint()), backupCode); + + OpenPgpV4Fingerprint fingerprint = SecretKeyBackupHelper.restoreSecretKeyBackup(restoreProvider, backup, backupCode); + + assertEquals(key.getFingerprint(), fingerprint); + + assertTrue(Arrays.equals(backupStore.getSecretKeyRingBytes(owner, fingerprint), restoreStore.getSecretKeyRingBytes(owner, fingerprint))); + assertTrue(Arrays.equals(backupStore.getPublicKeyRingBytes(owner, fingerprint), restoreStore.getPublicKeyRingBytes(owner, fingerprint))); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java index 4134a0989..7207f6f65 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java @@ -21,12 +21,10 @@ import static org.jivesoftware.smackx.ox.util.PubSubDelegate.PEP_NODE_PUBLIC_KEY import static org.jivesoftware.smackx.ox.util.PubSubDelegate.fetchPubkey; import static org.jivesoftware.smackx.ox.util.PubSubDelegate.publishPublicKey; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.SecureRandom; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -65,6 +63,7 @@ import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; +import org.jivesoftware.smackx.ox.exception.NoBackupFoundException; import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; import org.jivesoftware.smackx.ox.listener.internal.CryptElementReceivedListener; import org.jivesoftware.smackx.ox.listener.internal.SignElementReceivedListener; @@ -297,11 +296,11 @@ public final class OpenPgpManager extends Manager { BareJid ownJid = connection().getUser().asBareJid(); - String backupCode = generateBackupPassword(); + String backupCode = SecretKeyBackupHelper.generateBackupPassword(); Set availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints(ownJid); Set selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs); - SecretkeyElement secretKey = createSecretkeyElement(ownJid, selectedKeyPairs, backupCode); + SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode); PubSubDelegate.depositSecretKey(connection(), secretKey); displayCodeCallback.displayBackupCode(backupCode); @@ -339,18 +338,19 @@ public final class OpenPgpManager extends Manager { SecretKeyRestoreSelectionCallback selectionCallback) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, SmackOpenPgpException, - InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException { + InvalidBackupCodeException, SmackException.NotLoggedInException, IOException, MissingUserIdOnKeyException, + NoBackupFoundException { throwIfNoProviderSet(); throwIfNotAuthenticated(); SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection()); if (backup == null) { - // TODO - return; + throw new NoBackupFoundException(); } - byte[] encrypted = Base64.decode(backup.getB64Data()); - byte[] decrypted = provider.symmetricallyDecryptWithPassword(encrypted, codeCallback.askForBackupCode()); - provider.importSecretKey(connection().getUser().asBareJid(), decrypted); - // TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try. + + String backupCode = codeCallback.askForBackupCode(); + + OpenPgpV4Fingerprint fingerprint = SecretKeyBackupHelper.restoreSecretKeyBackup(provider, backup, backupCode); + provider.getStore().setPrimaryOpenPgpKeyPairFingerprint(fingerprint); } /** @@ -505,34 +505,6 @@ public final class OpenPgpManager extends Manager { provider.importPublicKey(owner, Base64.decode(base64)); } - /** - * Generate a secure backup code. - * - * @see XEP-0373 §5.3 - * @return backup code - */ - private static String generateBackupPassword() { - final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; - SecureRandom random = new SecureRandom(); - StringBuilder code = new StringBuilder(); - - // 6 blocks - for (int i = 0; i < 6; i++) { - - // of 4 chars - for (int j = 0; j < 4; j++) { - char c = alphabet.charAt(random.nextInt(alphabet.length())); - code.append(c); - } - - // dash after every block except the last one - if (i != 5) { - code.append('-'); - } - } - return code.toString(); - } - private PubkeyElement createPubkeyElement(BareJid owner, OpenPgpV4Fingerprint fingerprint, Date date) @@ -545,28 +517,6 @@ public final class OpenPgpManager extends Manager { return new PubkeyElement(new PubkeyElement.PubkeyDataElement(Base64.encode(bytes)), date); } - private SecretkeyElement createSecretkeyElement(BareJid owner, - Set fingerprints, - String backupCode) throws SmackOpenPgpException, IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - for (OpenPgpV4Fingerprint fingerprint : fingerprints) { - try { - byte[] bytes = provider.getStore().getSecretKeyRingBytes(owner, fingerprint); - buffer.write(bytes); - } catch (MissingOpenPgpKeyPairException | IOException e) { - LOGGER.log(Level.WARNING, "Cannot backup secret key " + Long.toHexString(fingerprint.getKeyId()) + ".", e); - } - - } - return createSecretkeyElement(buffer.toByteArray(), backupCode); - } - - private SecretkeyElement createSecretkeyElement(byte[] keys, String backupCode) - throws SmackOpenPgpException, IOException { - byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode); - return new SecretkeyElement(Base64.encode(encrypted)); - } - void addSigncryptReceivedListener(SigncryptElementReceivedListener listener) { signcryptElementReceivedListeners.add(listener); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java index 750f3dc1e..0d8aeadb8 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpProvider.java @@ -139,5 +139,8 @@ public interface OpenPgpProvider { OpenPgpV4Fingerprint importSecretKey(BareJid owner, byte[] bytes) throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException; + OpenPgpV4Fingerprint importSecretKey(byte[] bytes) + throws MissingUserIdOnKeyException, SmackOpenPgpException, IOException; + OpenPgpStore getStore(); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelper.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelper.java new file mode 100644 index 000000000..6f2e82a78 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelper.java @@ -0,0 +1,89 @@ +package org.jivesoftware.smackx.ox; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; +import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; +import org.jivesoftware.smackx.ox.exception.SmackOpenPgpException; + +import org.jxmpp.jid.BareJid; + +public class SecretKeyBackupHelper { + + private static final Logger LOGGER = Logger.getLogger(SecretKeyBackupHelper.class.getName()); + + /** + * Generate a secure backup code. + * + * @see XEP-0373 §5.3 + * @return backup code + */ + public static String generateBackupPassword() { + final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; + SecureRandom random = new SecureRandom(); + StringBuilder code = new StringBuilder(); + + // 6 blocks + for (int i = 0; i < 6; i++) { + + // of 4 chars + for (int j = 0; j < 4; j++) { + char c = alphabet.charAt(random.nextInt(alphabet.length())); + code.append(c); + } + + // dash after every block except the last one + if (i != 5) { + code.append('-'); + } + } + return code.toString(); + } + + public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider, + BareJid owner, + Set fingerprints, + String backupCode) throws SmackOpenPgpException, IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + for (OpenPgpV4Fingerprint fingerprint : fingerprints) { + try { + byte[] bytes = provider.getStore().getSecretKeyRingBytes(owner, fingerprint); + buffer.write(bytes); + } catch (MissingOpenPgpKeyPairException | IOException e) { + LOGGER.log(Level.WARNING, "Cannot backup secret key " + Long.toHexString(fingerprint.getKeyId()) + ".", e); + } + + } + return createSecretkeyElement(provider, buffer.toByteArray(), backupCode); + } + + public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider, + byte[] keys, + String backupCode) + throws SmackOpenPgpException, IOException { + byte[] encrypted = provider.symmetricallyEncryptWithPassword(keys, backupCode); + return new SecretkeyElement(Base64.encode(encrypted)); + } + + public static OpenPgpV4Fingerprint restoreSecretKeyBackup(OpenPgpProvider provider, SecretkeyElement backup, String backupCode) + throws InvalidBackupCodeException, IOException, MissingUserIdOnKeyException, SmackOpenPgpException { + byte[] encrypted = Base64.decode(backup.getB64Data()); + + byte[] decrypted; + try { + decrypted = provider.symmetricallyDecryptWithPassword(encrypted, backupCode); + } catch (IOException | SmackOpenPgpException e) { + throw new InvalidBackupCodeException("Could not decrypt secret key backup. Possibly wrong passphrase?", e); + } + + return provider.importSecretKey(decrypted); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java index 3836f68c7..44ab615b4 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java @@ -22,4 +22,8 @@ package org.jivesoftware.smackx.ox.exception; public class InvalidBackupCodeException extends Exception { private static final long serialVersionUID = 1L; + + public InvalidBackupCodeException(String message, Throwable e) { + super(message, e); + } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/NoBackupFoundException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/NoBackupFoundException.java index afa07025a..8056cec85 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/NoBackupFoundException.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/NoBackupFoundException.java @@ -16,5 +16,7 @@ */ package org.jivesoftware.smackx.ox.exception; -public class NoBackupFoundException { +public class NoBackupFoundException extends Exception { + + private static final long serialVersionUID = 1L; }