mirror of
https://github.com/vanitasvitae/Smack.git
synced 2024-11-23 20:42:06 +01:00
Move backup related methods to Helper class
This commit is contained in:
parent
d86f3ef2e1
commit
ed3c4236cf
8 changed files with 192 additions and 66 deletions
|
@ -38,7 +38,6 @@ public abstract class AbstractPainlessOpenPgpStore implements PainlessOpenPgpSto
|
|||
private OpenPgpV4Fingerprint primaryKeyFingerprint = null;
|
||||
private final SecretKeyRingProtector secretKeyRingProtector;
|
||||
|
||||
|
||||
@Override
|
||||
public OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint() {
|
||||
return primaryKeyFingerprint;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
|
@ -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<OpenPgpV4Fingerprint> availableKeyPairs = provider.getStore().getAvailableKeyPairFingerprints(ownJid);
|
||||
Set<OpenPgpV4Fingerprint> 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 <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm140425111347232">XEP-0373 §5.3</a>
|
||||
* @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<OpenPgpV4Fingerprint> 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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 <a href="https://xmpp.org/extensions/xep-0373.html#sect-idm140425111347232">XEP-0373 §5.3</a>
|
||||
* @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<OpenPgpV4Fingerprint> 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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,5 +16,7 @@
|
|||
*/
|
||||
package org.jivesoftware.smackx.ox.exception;
|
||||
|
||||
public class NoBackupFoundException {
|
||||
public class NoBackupFoundException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue