/** * Build a new session from a {@link org.whispersystems.libaxolotl.state.PreKeyBundle} retrieved from * a server. * * @param preKey A PreKey for the destination recipient, retrieved from a server. * @throws InvalidKeyException when the {@link org.whispersystems.libaxolotl.state.PreKeyBundle} is * badly formatted. * @throws org.whispersystems.libaxolotl.UntrustedIdentityException when the sender's * {@link IdentityKey} is not * trusted. */ public void process(PreKeyBundle preKey) { lock (SessionCipher.SESSION_LOCK) { if (!identityKeyStore.IsTrustedIdentity(remoteAddress.getName(), preKey.getIdentityKey())) { throw new UntrustedIdentityException(remoteAddress.getName(), preKey.getIdentityKey()); } if (preKey.getSignedPreKey() != null && !Curve.verifySignature(preKey.getIdentityKey().getPublicKey(), preKey.getSignedPreKey().serialize(), preKey.getSignedPreKeySignature())) { throw new InvalidKeyException("Invalid signature on device key!"); } if (preKey.getSignedPreKey() == null && preKey.getPreKey() == null) { throw new InvalidKeyException("Both signed and unsigned prekeys are absent!"); } bool supportsV3 = preKey.getSignedPreKey() != null; SessionRecord sessionRecord = sessionStore.LoadSession(remoteAddress); ECKeyPair ourBaseKey = Curve.generateKeyPair(); ECPublicKey theirSignedPreKey = supportsV3 ? preKey.getSignedPreKey() : preKey.getPreKey(); ECPublicKey test = preKey.getPreKey(); // TODO: cleanup May <ECPublicKey> theirOneTimePreKey = (test == null) ? May <ECPublicKey> .NoValue : new May <ECPublicKey>(test); May <uint> theirOneTimePreKeyId = theirOneTimePreKey.HasValue ? new May <uint>(preKey.getPreKeyId()) : May <uint> .NoValue; AliceAxolotlParameters.Builder parameters = AliceAxolotlParameters.newBuilder(); parameters.setOurBaseKey(ourBaseKey) .setOurIdentityKey(identityKeyStore.GetIdentityKeyPair()) .setTheirIdentityKey(preKey.getIdentityKey()) .setTheirSignedPreKey(theirSignedPreKey) .setTheirRatchetKey(theirSignedPreKey) .setTheirOneTimePreKey(supportsV3 ? theirOneTimePreKey : May <ECPublicKey> .NoValue); if (!sessionRecord.isFresh()) { sessionRecord.archiveCurrentState(); } RatchetingSession.initializeSession(sessionRecord.getSessionState(), supportsV3 ? (uint)3 : 2, parameters.create()); sessionRecord.getSessionState().setUnacknowledgedPreKeyMessage(theirOneTimePreKeyId, preKey.getSignedPreKeyId(), ourBaseKey.getPublicKey()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(preKey.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(ourBaseKey.getPublicKey().serialize()); sessionStore.StoreSession(remoteAddress, sessionRecord); identityKeyStore.SaveIdentity(remoteAddress.getName(), preKey.getIdentityKey()); } }
private KeyExchangeMessage processInitiate(KeyExchangeMessage message) { uint flags = KeyExchangeMessage.RESPONSE_FLAG; SessionRecord sessionRecord = sessionStore.LoadSession(remoteAddress); if (message.getVersion() >= 3 && !Curve.verifySignature(message.getIdentityKey().getPublicKey(), message.getBaseKey().serialize(), message.getBaseKeySignature())) { throw new InvalidKeyException("Bad signature!"); } SymmetricAxolotlParameters.Builder builder = SymmetricAxolotlParameters.newBuilder(); if (!sessionRecord.getSessionState().hasPendingKeyExchange()) { builder.setOurIdentityKey(identityKeyStore.GetIdentityKeyPair()) .setOurBaseKey(Curve.generateKeyPair()) .setOurRatchetKey(Curve.generateKeyPair()); } else { builder.setOurIdentityKey(sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey()) .setOurBaseKey(sessionRecord.getSessionState().getPendingKeyExchangeBaseKey()) .setOurRatchetKey(sessionRecord.getSessionState().getPendingKeyExchangeRatchetKey()); flags |= KeyExchangeMessage.SIMULTAENOUS_INITIATE_FLAG; } builder.setTheirBaseKey(message.getBaseKey()) .setTheirRatchetKey(message.getRatchetKey()) .setTheirIdentityKey(message.getIdentityKey()); SymmetricAxolotlParameters parameters = builder.create(); if (!sessionRecord.isFresh()) { sessionRecord.archiveCurrentState(); } RatchetingSession.initializeSession(sessionRecord.getSessionState(), Math.Min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), parameters); sessionStore.StoreSession(remoteAddress, sessionRecord); identityKeyStore.SaveIdentity(remoteAddress.getName(), message.getIdentityKey()); byte[] baseKeySignature = Curve.calculateSignature(parameters.getOurIdentityKey().getPrivateKey(), parameters.getOurBaseKey().getPublicKey().serialize()); return(new KeyExchangeMessage(sessionRecord.getSessionState().getSessionVersion(), message.getSequence(), flags, parameters.getOurBaseKey().getPublicKey(), baseKeySignature, parameters.getOurRatchetKey().getPublicKey(), parameters.getOurIdentityKey().getPublicKey())); }
private void processResponse(KeyExchangeMessage message) { SessionRecord sessionRecord = sessionStore.LoadSession(remoteAddress); SessionState sessionState = sessionRecord.getSessionState(); bool hasPendingKeyExchange = sessionState.hasPendingKeyExchange(); bool isSimultaneousInitiateResponse = message.isResponseForSimultaneousInitiate(); if (!hasPendingKeyExchange || sessionState.getPendingKeyExchangeSequence() != message.getSequence()) { //Log.w(TAG, "No matching sequence for response. Is simultaneous initiate response: " + isSimultaneousInitiateResponse); if (!isSimultaneousInitiateResponse) { throw new StaleKeyExchangeException(); } else { return; } } SymmetricAxolotlParameters.Builder parameters = SymmetricAxolotlParameters.newBuilder(); parameters.setOurBaseKey(sessionRecord.getSessionState().getPendingKeyExchangeBaseKey()) .setOurRatchetKey(sessionRecord.getSessionState().getPendingKeyExchangeRatchetKey()) .setOurIdentityKey(sessionRecord.getSessionState().getPendingKeyExchangeIdentityKey()) .setTheirBaseKey(message.getBaseKey()) .setTheirRatchetKey(message.getRatchetKey()) .setTheirIdentityKey(message.getIdentityKey()); if (!sessionRecord.isFresh()) { sessionRecord.archiveCurrentState(); } RatchetingSession.initializeSession(sessionRecord.getSessionState(), Math.Min(message.getMaxVersion(), CiphertextMessage.CURRENT_VERSION), parameters.create()); if (sessionRecord.getSessionState().getSessionVersion() >= 3 && !Curve.verifySignature(message.getIdentityKey().getPublicKey(), message.getBaseKey().serialize(), message.getBaseKeySignature())) { throw new InvalidKeyException("Base key signature doesn't match!"); } sessionStore.StoreSession(remoteAddress, sessionRecord); identityKeyStore.SaveIdentity(remoteAddress.getName(), message.getIdentityKey()); }
private May <uint> processV3(SessionRecord sessionRecord, PreKeySignalMessage message) { if (sessionRecord.hasSessionState(message.getMessageVersion(), message.getBaseKey().serialize())) { Debug.WriteLine("We've already setup a session for this V3 message, letting bundled message fall through..."); return(May <uint> .NoValue); } ECKeyPair ourSignedPreKey = signedPreKeyStore.LoadSignedPreKey(message.getSignedPreKeyId()).getKeyPair(); BobSignalProtocolParameters.Builder parameters = BobSignalProtocolParameters.newBuilder(); parameters.setTheirBaseKey(message.getBaseKey()) .setTheirIdentityKey(message.getIdentityKey()) .setOurIdentityKey(identityKeyStore.GetIdentityKeyPair()) .setOurSignedPreKey(ourSignedPreKey) .setOurRatchetKey(ourSignedPreKey); if (message.getPreKeyId().HasValue) { parameters.setOurOneTimePreKey(new May <ECKeyPair>(preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair())); } else { parameters.setOurOneTimePreKey(May <ECKeyPair> .NoValue); } if (!sessionRecord.isFresh()) { sessionRecord.archiveCurrentState(); } RatchetingSession.initializeSession(sessionRecord.getSessionState(), parameters.create()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize()); if (message.getPreKeyId().HasValue&& message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE) { return(message.getPreKeyId()); } else { return(May <uint> .NoValue); } }
public static async Task SaveIdentityLocked(SignalProtocolAddress address, string identity) { LinkedList <SignalMessage> messages = null; lock (DBLock) { using (var ctx = new LibsignalDBContext()) { var old = ctx.Identities .Where(i => i.Username == address.Name) .FirstOrDefault(); //could contain stale data if (old == null) { ctx.Identities.Add(new SignalIdentity() { IdentityKey = identity, Username = address.Name, VerifiedStatus = VerifiedStatus.Default }); } else if (old.IdentityKey != identity) { if (old.VerifiedStatus == VerifiedStatus.Verified) { old.VerifiedStatus = VerifiedStatus.Unverified; } old.IdentityKey = identity; var oldSessions = ctx.Sessions .Where(s => s.Username == address.Name); foreach (var oldSession in oldSessions) { SessionRecord sessionRecord = new SessionRecord(Base64.Decode(oldSession.Session)); sessionRecord.archiveCurrentState(); oldSession.Session = Base64.EncodeBytes(sessionRecord.serialize()); SessionsCache[GetSessionCacheIndex(address.Name, oldSession.DeviceId)] = sessionRecord; } messages = InsertIdentityChangedMessages(address.Name); } ctx.SaveChanges(); } } if (messages != null) { await SignalLibHandle.Instance.DispatchHandleIdentityKeyChange(messages); } }
private May <uint> processV2(SessionRecord sessionRecord, PreKeyWhisperMessage message) { if (!message.getPreKeyId().HasValue) { throw new InvalidKeyIdException("V2 message requires one time prekey id!"); } if (!preKeyStore.ContainsPreKey(message.getPreKeyId().ForceGetValue()) && sessionStore.ContainsSession(remoteAddress)) { //Log.w(TAG, "We've already processed the prekey part of this V2 session, letting bundled message fall through..."); return(May <uint> .NoValue); //May.absent(); } ECKeyPair ourPreKey = preKeyStore.LoadPreKey(message.getPreKeyId().ForceGetValue()).getKeyPair(); BobAxolotlParameters.Builder parameters = BobAxolotlParameters.newBuilder(); parameters.setOurIdentityKey(identityKeyStore.GetIdentityKeyPair()) .setOurSignedPreKey(ourPreKey) .setOurRatchetKey(ourPreKey) .setOurOneTimePreKey(May <ECKeyPair> .NoValue) //absent .setTheirIdentityKey(message.getIdentityKey()) .setTheirBaseKey(message.getBaseKey()); if (!sessionRecord.isFresh()) { sessionRecord.archiveCurrentState(); } RatchetingSession.initializeSession(sessionRecord.getSessionState(), message.getMessageVersion(), parameters.create()); sessionRecord.getSessionState().setLocalRegistrationId(identityKeyStore.GetLocalRegistrationId()); sessionRecord.getSessionState().setRemoteRegistrationId(message.getRegistrationId()); sessionRecord.getSessionState().setAliceBaseKey(message.getBaseKey().serialize()); if (message.getPreKeyId().ForceGetValue() != Medium.MAX_VALUE) { return(message.getPreKeyId()); } else { return(May <uint> .NoValue); // May.absent(); } }