diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleIdentityStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleIdentityStore.java index a575e5649..168b5a13a 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleIdentityStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleIdentityStore.java @@ -27,9 +27,9 @@ import org.jxmpp.jid.BareJid; public interface BouncyCastleIdentityStore { - void storePubkeyList(BareJid jid, PublicKeysListElement list) throws FileNotFoundException, IOException; + void storeActivePubkeyList(BareJid jid, PublicKeysListElement list) throws FileNotFoundException, IOException; - PublicKeysListElement loadPubkeyList(BareJid jid) throws FileNotFoundException, IOException; + PublicKeysListElement loadActivePubkeyList(BareJid jid) throws FileNotFoundException, IOException; void storePublicKeys(BareJid jid, PGPPublicKeyRingCollection keys); diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java index 7ff829069..5c83c4f48 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProvider.java @@ -28,6 +28,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smackx.ox.OpenPgpMessage; import org.jivesoftware.smackx.ox.OpenPgpProvider; @@ -35,22 +36,30 @@ import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SignElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; +import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPSymmetricEncryptionAlgorithms; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeySize; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.XmppKeySelectionStrategy; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfig; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs; +import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.util.encoders.Hex; import org.bouncycastle.util.io.Streams; import org.jxmpp.jid.BareJid; @@ -83,6 +92,38 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } } + @Override + public SecretkeyElement createSecretkeyElement(String password) throws CorruptedOpenPgpKeyException { + try { + // Our unencrypted secret key + PGPSecretKey secretKey = ourKeys.getSecretKeyRings().getSecretKey(ourKeyId); + + PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder() + .setProvider(BouncyGPG.getProvider()) + .build() + .get(HashAlgorithmTags.SHA1); + + PBESecretKeyEncryptor encryptor = new JcePBESecretKeyEncryptorBuilder( + PGPSymmetricEncryptionAlgorithms.AES_256.getAlgorithmId()) + .setProvider(BouncyGPG.getProvider()) + .build(password.toCharArray()); + + PGPSecretKey encrypted = new PGPSecretKey( + secretKey.extractPrivateKey(null), + secretKey.getPublicKey(), + calculator, + true, + encryptor); + + byte[] base64 = Base64.encode(encrypted.getEncoded()); + + return new SecretkeyElement(base64); + + } catch (PGPException | IOException e) { + throw new CorruptedOpenPgpKeyException(e); + } + } + @Override public void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException { byte[] decoded = Base64.decode(element.getDataElement().getB64Data()); @@ -159,6 +200,9 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { @Override public OpenPgpElement sign(SignElement element) throws Exception { + + throw new SmackException.FeatureNotSupportedException("Feature not implemented for now."); + /* InMemoryKeyring signingConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); // Add our secret keys to signing config @@ -170,16 +214,19 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { // TODO: Implement return null; + */ } @Override public OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception { // TODO: Implement - return null; + throw new SmackException.FeatureNotSupportedException("Feature not implemented for now."); } @Override public OpenPgpMessage decrypt(OpenPgpElement element) throws Exception { + throw new SmackException.FeatureNotSupportedException("Feature not implemented for now."); + /* InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); // Add our secret keys to decryption config @@ -201,10 +248,13 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { Streams.pipeAll(decrypted, decryptedOut); return new OpenPgpMessage(OpenPgpMessage.State.crypt, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8"))); + */ } @Override public OpenPgpElement encrypt(CryptElement element, Set recipients) throws Exception { + throw new SmackException.FeatureNotSupportedException("Feature not implemented for now."); + /* if (recipients.isEmpty()) { throw new IllegalArgumentException("Set of recipients must not be empty"); } @@ -249,6 +299,7 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { String base64 = Base64.encodeToString(encryptedOut.toByteArray()); return new OpenPgpElement(base64); + */ } @Override diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStore.java index 1aaa36844..4ba0d17f4 100644 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStore.java +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStore.java @@ -54,7 +54,7 @@ public class FileBasedBouncyCastleIdentityStore implements BouncyCastleIdentityS } @Override - public void storePubkeyList(BareJid jid, PublicKeysListElement list) throws IOException { + public void storeActivePubkeyList(BareJid jid, PublicKeysListElement list) throws IOException { File contactsDir = contactsDir(jid); File destination = new File(contactsDir, "pubkey_list"); DataOutputStream dataOut = new DataOutputStream(new FileOutputStream(destination)); @@ -69,7 +69,7 @@ public class FileBasedBouncyCastleIdentityStore implements BouncyCastleIdentityS } @Override - public PublicKeysListElement loadPubkeyList(BareJid jid) throws IOException { + public PublicKeysListElement loadActivePubkeyList(BareJid jid) throws IOException { File contactsDir = contactsDir(jid); File source = new File(contactsDir, "pubkey_list"); if (!source.exists()) { diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStoreTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStoreTest.java index c421f7687..5605f43c4 100644 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStoreTest.java +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStoreTest.java @@ -66,11 +66,11 @@ public class FileBasedBouncyCastleIdentityStoreTest extends SmackTestSuite { FileBasedBouncyCastleIdentityStore store = new FileBasedBouncyCastleIdentityStore(storePath); - PublicKeysListElement shouldBeNull = store.loadPubkeyList(jid); + PublicKeysListElement shouldBeNull = store.loadActivePubkeyList(jid); assertNull(shouldBeNull); - store.storePubkeyList(jid, list); + store.storeActivePubkeyList(jid, list); - PublicKeysListElement retrieved = store.loadPubkeyList(jid); + PublicKeysListElement retrieved = store.loadActivePubkeyList(jid); assertEquals(list.getMetadata(), retrieved.getMetadata()); } 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 fde222f3d..e7290c763 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 @@ -16,6 +16,7 @@ */ package org.jivesoftware.smackx.ox; +import java.security.SecureRandom; import java.util.Date; import java.util.List; import java.util.Map; @@ -30,8 +31,10 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; import org.jivesoftware.smackx.pep.PEPListener; import org.jivesoftware.smackx.pep.PEPManager; @@ -57,6 +60,11 @@ public final class OpenPgpManager extends Manager { */ public static final String PEP_NODE_PUBLIC_KEYS = "urn:xmpp:openpgp:0:public-keys"; + /** + * Name of the OX secret key node. + */ + public static final String PEP_NODE_SECRET_KEY = "urn:xmpp:openpgp:0:secret-key"; + /** * Feature to be announced using the {@link ServiceDiscoveryManager} to subscribe to the OX metadata node. * @@ -272,12 +280,20 @@ public final class OpenPgpManager extends Manager { /** * TODO: Implement and document. */ - public void depositSecretKey() { + public void depositSecretKey(DisplayBackupCodeCallback callback) + throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { ensureProviderIsSet(); - // Create key backup by appending serialized unencrypted secret keys. - // Encrypt the backup using a random generated password - // Publish the backup to the secret key node (whitelist protected) - // Display the backup key to the user + + String password = generateBackupPassword(); + SecretkeyElement secretKeyElement = provider.createSecretkeyElement(password); + + PubSubManager pm = PubSubManager.getInstance(connection()); + LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY); + PubSubHelper.whitelist(secretKeyNode); + + secretKeyNode.publish(new PayloadItem<>(secretKeyElement)); + callback.displayBackupCode(password); } /** @@ -349,4 +365,32 @@ public final class OpenPgpManager extends Manager { } } }; + + /** + * Generate a secure backup code. + * + * @see XEP-0373 ยง5.3 + * @return backup code + */ + private 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(); + } } 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 e2f50d1e8..8a8401d1d 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 @@ -22,6 +22,7 @@ import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; +import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.jivesoftware.smackx.ox.element.SignElement; import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; @@ -151,4 +152,6 @@ public interface OpenPgpProvider { * @throws CorruptedOpenPgpKeyException if for some reason the fingerprint cannot be derived from the key pair. */ String getFingerprint() throws CorruptedOpenPgpKeyException; + + SecretkeyElement createSecretkeyElement(String password) throws CorruptedOpenPgpKeyException; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubHelper.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubHelper.java index 305a886c9..10532f8a0 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubHelper.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubHelper.java @@ -21,16 +21,18 @@ import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smackx.pubsub.AccessModel; import org.jivesoftware.smackx.pubsub.ConfigureForm; import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.xdata.packet.DataForm; public class PubSubHelper { public static void whitelist(LeafNode node) throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { - ConfigureForm config = node.getNodeConfiguration(); - if (config.getAccessModel() != AccessModel.whitelist) { - config.setAccessModel(AccessModel.whitelist); - node.sendConfigurationForm(config); + ConfigureForm old = node.getNodeConfiguration(); + if (old.getAccessModel() != AccessModel.whitelist) { + ConfigureForm _new = new ConfigureForm(DataForm.Type.submit); + _new.setAccessModel(AccessModel.whitelist); + node.sendConfigurationForm(_new); } } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/AskForBackupCodeCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/AskForBackupCodeCallback.java new file mode 100644 index 000000000..d1a536b69 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/AskForBackupCodeCallback.java @@ -0,0 +1,21 @@ +/** + * + * 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; + +public interface AskForBackupCodeCallback { + String askForBackupCode(); +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/DisplayBackupCodeCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/DisplayBackupCodeCallback.java new file mode 100644 index 000000000..5eb8889dc --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/DisplayBackupCodeCallback.java @@ -0,0 +1,21 @@ +/** + * + * 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; + +public interface DisplayBackupCodeCallback { + void displayBackupCode(String backupCode); +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/package-info.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/package-info.java new file mode 100644 index 000000000..77b85c531 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2017 Florian Schmaus. + * + * 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. + */ +/** + * Callback classes for XEP-0373: OpenPGP for XMPP. + */ +package org.jivesoftware.smackx.ox.callback;