001/**
002 *
003 * Copyright 2018 Paul Schaub.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.ox.util;
018
019import java.io.ByteArrayOutputStream;
020import java.io.IOException;
021import java.util.Set;
022
023import org.jivesoftware.smack.util.StringUtils;
024import org.jivesoftware.smack.util.stringencoder.Base64;
025
026import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase;
027import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider;
028import org.jivesoftware.smackx.ox.element.SecretkeyElement;
029import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException;
030import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException;
031
032import org.bouncycastle.openpgp.PGPException;
033import org.bouncycastle.openpgp.PGPSecretKeyRing;
034import org.jxmpp.jid.BareJid;
035import org.pgpainless.PGPainless;
036import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
037import org.pgpainless.key.OpenPgpV4Fingerprint;
038import org.pgpainless.util.Passphrase;
039
040/**
041 * Helper class which provides some functions needed for backup/restore of the users secret key to/from their private
042 * PubSub node.
043 */
044public class SecretKeyBackupHelper {
045
046    /**
047     * Generate a secure backup code.
048     * This code can be used to encrypt a secret key backup and follows the form described in XEP-0373 §5.3.
049     *
050     * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption">
051     *     XEP-0373 §5.4 Encrypting the Secret Key Backup</a>
052     *
053     * @return backup code
054     */
055    public static OpenPgpSecretKeyBackupPassphrase generateBackupPassword() {
056        return new OpenPgpSecretKeyBackupPassphrase(StringUtils.secureOfflineAttackSafeRandomString());
057    }
058
059    /**
060     * Create a {@link SecretkeyElement} which contains the secret keys listed in {@code fingerprints} and is encrypted
061     * symmetrically using the {@code backupCode}.
062     *
063     * @param provider {@link OpenPgpProvider} for symmetric encryption.
064     * @param owner owner of the secret keys (usually our jid).
065     * @param fingerprints set of {@link OpenPgpV4Fingerprint}s of the keys which are going to be backed up.
066     * @param backupCode passphrase for symmetric encryption.
067     * @return {@link SecretkeyElement}
068     *
069     * @throws PGPException PGP is brittle
070     * @throws IOException IO is dangerous
071     * @throws MissingOpenPgpKeyException in case one of the keys whose fingerprint is in {@code fingerprints} is
072     * not accessible.
073     */
074    public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider,
075                                                    BareJid owner,
076                                                    Set<OpenPgpV4Fingerprint> fingerprints,
077                                                    OpenPgpSecretKeyBackupPassphrase backupCode)
078            throws PGPException, IOException, MissingOpenPgpKeyException {
079        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
080
081        for (OpenPgpV4Fingerprint fingerprint : fingerprints) {
082
083                PGPSecretKeyRing key = provider.getStore().getSecretKeyRing(owner, fingerprint);
084                if (key == null) {
085                    throw new MissingOpenPgpKeyException(owner, fingerprint);
086                }
087
088                byte[] bytes = key.getEncoded();
089                buffer.write(bytes);
090        }
091        return createSecretkeyElement(buffer.toByteArray(), backupCode);
092    }
093
094    /**
095     * Create a {@link SecretkeyElement} which contains the secret keys which are serialized in {@code keys} and is
096     * symmetrically encrypted using the {@code backupCode}.
097     *
098     * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption">
099     *     XEP-0373 §5.4 Encrypting the Secret Key Backup</a>
100     *
101     * @param keys serialized OpenPGP secret keys in transferable key format
102     * @param backupCode passphrase for symmetric encryption
103     * @return {@link SecretkeyElement}
104     *
105     * @throws PGPException PGP is brittle
106     * @throws IOException IO is dangerous
107     */
108    public static SecretkeyElement createSecretkeyElement(byte[] keys,
109                                                          OpenPgpSecretKeyBackupPassphrase backupCode)
110            throws PGPException, IOException {
111        byte[] encrypted = PGPainless.encryptWithPassword(keys, new Passphrase(backupCode.toString().toCharArray()),
112                SymmetricKeyAlgorithm.AES_256);
113        return new SecretkeyElement(Base64.encode(encrypted));
114    }
115
116    /**
117     * Decrypt a secret key backup and return the {@link PGPSecretKeyRing} contained in it.
118     * TODO: Return a PGPSecretKeyRingCollection instead?
119     *
120     * @param backup encrypted {@link SecretkeyElement} containing the backup
121     * @param backupCode passphrase for decrypting the {@link SecretkeyElement}.
122     * @return the TODO javadoc me please
123     * @throws InvalidBackupCodeException in case the provided backup code is invalid.
124     * @throws IOException IO is dangerous.
125     * @throws PGPException PGP is brittle.
126     */
127    public static PGPSecretKeyRing restoreSecretKeyBackup(SecretkeyElement backup, OpenPgpSecretKeyBackupPassphrase backupCode)
128            throws InvalidBackupCodeException, IOException, PGPException {
129        byte[] encrypted = Base64.decode(backup.getB64Data());
130
131        byte[] decrypted;
132        try {
133            decrypted = PGPainless.decryptWithPassword(encrypted, new Passphrase(backupCode.toString().toCharArray()));
134        } catch (IOException | PGPException e) {
135            throw new InvalidBackupCodeException("Could not decrypt secret key backup. Possibly wrong passphrase?", e);
136        }
137
138        return PGPainless.readKeyRing().secretKeyRing(decrypted);
139    }
140}