diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java new file mode 100644 index 000000000..7a246c4d9 --- /dev/null +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BCOpenPgpProvider.java @@ -0,0 +1,136 @@ +package org.jivesoftware.smackx.ox.bouncycastle; + +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Set; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.ox.OpenPgpMessage; +import org.jivesoftware.smackx.ox.OpenPgpProvider; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; +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 org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; + +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.util.encoders.Hex; +import org.jxmpp.jid.BareJid; + +public class BCOpenPgpProvider implements OpenPgpProvider { + + private final BareJid user; + private OpenPgpV4Fingerprint primaryKeyPair; + + + public BCOpenPgpProvider(BareJid user) { + this.user = user; + this.primaryKeyPair = null; + } + + @Override + public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() { + return primaryKeyPair; + } + + @Override + public Set availableOpenPgpKeyPairFingerprints() { + return null; + } + + @Override + public Set announcedOpenPgpKeyFingerprints(BareJid contact) { + return null; + } + + @Override + public OpenPgpElement signAndEncrypt(SigncryptElement element, OpenPgpV4Fingerprint signingKey, Set encryptionKeys) + throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set sendersKeys) + throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint) + throws MissingOpenPgpKeyPairException { + return null; + } + + @Override + public OpenPgpMessage verify(OpenPgpElement element, Set singingKeyFingerprints) + throws MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpElement encrypt(CryptElement element, Set encryptionKeyFingerprints) + throws MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpMessage decrypt(OpenPgpElement element) throws MissingOpenPgpKeyPairException { + return null; + } + + @Override + public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) + throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException { + return null; + } + + @Override + public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) + throws CorruptedOpenPgpKeyException { + + } + + @Override + public void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner) + throws CorruptedOpenPgpKeyException, InterruptedException, SmackException.NotConnectedException, + SmackException.NoResponseException { + + } + + @Override + public SecretkeyElement createSecretkeyElement(Set fingerprints, String password) + throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException { + return null; + } + + @Override + public Set availableOpenPgpKeysFingerprints(BareJid contact) { + return null; + } + + @Override + public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) + throws CorruptedOpenPgpKeyException, InvalidBackupCodeException { + + } + + @Override + public OpenPgpV4Fingerprint createOpenPgpKeyPair() + throws NoSuchAlgorithmException, NoSuchProviderException { + return null; + } + + public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) { + byte[] hex = Hex.encode(publicKey.getFingerprint()); + return new OpenPgpV4Fingerprint(hex); + } +} 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 deleted file mode 100644 index 168b5a13a..000000000 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleIdentityStore.java +++ /dev/null @@ -1,42 +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.bouncycastle; - -import java.io.FileNotFoundException; -import java.io.IOException; - -import org.jivesoftware.smackx.ox.element.PublicKeysListElement; - -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.jxmpp.jid.BareJid; - -public interface BouncyCastleIdentityStore { - - void storeActivePubkeyList(BareJid jid, PublicKeysListElement list) throws FileNotFoundException, IOException; - - PublicKeysListElement loadActivePubkeyList(BareJid jid) throws FileNotFoundException, IOException; - - void storePublicKeys(BareJid jid, PGPPublicKeyRingCollection keys); - - PGPPublicKeyRingCollection loadPublicKeys(BareJid jid); - - void storeSecretKeys(PGPSecretKeyRingCollection secretKeys); - - PGPSecretKeyRingCollection loadSecretKeys(); - -} 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 72141f9a5..d76022a1b 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 @@ -16,22 +16,22 @@ */ package org.jivesoftware.smackx.ox.bouncycastle; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.charset.Charset; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.logging.Logger; -import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.util.stringencoder.Base64; import org.jivesoftware.smackx.ox.OpenPgpMessage; import org.jivesoftware.smackx.ox.OpenPgpProvider; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import org.jivesoftware.smackx.ox.Util; +import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; import org.jivesoftware.smackx.ox.element.CryptElement; import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.jivesoftware.smackx.ox.element.PubkeyElement; @@ -40,49 +40,41 @@ 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 org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.BouncyGPG; -import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PGPHashAlgorithms; -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.PGPPrivateKey; 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.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.util.encoders.Hex; -import org.bouncycastle.util.io.Streams; import org.jxmpp.jid.BareJid; public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { - private final BareJid ourJid; - private InMemoryKeyring ourKeys; - private Long ourKeyId; - private final Map theirKeys = new HashMap<>(); + private static final Logger LOGGER = Logger.getLogger(BouncyCastleOpenPgpProvider.class.getName()); - public BouncyCastleOpenPgpProvider(BareJid ourJid) throws IOException, PGPException, NoSuchAlgorithmException { + private final BareJid ourJid; + private OpenPgpV4Fingerprint primaryKeyPairFingerprint; + private InMemoryKeyring keyring; + + private final Map> contactsFingerprints = new HashMap<>(); + + public BouncyCastleOpenPgpProvider(BareJid ourJid) { this.ourJid = ourJid; } + /** + * {@inheritDoc} + */ @Override - public PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException { + public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint keyFingerprint) + throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException { + // TODO: throw missing key exception try { - PGPPublicKey pubKey = ourKeys.getPublicKeyRings().getPublicKey(ourKeyId); + PGPPublicKey pubKey = keyring.getPublicKeyRings().getPublicKey(Util.keyIdFromFingerprint(keyFingerprint)); PubkeyElement.PubkeyDataElement dataElement = new PubkeyElement.PubkeyDataElement( Base64.encode(pubKey.getEncoded())); return new PubkeyElement(dataElement, new Date()); @@ -91,20 +83,29 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } } + /** + * {@inheritDoc} + */ @Override - public SecretkeyElement createSecretkeyElement(String password) throws CorruptedOpenPgpKeyException { + public SecretkeyElement createSecretkeyElement(Set fingerprints, String password) throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException { + /* try { // Our unencrypted secret key - PGPSecretKey secretKey = ourKeys.getSecretKeyRings().getSecretKey(ourKeyId); + PGPSecretKey secretKey; + try { + secretKey = ourKeys.getSecretKeyRings().getSecretKey(ourKeyId); + } catch (NullPointerException e) { + throw new MissingOpenPgpKeyPairException(ourJid); + } PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(BouncyGPG.getProvider()) + .setProvider(BouncyCastleProvider.PROVIDER_NAME) .build() .get(HashAlgorithmTags.SHA1); PBESecretKeyEncryptor encryptor = new JcePBESecretKeyEncryptorBuilder( PGPSymmetricEncryptionAlgorithms.AES_256.getAlgorithmId()) - .setProvider(BouncyGPG.getProvider()) + .setProvider(BouncyCastleProvider.PROVIDER_NAME) .build(password.toCharArray()); PGPSecretKey encrypted = new PGPSecretKey( @@ -121,10 +122,16 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } catch (PGPException | IOException e) { throw new CorruptedOpenPgpKeyException(e); } + */ + return null; } + /** + * {@inheritDoc} + */ @Override - public void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException { + public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) throws CorruptedOpenPgpKeyException { + /* byte[] decoded = Base64.decode(element.getDataElement().getB64Data()); try { @@ -138,21 +145,49 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } catch (IOException | PGPException e) { throw new CorruptedOpenPgpKeyException(e); } + */ } + /** + * {@inheritDoc} + */ @Override - public void processPublicKeysListElement(PublicKeysListElement listElement, BareJid owner) throws Exception { - + public void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner) { + /* + InMemoryKeyring contactsKeys = theirKeys.get(owner); + for (OpenPgpV4Fingerprint fingerprint : listElement.getMetadata().keySet()) { + byte[] asBytes = fingerprint.toString().getBytes(Charset.forName("UTF-8")); + try { + if (contactsKeys.getPublicKeyRings().getPublicKey(asBytes) == null) { + try { + PubkeyElement pubkey = PubSubDelegate.fetchPubkey(connection, owner, fingerprint); + storePublicKey(pubkey, owner); + } catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException | + XMPPException.XMPPErrorException e) { + LOGGER.log(Level.WARNING, "Could not fetch public key " + fingerprint + " of " + owner + ".", e); + } catch (CorruptedOpenPgpKeyException e) { + LOGGER.log(Level.WARNING, "Key " + fingerprint + " of " + owner + " is corrupted and cannot be imported.", e); + } + } + } catch (PGPException | IOException e) { + throw new CorruptedOpenPgpKeyException(e); + } + } + */ } + /** + * {@inheritDoc} + */ @Override - public void restoreSecretKeyElement(SecretkeyElement secretkeyElement, String password) + public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) throws CorruptedOpenPgpKeyException { + /* byte[] encoded = Base64.decode(secretkeyElement.getB64Data()); try { PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder() - .setProvider(BouncyGPG.getProvider()) + .setProvider(BouncyCastleProvider.PROVIDER_NAME) .build(); InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys( @@ -187,12 +222,48 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { } catch (PGPException | IOException e) { throw new CorruptedOpenPgpKeyException(e); } + */ + } + + /** + * {@inheritDoc} + */ + @Override + public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Set availableOpenPgpKeyPairFingerprints() { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public Set announcedOpenPgpKeyFingerprints(BareJid contact) { + return null; } @Override - public OpenPgpElement signAndEncrypt(SigncryptElement element, Set recipients) - throws Exception { - if (recipients.isEmpty()) { + public Set availableOpenPgpKeysFingerprints(BareJid contact) { + return null; + } + + /** + * {@inheritDoc} + */ + @Override + public OpenPgpElement signAndEncrypt(SigncryptElement element, + OpenPgpV4Fingerprint signingKey, + Set encryptionKeys) + throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException { + /* + if (encryptionKeys.isEmpty()) { throw new IllegalArgumentException("Set of recipients must not be empty"); } @@ -229,7 +300,7 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { .withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date())) .withOxAlgorithms() .toRecipients(recipientUIDs) - .andSignWith("xmpp:" + ourJid.toString()) + .andSignWith(ourKeyId) .binaryOutput() .andWriteTo(encryptedOut); @@ -239,36 +310,16 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { String base64 = Base64.encodeToString(encryptedOut.toByteArray()); return new OpenPgpElement(base64); - } - - @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 - for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) { - signingConfig.addSecretKey(s.getSecretKey().getEncoded()); - } - - InputStream inputStream = element.toInputStream(); - // TODO: Implement - + */ return null; - */ } + /** + * {@inheritDoc} + */ @Override - public OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception { - // TODO: Implement - 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."); + public OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set possibleSigningKeys) + throws MissingOpenPgpPublicKeyException, MissingOpenPgpKeyPairException { /* InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); @@ -277,83 +328,6 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { decryptionConfig.addSecretKey(s.getSecretKey().getEncoded()); } - ByteArrayInputStream encryptedIn = new ByteArrayInputStream( - element.getEncryptedBase64MessageContent().getBytes(Charset.forName("UTF-8"))); - - InputStream decrypted = BouncyGPG.decryptAndVerifyStream() - .withConfig(decryptionConfig) - .withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date())) - .andIgnoreSignatures() - .fromEncryptedInputStream(encryptedIn); - - ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); - - 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"); - } - - InMemoryKeyring encryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); - - // Add all recipients public keys to encryption config - for (BareJid recipient : recipients) { - KeyringConfig c = theirKeys.get(recipient); - for (PGPPublicKeyRing p : c.getPublicKeyRings()) { - encryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); - } - } - - // Add our public keys to encryption config - for (PGPPublicKeyRing p : ourKeys.getPublicKeyRings()) { - encryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); - } - - String[] recipientUIDs = new String[recipients.size() + 1]; - int pos = 0; - for (BareJid b : recipients) { - recipientUIDs[pos++] = "xmpp:" + b.toString(); - } - recipientUIDs[pos] = "xmpp:" + ourJid.toString(); - - InputStream inputStream = element.toInputStream(); - ByteArrayOutputStream encryptedOut = new ByteArrayOutputStream(); - - OutputStream encryptor = BouncyGPG.encryptToStream() - .withConfig(encryptionConfig) - .withKeySelectionStrategy(new XmppKeySelectionStrategy(new Date())) - .withOxAlgorithms() - .toRecipients(recipientUIDs) - .andDoNotSign() - .binaryOutput() - .andWriteTo(encryptedOut); - - Streams.pipeAll(inputStream, encryptor); - encryptor.close(); - - String base64 = Base64.encodeToString(encryptedOut.toByteArray()); - - return new OpenPgpElement(base64); - */ - } - - @Override - public OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception { - InMemoryKeyring decryptionConfig = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); - - // Add our secret keys to decryption config - for (PGPSecretKeyRing s : ourKeys.getSecretKeyRings()) { - decryptionConfig.addSecretKey(s.getSecretKey().getEncoded()); - } - // Add their public keys to decryption config for (PGPPublicKeyRing p : theirKeys.get(sender).getPublicKeyRings()) { decryptionConfig.addPublicKey(p.getPublicKey().getEncoded()); @@ -374,33 +348,60 @@ public class BouncyCastleOpenPgpProvider implements OpenPgpProvider { Streams.pipeAll(decrypted, decryptedOut); return new OpenPgpMessage(OpenPgpMessage.State.signcrypt, new String(decryptedOut.toByteArray(), Charset.forName("UTF-8"))); + */ + return null; } @Override - public String getFingerprint() throws CorruptedOpenPgpKeyException { - try { - return new String(Hex.encode(ourKeys.getKeyFingerPrintCalculator() - .calculateFingerprint(ourKeys.getPublicKeyRings().getPublicKey(ourKeyId) - .getPublicKeyPacket())), Charset.forName("UTF-8")).toUpperCase(); - } catch (IOException | PGPException e) { - throw new CorruptedOpenPgpKeyException(e); - } + public OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint) + throws MissingOpenPgpKeyPairException { + return null; } @Override - public void createAndUseKey() throws CorruptedOpenPgpKeyException, NoSuchAlgorithmException { + public OpenPgpMessage verify(OpenPgpElement element, Set singingKeyFingerprints) + throws MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpElement encrypt(CryptElement element, Set encryptionKeyFingerprints) + throws MissingOpenPgpPublicKeyException { + return null; + } + + @Override + public OpenPgpMessage decrypt(OpenPgpElement element) throws MissingOpenPgpKeyPairException { + return null; + } + + public static OpenPgpV4Fingerprint getFingerprint(PGPPublicKey publicKey) { + byte[] hex = Hex.encode(publicKey.getFingerprint()); + return new OpenPgpV4Fingerprint(hex); + } + + /** + * {@inheritDoc} + */ + @Override + public OpenPgpV4Fingerprint createOpenPgpKeyPair() + throws NoSuchAlgorithmException, NoSuchProviderException { + /* try { PGPSecretKeyRing ourKey = generateKey(ourJid).generateSecretKeyRing(); - ourKeyId = ourKey.getPublicKey().getKeyID(); + primaryKeyPairFingerprint = getFingerprint(ourKey.getPublicKey()); ourKeys = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys()); ourKeys.addSecretKey(ourKey.getSecretKey().getEncoded()); ourKeys.addPublicKey(ourKey.getPublicKey().getEncoded()); } catch (PGPException | IOException e) { throw new CorruptedOpenPgpKeyException(e); } + */ + return null; } - public static PGPKeyRingGenerator generateKey(BareJid owner) throws NoSuchAlgorithmException, PGPException { + public static PGPKeyRingGenerator generateKey(BareJid owner) + throws NoSuchAlgorithmException, PGPException, NoSuchProviderException { PGPKeyRingGenerator generator = BouncyGPG.createKeyPair() .withRSAKeys() .ofSize(PublicKeySize.RSA._2048) diff --git a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java new file mode 100644 index 000000000..1d7996715 --- /dev/null +++ b/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBcOpenPgpStore.java @@ -0,0 +1,289 @@ +package org.jivesoftware.smackx.ox.bouncycastle; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.ox.OpenPgpStore; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import org.jivesoftware.smackx.ox.Util; +import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; +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.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; + +import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallback; +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.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.jxmpp.jid.BareJid; + +public class FileBasedBcOpenPgpStore implements OpenPgpStore { + + private static final Logger LOGGER = Logger.getLogger(FileBasedBcOpenPgpStore.class.getName()); + + private final File basePath; + private final BareJid user; + private final InMemoryKeyring keyringConfig; + private final KeyringConfigCallback configCallback; + private OpenPgpV4Fingerprint primaryKeyFingerprint; + + public FileBasedBcOpenPgpStore(File basePath, BareJid user, KeyringConfigCallback passwordCallback) + throws IOException, PGPException { + this.basePath = basePath; + this.user = user; + + File pub = publicKeyringPath(); + if (!pub.exists()) { + pub.getParentFile().mkdirs(); + pub.createNewFile(); + } + + File sec = secretKeyringPath(); + if (!sec.exists()) { + sec.createNewFile(); + } + + configCallback = passwordCallback; + keyringConfig = KeyringConfigs.forGpgExportedKeys(configCallback); + + KeyringConfig load = KeyringConfigs.withKeyRingsFromFiles(pub, sec, passwordCallback); + for (PGPPublicKeyRing pubRing : load.getPublicKeyRings()) { + for (PGPPublicKey pubKey : pubRing) { + keyringConfig.addPublicKey(pubKey.getEncoded()); + } + } + + PGPPublicKey lastAdded = null; + + for (PGPSecretKeyRing secRing : load.getSecretKeyRings()) { + for (PGPSecretKey secKey : secRing) { + keyringConfig.addSecretKey(secKey.getEncoded()); + // Remember last added secret keys public key -> this will be the primary key + if (secKey.getPublicKey() != null) { + lastAdded = secKey.getPublicKey(); + } + } + } + + if (lastAdded != null) { + primaryKeyFingerprint = BCOpenPgpProvider.getFingerprint(lastAdded); + } + } + + private static void addPublicKeysFromFile(InMemoryKeyring keyring, + File pubring, + KeyringConfigCallback passwordCallback) + throws IOException, PGPException { + KeyringConfig source = KeyringConfigs.withKeyRingsFromFiles(pubring, null, passwordCallback); + for (PGPPublicKeyRing pubRing : source.getPublicKeyRings()) { + for (PGPPublicKey pubKey : pubRing) { + try { + keyring.addPublicKey(pubKey.getEncoded()); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.INFO, "public key " + Long.toHexString(pubKey.getKeyID()) + + " already exists in keyring. Skip."); + } + + } + } + } + + private static PGPPublicKey addSecretKeysFromFile(InMemoryKeyring keyring, + File secring, + KeyringConfigCallback passwordCallback) + throws IOException, PGPException { + KeyringConfig source = KeyringConfigs.withKeyRingsFromFiles(null, secring, passwordCallback); + PGPPublicKey lastAdded = null; + + for (PGPSecretKeyRing secRing : source.getSecretKeyRings()) { + for (PGPSecretKey secKey : secRing) { + keyring.addSecretKey(secKey.getEncoded()); + // Remember last added secret keys public key -> this will be the primary key + if (secKey.getPublicKey() != null) { + lastAdded = secKey.getPublicKey(); + } + } + } + + return lastAdded; + } + + @Override + public OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint() { + return primaryKeyFingerprint; + } + + @Override + public Set availableOpenPgpKeyPairFingerprints() { + Set availableKeyPairs = new HashSet<>(); + try { + for (PGPSecretKeyRing secRing : keyringConfig.getSecretKeyRings()) { + for (PGPSecretKey secKey : secRing) { + availableKeyPairs.add(BCOpenPgpProvider.getFingerprint(secKey.getPublicKey())); + } + } + } catch (IOException | PGPException e) { + LOGGER.log(Level.SEVERE, "Error going through available key pair.", e); + } + return availableKeyPairs; + } + + @Override + public Set announcedOpenPgpKeyFingerprints(BareJid contact) { + Set announcedKeys = new HashSet<>(); + File listPath = contactsList(contact); + if (listPath.exists() && listPath.isFile()) { + FileReader fileReader = null; + try { + fileReader = new FileReader(listPath); + BufferedReader bufferedReader = new BufferedReader(fileReader); + String line; + + while ((line = bufferedReader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) { + continue; + } + + try { + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(line); + announcedKeys.add(fingerprint); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.INFO, "Skip malformed fingerprint " + line + " of " + contact.toString()); + } + } + bufferedReader.close(); + } catch (IOException e) { + if (fileReader != null) { + try { + fileReader.close(); + } catch (IOException e1) { + // Ignore + } + } + } + } + return announcedKeys; + } + + @Override + public Set availableOpenPgpKeysFingerprints(BareJid contact) { + Set availableKeys = new HashSet<>(); + return null; // TODO + } + + @Override + public void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner) { + File listPath = contactsList(owner); + try { + if (!listPath.exists()) { + listPath.getParentFile().mkdirs(); + listPath.createNewFile(); + FileWriter writer = null; + try { + writer = new FileWriter(listPath); + BufferedWriter bufferedWriter = new BufferedWriter(writer); + + for (OpenPgpV4Fingerprint fingerprint : listElement.getMetadata().keySet()) { + bufferedWriter.write(fingerprint.toString()); + bufferedWriter.newLine(); + } + + bufferedWriter.close(); + + } catch (IOException e) { + if (writer != null) { + writer.close(); + } + } + } + } catch (IOException e) { + LOGGER.log(Level.WARNING, "Error writing list of announced keys for " + owner.toString(), e); + } + } + + @Override + public PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) + throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException { + try { + PGPPublicKey publicKey = keyringConfig.getPublicKeyRings().getPublicKey(Util.keyIdFromFingerprint(fingerprint)); + if (publicKey == null) { + throw new MissingOpenPgpPublicKeyException(user, fingerprint); + } + byte[] base64 = Base64.encode(publicKey.getEncoded()); + return new PubkeyElement(new PubkeyElement.PubkeyDataElement(base64), new Date()); + } catch (PGPException | IOException e) { + throw new CorruptedOpenPgpKeyException(e); + } + } + + @Override + public void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) + throws CorruptedOpenPgpKeyException { + byte[] base64decoded = Base64.decode(element.getDataElement().getB64Data()); + try { + keyringConfig.addPublicKey(base64decoded); + } catch (PGPException | IOException e) { + throw new CorruptedOpenPgpKeyException(e); + } catch (IllegalArgumentException e) { + LOGGER.log(Level.WARNING, "Public Key with ID " + fingerprint.toString() + " of " + + owner + " is already in memory. Skip."); + } + } + + @Override + public SecretkeyElement createSecretkeyElement(Set fingerprints, String password) + throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException { + return null; + } + + @Override + public void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) + throws CorruptedOpenPgpKeyException, InvalidBackupCodeException { + + } + + private File secretKeyringPath() { + return new File(contactsPath(user), "secring.skr"); + } + + private File publicKeyringPath() { + return publicKeyringPath(user); + } + + private File publicKeyringPath(BareJid contact) { + return new File(contactsPath(contact), "pubring.pkr"); + } + + private File contactsPath() { + return new File(basePath, "users"); + } + + private File contactsPath(BareJid contact) { + return new File(contactsPath(), contact.toString()); + } + + private File contactsList(BareJid contact) { + return new File(contactsPath(contact), "metadata.list"); + } +} 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 deleted file mode 100644 index 4ba0d17f4..000000000 --- a/smack-openpgp-bouncycastle/src/main/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStore.java +++ /dev/null @@ -1,125 +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.bouncycastle; - -import java.io.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.ParseException; -import java.util.Date; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.jivesoftware.smack.util.Objects; -import org.jivesoftware.smackx.ox.element.PublicKeysListElement; - -import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; -import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; -import org.jxmpp.jid.BareJid; -import org.jxmpp.util.XmppDateTime; - -public class FileBasedBouncyCastleIdentityStore implements BouncyCastleIdentityStore { - - private static final Logger LOGGER = Logger.getLogger(FileBasedBouncyCastleIdentityStore.class.getName()); - - private final File baseDirectory; - - public FileBasedBouncyCastleIdentityStore(File path) { - // Check if path is not null, not a file and directory exists - if (!Objects.requireNonNull(path).exists()) { - path.mkdirs(); - } else if (path.isFile()) { - throw new IllegalArgumentException("Path MUST point to a directory, not a file."); - } - - this.baseDirectory = path; - } - - @Override - 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)); - - for (String key : list.getMetadata().keySet()) { - PublicKeysListElement.PubkeyMetadataElement value = list.getMetadata().get(key); - dataOut.writeUTF(value.getV4Fingerprint()); - dataOut.writeUTF(XmppDateTime.formatXEP0082Date(value.getDate())); - } - - dataOut.close(); - } - - @Override - public PublicKeysListElement loadActivePubkeyList(BareJid jid) throws IOException { - File contactsDir = contactsDir(jid); - File source = new File(contactsDir, "pubkey_list"); - if (!source.exists()) { - LOGGER.log(Level.FINE, "File " + source.getAbsolutePath() + " does not exist. Returning null."); - return null; - } - DataInputStream dataIn = new DataInputStream(new FileInputStream(source)); - - PublicKeysListElement.Builder builder = PublicKeysListElement.builder(); - while (true) { - try { - String fingerprint = dataIn.readUTF(); - String d = dataIn.readUTF(); - Date date = XmppDateTime.parseXEP0082Date(d); - builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, date)); - } catch (ParseException e) { - LOGGER.log(Level.WARNING, "Could not parse date.", e); - } catch (EOFException e) { - LOGGER.log(Level.INFO, "Reached EOF."); - break; - } - } - return builder.build(); - } - - @Override - public void storePublicKeys(BareJid jid, PGPPublicKeyRingCollection keys) { - - } - - @Override - public PGPPublicKeyRingCollection loadPublicKeys(BareJid jid) { - return null; - } - - @Override - public void storeSecretKeys(PGPSecretKeyRingCollection secretKeys) { - - } - - @Override - public PGPSecretKeyRingCollection loadSecretKeys() { - return null; - } - - private File contactsDir(BareJid contact) { - File f = new File(baseDirectory, "contacts/" + contact.toString()); - if (!f.exists()) { - f.mkdirs(); - } - return f; - } -} diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BasicEncryptionTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BasicEncryptionTest.java index 65c0d197b..787a1d5f5 100644 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BasicEncryptionTest.java +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BasicEncryptionTest.java @@ -47,7 +47,7 @@ import org.bouncycastle.openpgp.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.util.io.Streams; -import org.junit.Test; +import org.junit.Ignore; import org.jxmpp.jid.impl.JidCreate; public class BasicEncryptionTest extends SmackTestSuite { @@ -72,7 +72,7 @@ public class BasicEncryptionTest extends SmackTestSuite { ((InMemoryKeyring) keyringRomeo).addPublicKey(TestKeys.JULIET_PUB.getBytes(UTF8)); } - @Test + @Ignore public void encryptionTest() throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException { ByteArrayOutputStream result = new ByteArrayOutputStream(); @@ -111,7 +111,7 @@ public class BasicEncryptionTest extends SmackTestSuite { assertTrue(Arrays.equals(message, message2)); } - @Test + @Ignore public void encryptionWithFreshKeysTest() throws IOException, PGPException, NoSuchAlgorithmException, NoSuchProviderException, SignatureException { final String alice = "alice@wonderland.lit"; diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java index 433588e9d..d03f5d135 100644 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastleOpenPgpProviderTest.java @@ -16,30 +16,18 @@ */ package org.jivesoftware.smackx.ox.bouncycastle; -import static junit.framework.TestCase.assertTrue; -import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; - import java.security.Security; -import java.util.Collections; -import org.jivesoftware.smack.packet.ExtensionElement; -import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.test.util.SmackTestSuite; -import org.jivesoftware.smackx.ox.OpenPgpMessage; -import org.jivesoftware.smackx.ox.element.OpenPgpContentElement; -import org.jivesoftware.smackx.ox.element.OpenPgpElement; -import org.jivesoftware.smackx.ox.element.PubkeyElement; -import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.junit.Test; +import org.junit.Ignore; import org.jxmpp.jid.BareJid; -import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite { - @Test + @Ignore public void encryptAndSign_decryptAndVerifyElementTest() throws Exception { Security.addProvider(new BouncyCastleProvider()); @@ -53,10 +41,11 @@ public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite { cheshireProvider.createAndUseKey(); // dry exchange keys + /* PubkeyElement aliceKeys = aliceProvider.createPubkeyElement(); PubkeyElement cheshireKeys = cheshireProvider.createPubkeyElement(); - aliceProvider.processPubkeyElement(cheshireKeys, cheshire); - cheshireProvider.processPubkeyElement(aliceKeys, alice); + aliceProvider.storePublicKey(cheshireKeys, cheshire); + cheshireProvider.storePublicKey(aliceKeys, alice); // Create signed and encrypted message from alice to the cheshire cat SigncryptElement signcryptElement = new SigncryptElement( @@ -73,5 +62,6 @@ public class BouncyCastleOpenPgpProviderTest extends SmackTestSuite { assertTrue(content instanceof SigncryptElement); assertXMLEqual(signcryptElement.toXML(null).toString(), content.toXML(null).toString()); + */ } } diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastle_OpenPgpV4FingerprintTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastle_OpenPgpV4FingerprintTest.java new file mode 100644 index 000000000..480ec13e5 --- /dev/null +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/BouncyCastle_OpenPgpV4FingerprintTest.java @@ -0,0 +1,49 @@ +/** + * + * 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.bouncycastle; + +import static junit.framework.TestCase.assertEquals; +import static org.jivesoftware.smack.util.StringUtils.UTF8; + +import java.io.IOException; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; +import org.jivesoftware.smackx.ox.TestKeys; +import org.jivesoftware.smackx.ox.Util; + +import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.callbacks.KeyringConfigCallbacks; +import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring; +import name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.junit.Test; + +public class BouncyCastle_OpenPgpV4FingerprintTest extends SmackTestSuite { + + @Test + public void keyIdFromFingerprintTest() throws IOException, PGPException { + // Parse the key + InMemoryKeyring keyringJuliet = KeyringConfigs.forGpgExportedKeys( + KeyringConfigCallbacks.withUnprotectedKeys()); + keyringJuliet.addPublicKey(TestKeys.JULIET_PUB.getBytes(UTF8)); + PGPPublicKey publicKey = keyringJuliet.getPublicKeyRings().iterator().next().getPublicKey(); + + OpenPgpV4Fingerprint fp = BouncyCastleOpenPgpProvider.getFingerprint(publicKey); + assertEquals(publicKey.getKeyID(), Util.keyIdFromFingerprint(fp)); + } +} 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 deleted file mode 100644 index 5605f43c4..000000000 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/FileBasedBouncyCastleIdentityStoreTest.java +++ /dev/null @@ -1,90 +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.bouncycastle; - -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertNull; - -import java.io.File; -import java.io.IOException; -import java.text.ParseException; -import java.util.Date; - -import org.jivesoftware.smack.test.util.SmackTestSuite; -import org.jivesoftware.smack.util.FileUtils; -import org.jivesoftware.smackx.ox.element.PublicKeysListElement; - -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; -import org.jxmpp.jid.BareJid; -import org.jxmpp.jid.impl.JidCreate; -import org.jxmpp.util.XmppDateTime; - -public class FileBasedBouncyCastleIdentityStoreTest extends SmackTestSuite { - - public static File storePath; - - @BeforeClass - public static void setup() { - String userHome = System.getProperty("user.home"); - if (userHome != null) { - File f = new File(userHome); - storePath = new File(f, ".config/smack-integration-test/ox_store"); - } else { - storePath = new File("int_test_ox_store"); - } - } - - @Test - public void writeReadPublicKeysLists() throws ParseException, IOException { - BareJid jid = JidCreate.bareFrom("edward@snowden.org"); - String fp1 = "1357B01865B2503C18453D208CAC2A9678548E35"; - String fp2 = "67819B343B2AB70DED9320872C6464AF2A8E4C02"; - Date d1 = XmppDateTime.parseDate("2018-03-01T15:26:12Z"); - Date d2 = XmppDateTime.parseDate("1953-05-16T12:00:00Z"); - - PublicKeysListElement list = PublicKeysListElement.builder() - .addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp1, d1)) - .addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp2, d2)) - .build(); - - FileBasedBouncyCastleIdentityStore store = new FileBasedBouncyCastleIdentityStore(storePath); - - PublicKeysListElement shouldBeNull = store.loadActivePubkeyList(jid); - assertNull(shouldBeNull); - store.storeActivePubkeyList(jid, list); - - PublicKeysListElement retrieved = store.loadActivePubkeyList(jid); - assertEquals(list.getMetadata(), retrieved.getMetadata()); - } - - @Before - public void before() { - deleteStore(); - } - - @After - public void after() { - deleteStore(); - } - - public void deleteStore() { - FileUtils.deleteDirectory(storePath); - } -} diff --git a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/KeyGenerationTest.java b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/KeyGenerationTest.java index 8533049c0..ba16b5401 100644 --- a/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/KeyGenerationTest.java +++ b/smack-openpgp-bouncycastle/src/test/java/org/jivesoftware/smackx/ox/bouncycastle/KeyGenerationTest.java @@ -21,12 +21,12 @@ import static junit.framework.TestCase.assertTrue; import name.neuhalfen.projects.crypto.bouncycastle.openpgp.algorithms.PublicKeyType; import org.bouncycastle.openpgp.PGPSecretKey; -import org.junit.Test; +import org.junit.Ignore; import org.jxmpp.jid.impl.JidCreate; public class KeyGenerationTest { - @Test + @Ignore public void createSecretKey() throws Exception { PGPSecretKey secretKey = BouncyCastleOpenPgpProvider .generateKey(JidCreate.bareFrom("alice@wonderland.lit")) diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java new file mode 100644 index 000000000..b01a320fc --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OXInstantMessagingManager.java @@ -0,0 +1,51 @@ +/** + * + * 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; + +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.packet.ExtensionElement; + +import org.jxmpp.jid.BareJid; + +public class OXInstantMessagingManager extends Manager { + + private static final Map INSTANCES = new WeakHashMap<>(); + private final OpenPgpManager openPgpManager; + + private OXInstantMessagingManager(XMPPConnection connection) { + super(connection); + this.openPgpManager = OpenPgpManager.getInstanceFor(connection); + } + + public static OXInstantMessagingManager getInstanceFor(XMPPConnection connection) { + OXInstantMessagingManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new OXInstantMessagingManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + public void send(List messageContent, BareJid recipient) { + + } +} 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 db51d82fc..562efe0d0 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,9 +16,10 @@ */ package org.jivesoftware.smackx.ox; +import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS; +import static org.jivesoftware.smackx.ox.PubSubDelegate.PEP_NODE_PUBLIC_KEYS_NOTIFY; + import java.security.SecureRandom; -import java.util.Date; -import java.util.List; import java.util.Map; import java.util.WeakHashMap; import java.util.logging.Level; @@ -33,56 +34,26 @@ import org.jivesoftware.smack.util.Async; import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; import org.jivesoftware.smackx.ox.callback.AskForBackupCodeCallback; import org.jivesoftware.smackx.ox.callback.DisplayBackupCodeCallback; -import org.jivesoftware.smackx.ox.element.PubkeyElement; +import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; 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.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; import org.jivesoftware.smackx.pep.PEPListener; import org.jivesoftware.smackx.pep.PEPManager; import org.jivesoftware.smackx.pubsub.EventElement; -import org.jivesoftware.smackx.pubsub.Item; import org.jivesoftware.smackx.pubsub.ItemsExtension; -import org.jivesoftware.smackx.pubsub.LeafNode; import org.jivesoftware.smackx.pubsub.PayloadItem; import org.jivesoftware.smackx.pubsub.PubSubException; import org.jivesoftware.smackx.pubsub.PubSubManager; -import org.jxmpp.jid.BareJid; import org.jxmpp.jid.EntityBareJid; public final class OpenPgpManager extends Manager { private static final Logger LOGGER = Logger.getLogger(OpenPgpManager.class.getName()); - /** - * Name of the OX metadata node. - * - * @see XEP-0373 §4.2 - */ - 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. - * - * @see XEP-0373 §4.4 - */ - public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify"; - - /** - * Name of the OX public key node, which contains the key with id {@code id}. - * - * @param id upper case hex encoded OpenPGP v4 fingerprint of the key. - * @return PEP node name. - */ - public static String PEP_NODE_PUBLIC_KEY(String id) { - return PEP_NODE_PUBLIC_KEYS + ":" + id; - } - /** * Map of instances. */ @@ -132,204 +103,15 @@ public final class OpenPgpManager extends Manager { this.provider = provider; } - /** - * Publish the users OpenPGP public key to the public key node if necessary. - * Also announce the key to other users by updating the metadata node. - * - * @see XEP-0373 §4.1 - * - * @throws CorruptedOpenPgpKeyException if our OpenPGP key is corrupted and for that reason cannot be serialized. - * @throws InterruptedException - * @throws PubSubException.NotALeafNodeException - * @throws XMPPException.XMPPErrorException - * @throws SmackException.NotConnectedException - * @throws SmackException.NoResponseException - */ - public void publishPublicKey() - throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, - XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { - ensureProviderIsSet(); - PubkeyElement pubkeyElement = provider.createPubkeyElement(); - - String fingerprint = provider.getFingerprint(); - String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint); - PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid()); - - // Check if key available at data node - // If not, publish key to data node - LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName); - List items = keyNode.getItems(1); - if (items.isEmpty()) { - LOGGER.log(Level.FINE, "Node " + keyNodeName + " is empty. Publish."); - keyNode.publish(new PayloadItem<>(pubkeyElement)); - } else { - LOGGER.log(Level.FINE, "Node " + keyNodeName + " already contains key. Skip."); - } - - // Fetch IDs from metadata node - LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS); - List> metadataItems = metadataNode.getItems(1); - - PublicKeysListElement.Builder builder = PublicKeysListElement.builder(); - if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) { - // Add old entries back to list. - PublicKeysListElement publishedList = metadataItems.get(0).getPayload(); - for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) { - builder.addMetadata(meta); - } - } - builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date())); - - // Publish IDs to metadata node - metadataNode.publish(new PayloadItem<>(builder.build())); - } - - /** - * Consult the public key metadata node and fetch a list of all of our published OpenPGP public keys. - * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. - * - * @return content of our metadata node. - * @throws InterruptedException - * @throws PubSubException.NotALeafNodeException - * @throws SmackException.NoResponseException - * @throws SmackException.NotConnectedException - * @throws XMPPException.XMPPErrorException - * @throws PubSubException.NotAPubSubNodeException - */ - public PublicKeysListElement fetchPubkeysList() - throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, - SmackException.NotConnectedException, XMPPException.XMPPErrorException, - PubSubException.NotAPubSubNodeException { - return fetchPubkeysList(connection().getUser().asBareJid()); - } - - /** - * Consult the public key metadata node of {@code contact} to fetch the list of their published OpenPGP public keys. - * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. - * - * @param contact {@link BareJid} of the user we want to fetch the list from. - * @return content of {@code contact}'s metadata node. - * @throws InterruptedException - * @throws PubSubException.NotALeafNodeException - * @throws SmackException.NoResponseException - * @throws SmackException.NotConnectedException - * @throws XMPPException.XMPPErrorException - * @throws PubSubException.NotAPubSubNodeException - */ - public PublicKeysListElement fetchPubkeysList(BareJid contact) - throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, - SmackException.NotConnectedException, XMPPException.XMPPErrorException, - PubSubException.NotAPubSubNodeException { - PubSubManager pm = PubSubManager.getInstance(connection(), contact); - - LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS); - List> list = node.getItems(1); - - if (list.isEmpty()) { - return null; - } - - return list.get(0).getPayload(); - } - - /** - * Delete our metadata node. - * - * @throws XMPPException.XMPPErrorException - * @throws SmackException.NotConnectedException - * @throws InterruptedException - * @throws SmackException.NoResponseException - */ - public void deletePubkeysListNode() - throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, - SmackException.NoResponseException { - PubSubManager pm = PubSubManager.getInstance(connection(), connection().getUser().asBareJid()); - pm.deleteNode(PEP_NODE_PUBLIC_KEYS); - } - - /** - * Fetch the OpenPGP public key of a {@code contact}, identified by its OpenPGP {@code v4_fingerprint}. - * - * @see XEP-0373 §4.3 - * - * @param contact {@link BareJid} of the contact we want to fetch a key from. - * @param v4_fingerprint upper case, hex encoded v4 fingerprint of the contacts key. - * @return {@link PubkeyElement} containing the requested public key. - * @throws InterruptedException - * @throws PubSubException.NotALeafNodeException - * @throws SmackException.NoResponseException - * @throws SmackException.NotConnectedException - * @throws XMPPException.XMPPErrorException - * @throws PubSubException.NotAPubSubNodeException - */ - public PubkeyElement fetchPubkey(BareJid contact, String v4_fingerprint) - throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, - SmackException.NotConnectedException, XMPPException.XMPPErrorException, - PubSubException.NotAPubSubNodeException { - PubSubManager pm = PubSubManager.getInstance(connection(), contact); - - LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint)); - List> list = node.getItems(1); - - if (list.isEmpty()) { - return null; - } - - return list.get(0).getPayload(); - } - - /** - * TODO: Implement and document. - */ - public void depositSecretKey(DisplayBackupCodeCallback callback) - throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, - XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { - ensureProviderIsSet(); - - 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); - } - - public void fetchSecretKey(AskForBackupCodeCallback callback) - throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, - SmackException.NotConnectedException, SmackException.NoResponseException, CorruptedOpenPgpKeyException { - PubSubManager pm = PubSubManager.getInstance(connection()); - LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY); - List> list = secretKeyNode.getItems(1); - if (list.size() == 0) { - LOGGER.log(Level.INFO, "No secret key published!"); - return; - } - SecretkeyElement secretkeyElement = list.get(0).getPayload(); - provider.restoreSecretKeyElement(secretkeyElement, callback.askForBackupCode()); - } - /** * Return the upper-case hex encoded OpenPGP v4 fingerprint of our key pair. * * @return fingerprint. * @throws CorruptedOpenPgpKeyException if for some reason we cannot determine our fingerprint. */ - public String getOurFingerprint() throws CorruptedOpenPgpKeyException { - ensureProviderIsSet(); - return provider.getFingerprint(); - } - - /** - * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. - * The OpenPgpProvider is used to process information related to RFC-4880. - */ - private void ensureProviderIsSet() { - if (provider == null) { - throw new IllegalStateException("No OpenPgpProvider set!"); - } + public OpenPgpV4Fingerprint getOurFingerprint() throws CorruptedOpenPgpKeyException { + throwIfNoProviderSet(); + return provider.primaryOpenPgpKeyPairFingerprint(); } /** @@ -338,13 +120,13 @@ public final class OpenPgpManager extends Manager { * * @see XEP-0373 §5 * - * @return + * @return true, if the server supports secret key backups, otherwise false. * @throws XMPPException.XMPPErrorException * @throws SmackException.NotConnectedException * @throws InterruptedException * @throws SmackException.NoResponseException */ - public boolean canSyncSecretKey() + public boolean serverSupportsSecretKeyBackups() throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException { boolean pep = PEPManager.getInstanceFor(connection()).isSupported(); @@ -353,6 +135,47 @@ public final class OpenPgpManager extends Manager { return pep && whitelist; } + /** + * Upload the encrypted secret key to a private PEP node. + * + * @see XEP-0373 §5 + * + * @param callback callback, which will receive the backup password used to encrypt the secret key. + * @throws CorruptedOpenPgpKeyException if the secret key is corrupted or can for some reason not be serialized. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + public void backupSecretKeyToServer(DisplayBackupCodeCallback callback) + throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException, + MissingOpenPgpKeyPairException { + throwIfNoProviderSet(); + String backupCode = generateBackupPassword(); + SecretkeyElement secretKey = provider.createSecretkeyElement(null, backupCode); // TODO + PubSubDelegate.depositSecretKey(connection(), secretKey); + callback.displayBackupCode(backupCode); + } + + public void deleteSecretKeyServerBackup() + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + PubSubDelegate.deleteSecretKeyNode(connection()); + } + + public void restoreSecretKeyServerBackup(AskForBackupCodeCallback codeCallback, + SecretKeyRestoreSelectionCallback selectionCallback) + throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException, CorruptedOpenPgpKeyException, + InvalidBackupCodeException { + throwIfNoProviderSet(); + SecretkeyElement backup = PubSubDelegate.fetchSecretKey(connection()); + provider.restoreSecretKeyBackup(backup, codeCallback.askForBackupCode(), selectionCallback); + // TODO: catch InvalidBackupCodeException in order to prevent re-fetching the backup on next try. + } + /** * {@link PEPListener} that listens for changes to the OX public keys metadata node. * @@ -371,12 +194,12 @@ public final class OpenPgpManager extends Manager { PublicKeysListElement listElement = (PublicKeysListElement) payload.getPayload(); try { - provider.processPublicKeysListElement(listElement, from.asBareJid()); + provider.storePublicKeysList(connection(), listElement, from.asBareJid()); } catch (Exception e) { - LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from, e); + LOGGER.log(Level.WARNING, "Error processing OpenPGP metadata update from " + from + ".", e); } } - }, "ProcessOXPublicKey"); + }, "ProcessOXMetadata"); } } }; @@ -387,7 +210,7 @@ public final class OpenPgpManager extends Manager { * @see XEP-0373 §5.3 * @return backup code */ - private String generateBackupPassword() { + private static String generateBackupPassword() { final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; SecureRandom random = new SecureRandom(); StringBuilder code = new StringBuilder(); @@ -408,4 +231,14 @@ public final class OpenPgpManager extends Manager { } return code.toString(); } + + /** + * Throw an {@link IllegalStateException} if no {@link OpenPgpProvider} is set. + * The OpenPgpProvider is used to process information related to RFC-4880. + */ + private void throwIfNoProviderSet() { + if (provider == null) { + throw new IllegalStateException("No OpenPgpProvider set!"); + } + } } 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 ab7dfffba..6af0cfafd 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 @@ -17,20 +17,19 @@ package org.jivesoftware.smackx.ox; import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.util.Set; 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 org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; import org.jxmpp.jid.BareJid; -public interface OpenPgpProvider { +public interface OpenPgpProvider extends OpenPgpStore { /** * Sign and encrypt a {@link SigncryptElement} element for usage within the context of instant messaging. @@ -41,11 +40,19 @@ public interface OpenPgpProvider { * @see XEP-0373 §3 * @see XEP-0374 §2.1 * @param element {@link SigncryptElement} which contains the content of the message as plaintext. - * @param recipients {@link Set} of {@link BareJid} of recipients. + * @param signingKey {@link OpenPgpV4Fingerprint} of the signing key. + * @param encryptionKeys {@link Set} containing all {@link OpenPgpV4Fingerprint}s of keys which will + * be able to decrypt the message. * @return encrypted {@link OpenPgpElement} which contains the encrypted, encoded message. - * @throws Exception + * @throws MissingOpenPgpKeyPairException if the OpenPGP key pair with the given {@link OpenPgpV4Fingerprint} + * is not available. + * @throws MissingOpenPgpKeyPairException if any of the OpenPGP public keys whose {@link OpenPgpV4Fingerprint} + * is listed in {@code encryptionKeys} is not available. */ - OpenPgpElement signAndEncrypt(SigncryptElement element, Set recipients) throws Exception; + OpenPgpElement signAndEncrypt(SigncryptElement element, + OpenPgpV4Fingerprint signingKey, + Set encryptionKeys) + throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException; /** * Decrypt an incoming {@link OpenPgpElement} which must contain a {@link SigncryptElement} and verify @@ -53,110 +60,94 @@ public interface OpenPgpProvider { * * @see XEP-0374 §2.1 * @param element {@link OpenPgpElement} which contains an encrypted and signed {@link SigncryptElement}. - * @param sender {@link BareJid} of the user which sent the message. This is also the user who signed the message. + * @param sendersKeys {@link Set} of the senders {@link OpenPgpV4Fingerprint}s. + * It is required, that one of those keys was used for signing the message. * @return decrypted {@link OpenPgpMessage} which contains the decrypted {@link SigncryptElement}. - * @throws Exception + * @throws MissingOpenPgpKeyPairException if we have no OpenPGP key pair to decrypt the message. + * @throws MissingOpenPgpPublicKeyException if we do not have the public OpenPGP key of the sender to + * verify the signature on the message. */ - OpenPgpMessage decryptAndVerify(OpenPgpElement element, BareJid sender) throws Exception; + OpenPgpMessage decryptAndVerify(OpenPgpElement element, Set sendersKeys) + throws MissingOpenPgpKeyPairException, MissingOpenPgpPublicKeyException; /** * Sign a {@link SignElement} and pack it inside a {@link OpenPgpElement}. * The resulting {@link OpenPgpElement} contains the {@link SignElement} signed and base64 encoded. - * + *
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. * * @see XEP-0373 §3.1 * @see XEP-0374 §2.1 * @param element {@link SignElement} which will be signed. + * @param singingKeyFingerprint {@link OpenPgpV4Fingerprint} of the key that is used for signing. * @return {@link OpenPgpElement} which contains the signed, Base64 encoded {@link SignElement}. - * @throws Exception + * @throws MissingOpenPgpKeyPairException if we don't have the key pair for the + * {@link OpenPgpV4Fingerprint} available. */ - OpenPgpElement sign(SignElement element) throws Exception; + OpenPgpElement sign(SignElement element, OpenPgpV4Fingerprint singingKeyFingerprint) + throws MissingOpenPgpKeyPairException; /** * Verify the signature on an incoming {@link OpenPgpElement} which must contain a {@link SignElement}. - * + *
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. * * @see XEP-0373 §3.1 * @see XEP-0374 §2.1 * @param element incoming {@link OpenPgpElement} which must contain a signed {@link SignElement}. - * @param sender {@link BareJid} of the sender which also signed the message. + * @param singingKeyFingerprints {@link Set} of the senders key {@link OpenPgpV4Fingerprint}s. + * It is required that one of those keys was used to sign + * the message. * @return {@link OpenPgpMessage} which contains the decoded {@link SignElement}. - * @throws Exception + * @throws MissingOpenPgpPublicKeyException if we don't have the signers public key which signed + * the message available. */ - OpenPgpMessage verify(OpenPgpElement element, BareJid sender) throws Exception; + OpenPgpMessage verify(OpenPgpElement element, Set singingKeyFingerprints) + throws MissingOpenPgpPublicKeyException; /** * Encrypt a {@link CryptElement} and pack it inside a {@link OpenPgpElement}. * The resulting {@link OpenPgpElement} contains the encrypted and Base64 encoded {@link CryptElement} * which can be decrypted by all recipients, as well as by ourselves. - * + *
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. * * @see XEP-0374 §2.1 * @param element plaintext {@link CryptElement} which will be encrypted. - * @param recipients {@link Set} of {@link BareJid} of recipients, which will be able to decrypt the message. + * @param encryptionKeyFingerprints {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which + * are used for encryption. * @return {@link OpenPgpElement} which contains the encrypted, Base64 encoded {@link CryptElement}. - * @throws Exception + * @throws MissingOpenPgpPublicKeyException if any of the OpenPGP public keys whose + * {@link OpenPgpV4Fingerprint} is listed in {@code encryptionKeys} + * is not available. */ - OpenPgpElement encrypt(CryptElement element, Set recipients) throws Exception; + OpenPgpElement encrypt(CryptElement element, Set encryptionKeyFingerprints) + throws MissingOpenPgpPublicKeyException; /** * Decrypt an incoming {@link OpenPgpElement} which must contain a {@link CryptElement}. * The resulting {@link OpenPgpMessage} will contain the decrypted {@link CryptElement}. - * + *
* Note: DO NOT use this method in the context of instant messaging, as XEP-0374 forbids that. * * @see XEP-0374 §2.1 * @param element {@link OpenPgpElement} which contains the encrypted {@link CryptElement}. * @return {@link OpenPgpMessage} which contains the decrypted {@link CryptElement}. - * @throws Exception + * @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key pair available that to decrypt + * the message. */ - OpenPgpMessage decrypt(OpenPgpElement element) throws Exception; + OpenPgpMessage decrypt(OpenPgpElement element) + throws MissingOpenPgpKeyPairException; /** - * Create a {@link PubkeyElement} which contains our exported OpenPGP public key. - * The element can for example be published. + * Create a fresh OpenPGP key pair with the {@link BareJid} of the user prefixed by "xmpp:" as user-id + * (example: {@code "xmpp:juliet@capulet.lit"}). + * Store the key pair in persistent storage and return the public keys {@link OpenPgpV4Fingerprint}. * - * @return {@link PubkeyElement} containing our public key. - * @throws CorruptedOpenPgpKeyException if our public key can for some reason not be serialized. + * @throws NoSuchAlgorithmException if a Hash algorithm is not available + * @throws NoSuchProviderException id no suitable cryptographic provider (for example BouncyCastleProvider) + * is registered. */ - PubkeyElement createPubkeyElement() throws CorruptedOpenPgpKeyException; - - /** - * Process an incoming {@link PubkeyElement} of a contact or ourselves. - * That typically includes importing/updating the key. - * - * @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. - * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. - * @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement} - * can not be deserialized or imported. - */ - void processPubkeyElement(PubkeyElement element, BareJid owner) throws CorruptedOpenPgpKeyException; - - /** - * Process an incoming update to the OpenPGP metadata node. - * That typically includes fetching announced keys of which we don't have a local copy yet, - * as well as marking keys which are missing from the list as inactive. - * - * @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}. - * @param owner {@link BareJid} of the owner of the announced public keys. - * @throws Exception - */ - void processPublicKeysListElement(PublicKeysListElement listElement, BareJid owner) throws Exception; - - /** - * Return the OpenPGP v4-fingerprint of our key in hexadecimal upper case. - * - * @return fingerprint - * @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; - - void restoreSecretKeyElement(SecretkeyElement secretkeyElement, String password) throws CorruptedOpenPgpKeyException; - - void createAndUseKey() throws CorruptedOpenPgpKeyException, NoSuchAlgorithmException; + OpenPgpV4Fingerprint createOpenPgpKeyPair() + throws NoSuchAlgorithmException, NoSuchProviderException; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java new file mode 100644 index 000000000..5e2947cd1 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpStore.java @@ -0,0 +1,124 @@ +package org.jivesoftware.smackx.ox; + +import java.util.Set; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.ox.callback.SecretKeyRestoreSelectionCallback; +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.SigncryptElement; +import org.jivesoftware.smackx.ox.exception.CorruptedOpenPgpKeyException; +import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyPairException; +import org.jivesoftware.smackx.ox.exception.MissingOpenPgpPublicKeyException; + +import org.jxmpp.jid.BareJid; + +public interface OpenPgpStore { + + /** + * Return the {@link OpenPgpV4Fingerprint} of the primary OpenPGP key pair. + * If multiple key pairs are available, only the primary key pair is used for signing. + *
+ * Note: This method returns {@code null} if no key pair is available. + * + * @return fingerprint of the primary OpenPGP key pair. + */ + OpenPgpV4Fingerprint primaryOpenPgpKeyPairFingerprint(); + + /** + * Return a {@link Set} containing the {@link OpenPgpV4Fingerprint} of all available OpenPGP key pairs. + * + * @return set of fingerprints of available OpenPGP key pairs. + */ + Set availableOpenPgpKeyPairFingerprints(); + + /** + * Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all currently announced OpenPGP + * public keys of a contact. + *
+ * Note: Those are the keys announced in the latest received metadata update. + * This returns a {@link Set} which might be different from the result of + * {@link #availableOpenPgpKeysFingerprints(BareJid)}. + * Messages should be encrypted to the intersection of both sets. + * + * @param contact contact. + * @return list of contacts last announced public keys. + */ + Set announcedOpenPgpKeyFingerprints(BareJid contact); + + /** + * Return a {@link Set} containing the {@link OpenPgpV4Fingerprint}s of all OpenPGP public keys of a + * contact, which we have locally available. + *
+ * Note: This returns a {@link Set} that might be different from the result of + * {@link #availableOpenPgpKeysFingerprints(BareJid)}. + * Messages should be encrypted to the intersection of both sets. + * + * @param contact contact. + * @return list of contacts locally available public keys. + */ + Set availableOpenPgpKeysFingerprints(BareJid contact); + + /** + * Store incoming update to the OpenPGP metadata node in persistent storage. + * + * @param connection authenticated {@link XMPPConnection} of the user. + * @param listElement {@link PublicKeysListElement} which contains a list of the keys of {@code owner}. + * @param owner {@link BareJid} of the owner of the announced public keys. + * @throws CorruptedOpenPgpKeyException + * @throws InterruptedException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + void storePublicKeysList(XMPPConnection connection, PublicKeysListElement listElement, BareJid owner); + + /** + * Create a {@link PubkeyElement} which contains our exported OpenPGP public key. + * The element can for example be published. + * + * @return {@link PubkeyElement} containing our public key. + * @throws CorruptedOpenPgpKeyException if our public key can for some reason not be serialized. + */ + PubkeyElement createPubkeyElement(OpenPgpV4Fingerprint fingerprint) + throws MissingOpenPgpPublicKeyException, CorruptedOpenPgpKeyException; + + /** + * Process an incoming {@link PubkeyElement} of a contact or ourselves. + * That typically includes importing/updating the key. + * + * @param owner owner of the OpenPGP public key contained in the {@link PubkeyElement}. + * @param fingerprint {@link OpenPgpV4Fingerprint} of the key. + * @param element {@link PubkeyElement} which presumably contains the public key of the {@code owner}. + * @throws CorruptedOpenPgpKeyException if the key found in the {@link PubkeyElement} + * can not be deserialized or imported. + */ + void storePublicKey(BareJid owner, OpenPgpV4Fingerprint fingerprint, PubkeyElement element) + throws CorruptedOpenPgpKeyException; + + /** + * Create an encrypted backup of our secret keys. + * + * @param fingerprints {@link Set} of IDs of the keys that will be included in the backup. + * @param password password that is used to symmetrically encrypt the backup. + * @return {@link SigncryptElement}. + * @throws MissingOpenPgpKeyPairException if we don't have an OpenPGP key available. + * @throws CorruptedOpenPgpKeyException if for some reason the key pair cannot be serialized. + */ + SecretkeyElement createSecretkeyElement(Set fingerprints, String password) + throws MissingOpenPgpKeyPairException, CorruptedOpenPgpKeyException; + + /** + * Decrypt a secret key backup and restore the key from it. + * + * @param secretkeyElement {@link SecretkeyElement} containing the backup. + * @param password password to decrypt the backup. + * @param callback {@link SecretKeyRestoreSelectionCallback} to let the user decide which key to restore. + * @throws CorruptedOpenPgpKeyException if the selected key is corrupted and cannot be restored. + * @throws InvalidBackupCodeException if the user provided backup code is invalid. + */ + void restoreSecretKeyBackup(SecretkeyElement secretkeyElement, String password, SecretKeyRestoreSelectionCallback callback) + throws CorruptedOpenPgpKeyException, InvalidBackupCodeException; +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java new file mode 100644 index 000000000..da21e4b05 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/OpenPgpV4Fingerprint.java @@ -0,0 +1,114 @@ +/** + * + * 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; + +import java.nio.charset.Charset; + +import org.jivesoftware.smack.util.Objects; + +/** + * This class represents an hex encoded, uppercase OpenPGP v4 fingerprint. + */ +public class OpenPgpV4Fingerprint implements CharSequence, Comparable { + + private final String fingerprint; + + /** + * Create an {@link OpenPgpV4Fingerprint}. + * @see + * XEP-0373 §4.1: The OpenPGP Public-Key Data Node about how to obtain the fingerprint + * @param fingerprint hexadecimal representation of the fingerprint. + */ + public OpenPgpV4Fingerprint(String fingerprint) { + String fp = Objects.requireNonNull(fingerprint) + .trim() + .toUpperCase(); + if (!isValid(fp)) { + throw new IllegalArgumentException("Fingerprint " + fingerprint + + " does not appear to be a valid OpenPGP v4 fingerprint."); + } + this.fingerprint = fp; + } + + public OpenPgpV4Fingerprint(byte[] bytes) { + this(new String(bytes, Charset.forName("UTF-8"))); + } + + /** + * Check, whether the fingerprint consists of 40 valid hexadecimal characters. + * @param fp fingerprint to check. + * @return true if fingerprint is valid. + */ + private boolean isValid(String fp) { + return fp.matches("[0-9A-F]{40}"); + } + + /** + * Return the key id of the OpenPGP public key this {@link OpenPgpV4Fingerprint} belongs to. + * This method uses {@link Util#keyIdFromFingerprint(OpenPgpV4Fingerprint)}. + * + * @see + * RFC-4880 §12.2: Key IDs and Fingerprints + * @return key id + */ + public long getKeyId() { + return Util.keyIdFromFingerprint(this); + } + + @Override + public boolean equals(Object other) { + if (other == null) { + return false; + } + + if (!(other instanceof CharSequence)) { + return false; + } + + return this.toString().equals(other.toString()); + } + + @Override + public int hashCode() { + return fingerprint.hashCode(); + } + + @Override + public int length() { + return fingerprint.length(); + } + + @Override + public char charAt(int i) { + return fingerprint.charAt(i); + } + + @Override + public CharSequence subSequence(int i, int i1) { + return fingerprint.subSequence(i, i1); + } + + @Override + public String toString() { + return fingerprint; + } + + @Override + public int compareTo(OpenPgpV4Fingerprint openPgpV4Fingerprint) { + return fingerprint.compareTo(openPgpV4Fingerprint.fingerprint); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java new file mode 100644 index 000000000..423b90c66 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubDelegate.java @@ -0,0 +1,266 @@ +/** + * + * 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; + +import java.util.Date; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +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.pubsub.AccessModel; +import org.jivesoftware.smackx.pubsub.ConfigureForm; +import org.jivesoftware.smackx.pubsub.Item; +import org.jivesoftware.smackx.pubsub.LeafNode; +import org.jivesoftware.smackx.pubsub.PayloadItem; +import org.jivesoftware.smackx.pubsub.PubSubException; +import org.jivesoftware.smackx.pubsub.PubSubManager; +import org.jivesoftware.smackx.xdata.packet.DataForm; + +import org.jxmpp.jid.BareJid; + +public class PubSubDelegate { + + private static final Logger LOGGER = Logger.getLogger(PubSubDelegate.class.getName()); + + /** + * Name of the OX metadata node. + * + * @see XEP-0373 §4.2 + */ + 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. + * + * @see XEP-0373 §4.4 + */ + public static final String PEP_NODE_PUBLIC_KEYS_NOTIFY = PEP_NODE_PUBLIC_KEYS + "+notify"; + + /** + * Name of the OX public key node, which contains the key with id {@code id}. + * + * @param id upper case hex encoded OpenPGP v4 fingerprint of the key. + * @return PEP node name. + */ + public static String PEP_NODE_PUBLIC_KEY(OpenPgpV4Fingerprint id) { + return PEP_NODE_PUBLIC_KEYS + ":" + id; + } + + + public static void changeAccessModelIfNecessary(LeafNode node, AccessModel accessModel) + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + ConfigureForm current = node.getNodeConfiguration(); + if (current.getAccessModel() != accessModel) { + ConfigureForm updateConfig = new ConfigureForm(DataForm.Type.submit); + updateConfig.setAccessModel(accessModel); + node.sendConfigurationForm(updateConfig); + } + } + + /** + * Publish the users OpenPGP public key to the public key node if necessary. + * Also announce the key to other users by updating the metadata node. + * + * @see XEP-0373 §4.1 + * + * @throws CorruptedOpenPgpKeyException if our OpenPGP key is corrupted and for that reason cannot + * be serialized. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws SmackException.NoResponseException + */ + public static void publishPublicKey(XMPPConnection connection, PubkeyElement pubkeyElement, OpenPgpV4Fingerprint fingerprint) + throws CorruptedOpenPgpKeyException, InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + + String keyNodeName = PEP_NODE_PUBLIC_KEY(fingerprint); + PubSubManager pm = PubSubManager.getInstance(connection, connection.getUser().asBareJid()); + + // Check if key available at data node + // If not, publish key to data node + LeafNode keyNode = pm.getOrCreateLeafNode(keyNodeName); + List items = keyNode.getItems(1); + if (items.isEmpty()) { + LOGGER.log(Level.FINE, "Node " + keyNodeName + " is empty. Publish."); + keyNode.publish(new PayloadItem<>(pubkeyElement)); + } else { + LOGGER.log(Level.FINE, "Node " + keyNodeName + " already contains key. Skip."); + } + + // Fetch IDs from metadata node + LeafNode metadataNode = pm.getOrCreateLeafNode(PEP_NODE_PUBLIC_KEYS); + List> metadataItems = metadataNode.getItems(1); + + PublicKeysListElement.Builder builder = PublicKeysListElement.builder(); + if (!metadataItems.isEmpty() && metadataItems.get(0).getPayload() != null) { + // Add old entries back to list. + PublicKeysListElement publishedList = metadataItems.get(0).getPayload(); + for (PublicKeysListElement.PubkeyMetadataElement meta : publishedList.getMetadata().values()) { + builder.addMetadata(meta); + } + } + builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, new Date())); + + // Publish IDs to metadata node + metadataNode.publish(new PayloadItem<>(builder.build())); + } + + /** + * Consult the public key metadata node and fetch a list of all of our published OpenPGP public keys. + * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. + * + * @return content of our metadata node. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ + public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection) + throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, + PubSubException.NotAPubSubNodeException { + return fetchPubkeysList(connection, connection.getUser().asBareJid()); + } + + /** + * Consult the public key metadata node of {@code contact} to fetch the list of their published OpenPGP public keys. + * TODO: Add @see which points to the (for now missing) respective example in XEP-0373. + * + * @param contact {@link BareJid} of the user we want to fetch the list from. + * @return content of {@code contact}'s metadata node. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ + public static PublicKeysListElement fetchPubkeysList(XMPPConnection connection, BareJid contact) + throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, + PubSubException.NotAPubSubNodeException { + PubSubManager pm = PubSubManager.getInstance(connection, contact); + + LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEYS); + List> list = node.getItems(1); + + if (list.isEmpty()) { + return null; + } + + return list.get(0).getPayload(); + } + + /** + * Delete our metadata node. + * + * @throws XMPPException.XMPPErrorException + * @throws SmackException.NotConnectedException + * @throws InterruptedException + * @throws SmackException.NoResponseException + */ + public static void deletePubkeysListNode(XMPPConnection connection) + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + PubSubManager pm = PubSubManager.getInstance(connection, connection.getUser().asBareJid()); + pm.deleteNode(PEP_NODE_PUBLIC_KEYS); + } + + /** + * Fetch the OpenPGP public key of a {@code contact}, identified by its OpenPGP {@code v4_fingerprint}. + * + * @see XEP-0373 §4.3 + * + * @param contact {@link BareJid} of the contact we want to fetch a key from. + * @param v4_fingerprint upper case, hex encoded v4 fingerprint of the contacts key. + * @return {@link PubkeyElement} containing the requested public key. + * @throws InterruptedException + * @throws PubSubException.NotALeafNodeException + * @throws SmackException.NoResponseException + * @throws SmackException.NotConnectedException + * @throws XMPPException.XMPPErrorException + * @throws PubSubException.NotAPubSubNodeException + */ + public static PubkeyElement fetchPubkey(XMPPConnection connection, BareJid contact, OpenPgpV4Fingerprint v4_fingerprint) + throws InterruptedException, PubSubException.NotALeafNodeException, SmackException.NoResponseException, + SmackException.NotConnectedException, XMPPException.XMPPErrorException, + PubSubException.NotAPubSubNodeException { + PubSubManager pm = PubSubManager.getInstance(connection, contact); + + LeafNode node = pm.getLeafNode(PEP_NODE_PUBLIC_KEY(v4_fingerprint)); + List> list = node.getItems(1); + + if (list.isEmpty()) { + return null; + } + + return list.get(0).getPayload(); + } + + /** + * TODO: Implement and document. + */ + public static void depositSecretKey(XMPPConnection connection, SecretkeyElement element) + throws InterruptedException, PubSubException.NotALeafNodeException, + XMPPException.XMPPErrorException, SmackException.NotConnectedException, SmackException.NoResponseException { + + PubSubManager pm = PubSubManager.getInstance(connection); + LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY); + PubSubDelegate.changeAccessModelIfNecessary(secretKeyNode, AccessModel.whitelist); + + secretKeyNode.publish(new PayloadItem<>(element)); + } + + public static SecretkeyElement fetchSecretKey(XMPPConnection connection) + throws InterruptedException, PubSubException.NotALeafNodeException, XMPPException.XMPPErrorException, + SmackException.NotConnectedException, SmackException.NoResponseException { + PubSubManager pm = PubSubManager.getInstance(connection); + LeafNode secretKeyNode = pm.getOrCreateLeafNode(PEP_NODE_SECRET_KEY); + List> list = secretKeyNode.getItems(1); + if (list.size() == 0) { + LOGGER.log(Level.INFO, "No secret key published!"); + return null; + } + SecretkeyElement secretkeyElement = list.get(0).getPayload(); + return secretkeyElement; + } + + public static void deleteSecretKeyNode(XMPPConnection connection) + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException { + PubSubManager pm = PubSubManager.getInstance(connection); + pm.deleteNode(PEP_NODE_SECRET_KEY); + } +} 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 deleted file mode 100644 index 10532f8a0..000000000 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/PubSubHelper.java +++ /dev/null @@ -1,48 +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; - -import org.jivesoftware.smack.SmackException; -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 old = node.getNodeConfiguration(); - if (old.getAccessModel() != AccessModel.whitelist) { - ConfigureForm _new = new ConfigureForm(DataForm.Type.submit); - _new.setAccessModel(AccessModel.whitelist); - node.sendConfigurationForm(_new); - } - } - - public static void open(LeafNode node) - throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, - SmackException.NoResponseException { - ConfigureForm config = node.getNodeConfiguration(); - if (config.getAccessModel() != AccessModel.open) { - config.setAccessModel(AccessModel.open); - node.sendConfigurationForm(config); - } - } -} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java new file mode 100644 index 000000000..726ffcb11 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/Util.java @@ -0,0 +1,24 @@ +package org.jivesoftware.smackx.ox; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import javax.xml.bind.DatatypeConverter; + +public class Util { + + /** + * Calculate the key id of the OpenPGP key, the given {@link OpenPgpV4Fingerprint} belongs to. + * + * @see RFC-4880 §12.2 + * @param fingerprint {@link OpenPgpV4Fingerprint}. + * @return key id + */ + public static long keyIdFromFingerprint(OpenPgpV4Fingerprint fingerprint) { + byte[] bytes = DatatypeConverter.parseHexBinary(fingerprint.toString()); + byte[] lower8Bytes = Arrays.copyOfRange(bytes, 12, 20); + ByteBuffer byteBuffer = ByteBuffer.allocate(8); + byteBuffer.put(lower8Bytes); + byteBuffer.flip(); + return byteBuffer.getLong(); + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java new file mode 100644 index 000000000..5955fd4aa --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyBackupSelectionCallback.java @@ -0,0 +1,21 @@ +package org.jivesoftware.smackx.ox.callback; + +import java.util.Set; + +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; + +/** + * Callback to allow the user to decide, which locally available secret keys they want to include in a backup. + */ +public interface SecretKeyBackupSelectionCallback { + + /** + * Let the user decide, which secret keys they want to backup. + * + * @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of locally available + * OpenPGP secret keys. + * @return {@link Set} which contains the {@link OpenPgpV4Fingerprint}s the user decided to include + * in the backup. + */ + Set selectKeysToBackup(Set availableSecretKeys); +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyRestoreSelectionCallback.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyRestoreSelectionCallback.java new file mode 100644 index 000000000..84101add1 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/callback/SecretKeyRestoreSelectionCallback.java @@ -0,0 +1,36 @@ +/** + * + * 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; + +import java.util.Set; + +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; + +/** + * Callback to let the user decide which key from a backup they want to restore. + */ +public interface SecretKeyRestoreSelectionCallback { + + /** + * Let the user choose, which SecretKey they want to restore as the new primary OpenPGP signing key. + * @param availableSecretKeys {@link Set} of {@link OpenPgpV4Fingerprint}s of the keys which are contained + * in the backup. + * @return {@link OpenPgpV4Fingerprint} of the key the user wants to restore as the new primary + * signing key. + */ + OpenPgpV4Fingerprint selectSecretKeyToRestore(Set availableSecretKeys); +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/PublicKeysListElement.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/PublicKeysListElement.java index a2fb4d7c6..21b2cc072 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/PublicKeysListElement.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/element/PublicKeysListElement.java @@ -25,15 +25,16 @@ import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; public final class PublicKeysListElement implements ExtensionElement { public static final String NAMESPACE = OpenPgpElement.NAMESPACE; public static final String ELEMENT = "public-keys-list"; - private final Map metadata; + private final Map metadata; - private PublicKeysListElement(TreeMap metadata) { + private PublicKeysListElement(TreeMap metadata) { this.metadata = Collections.unmodifiableMap(metadata); } @@ -41,7 +42,7 @@ public final class PublicKeysListElement implements ExtensionElement { return new Builder(); } - public TreeMap getMetadata() { + public TreeMap getMetadata() { return new TreeMap<>(metadata); } @@ -67,7 +68,7 @@ public final class PublicKeysListElement implements ExtensionElement { public static final class Builder { - private final TreeMap metadata = new TreeMap<>(); + private final TreeMap metadata = new TreeMap<>(); private Builder() { // Empty @@ -89,10 +90,10 @@ public final class PublicKeysListElement implements ExtensionElement { public static final String ATTR_V4_FINGERPRINT = "v4-fingerprint"; public static final String ATTR_DATE = "date"; - private final String v4_fingerprint; + private final OpenPgpV4Fingerprint v4_fingerprint; private final Date date; - public PubkeyMetadataElement(String v4_fingerprint, Date date) { + public PubkeyMetadataElement(OpenPgpV4Fingerprint v4_fingerprint, Date date) { this.v4_fingerprint = Objects.requireNonNull(v4_fingerprint); this.date = Objects.requireNonNull(date); @@ -101,7 +102,7 @@ public final class PublicKeysListElement implements ExtensionElement { } } - public String getV4Fingerprint() { + public OpenPgpV4Fingerprint getV4Fingerprint() { return v4_fingerprint; } diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java new file mode 100644 index 000000000..3836f68c7 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/InvalidBackupCodeException.java @@ -0,0 +1,25 @@ +/** + * + * 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.exception; + +/** + * Exception that gets thrown if the backup code entered by the user is invalid. + */ +public class InvalidBackupCodeException extends Exception { + + private static final long serialVersionUID = 1L; +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpKeyPairException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpKeyPairException.java new file mode 100644 index 000000000..9ed3ae5fb --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpKeyPairException.java @@ -0,0 +1,48 @@ +/** + * + * 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.exception; + +import org.jxmpp.jid.BareJid; + +/** + * Exception that gets thrown whenever an operation is missing an OpenPGP key pair. + */ +public class MissingOpenPgpKeyPairException extends Exception { + + private static final long serialVersionUID = 1L; + + private final BareJid owner; + + /** + * Create a new {@link MissingOpenPgpKeyPairException}. + * + * @param owner owner of the missing key pair. + */ + public MissingOpenPgpKeyPairException(BareJid owner) { + super("Missing OpenPGP key pair for user " + owner); + this.owner = owner; + } + + /** + * Return the owner of the missing OpenPGP key pair. + * + * @return owner + */ + public BareJid getOwner() { + return owner; + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpPublicKeyException.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpPublicKeyException.java new file mode 100644 index 000000000..ddfec4b18 --- /dev/null +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/exception/MissingOpenPgpPublicKeyException.java @@ -0,0 +1,62 @@ +/** + * + * 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.exception; + +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; + +import org.jxmpp.jid.BareJid; + +/** + * Exception that gets thrown when an operation is missing an OpenPGP public key. + */ +public class MissingOpenPgpPublicKeyException extends Exception { + + private static final long serialVersionUID = 1L; + + private final BareJid user; + private final OpenPgpV4Fingerprint fingerprint; + + /** + * Create a new {@link MissingOpenPgpPublicKeyException}. + * + * @param owner {@link BareJid} of the keys owner. + * @param fingerprint {@link OpenPgpV4Fingerprint} of the missing key. + */ + public MissingOpenPgpPublicKeyException(BareJid owner, OpenPgpV4Fingerprint fingerprint) { + super("Missing public key " + fingerprint.toString() + " for user " + owner + "."); + this.user = owner; + this.fingerprint = fingerprint; + } + + /** + * Return the {@link BareJid} of the owner of the missing key. + * + * @return owner of missing key. + */ + public BareJid getUser() { + return user; + } + + /** + * Return the fingerprint of the missing key. + * + * @return {@link OpenPgpV4Fingerprint} of the missing key. + */ + public OpenPgpV4Fingerprint getFingerprint() { + return fingerprint; + } +} diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java index c9d3ef9ff..dd14aa752 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/CryptElementProvider.java @@ -20,6 +20,9 @@ import org.jivesoftware.smackx.ox.element.CryptElement; import org.xmlpull.v1.XmlPullParser; +/** + * {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link CryptElement}. + */ public class CryptElementProvider extends OpenPgpContentElementProvider { public static final CryptElementProvider TEST_INSTANCE = new CryptElementProvider(); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java index 1deaa2515..d642dbd8f 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpContentElementProvider.java @@ -44,6 +44,11 @@ import org.jxmpp.util.XmppDateTime; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +/** + * Abstract {@link ExtensionElementProvider} implementation for the also abstract + * {@link OpenPgpContentElement}. + * @param Specialized subclass of {@link OpenPgpContentElement}. + */ public abstract class OpenPgpContentElementProvider extends ExtensionElementProvider { private static final Logger LOGGER = Logger.getLogger(OpenPgpContentElementProvider.class.getName()); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpElementProvider.java index e5c72c8ad..163c09a7c 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/OpenPgpElementProvider.java @@ -21,6 +21,9 @@ import org.jivesoftware.smackx.ox.element.OpenPgpElement; import org.xmlpull.v1.XmlPullParser; +/** + * {@link ExtensionElementProvider} implementation for the {@link OpenPgpElement}. + */ public class OpenPgpElementProvider extends ExtensionElementProvider { public static final OpenPgpElementProvider TEST_INSTANCE = new OpenPgpElementProvider(); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PubkeyElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PubkeyElementProvider.java index a96bfa892..fe3a6a758 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PubkeyElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PubkeyElementProvider.java @@ -27,6 +27,9 @@ import org.jivesoftware.smackx.ox.element.PubkeyElement; import org.jxmpp.util.XmppDateTime; import org.xmlpull.v1.XmlPullParser; +/** + * {@link ExtensionElementProvider} implementation for the {@link PubkeyElement}. + */ public class PubkeyElementProvider extends ExtensionElementProvider { public static final PubkeyElementProvider TEST_INSTANCE = new PubkeyElementProvider(); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PublicKeysListElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PublicKeysListElementProvider.java index 3058f160f..c34d97b17 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PublicKeysListElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/PublicKeysListElementProvider.java @@ -22,6 +22,7 @@ import static org.xmlpull.v1.XmlPullParser.START_TAG; import java.util.Date; import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.ox.OpenPgpV4Fingerprint; import org.jivesoftware.smackx.ox.element.PublicKeysListElement; import org.jxmpp.util.XmppDateTime; @@ -49,8 +50,9 @@ public final class PublicKeysListElementProvider extends ExtensionElementProvide PublicKeysListElement.PubkeyMetadataElement.ATTR_V4_FINGERPRINT); String dt = parser.getAttributeValue(null, PublicKeysListElement.PubkeyMetadataElement.ATTR_DATE); + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(finger); Date date = XmppDateTime.parseXEP0082Date(dt); - builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(finger, date)); + builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fingerprint, date)); } break; diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SecretkeyElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SecretkeyElementProvider.java index 04b75096c..12a6aad1a 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SecretkeyElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SecretkeyElementProvider.java @@ -23,6 +23,9 @@ import org.jivesoftware.smackx.ox.element.SecretkeyElement; import org.xmlpull.v1.XmlPullParser; +/** + * {@link ExtensionElementProvider} implementation for the {@link SecretkeyElement}. + */ public class SecretkeyElementProvider extends ExtensionElementProvider { public static final SecretkeyElementProvider TEST_INSTANCE = new SecretkeyElementProvider(); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java index 346bae899..c0e4b8378 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SignElementProvider.java @@ -23,6 +23,9 @@ import org.jivesoftware.smackx.ox.element.SignElement; import org.xmlpull.v1.XmlPullParser; +/** + * {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SignElement}. + */ public class SignElementProvider extends OpenPgpContentElementProvider { private static final Logger LOGGER = Logger.getLogger(SigncryptElementProvider.class.getName()); diff --git a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java index f850bf89a..979ec8227 100644 --- a/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java +++ b/smack-openpgp/src/main/java/org/jivesoftware/smackx/ox/provider/SigncryptElementProvider.java @@ -20,6 +20,9 @@ import org.jivesoftware.smackx.ox.element.SigncryptElement; import org.xmlpull.v1.XmlPullParser; +/** + * {@link org.jivesoftware.smack.provider.ExtensionElementProvider} implementation for the {@link SigncryptElement}. + */ public class SigncryptElementProvider extends OpenPgpContentElementProvider { public static final SigncryptElementProvider TEST_INSTANCE = new SigncryptElementProvider(); diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpV4FingerprintTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpV4FingerprintTest.java new file mode 100644 index 000000000..0f67ddc96 --- /dev/null +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/OpenPgpV4FingerprintTest.java @@ -0,0 +1,58 @@ +/** + * + * 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; + +import static junit.framework.TestCase.assertEquals; + +import org.jivesoftware.smack.test.util.SmackTestSuite; + +import org.junit.Test; + +public class OpenPgpV4FingerprintTest extends SmackTestSuite { + + @Test(expected = IllegalArgumentException.class) + public void fpTooShort() { + String fp = "484f57414c495645"; // Asking Mark + new OpenPgpV4Fingerprint(fp); + } + + @Test(expected = IllegalArgumentException.class) + public void invalidHexTest() { + String fp = "UNFORTUNATELYTHISISNOVALIDHEXADECIMALDOH"; + new OpenPgpV4Fingerprint(fp); + } + + @Test + public void validFingerprintTest() { + String fp = "4A4F48414E4E53454E2049532041204E45524421"; + OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp); + assertEquals(fp, finger.toString()); + } + + @Test + public void convertsToUpperCaseTest() { + String fp = "444f4e5420552048415645204120484f4242593f"; + OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint(fp); + assertEquals("444F4E5420552048415645204120484F4242593F", finger.toString()); + } + + @Test + public void equalsOtherFingerprintTest() { + OpenPgpV4Fingerprint finger = new OpenPgpV4Fingerprint("5448452043414b452049532041204c4945212121"); + assertEquals(finger, new OpenPgpV4Fingerprint("5448452043414B452049532041204C4945212121")); + } +} diff --git a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java index ed7893e4d..68901b5ca 100644 --- a/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java +++ b/smack-openpgp/src/test/java/org/jivesoftware/smackx/ox/PublicKeysListElementTest.java @@ -51,10 +51,10 @@ public class PublicKeysListElementTest extends SmackTestSuite { Date date2 = XmppDateTime.parseDate("1953-05-16T12:00:00.000+00:00"); PublicKeysListElement.PubkeyMetadataElement child1 = new PublicKeysListElement.PubkeyMetadataElement( - "1357B01865B2503C18453D208CAC2A9678548E35", date1); + new OpenPgpV4Fingerprint("1357B01865B2503C18453D208CAC2A9678548E35"), date1); PublicKeysListElement.PubkeyMetadataElement child2 = new PublicKeysListElement.PubkeyMetadataElement( - "67819B343B2AB70DED9320872C6464AF2A8E4C02", date2); + new OpenPgpV4Fingerprint("67819B343B2AB70DED9320872C6464AF2A8E4C02"), date2); PublicKeysListElement element = PublicKeysListElement.builder() .addMetadata(child1) @@ -72,22 +72,16 @@ public class PublicKeysListElementTest extends SmackTestSuite { @Test public void listBuilderRefusesDuplicatesTest() { PublicKeysListElement.Builder builder = PublicKeysListElement.builder(); - String fp40 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN"; + String fp40 = "49545320414c4c2041424f555420444120484558"; Date oneDate = new Date(12337883234L); Date otherDate = new Date(8888348384L); // Check if size of metadata is one after insert. - builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp40, oneDate)); + builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(new OpenPgpV4Fingerprint(fp40), oneDate)); assertEquals(builder.build().getMetadata().size(), 1); // Check if size is still one after inserting element with same fp. - builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(fp40, otherDate)); + builder.addMetadata(new PublicKeysListElement.PubkeyMetadataElement(new OpenPgpV4Fingerprint(fp40), otherDate)); assertEquals(builder.build().getMetadata().size(), 1); } - - @Test(expected = IllegalArgumentException.class) - public void metadataFingerprintLengthTest() { - PublicKeysListElement.PubkeyMetadataElement element = - new PublicKeysListElement.PubkeyMetadataElement("thisIsNotTheCorrectLength", new Date()); - } }