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