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.ByteArrayInputStream; 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.Set; 024 025import org.jivesoftware.smack.util.StringUtils; 026import org.jivesoftware.smack.util.stringencoder.Base64; 027 028import org.jivesoftware.smackx.ox.OpenPgpSecretKeyBackupPassphrase; 029import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; 030import org.jivesoftware.smackx.ox.element.SecretkeyElement; 031import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; 032import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; 033 034import org.bouncycastle.openpgp.PGPException; 035import org.bouncycastle.openpgp.PGPSecretKeyRing; 036import org.bouncycastle.util.io.Streams; 037import org.jxmpp.jid.BareJid; 038import org.pgpainless.PGPainless; 039import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 040import org.pgpainless.decryption_verification.ConsumerOptions; 041import org.pgpainless.decryption_verification.DecryptionStream; 042import org.pgpainless.encryption_signing.EncryptionOptions; 043import org.pgpainless.encryption_signing.EncryptionStream; 044import org.pgpainless.encryption_signing.ProducerOptions; 045import org.pgpainless.exception.MissingDecryptionMethodException; 046import org.pgpainless.key.OpenPgpV4Fingerprint; 047import org.pgpainless.util.Passphrase; 048 049/** 050 * Helper class which provides some functions needed for backup/restore of the users secret key to/from their private 051 * PubSub node. 052 */ 053public class SecretKeyBackupHelper { 054 055 /** 056 * Generate a secure backup code. 057 * This code can be used to encrypt a secret key backup and follows the form described in XEP-0373 §5.3. 058 * 059 * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption"> 060 * XEP-0373 §5.4 Encrypting the Secret Key Backup</a> 061 * 062 * @return backup code 063 */ 064 public static OpenPgpSecretKeyBackupPassphrase generateBackupPassword() { 065 return new OpenPgpSecretKeyBackupPassphrase(StringUtils.secureOfflineAttackSafeRandomString()); 066 } 067 068 /** 069 * Create a {@link SecretkeyElement} which contains the secret keys listed in {@code fingerprints} and is encrypted 070 * symmetrically using the {@code backupCode}. 071 * 072 * @param provider {@link OpenPgpProvider} for symmetric encryption. 073 * @param owner owner of the secret keys (usually our jid). 074 * @param fingerprints set of {@link OpenPgpV4Fingerprint}s of the keys which are going to be backed up. 075 * @param backupCode passphrase for symmetric encryption. 076 * @return {@link SecretkeyElement} 077 * 078 * @throws PGPException PGP is brittle 079 * @throws IOException IO is dangerous 080 * @throws MissingOpenPgpKeyException in case one of the keys whose fingerprint is in {@code fingerprints} is 081 * not accessible. 082 */ 083 public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider, 084 BareJid owner, 085 Set<OpenPgpV4Fingerprint> fingerprints, 086 OpenPgpSecretKeyBackupPassphrase backupCode) 087 throws PGPException, IOException, MissingOpenPgpKeyException { 088 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 089 090 for (OpenPgpV4Fingerprint fingerprint : fingerprints) { 091 092 PGPSecretKeyRing key = provider.getStore().getSecretKeyRing(owner, fingerprint); 093 if (key == null) { 094 throw new MissingOpenPgpKeyException(owner, fingerprint); 095 } 096 097 byte[] bytes = key.getEncoded(); 098 buffer.write(bytes); 099 } 100 return createSecretkeyElement(buffer.toByteArray(), backupCode); 101 } 102 103 /** 104 * Create a {@link SecretkeyElement} which contains the secret keys which are serialized in {@code keys} and is 105 * symmetrically encrypted using the {@code backupCode}. 106 * 107 * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption"> 108 * XEP-0373 §5.4 Encrypting the Secret Key Backup</a> 109 * 110 * @param keys serialized OpenPGP secret keys in transferable key format 111 * @param backupCode passphrase for symmetric encryption 112 * @return {@link SecretkeyElement} 113 * 114 * @throws PGPException PGP is brittle 115 * @throws IOException IO is dangerous 116 */ 117 public static SecretkeyElement createSecretkeyElement(byte[] keys, 118 OpenPgpSecretKeyBackupPassphrase backupCode) 119 throws PGPException, IOException { 120 InputStream keyStream = new ByteArrayInputStream(keys); 121 ByteArrayOutputStream cryptOut = new ByteArrayOutputStream(); 122 EncryptionOptions encOpts = new EncryptionOptions() 123 .addPassphrase(Passphrase.fromPassword(backupCode.toString())); 124 encOpts.overrideEncryptionAlgorithm(SymmetricKeyAlgorithm.AES_256); 125 126 EncryptionStream encryptionStream = PGPainless.encryptAndOrSign() 127 .onOutputStream(cryptOut) 128 .withOptions(ProducerOptions.encrypt(encOpts) 129 .setAsciiArmor(false)); 130 131 Streams.pipeAll(keyStream, encryptionStream); 132 encryptionStream.close(); 133 134 return new SecretkeyElement(Base64.encode(cryptOut.toByteArray())); 135 } 136 137 /** 138 * Decrypt a secret key backup and return the {@link PGPSecretKeyRing} contained in it. 139 * TODO: Return a PGPSecretKeyRingCollection instead? 140 * 141 * @param backup encrypted {@link SecretkeyElement} containing the backup 142 * @param backupCode passphrase for decrypting the {@link SecretkeyElement}. 143 * @return the TODO javadoc me please 144 * @throws InvalidBackupCodeException in case the provided backup code is invalid. 145 * @throws IOException IO is dangerous. 146 * @throws PGPException PGP is brittle. 147 */ 148 public static PGPSecretKeyRing restoreSecretKeyBackup(SecretkeyElement backup, OpenPgpSecretKeyBackupPassphrase backupCode) 149 throws InvalidBackupCodeException, IOException, PGPException { 150 byte[] encrypted = Base64.decode(backup.getB64Data()); 151 InputStream encryptedIn = new ByteArrayInputStream(encrypted); 152 ByteArrayOutputStream plaintextOut = new ByteArrayOutputStream(); 153 154 try { 155 DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify() 156 .onInputStream(encryptedIn) 157 .withOptions(new ConsumerOptions() 158 .addDecryptionPassphrase(Passphrase.fromPassword(backupCode.toString()))); 159 160 Streams.pipeAll(decryptionStream, plaintextOut); 161 decryptionStream.close(); 162 } catch (MissingDecryptionMethodException e) { 163 throw new InvalidBackupCodeException("Could not decrypt secret key backup. Possibly wrong passphrase?", e); 164 } 165 166 byte[] decrypted = plaintextOut.toByteArray(); 167 return PGPainless.readKeyRing().secretKeyRing(decrypted); 168 } 169}