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; 018 019import java.io.IOException; 020import java.util.Collections; 021import java.util.Date; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.Iterator; 025import java.util.Map; 026import java.util.Set; 027import java.util.logging.Level; 028import java.util.logging.Logger; 029 030import org.jivesoftware.smack.SmackException; 031import org.jivesoftware.smack.XMPPConnection; 032import org.jivesoftware.smack.XMPPException; 033import org.jivesoftware.smack.util.stringencoder.Base64; 034import org.jivesoftware.smackx.ox.element.PubkeyElement; 035import org.jivesoftware.smackx.ox.element.PublicKeysListElement; 036import org.jivesoftware.smackx.ox.exception.MissingUserIdOnKeyException; 037import org.jivesoftware.smackx.ox.store.definition.OpenPgpStore; 038import org.jivesoftware.smackx.ox.store.definition.OpenPgpTrustStore; 039import org.jivesoftware.smackx.ox.util.OpenPgpPubSubUtil; 040import org.jivesoftware.smackx.pubsub.LeafNode; 041import org.jivesoftware.smackx.pubsub.PubSubException; 042 043import org.bouncycastle.openpgp.PGPException; 044import org.bouncycastle.openpgp.PGPPublicKeyRing; 045import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; 046import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator; 047import org.jxmpp.jid.BareJid; 048import org.pgpainless.key.OpenPgpV4Fingerprint; 049import org.pgpainless.key.info.KeyRingInfo; 050 051/** 052 * The OpenPgpContact is sort of a specialized view on the OpenPgpStore, which gives you access to the information 053 * about the user. It also allows contact-specific actions like fetching the contacts keys from PubSub etc. 054 */ 055public class OpenPgpContact { 056 057 private final Logger LOGGER; 058 059 protected final BareJid jid; 060 protected final OpenPgpStore store; 061 protected final Map<OpenPgpV4Fingerprint, Throwable> unfetchableKeys = new HashMap<>(); 062 063 /** 064 * Create a new OpenPgpContact. 065 * 066 * @param jid {@link BareJid} of the contact. 067 * @param store {@link OpenPgpStore}. 068 */ 069 public OpenPgpContact(BareJid jid, OpenPgpStore store) { 070 this.jid = jid; 071 this.store = store; 072 LOGGER = Logger.getLogger(OpenPgpContact.class.getName() + ":" + jid.toString()); 073 } 074 075 /** 076 * Return the jid of the contact. 077 * 078 * @return jid TODO javadoc me please 079 */ 080 public BareJid getJid() { 081 return jid; 082 } 083 084 /** 085 * Return any available public keys of the user. The result might also contain outdated or invalid keys. 086 * 087 * @return any keys of the contact. 088 * 089 * @throws IOException IO is dangerous 090 * @throws PGPException PGP is brittle 091 */ 092 public PGPPublicKeyRingCollection getAnyPublicKeys() throws IOException, PGPException { 093 return store.getPublicKeysOf(jid); 094 } 095 096 /** 097 * Return any announced public keys. This is the set returned by {@link #getAnyPublicKeys()} with non-announced 098 * keys and keys which lack a user-id with the contacts jid removed. 099 * 100 * @return announced keys of the contact 101 * 102 * @throws IOException IO is dangerous 103 * @throws PGPException PGP is brittle 104 */ 105 public PGPPublicKeyRingCollection getAnnouncedPublicKeys() throws IOException, PGPException { 106 PGPPublicKeyRingCollection anyKeys = getAnyPublicKeys(); 107 Map<OpenPgpV4Fingerprint, Date> announced = store.getAnnouncedFingerprintsOf(jid); 108 109 PGPPublicKeyRingCollection announcedKeysCollection = null; 110 for (OpenPgpV4Fingerprint announcedFingerprint : announced.keySet()) { 111 PGPPublicKeyRing ring = anyKeys.getPublicKeyRing(announcedFingerprint.getKeyId()); 112 113 if (ring == null) continue; 114 115 if (!new KeyRingInfo(ring).isUserIdValid("xmpp:" + getJid().toString())) { 116 LOGGER.log(Level.WARNING, "Ignore key " + Long.toHexString(ring.getPublicKey().getKeyID()) + 117 " as it lacks the user-id \"xmpp" + getJid().toString() + "\""); 118 continue; 119 } 120 121 if (announcedKeysCollection == null) { 122 announcedKeysCollection = new PGPPublicKeyRingCollection(Collections.singleton(ring)); 123 } else { 124 announcedKeysCollection = PGPPublicKeyRingCollection.addPublicKeyRing(announcedKeysCollection, ring); 125 } 126 } 127 128 return announcedKeysCollection; 129 } 130 131 /** 132 * Return a {@link PGPPublicKeyRingCollection}, which contains all keys from {@code keys}, which are marked with the 133 * {@link OpenPgpTrustStore.Trust} state of {@code trust}. 134 * 135 * @param keys {@link PGPPublicKeyRingCollection} 136 * @param trust {@link OpenPgpTrustStore.Trust} 137 * 138 * @return all keys from {@code keys} with trust state {@code trust}. 139 * 140 * @throws IOException IO error 141 */ 142 protected PGPPublicKeyRingCollection getPublicKeysOfTrustState(PGPPublicKeyRingCollection keys, 143 OpenPgpTrustStore.Trust trust) 144 throws IOException { 145 146 if (keys == null) { 147 return null; 148 } 149 150 Set<PGPPublicKeyRing> toRemove = new HashSet<>(); 151 Iterator<PGPPublicKeyRing> iterator = keys.iterator(); 152 while (iterator.hasNext()) { 153 PGPPublicKeyRing ring = iterator.next(); 154 OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(ring); 155 if (store.getTrust(getJid(), fingerprint) != trust) { 156 toRemove.add(ring); 157 } 158 } 159 160 for (PGPPublicKeyRing ring : toRemove) { 161 keys = PGPPublicKeyRingCollection.removePublicKeyRing(keys, ring); 162 } 163 164 if (!keys.iterator().hasNext()) { 165 return null; 166 } 167 168 return keys; 169 } 170 171 /** 172 * Return a {@link PGPPublicKeyRingCollection} which contains all public keys of the contact, which are announced, 173 * as well as marked as {@link OpenPgpStore.Trust#trusted}. 174 * 175 * @return announced, trusted keys. 176 * 177 * @throws IOException IO error 178 * @throws PGPException PGP error 179 */ 180 public PGPPublicKeyRingCollection getTrustedAnnouncedKeys() 181 throws IOException, PGPException { 182 PGPPublicKeyRingCollection announced = getAnnouncedPublicKeys(); 183 PGPPublicKeyRingCollection trusted = getPublicKeysOfTrustState(announced, OpenPgpTrustStore.Trust.trusted); 184 return trusted; 185 } 186 187 /** 188 * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state 189 * {@link OpenPgpStore.Trust#trusted}. 190 * 191 * @return trusted fingerprints 192 * 193 * @throws IOException IO error 194 * @throws PGPException PGP error 195 */ 196 public Set<OpenPgpV4Fingerprint> getTrustedFingerprints() 197 throws IOException, PGPException { 198 return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.trusted); 199 } 200 201 /** 202 * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state 203 * {@link OpenPgpStore.Trust#untrusted}. 204 * 205 * @return untrusted fingerprints 206 * 207 * @throws IOException IO error 208 * @throws PGPException PGP error 209 */ 210 public Set<OpenPgpV4Fingerprint> getUntrustedFingerprints() 211 throws IOException, PGPException { 212 return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.untrusted); 213 } 214 215 /** 216 * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys of the contact, which have the trust state 217 * {@link OpenPgpStore.Trust#undecided}. 218 * 219 * @return undecided fingerprints 220 * 221 * @throws IOException IO error 222 * @throws PGPException PGP error 223 */ 224 public Set<OpenPgpV4Fingerprint> getUndecidedFingerprints() 225 throws IOException, PGPException { 226 return getFingerprintsOfKeysWithState(getAnyPublicKeys(), OpenPgpTrustStore.Trust.undecided); 227 } 228 229 /** 230 * Return a {@link Set} of {@link OpenPgpV4Fingerprint}s of all keys in {@code publicKeys}, which are marked with the 231 * {@link OpenPgpTrustStore.Trust} of {@code trust}. 232 * 233 * @param publicKeys {@link PGPPublicKeyRingCollection} of keys which are iterated. 234 * @param trust {@link OpenPgpTrustStore.Trust} state. 235 * @return {@link Set} of fingerprints 236 * 237 * @throws IOException IO error 238 */ 239 public Set<OpenPgpV4Fingerprint> getFingerprintsOfKeysWithState(PGPPublicKeyRingCollection publicKeys, 240 OpenPgpTrustStore.Trust trust) 241 throws IOException { 242 PGPPublicKeyRingCollection keys = getPublicKeysOfTrustState(publicKeys, trust); 243 Set<OpenPgpV4Fingerprint> fingerprints = new HashSet<>(); 244 245 if (keys == null) { 246 return fingerprints; 247 } 248 249 for (PGPPublicKeyRing ring : keys) { 250 fingerprints.add(new OpenPgpV4Fingerprint(ring)); 251 } 252 253 return fingerprints; 254 } 255 256 /** 257 * Determine the {@link OpenPgpTrustStore.Trust} state of the key identified by the {@code fingerprint}. 258 * 259 * @param fingerprint {@link OpenPgpV4Fingerprint} of the key 260 * @return trust record 261 * 262 * @throws IOException IO error 263 */ 264 public OpenPgpTrustStore.Trust getTrust(OpenPgpV4Fingerprint fingerprint) 265 throws IOException { 266 return store.getTrust(getJid(), fingerprint); 267 } 268 269 /** 270 * Determine, whether the key identified by the {@code fingerprint} is marked as 271 * {@link OpenPgpTrustStore.Trust#trusted} or not. 272 * 273 * @param fingerprint {@link OpenPgpV4Fingerprint} of the key 274 * @return true, if the key is marked as trusted, false otherwise 275 * 276 * @throws IOException IO error 277 */ 278 public boolean isTrusted(OpenPgpV4Fingerprint fingerprint) 279 throws IOException { 280 return getTrust(fingerprint) == OpenPgpTrustStore.Trust.trusted; 281 } 282 283 /** 284 * Mark a key as {@link OpenPgpStore.Trust#trusted}. 285 * 286 * @param fingerprint {@link OpenPgpV4Fingerprint} of the key to mark as trusted. 287 * 288 * @throws IOException IO error 289 */ 290 public void trust(OpenPgpV4Fingerprint fingerprint) 291 throws IOException { 292 store.setTrust(getJid(), fingerprint, OpenPgpTrustStore.Trust.trusted); 293 } 294 295 /** 296 * Mark a key as {@link OpenPgpStore.Trust#untrusted}. 297 * 298 * @param fingerprint {@link OpenPgpV4Fingerprint} of the key to mark as untrusted. 299 * 300 * @throws IOException IO error 301 */ 302 public void distrust(OpenPgpV4Fingerprint fingerprint) 303 throws IOException { 304 store.setTrust(getJid(), fingerprint, OpenPgpTrustStore.Trust.untrusted); 305 } 306 307 /** 308 * Determine, whether there are keys available, for which we did not yet decided whether to trust them or not. 309 * 310 * @return more than 0 keys with trust state {@link OpenPgpTrustStore.Trust#undecided}. 311 * 312 * @throws IOException I/O error reading the keys or trust records. 313 * @throws PGPException PGP error reading the keys. 314 */ 315 public boolean hasUndecidedKeys() 316 throws IOException, PGPException { 317 return getUndecidedFingerprints().size() != 0; 318 } 319 320 /** 321 * Return a {@link Map} of any unfetchable keys fingerprints and the cause of them not being fetched. 322 * 323 * @return unfetchable keys 324 */ 325 public Map<OpenPgpV4Fingerprint, Throwable> getUnfetchableKeys() { 326 return new HashMap<>(unfetchableKeys); 327 } 328 329 /** 330 * Update the contacts keys by consulting the users PubSub nodes. 331 * This method fetches the users metadata node and then tries to fetch any announced keys. 332 * 333 * @param connection our {@link XMPPConnection}. 334 * 335 * @throws InterruptedException In case the thread gets interrupted. 336 * @throws SmackException.NotConnectedException in case the connection is not connected. 337 * @throws SmackException.NoResponseException in case the server doesn't respond. 338 * @throws XMPPException.XMPPErrorException in case of an XMPP protocol error. 339 * @throws PubSubException.NotALeafNodeException in case the metadata node is not a {@link LeafNode}. 340 * @throws PubSubException.NotAPubSubNodeException in case the metadata node is not a PubSub node. 341 * @throws IOException IO is brittle. 342 */ 343 public void updateKeys(XMPPConnection connection) throws InterruptedException, SmackException.NotConnectedException, 344 SmackException.NoResponseException, XMPPException.XMPPErrorException, PubSubException.NotALeafNodeException, 345 PubSubException.NotAPubSubNodeException, IOException { 346 PublicKeysListElement metadata = OpenPgpPubSubUtil.fetchPubkeysList(connection, getJid()); 347 if (metadata == null) { 348 return; 349 } 350 351 updateKeys(connection, metadata); 352 } 353 354 /** 355 * Update the contacts keys using a prefetched {@link PublicKeysListElement}. 356 * 357 * @param connection our {@link XMPPConnection}. 358 * @param metadata pre-fetched OX metadata node of the contact. 359 * 360 * @throws InterruptedException in case the thread gets interrupted. 361 * @throws SmackException.NotConnectedException in case the connection is not connected. 362 * @throws SmackException.NoResponseException in case the server doesn't respond. 363 * @throws IOException IO is dangerous. 364 */ 365 public void updateKeys(XMPPConnection connection, PublicKeysListElement metadata) 366 throws InterruptedException, SmackException.NotConnectedException, SmackException.NoResponseException, 367 IOException { 368 369 Map<OpenPgpV4Fingerprint, Date> fingerprintsAndDates = new HashMap<>(); 370 for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) { 371 fingerprintsAndDates.put(fingerprint, metadata.getMetadata().get(fingerprint).getDate()); 372 } 373 374 store.setAnnouncedFingerprintsOf(getJid(), fingerprintsAndDates); 375 Map<OpenPgpV4Fingerprint, Date> fetchDates = store.getPublicKeyFetchDates(getJid()); 376 377 for (OpenPgpV4Fingerprint fingerprint : metadata.getMetadata().keySet()) { 378 Date fetchDate = fetchDates.get(fingerprint); 379 if (fetchDate != null && fingerprintsAndDates.get(fingerprint) != null && fetchDate.after(fingerprintsAndDates.get(fingerprint))) { 380 LOGGER.log(Level.FINE, "Skip key " + Long.toHexString(fingerprint.getKeyId()) + " as we already have the most recent version. " + 381 "Last announced: " + fingerprintsAndDates.get(fingerprint).toString() + " Last fetched: " + fetchDate.toString()); 382 continue; 383 } 384 try { 385 PubkeyElement key = OpenPgpPubSubUtil.fetchPubkey(connection, getJid(), fingerprint); 386 unfetchableKeys.remove(fingerprint); 387 fetchDates.put(fingerprint, new Date()); 388 if (key == null) { 389 LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) + 390 " can not be imported: Is null"); 391 unfetchableKeys.put(fingerprint, new NullPointerException("Public key is null.")); 392 continue; 393 } 394 PGPPublicKeyRing keyRing = new PGPPublicKeyRing(Base64.decode(key.getDataElement().getB64Data()), new BcKeyFingerprintCalculator()); 395 store.importPublicKey(getJid(), keyRing); 396 } catch (PubSubException.NotAPubSubNodeException | PubSubException.NotALeafNodeException | 397 XMPPException.XMPPErrorException e) { 398 LOGGER.log(Level.WARNING, "Error fetching public key " + Long.toHexString(fingerprint.getKeyId()), e); 399 unfetchableKeys.put(fingerprint, e); 400 } catch (PGPException | IOException e) { 401 LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) + 402 " can not be imported.", e); 403 unfetchableKeys.put(fingerprint, e); 404 } catch (MissingUserIdOnKeyException e) { 405 LOGGER.log(Level.WARNING, "Public key " + Long.toHexString(fingerprint.getKeyId()) + 406 " is missing the user-id \"xmpp:" + getJid() + "\". Refuse to import it.", e); 407 unfetchableKeys.put(fingerprint, e); 408 } 409 } 410 store.setPublicKeyFetchDates(getJid(), fetchDates); 411 } 412}