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.security.SecureRandom; 022import java.util.Set; 023 024import org.jivesoftware.smack.util.stringencoder.Base64; 025import org.jivesoftware.smackx.ox.crypto.OpenPgpProvider; 026import org.jivesoftware.smackx.ox.element.SecretkeyElement; 027import org.jivesoftware.smackx.ox.exception.InvalidBackupCodeException; 028import org.jivesoftware.smackx.ox.exception.MissingOpenPgpKeyException; 029 030import org.bouncycastle.openpgp.PGPException; 031import org.bouncycastle.openpgp.PGPSecretKeyRing; 032import org.jxmpp.jid.BareJid; 033import org.pgpainless.PGPainless; 034import org.pgpainless.algorithm.SymmetricKeyAlgorithm; 035import org.pgpainless.key.OpenPgpV4Fingerprint; 036import org.pgpainless.util.Passphrase; 037 038/** 039 * Helper class which provides some functions needed for backup/restore of the users secret key to/from their private 040 * PubSub node. 041 */ 042public class SecretKeyBackupHelper { 043 044 /** 045 * Generate a secure backup code. 046 * This code can be used to encrypt a secret key backup and follows the form described in XEP-0373 §5.3. 047 * 048 * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption"> 049 * XEP-0373 §5.4 Encrypting the Secret Key Backup</a> 050 * 051 * @return backup code 052 */ 053 public static String generateBackupPassword() { 054 final String alphabet = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; 055 final int len = alphabet.length(); 056 SecureRandom random = new SecureRandom(); 057 StringBuilder code = new StringBuilder(29); 058 059 // 6 blocks 060 for (int i = 0; i < 6; i++) { 061 062 // of 4 chars 063 for (int j = 0; j < 4; j++) { 064 char c = alphabet.charAt(random.nextInt(len)); 065 code.append(c); 066 } 067 068 // dash after every block except the last one 069 if (i != 5) { 070 code.append('-'); 071 } 072 } 073 return code.toString(); 074 } 075 076 /** 077 * Create a {@link SecretkeyElement} which contains the secret keys listed in {@code fingerprints} and is encrypted 078 * symmetrically using the {@code backupCode}. 079 * 080 * @param provider {@link OpenPgpProvider} for symmetric encryption. 081 * @param owner owner of the secret keys (usually our jid). 082 * @param fingerprints set of {@link OpenPgpV4Fingerprint}s of the keys which are going to be backed up. 083 * @param backupCode passphrase for symmetric encryption. 084 * @return {@link SecretkeyElement} 085 * 086 * @throws PGPException PGP is brittle 087 * @throws IOException IO is dangerous 088 * @throws MissingOpenPgpKeyException in case one of the keys whose fingerprint is in {@code fingerprints} is 089 * not accessible. 090 */ 091 public static SecretkeyElement createSecretkeyElement(OpenPgpProvider provider, 092 BareJid owner, 093 Set<OpenPgpV4Fingerprint> fingerprints, 094 String backupCode) 095 throws PGPException, IOException, MissingOpenPgpKeyException { 096 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 097 098 for (OpenPgpV4Fingerprint fingerprint : fingerprints) { 099 100 PGPSecretKeyRing key = provider.getStore().getSecretKeyRing(owner, fingerprint); 101 if (key == null) { 102 throw new MissingOpenPgpKeyException(owner, fingerprint); 103 } 104 105 byte[] bytes = key.getEncoded(); 106 buffer.write(bytes); 107 } 108 return createSecretkeyElement(buffer.toByteArray(), backupCode); 109 } 110 111 /** 112 * Create a {@link SecretkeyElement} which contains the secret keys which are serialized in {@code keys} and is 113 * symmetrically encrypted using the {@code backupCode}. 114 * 115 * @see <a href="https://xmpp.org/extensions/xep-0373.html#backup-encryption"> 116 * XEP-0373 §5.4 Encrypting the Secret Key Backup</a> 117 * 118 * @param keys serialized OpenPGP secret keys in transferable key format 119 * @param backupCode passphrase for symmetric encryption 120 * @return {@link SecretkeyElement} 121 * 122 * @throws PGPException PGP is brittle 123 * @throws IOException IO is dangerous 124 */ 125 public static SecretkeyElement createSecretkeyElement(byte[] keys, 126 String backupCode) 127 throws PGPException, IOException { 128 byte[] encrypted = PGPainless.encryptWithPassword(keys, new Passphrase(backupCode.toCharArray()), 129 SymmetricKeyAlgorithm.AES_256); 130 return new SecretkeyElement(Base64.encode(encrypted)); 131 } 132 133 /** 134 * Decrypt a secret key backup and return the {@link PGPSecretKeyRing} contained in it. 135 * TODO: Return a PGPSecretKeyRingCollection instead? 136 * 137 * @param backup encrypted {@link SecretkeyElement} containing the backup 138 * @param backupCode passphrase for decrypting the {@link SecretkeyElement}. 139 * @return the 140 * @throws InvalidBackupCodeException in case the provided backup code is invalid. 141 * @throws IOException IO is dangerous. 142 * @throws PGPException PGP is brittle. 143 */ 144 public static PGPSecretKeyRing restoreSecretKeyBackup(SecretkeyElement backup, String backupCode) 145 throws InvalidBackupCodeException, IOException, PGPException { 146 byte[] encrypted = Base64.decode(backup.getB64Data()); 147 148 byte[] decrypted; 149 try { 150 decrypted = PGPainless.decryptWithPassword(encrypted, new Passphrase(backupCode.toCharArray())); 151 } catch (IOException | PGPException e) { 152 throw new InvalidBackupCodeException("Could not decrypt secret key backup. Possibly wrong passphrase?", e); 153 } 154 155 return PGPainless.readKeyRing().secretKeyRing(decrypted); 156 } 157}