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}