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.crypto; 018 019import java.io.ByteArrayOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.jivesoftware.smack.XMPPConnection; 028import org.jivesoftware.smack.util.Objects; 029import org.jivesoftware.smack.util.stringencoder.Base64; 030 031import org.jivesoftware.smackx.ox.OpenPgpContact; 032import org.jivesoftware.smackx.ox.OpenPgpMessage; 033import org.jivesoftware.smackx.ox.OpenPgpSelf; 034import org.jivesoftware.smackx.ox.element.CryptElement; 035import org.jivesoftware.smackx.ox.element.OpenPgpElement; 036import org.jivesoftware.smackx.ox.element.SignElement; 037import org.jivesoftware.smackx.ox.element.SigncryptElement; 038import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; 039 040import org.bouncycastle.openpgp.PGPException; 041import org.bouncycastle.openpgp.PGPPublicKey; 042import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 043import org.bouncycastle.util.io.Streams; 044import org.pgpainless.PGPainless; 045import org.pgpainless.decryption_verification.DecryptionStream; 046import org.pgpainless.decryption_verification.MissingPublicKeyCallback; 047import org.pgpainless.decryption_verification.OpenPgpMetadata; 048import org.pgpainless.encryption_signing.EncryptionStream; 049 050public class PainlessOpenPgpProvider implements OpenPgpProvider { 051 052 private static final Logger LOGGER = Logger.getLogger(PainlessOpenPgpProvider.class.getName()); 053 054 private final OpenPgpStore store; 055 056 public PainlessOpenPgpProvider(OpenPgpStore store) { 057 this.store = Objects.requireNonNull(store); 058 } 059 060 @Override 061 public OpenPgpStore getStore() { 062 return store; 063 } 064 065 @Override 066 public OpenPgpElementAndMetadata signAndEncrypt(SigncryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients) 067 throws IOException, PGPException { 068 InputStream plainText = element.toInputStream(); 069 ByteArrayOutputStream cipherText = new ByteArrayOutputStream(); 070 071 ArrayList<PGPPublicKeyRingCollection> recipientKeys = new ArrayList<>(); 072 for (OpenPgpContact contact : recipients) { 073 PGPPublicKeyRingCollection keys = contact.getTrustedAnnouncedKeys(); 074 if (keys != null) { 075 recipientKeys.add(keys); 076 } else { 077 LOGGER.log(Level.WARNING, "There are no suitable keys for contact " + contact.getJid().toString()); 078 } 079 } 080 081 EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText) 082 .toRecipients(recipientKeys.toArray(new PGPPublicKeyRingCollection[] {})) 083 .andToSelf(self.getTrustedAnnouncedKeys()) 084 .usingSecureAlgorithms() 085 .signWith(getStore().getKeyRingProtector(), self.getSigningKeyRing()) 086 .noArmor(); 087 088 Streams.pipeAll(plainText, cipherStream); 089 plainText.close(); 090 cipherStream.flush(); 091 cipherStream.close(); 092 cipherText.close(); 093 094 String base64 = Base64.encodeToString(cipherText.toByteArray()); 095 OpenPgpElement openPgpElement = new OpenPgpElement(base64); 096 097 return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult()); 098 } 099 100 @Override 101 public OpenPgpElementAndMetadata sign(SignElement element, OpenPgpSelf self) 102 throws IOException, PGPException { 103 InputStream plainText = element.toInputStream(); 104 ByteArrayOutputStream cipherText = new ByteArrayOutputStream(); 105 106 EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText) 107 .doNotEncrypt() 108 .signWith(getStore().getKeyRingProtector(), self.getSigningKeyRing()) 109 .noArmor(); 110 111 Streams.pipeAll(plainText, cipherStream); 112 plainText.close(); 113 cipherStream.flush(); 114 cipherStream.close(); 115 cipherText.close(); 116 117 String base64 = Base64.encodeToString(cipherText.toByteArray()); 118 OpenPgpElement openPgpElement = new OpenPgpElement(base64); 119 120 return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult()); 121 } 122 123 @Override 124 public OpenPgpElementAndMetadata encrypt(CryptElement element, OpenPgpSelf self, Collection<OpenPgpContact> recipients) 125 throws IOException, PGPException { 126 InputStream plainText = element.toInputStream(); 127 ByteArrayOutputStream cipherText = new ByteArrayOutputStream(); 128 129 ArrayList<PGPPublicKeyRingCollection> recipientKeys = new ArrayList<>(); 130 for (OpenPgpContact contact : recipients) { 131 PGPPublicKeyRingCollection keys = contact.getTrustedAnnouncedKeys(); 132 if (keys != null) { 133 recipientKeys.add(keys); 134 } else { 135 LOGGER.log(Level.WARNING, "There are no suitable keys for contact " + contact.getJid().toString()); 136 } 137 } 138 139 EncryptionStream cipherStream = PGPainless.createEncryptor().onOutputStream(cipherText) 140 .toRecipients(recipientKeys.toArray(new PGPPublicKeyRingCollection[] {})) 141 .andToSelf(self.getTrustedAnnouncedKeys()) 142 .usingSecureAlgorithms() 143 .doNotSign() 144 .noArmor(); 145 146 Streams.pipeAll(plainText, cipherStream); 147 plainText.close(); 148 cipherStream.flush(); 149 cipherStream.close(); 150 cipherText.close(); 151 152 String base64 = Base64.encodeToString(cipherText.toByteArray()); 153 OpenPgpElement openPgpElement = new OpenPgpElement(base64); 154 155 return new OpenPgpElementAndMetadata(openPgpElement, cipherStream.getResult()); 156 } 157 158 @Override 159 public OpenPgpMessage decryptAndOrVerify(XMPPConnection connection, OpenPgpElement element, final OpenPgpSelf self, final OpenPgpContact sender) throws IOException, PGPException { 160 ByteArrayOutputStream plainText = new ByteArrayOutputStream(); 161 InputStream cipherText = element.toInputStream(); 162 163 PGPPublicKeyRingCollection announcedPublicKeys = sender.getAnnouncedPublicKeys(); 164 if (announcedPublicKeys == null) { 165 LOGGER.log(Level.INFO, "Received a message from " + sender.getJid() + " but we have no keys yet. Try fetching them."); 166 try { 167 sender.updateKeys(connection); 168 announcedPublicKeys = sender.getAnnouncedPublicKeys(); 169 } catch (Exception e) { 170 LOGGER.log(Level.SEVERE, "Fetching keys of " + sender.getJid() + " failed. Abort decryption and discard message.", e); 171 throw new PGPException("Abort decryption due to lack of keys.", e); 172 } 173 } 174 175 MissingPublicKeyCallback missingPublicKeyCallback = new MissingPublicKeyCallback() { 176 @Override 177 public PGPPublicKey onMissingPublicKeyEncountered(Long keyId) { 178 try { 179 sender.updateKeys(connection); 180 return sender.getAnyPublicKeys().getPublicKey(keyId); 181 } catch (Exception e) { 182 LOGGER.log(Level.WARNING, "Cannot fetch missing key " + keyId, e); 183 return null; 184 } 185 } 186 }; 187 188 DecryptionStream cipherStream = PGPainless.createDecryptor().onInputStream(cipherText) 189 .decryptWith(getStore().getKeyRingProtector(), self.getSecretKeys()) 190 .verifyWith(announcedPublicKeys) 191 .handleMissingPublicKeysWith(missingPublicKeyCallback) 192 .build(); 193 194 Streams.pipeAll(cipherStream, plainText); 195 196 cipherText.close(); 197 cipherStream.close(); 198 plainText.close(); 199 200 OpenPgpMetadata info = cipherStream.getResult(); 201 202 OpenPgpMessage.State state; 203 if (info.isSigned()) { 204 if (info.isEncrypted()) { 205 state = OpenPgpMessage.State.signcrypt; 206 } else { 207 state = OpenPgpMessage.State.sign; 208 } 209 } else if (info.isEncrypted()) { 210 state = OpenPgpMessage.State.crypt; 211 } else { 212 throw new PGPException("Received message appears to be neither encrypted, nor signed."); 213 } 214 215 return new OpenPgpMessage(plainText.toByteArray(), state, info); 216 } 217}