From bb3c491e0c88661506bdee2100004c029b065943 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 22:15:38 +0200 Subject: [PATCH 1/3] StringUtils: Fix typo and expose unambiguous alphabet as string --- .../jivesoftware/smack/util/StringUtils.java | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java index c966b2d34..77bc402c3 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/StringUtils.java @@ -270,7 +270,12 @@ public class StringUtils { /** * 24 upper case characters from the latin alphabet and numbers without '0' and 'O'. */ - private static final char[] UNAMBIGUOUS_NUMBERS_AND_LETTER = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ".toCharArray(); + public static final String UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; + + /** + * 24 upper case characters from the latin alphabet and numbers without '0' and 'O'. + */ + private static final char[] UNAMBIGUOUS_NUMBERS_AND_LETTERS = UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING.toCharArray(); /** * Returns a random String of numbers and letters (lower and upper case) @@ -294,14 +299,14 @@ public class StringUtils { // See also https://www.grc.com/haystack.htm final int REQUIRED_LENGTH = 10; - return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTER, REQUIRED_LENGTH); + return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH); } public static String secureUniqueRandomString() { // 34^13 = 8.11e19 possible combinations, which is > 2^64. final int REQUIRED_LENGTH = 13; - return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTER, REQUIRED_LENGTH); + return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH); } /** @@ -324,7 +329,7 @@ public class StringUtils { // See also https://www.grc.com/haystack.htm final int REQUIRED_LENGTH = 24; - return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTER, REQUIRED_LENGTH); + return randomString(RandomUtil.SECURE_RANDOM.get(), UNAMBIGUOUS_NUMBERS_AND_LETTERS, REQUIRED_LENGTH); } private static final int RANDOM_STRING_CHUNK_SIZE = 4; @@ -368,8 +373,8 @@ public class StringUtils { char[] randomChars = new char[length]; for (int i = 0; i < length; i++) { - int index = random.nextInt(UNAMBIGUOUS_NUMBERS_AND_LETTER.length); - randomChars[i] = UNAMBIGUOUS_NUMBERS_AND_LETTER[index]; + int index = random.nextInt(UNAMBIGUOUS_NUMBERS_AND_LETTERS.length); + randomChars[i] = UNAMBIGUOUS_NUMBERS_AND_LETTERS[index]; } return new String(randomChars); } From 075e65ad40d856471804b088b9491c73d384c052 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 22:20:50 +0200 Subject: [PATCH 2/3] Fix indentation --- .../main/java/org/jivesoftware/smackx/ox/OpenPgpManager.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ce49dbde6..3eb119c40 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 @@ -551,7 +551,7 @@ public final class OpenPgpManager extends Manager { if (contentElement instanceof SigncryptElement) { for (SigncryptElementReceivedListener l : signcryptElementReceivedListeners) { l.signcryptElementReceived(contact, message, (SigncryptElement) contentElement, - decrypted.getMetadata()); + decrypted.getMetadata()); } return; } @@ -566,7 +566,7 @@ public final class OpenPgpManager extends Manager { if (contentElement instanceof CryptElement) { for (CryptElementReceivedListener l : cryptElementReceivedListeners) { l.cryptElementReceived(contact, message, (CryptElement) contentElement, - decrypted.getMetadata()); + decrypted.getMetadata()); } return; } From 6e57ea0873e45acd70855a1149f9ee4cbf231cd3 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 15 Jul 2020 22:22:42 +0200 Subject: [PATCH 3/3] Allow user-supplied secret key backup passphrases Instead of passing the passphrase back to the user via a DisplayBackupCodeCallback, we directly return the passphrase which is now represented by a class. Also we now allow the user to provide the passphrase. --- .../ox/OXSecretKeyBackupIntegrationTest.java | 37 ++--------- .../smackx/ox/OpenPgpManager.java | 47 +++++++++++--- .../ox/OpenPgpSecretKeyBackupPassphrase.java | 64 +++++++++++++++++++ .../backup/AskForBackupCodeCallback.java | 4 +- .../backup/DisplayBackupCodeCallback.java | 32 ---------- .../smackx/ox/util/SecretKeyBackupHelper.java | 15 +++-- .../OpenPgpSecretKeyBackupPassphraseTest.java | 57 +++++++++++++++++ .../smackx/ox/SecretKeyBackupHelperTest.java | 14 ++-- 8 files changed, 183 insertions(+), 87 deletions(-) create mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphrase.java delete mode 100644 smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/DisplayBackupCodeCallback.java create mode 100644 smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphraseTest.java diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java index ba8d378bb..8031301ad 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/ox/OXSecretKeyBackupIntegrationTest.java @@ -16,6 +16,7 @@ */ package org.jivesoftware.smackx.ox; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; @@ -26,17 +27,11 @@ import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.util.Arrays; -import java.util.Set; import java.util.logging.Level; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.StringUtils; - -import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; -import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback; -import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback; import org.jivesoftware.smackx.ox.crypto.PainlessOpenPgpProvider; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; @@ -64,10 +59,6 @@ public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegration private static final File beforePath = new File(tempDir, "ox_backup_" + sessionId); private static final File afterPath = new File(tempDir, "ox_restore_" + sessionId); - private String backupCode = null; - - private OpenPgpManager openPgpManager; - /** * This integration test tests the basic secret key backup and restore functionality as described * in XEP-0373 §5. @@ -123,7 +114,7 @@ public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegration OpenPgpStore beforeStore = new FileBasedOpenPgpStore(beforePath); beforeStore.setKeyRingProtector(new UnprotectedKeysProtector()); PainlessOpenPgpProvider beforeProvider = new PainlessOpenPgpProvider(beforeStore); - openPgpManager = OpenPgpManager.getInstanceFor(aliceConnection); + OpenPgpManager openPgpManager = OpenPgpManager.getInstanceFor(aliceConnection); openPgpManager.setOpenPgpProvider(beforeProvider); OpenPgpSelf self = openPgpManager.getOpenPgpSelf(); @@ -141,29 +132,15 @@ public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegration PGPPublicKeyRing beforePub = beforeStore.getPublicKeyRing(alice, keyFingerprint); assertNotNull(beforePub); - openPgpManager.backupSecretKeyToServer(new DisplayBackupCodeCallback() { - @Override - public void displayBackupCode(String backupCode) { - OXSecretKeyBackupIntegrationTest.this.backupCode = backupCode; - } - }, new SecretKeyBackupSelectionCallback() { - @Override - public Set selectKeysToBackup(Set availableSecretKeys) { - return availableSecretKeys; - } - }); + OpenPgpSecretKeyBackupPassphrase backupPassphrase = + openPgpManager.backupSecretKeyToServer(availableSecretKeys -> availableSecretKeys); FileBasedOpenPgpStore afterStore = new FileBasedOpenPgpStore(afterPath); afterStore.setKeyRingProtector(new UnprotectedKeysProtector()); PainlessOpenPgpProvider afterProvider = new PainlessOpenPgpProvider(afterStore); openPgpManager.setOpenPgpProvider(afterProvider); - OpenPgpV4Fingerprint fingerprint = openPgpManager.restoreSecretKeyServerBackup(new AskForBackupCodeCallback() { - @Override - public String askForBackupCode() { - return backupCode; - } - }); + OpenPgpV4Fingerprint fingerprint = openPgpManager.restoreSecretKeyServerBackup(() -> backupPassphrase); assertEquals(keyFingerprint, fingerprint); @@ -173,10 +150,10 @@ public class OXSecretKeyBackupIntegrationTest extends AbstractOpenPgpIntegration PGPSecretKeyRing afterSec = afterStore.getSecretKeyRing(alice, keyFingerprint); assertNotNull(afterSec); - assertTrue(Arrays.equals(beforeSec.getEncoded(), afterSec.getEncoded())); + assertArrayEquals(beforeSec.getEncoded(), afterSec.getEncoded()); PGPPublicKeyRing afterPub = afterStore.getPublicKeyRing(alice, keyFingerprint); assertNotNull(afterPub); - assertTrue(Arrays.equals(beforePub.getEncoded(), afterPub.getEncoded())); + assertArrayEquals(beforePub.getEncoded(), afterPub.getEncoded()); } } 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 3eb119c40..ca1517d0e 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 @@ -1,6 +1,6 @@ /** * - * Copyright 2017-2020 Florian Schmaus, 2018 Paul Schaub. + * Copyright 2018-2020 Paul Schaub, 2017-2020 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -46,7 +46,6 @@ import org.jivesoftware.smack.xml.XmlPullParserException; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.callback.backup.AskForBackupCodeCallback; -import org.jivesoftware.smackx.ox.callback.backup.DisplayBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.backup.SecretKeyBackupSelectionCallback; import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; import org.jivesoftware.smackx.ox.element.CryptElement; @@ -389,8 +388,9 @@ public final class OpenPgpManager extends Manager { * * @see XEP-0373 §5 * - * @param displayCodeCallback callback, which will receive the backup password used to encrypt the secret key. * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. + * @return secret key passphrase used to encrypt the backup. + * * @throws InterruptedException if the thread is interrupted. * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. @@ -402,8 +402,38 @@ public final class OpenPgpManager extends Manager { * @throws PGPException PGP is brittle * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. */ - public void backupSecretKeyToServer(DisplayBackupCodeCallback displayCodeCallback, - SecretKeyBackupSelectionCallback selectKeyCallback) + public OpenPgpSecretKeyBackupPassphrase backupSecretKeyToServer(SecretKeyBackupSelectionCallback selectKeyCallback) + throws InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, + SmackException.NotLoggedInException, IOException, + SmackException.FeatureNotSupportedException, PGPException, MissingOpenPgpKeyException { + OpenPgpSecretKeyBackupPassphrase passphrase = SecretKeyBackupHelper.generateBackupPassword(); + backupSecretKeyToServer(selectKeyCallback, passphrase); + return passphrase; + } + + /** + * Upload the encrypted secret key to a private PEP node. + * The backup is encrypted using the provided secret key passphrase. + * + * @see XEP-0373 §5 + * + * @param selectKeyCallback callback, which will receive the users choice of which keys will be backed up. @param selectKeyCallback + * @param passphrase secret key passphrase + * + * @throws InterruptedException if the thread is interrupted. + * @throws PubSubException.NotALeafNodeException if the private node is not a {@link LeafNode}. + * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. + * @throws SmackException.NotConnectedException if we are not connected. + * @throws SmackException.NoResponseException if the server doesn't respond. + * @throws SmackException.NotLoggedInException if we are not logged in. + * @throws IOException IO is dangerous. + * @throws SmackException.FeatureNotSupportedException if the server doesn't support the PubSub whitelist access model. + * @throws PGPException PGP is brittle + * @throws MissingOpenPgpKeyException in case we have no OpenPGP key pair to back up. + */ + public void backupSecretKeyToServer(SecretKeyBackupSelectionCallback selectKeyCallback, + OpenPgpSecretKeyBackupPassphrase passphrase) throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, SmackException.NotLoggedInException, IOException, @@ -413,8 +443,6 @@ public final class OpenPgpManager extends Manager { BareJid ownJid = connection().getUser().asBareJid(); - String backupCode = SecretKeyBackupHelper.generateBackupPassword(); - PGPSecretKeyRingCollection secretKeyRings = provider.getStore().getSecretKeysOf(ownJid); Set availableKeyPairs = new HashSet<>(); @@ -424,10 +452,9 @@ public final class OpenPgpManager extends Manager { Set selectedKeyPairs = selectKeyCallback.selectKeysToBackup(availableKeyPairs); - SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, backupCode); + SecretkeyElement secretKey = SecretKeyBackupHelper.createSecretkeyElement(provider, ownJid, selectedKeyPairs, passphrase); OpenPgpPubSubUtil.depositSecretKey(connection(), secretKey); - displayCodeCallback.displayBackupCode(backupCode); } /** @@ -476,7 +503,7 @@ public final class OpenPgpManager extends Manager { throw new NoBackupFoundException(); } - String backupCode = codeCallback.askForBackupCode(); + OpenPgpSecretKeyBackupPassphrase backupCode = codeCallback.askForBackupCode(); PGPSecretKeyRing secretKeys = SecretKeyBackupHelper.restoreSecretKeyBackup(backup, backupCode); provider.getStore().importSecretKey(getJidOrThrow(), secretKeys); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphrase.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphrase.java new file mode 100644 index 000000000..cc96bbd4f --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphrase.java @@ -0,0 +1,64 @@ +/** + * + * Copyright 2020 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.ox; + +import static org.jivesoftware.smack.util.StringUtils.UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING; + +import java.util.regex.Pattern; + +/** + * Represents a secret key backup passphrase whose format is described in XEP-0373 §5.3. + * + * @see + * XEP-0373 §5.4 Encrypting the Secret Key Backup + */ +public class OpenPgpSecretKeyBackupPassphrase implements CharSequence { + + private static final Pattern PASSPHRASE_PATTERN = Pattern.compile( + "^([" + UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING + "]{4}-){5}" + + "[" + UNAMBIGUOUS_NUMBERS_AND_LETTERS_STRING + "]{4}$"); + + private final String passphrase; + + public OpenPgpSecretKeyBackupPassphrase(String passphrase) { + if (!PASSPHRASE_PATTERN.matcher(passphrase).matches()) { + throw new IllegalArgumentException("Passphrase must be 24 upper case letters and numbers from the english " + + "alphabet without 'O' and '0', divided into blocks of 4 and separated with dashes ('-')."); + } + this.passphrase = passphrase; + } + + @Override + public int length() { + return passphrase.length(); + } + + @Override + public char charAt(int i) { + return passphrase.charAt(i); + } + + @Override + public CharSequence subSequence(int i, int i1) { + return passphrase.subSequence(i, i1); + } + + @Override + public String toString() { + return passphrase; + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/AskForBackupCodeCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/AskForBackupCodeCallback.java index 4a6653012..5d7a214bc 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/AskForBackupCodeCallback.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/AskForBackupCodeCallback.java @@ -16,6 +16,8 @@ */ package org.jivesoftware.smackx.ox.callback.backup; +import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; + public interface AskForBackupCodeCallback { /** @@ -27,5 +29,5 @@ public interface AskForBackupCodeCallback { * * @return backup code provided by the user. */ - String askForBackupCode(); + OpenPgpSecretKeyBackupPassphrase askForBackupCode(); } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/DisplayBackupCodeCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/DisplayBackupCodeCallback.java deleted file mode 100644 index bc41a92bb..000000000 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/backup/DisplayBackupCodeCallback.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * - * Copyright 2018 Paul Schaub. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.jivesoftware.smackx.ox.callback.backup; - -public interface DisplayBackupCodeCallback { - - /** - * This method is used to provide a client access to the generated backup code. - * The client can then go ahead and display the code to the user. - * The backup code follows the format described in XEP-0373 §5.3 - * - * @see - * XEP-0373 §5.4 Encrypting the Secret Key Backup - * - * @param backupCode backup code - */ - void displayBackupCode(String backupCode); -} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java index 59f0fd56e..978f22eb4 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/util/SecretKeyBackupHelper.java @@ -23,6 +23,7 @@ import java.util.Set; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; @@ -51,8 +52,8 @@ public class SecretKeyBackupHelper { * * @return backup code */ - public static String generateBackupPassword() { - return StringUtils.secureOfflineAttackSafeRandomString(); + public static OpenPgpSecretKeyBackupPassphrase generateBackupPassword() { + return new OpenPgpSecretKeyBackupPassphrase(StringUtils.secureOfflineAttackSafeRandomString()); } /** @@ -73,7 +74,7 @@ public class SecretKeyBackupHelper { public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider, BareJid owner, Set fingerprints, - String backupCode) + OpenPgpSecretKeyBackupPassphrase backupCode) throws PGPException, IOException, MissingOpenPgpKeyException { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); @@ -105,9 +106,9 @@ public class SecretKeyBackupHelper { * @throws IOException IO is dangerous */ public static SecretkeyElement createSecretkeyElement(byte[] keys, - String backupCode) + OpenPgpSecretKeyBackupPassphrase backupCode) throws PGPException, IOException { - byte[] encrypted = PGPainless.encryptWithPassword(keys, new Passphrase(backupCode.toCharArray()), + byte[] encrypted = PGPainless.encryptWithPassword(keys, new Passphrase(backupCode.toString().toCharArray()), SymmetricKeyAlgorithm.AES_256); return new SecretkeyElement(Base64.encode(encrypted)); } @@ -123,13 +124,13 @@ public class SecretKeyBackupHelper { * @throws IOException IO is dangerous. * @throws PGPException PGP is brittle. */ - public static PGPSecretKeyRing restoreSecretKeyBackup(SecretkeyElement backup, String backupCode) + public static PGPSecretKeyRing restoreSecretKeyBackup(SecretkeyElement backup, OpenPgpSecretKeyBackupPassphrase backupCode) throws InvalidBackupCodeException, IOException, PGPException { byte[] encrypted = Base64.decode(backup.getB64Data()); byte[] decrypted; try { - decrypted = PGPainless.decryptWithPassword(encrypted, new Passphrase(backupCode.toCharArray())); + decrypted = PGPainless.decryptWithPassword(encrypted, new Passphrase(backupCode.toString().toCharArray())); } catch (IOException | PGPException e) { throw new InvalidBackupCodeException("Could not decrypt secret key backup. Possibly wrong passphrase?", e); } diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphraseTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphraseTest.java new file mode 100644 index 000000000..3c910a006 --- /dev/null +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpSecretKeyBackupPassphraseTest.java @@ -0,0 +1,57 @@ +/** + * + * Copyright 2020 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.ox; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import org.jivesoftware.smackx.ox.util.SecretKeyBackupHelper; + +import org.junit.jupiter.api.Test; + +public class OpenPgpSecretKeyBackupPassphraseTest { + + @Test + public void secretKeyPassphraseConstructorTest() { + OpenPgpSecretKeyBackupPassphrase valid = + new OpenPgpSecretKeyBackupPassphrase("TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW"); + + assertNotNull(valid); + for (int i = 0; i < 50; i++) { + assertNotNull(SecretKeyBackupHelper.generateBackupPassword()); + } + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("TWNKKD5YMT3TE1GSDRDBKVTW")); + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("0123-4567-89AB-CDEF-GHIJ-KLMN")); + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("CONT-AINS-ILLE-GALL-ETTE-RSO0")); + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("TWNK-KD5Y-MT3T-E1GS-DRDB-")); + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("TWNK-KD5Y-MT3T-E1GS-DRDB-KVTW-ADDD")); + + assertThrows(IllegalArgumentException.class, + () -> new OpenPgpSecretKeyBackupPassphrase("TWNK KD5Y MT3T E1GS DRDB KVTW")); + + } +} diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelperTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelperTest.java index e7acc3201..ceaf50638 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelperTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/SecretKeyBackupHelperTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2018 Paul Schaub. + * Copyright 2018-2020 Paul Schaub. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,17 +18,16 @@ package org.jivesoftware.smackx.ox; import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; +import static org.junit.Assert.assertArrayEquals; 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.test.util.SmackTestSuite; - import org.jivesoftware.smackx.ox.crypto.PainlessOpenPgpProvider; import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; @@ -60,7 +59,7 @@ public class SecretKeyBackupHelperTest extends SmackTestSuite { public void backupPasswordGenerationTest() { final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; - String backupCode = SecretKeyBackupHelper.generateBackupPassword(); + OpenPgpSecretKeyBackupPassphrase backupCode = SecretKeyBackupHelper.generateBackupPassword(); assertEquals(29, backupCode.length()); for (int i = 0; i < backupCode.length(); i++) { if ((i + 1) % 5 == 0) { @@ -86,12 +85,13 @@ public class SecretKeyBackupHelperTest extends SmackTestSuite { provider.getStore().importSecretKey(jid, keyRing.getSecretKeys()); // Create encrypted backup - String backupCode = SecretKeyBackupHelper.generateBackupPassword(); - SecretkeyElement element = SecretKeyBackupHelper.createSecretkeyElement(provider, jid, Collections.singleton(new OpenPgpV4Fingerprint(keyRing.getSecretKeys())), backupCode); + OpenPgpSecretKeyBackupPassphrase backupCode = SecretKeyBackupHelper.generateBackupPassword(); + SecretkeyElement element = SecretKeyBackupHelper.createSecretkeyElement(provider, jid, + Collections.singleton(new OpenPgpV4Fingerprint(keyRing.getSecretKeys())), backupCode); // Decrypt backup and compare PGPSecretKeyRing secretKeyRing = SecretKeyBackupHelper.restoreSecretKeyBackup(element, backupCode); - assertTrue(Arrays.equals(keyRing.getSecretKeys().getEncoded(), secretKeyRing.getEncoded())); + assertArrayEquals(keyRing.getSecretKeys().getEncoded(), secretKeyRing.getEncoded()); } @AfterClass