Move backup related methods to Helper class

This commit is contained in:
Paul Schaub 2018-06-21 17:45:22 +02:00
parent d86f3ef2e1
commit ed3c4236cf
8 changed files with 192 additions and 66 deletions

View File

@ -38,7 +38,6 @@ public abstract class AbstractPainlessOpenPgpStore implements PainlessOpenPgpSto
private OpenPgpV4Fingerprint primaryKeyFingerprint = null;
private final SecretKeyRingProtector secretKeyRingProtector;
@Override
public OpenPgpV4Fingerprint getPrimaryOpenPgpKeyPairFingerprint() {
return primaryKeyFingerprint;

View File

@ -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);

View File

@ -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)));
}
}

View File

@ -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);
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -16,5 +16,7 @@
*/
package org.jivesoftware.smackx.ox.exception;
public class NoBackupFoundException {
public class NoBackupFoundException extends Exception {
private static final long serialVersionUID = 1L;
}