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.util.logging.Level; 024import java.util.logging.Logger; 025 026import org.jivesoftware.smackx.omemo.OmemoManager; 027import org.jivesoftware.smackx.omemo.OmemoRatchet; 028import org.jivesoftware.smackx.omemo.OmemoStore; 029import org.jivesoftware.smackx.omemo.element.OmemoElement; 030import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException; 031import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; 032import org.jivesoftware.smackx.omemo.exceptions.NoRawSessionException; 033import org.jivesoftware.smackx.omemo.exceptions.UntrustedOmemoIdentityException; 034import org.jivesoftware.smackx.omemo.internal.CiphertextTuple; 035import org.jivesoftware.smackx.omemo.internal.OmemoDevice; 036 037import org.whispersystems.libsignal.DuplicateMessageException; 038import org.whispersystems.libsignal.IdentityKey; 039import org.whispersystems.libsignal.IdentityKeyPair; 040import org.whispersystems.libsignal.InvalidKeyException; 041import org.whispersystems.libsignal.InvalidKeyIdException; 042import org.whispersystems.libsignal.InvalidMessageException; 043import org.whispersystems.libsignal.InvalidVersionException; 044import org.whispersystems.libsignal.LegacyMessageException; 045import org.whispersystems.libsignal.NoSessionException; 046import org.whispersystems.libsignal.SessionCipher; 047import org.whispersystems.libsignal.SignalProtocolAddress; 048import org.whispersystems.libsignal.UntrustedIdentityException; 049import org.whispersystems.libsignal.ecc.ECPublicKey; 050import org.whispersystems.libsignal.protocol.CiphertextMessage; 051import org.whispersystems.libsignal.protocol.PreKeySignalMessage; 052import org.whispersystems.libsignal.protocol.SignalMessage; 053import org.whispersystems.libsignal.state.PreKeyBundle; 054import org.whispersystems.libsignal.state.PreKeyRecord; 055import org.whispersystems.libsignal.state.SessionRecord; 056import org.whispersystems.libsignal.state.SignedPreKeyRecord; 057 058public class SignalOmemoRatchet 059 extends OmemoRatchet<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, SessionRecord, 060 SignalProtocolAddress, ECPublicKey, PreKeyBundle, SessionCipher> { 061 062 private static final Logger LOGGER = Logger.getLogger(OmemoRatchet.class.getName()); 063 private final SignalOmemoStoreConnector storeConnector; 064 065 SignalOmemoRatchet(OmemoManager omemoManager, 066 OmemoStore<IdentityKeyPair, IdentityKey, PreKeyRecord, SignedPreKeyRecord, 067 SessionRecord, SignalProtocolAddress, ECPublicKey, PreKeyBundle, 068 SessionCipher> store) { 069 super(omemoManager, store); 070 this.storeConnector = new SignalOmemoStoreConnector(omemoManager, store); 071 } 072 073 @Override 074 public byte[] doubleRatchetDecrypt(OmemoDevice sender, byte[] encryptedKey) 075 throws CorruptedOmemoKeyException, NoRawSessionException, CryptoFailedException, 076 UntrustedOmemoIdentityException { 077 078 SessionCipher cipher = getCipher(sender); 079 byte[] decryptedKey; 080 081 // Try to handle the message as a PreKeySignalMessage... 082 try { 083 PreKeySignalMessage preKeyMessage = new PreKeySignalMessage(encryptedKey); 084 085 if (!preKeyMessage.getPreKeyId().isPresent()) { 086 throw new CryptoFailedException("PreKeyMessage did not contain a preKeyId."); 087 } 088 089 IdentityKey messageIdentityKey = preKeyMessage.getIdentityKey(); 090 IdentityKey previousIdentityKey = store.loadOmemoIdentityKey(storeConnector.getOurDevice(), sender); 091 092 if (previousIdentityKey != null && 093 !previousIdentityKey.getFingerprint().equals(messageIdentityKey.getFingerprint())) { 094 throw new UntrustedOmemoIdentityException(sender, 095 store.keyUtil().getFingerprintOfIdentityKey(previousIdentityKey), 096 store.keyUtil().getFingerprintOfIdentityKey(messageIdentityKey)); 097 } 098 099 try { 100 decryptedKey = cipher.decrypt(preKeyMessage); 101 } 102 catch (UntrustedIdentityException e) { 103 throw new AssertionError("Signals trust management MUST be disabled."); 104 } 105 catch (LegacyMessageException | InvalidKeyException e) { 106 throw new CryptoFailedException(e); 107 } 108 catch (InvalidKeyIdException e) { 109 throw new NoRawSessionException(sender, e); 110 } 111 catch (DuplicateMessageException e) { 112 LOGGER.log(Level.INFO, "Decryption of PreKeyMessage from " + sender + 113 " failed, since the message has been decrypted before."); 114 return null; 115 } 116 117 } catch (InvalidVersionException | InvalidMessageException noPreKeyMessage) { 118 // ...if that fails, handle it as a SignalMessage 119 try { 120 SignalMessage message = new SignalMessage(encryptedKey); 121 decryptedKey = getCipher(sender).decrypt(message); 122 } 123 catch (UntrustedIdentityException e) { 124 throw new AssertionError("Signals trust management MUST be disabled."); 125 } 126 catch (InvalidMessageException | NoSessionException e) { 127 throw new NoRawSessionException(sender, e); 128 } 129 catch (LegacyMessageException e) { 130 throw new CryptoFailedException(e); 131 } 132 catch (DuplicateMessageException e1) { 133 LOGGER.log(Level.INFO, "Decryption of SignalMessage from " + sender + 134 " failed, since the message has been decrypted before."); 135 return null; 136 } 137 } 138 139 return decryptedKey; 140 } 141 142 @Override 143 public CiphertextTuple doubleRatchetEncrypt(OmemoDevice recipient, byte[] messageKey) { 144 CiphertextMessage ciphertextMessage; 145 try { 146 ciphertextMessage = getCipher(recipient).encrypt(messageKey); 147 } catch (UntrustedIdentityException e) { 148 throw new AssertionError("Signals trust management MUST be disabled."); 149 } 150 151 // TODO: Figure out, if this is enough... 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}