001/** 002 * 003 * Copyright 2017 Paul Schaub 004 * 005 * This file is part of smack-omemo-signal. 006 * 007 * smack-omemo-signal is free software; you can redistribute it and/or modify 008 * it under the terms of the GNU General Public License as published by 009 * the Free Software Foundation; either version 3 of the License, or 010 * (at your option) any later version. 011 * 012 * This program is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 015 * GNU General Public License for more details. 016 * 017 * You should have received a copy of the GNU General Public License 018 * along with this program; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jivesoftware.smackx.omemo.signal; 022 023import java.io.IOException; 024import java.util.logging.Level; 025import java.util.logging.Logger; 026 027import org.jivesoftware.smackx.omemo.OmemoManager; 028import org.jivesoftware.smackx.omemo.OmemoRatchet; 029import org.jivesoftware.smackx.omemo.OmemoStore; 030import org.jivesoftware.smackx.omemo.element.OmemoElement; 031import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 032import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 033import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 034import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException; 035import org.jivesoftware.smackx.omemo.internal.CiphertextTuple; 036import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 037 038import org.whispersystems.libsignal.DuplicateMessageException; 039import org.whispersystems.libsignal.IdentityKey; 040import org.whispersystems.libsignal.IdentityKeyPair; 041import org.whispersystems.libsignal.InvalidKeyException; 042import org.whispersystems.libsignal.InvalidKeyIdException; 043import org.whispersystems.libsignal.InvalidMessageException; 044import org.whispersystems.libsignal.InvalidVersionException; 045import org.whispersystems.libsignal.LegacyMessageException; 046import org.whispersystems.libsignal.NoSessionException; 047import org.whispersystems.libsignal.SessionCipher; 048import org.whispersystems.libsignal.SignalProtocolAddress; 049import org.whispersystems.libsignal.UntrustedIdentityException; 050import org.whispersystems.libsignal.ecc.ECPublicKey; 051import org.whispersystems.libsignal.protocol.CiphertextMessage; 052import org.whispersystems.libsignal.protocol.PreKeySignalMessage; 053import org.whispersystems.libsignal.protocol.SignalMessage; 054import org.whispersystems.libsignal.state.PreKeyBundle; 055import org.whispersystems.libsignal.state.PreKeyRecord; 056import org.whispersystems.libsignal.state.SessionRecord; 057import org.whispersystems.libsignal.state.SignedPreKeyRecord; 058 059public class SignalOmemoRatchet 060 extends OmemoRatchet<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, 061 SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> { 062 063 private static final Logger LOGGER = Logger.getLogger(OmemoRatchet.class.getName()); 064 private final SignalOmemoStoreConnector storeConnector; 065 066 SignalOmemoRatchet(OmemoManager omemoManager, 067 OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, 068 SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, 069 SessionCipher> store) { 070 super(omemoManager, store); 071 this.storeConnector = new SignalOmemoStoreConnector(omemoManager, store); 072 } 073 074 @Override 075 public byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey) 076 throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException, 077 UntrustedOmemoIdentityException, IOException { 078 079 SessionCipher cipher = getCipher(sender); 080 byte[] decryptedKey; 081 082 // Try to handle the message as a PreKeySignalMessage... 083 try { 084 PreKeySignalMessage preKeyMessage = new PreKeySignalMessage(encryptedKey); 085 086 if (!preKeyMessage.getPreKeyId().isPresent()) { 087 throw new CryptoFailedException("PreKeyMessage did not contain a preKeyId."); 088 } 089 090 IdentityKey messageIdentityKey = preKeyMessage.getIdentityKey(); 091 IdentityKey previousIdentityKey = store.loadOmemoIdentityKey(storeConnector.getOurDevice(), sender); 092 093 if (previousIdentityKey != null && 094 !previousIdentityKey.getFingerprint().equals(messageIdentityKey.getFingerprint())) { 095 throw new UntrustedOmemoIdentityException(sender, 096 store.keyUtil().getFingerprintOfIdentityKey(previousIdentityKey), 097 store.keyUtil().getFingerprintOfIdentityKey(messageIdentityKey)); 098 } 099 100 try { 101 decryptedKey = cipher.decrypt(preKeyMessage); 102 } 103 catch (UntrustedIdentityException e) { 104 throw new AssertionError("Signals trust management MUST be disabled."); 105 } 106 catch (LegacyMessageException | InvalidKeyException e) { 107 throw new CryptoFailedException(e); 108 } 109 catch (InvalidKeyIdException e) { 110 throw new NoRawSessionException(sender, e); 111 } 112 catch (DuplicateMessageException e) { 113 LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender + 114 " failed, since the message has been decrypted before."); 115 return null; 116 } 117 118 } catch (InvalidVersionException | InvalidMessageException noPreKeyMessage) { 119 // ...if that fails, handle it as a SignalMessage 120 try { 121 SignalMessage message = new SignalMessage(encryptedKey); 122 decryptedKey = getCipher(sender).decrypt(message); 123 } 124 catch (UntrustedIdentityException e) { 125 throw new AssertionError("Signals trust management MUST be disabled."); 126 } 127 catch (InvalidMessageException | NoSessionException e) { 128 throw new NoRawSessionException(sender, e); 129 } 130 catch (LegacyMessageException e) { 131 throw new CryptoFailedException(e); 132 } 133 catch (DuplicateMessageException e1) { 134 LOGGER.log(Level.INFO, "Decryption of SignalMessage from " + sender + 135 " failed, since the message has been decrypted before."); 136 return null; 137 } 138 } 139 140 return decryptedKey; 141 } 142 143 @Override 144 public CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey) { 145 CiphertextMessage ciphertextMessage; 146 try { 147 ciphertextMessage = getCipher(recipient).encrypt(messageKey); 148 } catch (UntrustedIdentityException e) { 149 throw new AssertionError("Signals trust management MUST be disabled."); 150 } 151 152 int type = ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE ? 153 OmemoElement.TYPE_OMEMO_PREKEY_MESSAGE : OmemoElement.TYPE_OMEMO_MESSAGE; 154 155 return new CiphertextTuple(ciphertextMessage.serialize(), type); 156 } 157 158 private SessionCipher getCipher(OmemoDevice device) { 159 return new SessionCipher(storeConnector, storeConnector, storeConnector, storeConnector, 160 SignalOmemoStoreConnector.asAddress(device)); 161 } 162}