mirror of
https://codeberg.org/Mercury-IM/Smack
synced 2024-12-22 20:47:57 +01:00
Merge pull request #408 from vanitasvitae/secretKeyBackupPassword
Allow user-supplied secret key backup passphrases
This commit is contained in:
commit
81cccaab91
9 changed files with 196 additions and 95 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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<OpenPgpV4Fingerprint> selectKeysToBackup(Set<OpenPgpV4Fingerprint> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
|
||||
*
|
||||
* @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 <a href="https://xmpp.org/extensions/xep-0373.html#synchro-pep">XEP-0373 §5</a>
|
||||
*
|
||||
* @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<OpenPgpV4Fingerprint> availableKeyPairs = new HashSet<>();
|
||||
|
@ -424,10 +452,9 @@ public final class OpenPgpManager extends Manager {
|
|||
|
||||
Set<OpenPgpV4Fingerprint> 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);
|
||||
|
@ -551,7 +578,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 +593,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;
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption">
|
||||
* XEP-0373 §5.4 Encrypting the Secret Key Backup</a>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption">
|
||||
* XEP-0373 §5.4 Encrypting the Secret Key Backup</a>
|
||||
*
|
||||
* @param backupCode backup code
|
||||
*/
|
||||
void displayBackupCode(String backupCode);
|
||||
}
|
|
@ -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<OpenPgpV4Fingerprint> 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);
|
||||
}
|
||||
|
|
|
@ -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"));
|
||||
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue