package im.actor.core.modules.encryption; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import im.actor.core.api.ApiEncryptionKey; import im.actor.core.api.ApiEncryptionKeyGroup; import im.actor.core.api.ApiEncryptionKeySignature; import im.actor.core.api.ApiUserOutPeer; import im.actor.core.api.rpc.RequestCreateNewKeyGroup; import im.actor.core.api.rpc.RequestLoadPrePublicKeys; import im.actor.core.api.rpc.RequestLoadPublicKey; import im.actor.core.api.rpc.RequestLoadPublicKeyGroups; import im.actor.core.api.rpc.RequestUploadPreKey; import im.actor.core.api.rpc.ResponseCreateNewKeyGroup; import im.actor.core.api.rpc.ResponsePublicKeyGroups; import im.actor.core.api.rpc.ResponsePublicKeys; import im.actor.core.api.rpc.ResponseVoid; import im.actor.core.entity.User; import im.actor.core.modules.ModuleContext; import im.actor.core.modules.encryption.entity.PrivateKeyStorage; import im.actor.core.modules.encryption.entity.PrivateKey; import im.actor.core.modules.encryption.entity.UserKeys; import im.actor.core.modules.encryption.entity.UserKeysGroup; import im.actor.core.modules.encryption.entity.PublicKey; import im.actor.core.modules.ModuleActor; import im.actor.core.util.RandomUtils; import im.actor.runtime.Crypto; import im.actor.runtime.Log; import im.actor.runtime.Storage; import im.actor.runtime.actors.ask.AskIntRequest; import im.actor.runtime.actors.ask.AskMessage; import im.actor.runtime.actors.ask.AskResult; import im.actor.runtime.collections.ManagedList; import im.actor.runtime.crypto.Curve25519KeyPair; import im.actor.runtime.function.Function; import im.actor.runtime.promise.Promise; import im.actor.runtime.crypto.Curve25519; import im.actor.runtime.crypto.ratchet.RatchetKeySignature; import im.actor.runtime.function.Consumer; import im.actor.runtime.promise.PromisesArray; import im.actor.runtime.function.Tuple2; import im.actor.runtime.storage.KeyValueStorage; /** * Key Management Actor. * 1) Generates and uploads own keys. * 2) Downloads and manages updates about foreign keys */ public class KeyManagerActor extends ModuleActor { private static final String TAG = "KeyManagerActor"; private KeyValueStorage encryptionKeysStorage; private HashMap<Integer, UserKeys> cachedUserKeys = new HashMap<Integer, UserKeys>(); private PrivateKeyStorage ownKeys; private boolean isReady = false; public KeyManagerActor(ModuleContext context) { super(context); } @Override public void preStart() { Log.d(TAG, "Starting KeyManager..."); // // Initialization key storage // encryptionKeysStorage = Storage.createKeyValue("encryption_keys"); // // Initialization own private keys // ownKeys = null; byte[] ownKeysStorage = encryptionKeysStorage.loadItem(0); if (ownKeysStorage != null) { try { ownKeys = new PrivateKeyStorage(ownKeysStorage); // If we need re-save key storage if (ownKeys.isWasRegenerated()) { encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); } } catch (IOException e) { e.printStackTrace(); } } if (ownKeys == null) { Curve25519KeyPair identityPrivate = Curve25519.keyGen(Crypto.randomBytes(64)); Curve25519KeyPair key0 = Curve25519.keyGen(Crypto.randomBytes(64)); ownKeys = new PrivateKeyStorage(0, new PrivateKey(RandomUtils.nextRid(), "curve25519", identityPrivate.getPrivateKey(), identityPrivate.getPublicKey()), new PrivateKey[]{ new PrivateKey(RandomUtils.nextRid(), "curve25519", key0.getPrivateKey(), key0.getPublicKey()) }, new PrivateKey[0]); encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); } // // Creating new key group if needed // if (ownKeys.getKeyGroupId() == 0) { ApiEncryptionKey identityKey = ownKeys.getIdentityKey().toApiKey(); ArrayList<ApiEncryptionKey> keys = ManagedList.of(ownKeys.getKeys()) .map(PrivateKey.TO_API); ArrayList<ApiEncryptionKeySignature> signatures = ManagedList.of(ownKeys.getKeys()) .map(PrivateKey.SIGN(ownKeys.getIdentityKey())); Log.d(TAG, "Creation of new key group"); api(new RequestCreateNewKeyGroup(identityKey, Configuration.SUPPORTED, keys, signatures)).then(new Consumer<ResponseCreateNewKeyGroup>() { @Override public void apply(ResponseCreateNewKeyGroup response) { ownKeys = ownKeys.setGroupId(response.getKeyGroupId()); encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); onMainKeysReady(); } }).failure(new Consumer<Exception>() { @Override public void apply(Exception e) { Log.w(TAG, "Keys upload error"); Log.e(TAG, e); // Just ignore } }); } else { onMainKeysReady(); } } private void onMainKeysReady() { Log.d(TAG, "Main Keys are ready"); // // Generation required pre keys // int missingKeysCount = Math.max(0, Configuration.EPHEMERAL_KEYS_COUNT - ownKeys.getPreKeys().length); if (missingKeysCount > 0) { ownKeys = ownKeys.appendPreKeys( ManagedList.of(PrivateKey.GENERATOR, missingKeysCount) .toArray(new PrivateKey[0])); encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); } // // Uploading own pre keys // final ManagedList<PrivateKey> pendingEphermalKeys = ManagedList.of(ownKeys.getPreKeys()) .filter(PrivateKey.NOT_UPLOADED); if (pendingEphermalKeys.size() > 0) { ArrayList<ApiEncryptionKey> uploadingKeys = pendingEphermalKeys.map(PrivateKey.TO_API); ArrayList<ApiEncryptionKeySignature> uploadingSignatures = pendingEphermalKeys.map(PrivateKey.SIGN(ownKeys.getIdentityKey())); api(new RequestUploadPreKey(ownKeys.getKeyGroupId(), uploadingKeys, uploadingSignatures)) .then(new Consumer<ResponseVoid>() { @Override public void apply(ResponseVoid responseVoid) { ownKeys = ownKeys.markAsUploaded(pendingEphermalKeys.toArray(new PrivateKey[pendingEphermalKeys.size()])); encryptionKeysStorage.addOrUpdateItem(0, ownKeys.toByteArray()); onAllKeysReady(); } }) .failure(new Consumer<Exception>() { @Override public void apply(Exception e) { Log.w(TAG, "Ephemeral keys upload error"); Log.e(TAG, e); // Ignore. This will freeze all encryption operations. } }); } else { onAllKeysReady(); } } private void onAllKeysReady() { // // Finished starting key manager // Log.d(TAG, "Key Manager started with key group #" + ownKeys.getKeyGroupId()); isReady = true; unstashAll(); } // // Own Keys fetching // /** * Fetching Own Identity key and group id */ private Promise<OwnIdentity> fetchOwnIdentity() { return Promise.success(new OwnIdentity(ownKeys.getKeyGroupId(), ownKeys.getIdentityKey())); } /** * Fetching own private pre key by public key * * @param publicKey public key material for search */ private Promise<PrivateKey> fetchPreKey(byte[] publicKey) { try { return Promise.success(ManagedList.of(ownKeys.getPreKeys()) .filter(PrivateKey.PRE_KEY_EQUALS(publicKey)) .first()); } catch (Exception e) { Log.d(TAG, "Unable to find own pre key " + Crypto.keyHash(publicKey)); for (PrivateKey p : ownKeys.getPreKeys()) { Log.d(TAG, "Have: " + Crypto.keyHash(p.getPublicKey())); } throw e; } } /** * Fetching own pre key by id * * @param keyId pre key id */ private Promise<PrivateKey> fetchPreKey(long keyId) { try { return Promise.success(ManagedList.of(ownKeys.getPreKeys()) .filter(PrivateKey.PRE_KEY_EQUALS_ID(keyId)) .first()); } catch (Exception e) { Log.d(TAG, "Unable to find own pre key #" + keyId); throw e; } } /** * Fetching own random pre key */ private Promise<PrivateKey> fetchPreKey() { return PromisesArray.of(ownKeys.getPreKeys()) .random(); } // // User keys fetching // /** * Fetching all user's key groups * * @param uid User's id */ private Promise<UserKeys> fetchUserGroups(final int uid) { User user = users().getValue(uid); if (user == null) { throw new RuntimeException("Unable to find user #" + uid); } final UserKeys userKeys = getCachedUserKeys(uid); if (userKeys != null) { return Promise.success(userKeys); } return api(new RequestLoadPublicKeyGroups(new ApiUserOutPeer(uid, user.getAccessHash()))) .map(new Function<ResponsePublicKeyGroups, ArrayList<UserKeysGroup>>() { @Override public ArrayList<UserKeysGroup> apply(ResponsePublicKeyGroups response) { ArrayList<UserKeysGroup> keysGroups = new ArrayList<>(); for (ApiEncryptionKeyGroup keyGroup : response.getPublicKeyGroups()) { UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); if (validatedKeysGroup != null) { keysGroups.add(validatedKeysGroup); } } return keysGroups; } }) .map(new Function<ArrayList<UserKeysGroup>, UserKeys>() { @Override public UserKeys apply(ArrayList<UserKeysGroup> userKeysGroups) { UserKeys userKeys = new UserKeys(uid, userKeysGroups.toArray(new UserKeysGroup[userKeysGroups.size()])); cacheUserKeys(userKeys); return userKeys; } }); } /** * Fetching user's pre key by key id * * @param uid User's id * @param keyGroupId User's key group id * @param keyId Key id */ private Promise<PublicKey> fetchUserPreKey(final int uid, final int keyGroupId, final long keyId) { User user = users().getValue(uid); if (user == null) { throw new RuntimeException("Unable to find user #" + uid); } return pickUserGroup(uid, keyGroupId) .flatMap(new Function<Tuple2<UserKeysGroup, UserKeys>, Promise<PublicKey>>() { @Override public Promise<PublicKey> apply(final Tuple2<UserKeysGroup, UserKeys> keysGroup) { // // Searching in cache // for (PublicKey p : keysGroup.getT1().getEphemeralKeys()) { if (p.getKeyId() == keyId) { return Promise.success(p); } } // // Downloading pre key // ArrayList<Long> ids = new ArrayList<Long>(); ids.add(keyId); final UserKeysGroup finalKeysGroup = keysGroup.getT1(); return api(new RequestLoadPublicKey(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId, ids)) .map(new Function<ResponsePublicKeys, PublicKey>() { @Override public PublicKey apply(ResponsePublicKeys responsePublicKeys) { if (responsePublicKeys.getPublicKey().size() == 0) { throw new RuntimeException("Unable to find public key on server"); } ApiEncryptionKeySignature sig = null; for (ApiEncryptionKeySignature s : responsePublicKeys.getSignatures()) { if (s.getKeyId() == keyId && "Ed25519".equals(s.getSignatureAlg())) { sig = s; break; } } if (sig == null) { throw new RuntimeException("Unable to find public key on server"); } ApiEncryptionKey key = responsePublicKeys.getPublicKey().get(0); byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); if (!Curve25519.verifySignature(keysGroup.getT1().getIdentityKey().getPublicKey(), keyHash, sig.getSignature())) { throw new RuntimeException("Key signature does not isMatch"); } PublicKey pkey = new PublicKey(keyId, key.getKeyAlg(), key.getKeyMaterial()); UserKeysGroup userKeysGroup = finalKeysGroup.addPublicKey(pkey); cacheUserKeys(keysGroup.getT2().removeUserKeyGroup(userKeysGroup.getKeyGroupId()) .addUserKeyGroup(userKeysGroup)); return pkey; } }); } }); } /** * Fetching user's random pre key * * @param uid User's id * @param keyGroupId User's key group id */ private Promise<PublicKey> fetchUserPreKey(final int uid, final int keyGroupId) { return pickUserGroup(uid, keyGroupId) .flatMap(new Function<Tuple2<UserKeysGroup, UserKeys>, Promise<PublicKey>>() { @Override public Promise<PublicKey> apply(final Tuple2<UserKeysGroup, UserKeys> keyGroups) { return api(new RequestLoadPrePublicKeys(new ApiUserOutPeer(uid, getUser(uid).getAccessHash()), keyGroupId)) .map(new Function<ResponsePublicKeys, PublicKey>() { @Override public PublicKey apply(ResponsePublicKeys response) { if (response.getPublicKey().size() == 0) { throw new RuntimeException("User doesn't have pre keys"); } ApiEncryptionKey key = response.getPublicKey().get(0); ApiEncryptionKeySignature sig = null; for (ApiEncryptionKeySignature s : response.getSignatures()) { if (s.getKeyId() == key.getKeyId() && "Ed25519".equals(s.getSignatureAlg())) { sig = s; break; } } if (sig == null) { throw new RuntimeException("Unable to find public key on server"); } byte[] keyHash = RatchetKeySignature.hashForSignature(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); if (!Curve25519.verifySignature(keyGroups.getT1().getIdentityKey().getPublicKey(), keyHash, sig.getSignature())) { throw new RuntimeException("Key signature does not isMatch"); } return new PublicKey(key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); } }); } }); } // // Keys updates handling // /** * Handler for adding new key group * * @param uid User's id * @param keyGroup Added key group */ private void onPublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup keyGroup) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { return; } UserKeysGroup validatedKeysGroup = validateUserKeysGroup(uid, keyGroup); if (validatedKeysGroup != null) { UserKeys updatedUserKeys = userKeys.addUserKeyGroup(validatedKeysGroup); cacheUserKeys(updatedUserKeys); context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); } } /** * Handler for removing key group * * @param uid User's id * @param keyGroupId Removed key group id */ private void onPublicKeysGroupRemoved(int uid, int keyGroupId) { UserKeys userKeys = getCachedUserKeys(uid); if (userKeys == null) { return; } UserKeys updatedUserKeys = userKeys.removeUserKeyGroup(keyGroupId); cacheUserKeys(updatedUserKeys); context().getEncryption().getEncryptedChatManager(uid) .send(new EncryptedPeerActor.KeyGroupUpdated(userKeys)); } // // Helper methods // private UserKeysGroup validateUserKeysGroup(int uid, ApiEncryptionKeyGroup keyGroup) { if (!"curve25519".equals(keyGroup.getIdentityKey().getKeyAlg())) { // Anything other than curve25519 is not supported Log.w(TAG, "(uid:" + uid + ") Unsupported identity key alg " + keyGroup.getIdentityKey().getKeyAlg()); return null; } PublicKey identity = new PublicKey( keyGroup.getIdentityKey().getKeyId(), keyGroup.getIdentityKey().getKeyAlg(), keyGroup.getIdentityKey().getKeyMaterial()); ArrayList<PublicKey> keys = new ArrayList<PublicKey>(); key_loop: for (ApiEncryptionKey key : keyGroup.getKeys()) { // // Validating signatures // for (ApiEncryptionKeySignature sig : keyGroup.getSignatures()) { if (!sig.getSignatureAlg().equals("Ed25519")) { // Anything other than Ed25519 is not supported Log.w(TAG, "(uid:" + uid + ") Unsupported signature algorithm " + sig.getSignatureAlg()); continue; } if (sig.getKeyId() != key.getKeyId()) { continue; } byte[] keyForSign = RatchetKeySignature.hashForSignature( key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial()); if (!Curve25519.verifySignature(identity.getPublicKey(), keyForSign, sig.getSignature())) { Log.w(TAG, "(uid:" + uid + ") Unable to verify signature for " + Crypto.keyHash(key.getKeyMaterial()) + " key"); continue key_loop; } } // // Adding key to collection // keys.add(new PublicKey( key.getKeyId(), key.getKeyAlg(), key.getKeyMaterial())); } if (keys.size() == 0) { Log.w(TAG, "(uid:" + uid + ") No valid keys in key group #" + keyGroup.getKeyGroupId()); } return new UserKeysGroup(keyGroup.getKeyGroupId(), identity, keys.toArray(new PublicKey[keys.size()]), new PublicKey[0]); } private Promise<Tuple2<UserKeysGroup, UserKeys>> pickUserGroup(int uid, final int keyGroupId) { return fetchUserGroups(uid) .map(userKeys -> { UserKeysGroup keysGroup = null; // for (UserKeysGroup g : userKeys.getUserKeysGroups()) { // if (g.getKeyGroupId() == keyGroupId) { // keysGroup = g; // } // } // if (keysGroup == null) { // throw new RuntimeException("Key Group #" + keyGroupId + " not found"); // } // return new Tuple2<>(keysGroup, userKeys); return null; }); } private UserKeys getCachedUserKeys(int uid) { UserKeys userKeys = cachedUserKeys.get(uid); if (userKeys == null) { byte[] cached = encryptionKeysStorage.loadItem(uid); if (cached != null) { try { userKeys = new UserKeys(cached); } catch (IOException e) { e.printStackTrace(); } } } return userKeys; } private void cacheUserKeys(UserKeys userKeys) { encryptionKeysStorage.addOrUpdateItem(userKeys.getUid(), userKeys.toByteArray()); cachedUserKeys.put(userKeys.getUid(), userKeys); } // // Messages // @Override public void onReceive(Object message) { if (!isReady && (message instanceof AskIntRequest || message instanceof PublicKeysGroupAdded || message instanceof PublicKeysGroupRemoved)) { stash(); return; } if (message instanceof PublicKeysGroupAdded) { PublicKeysGroupAdded publicKeysGroupAdded = (PublicKeysGroupAdded) message; onPublicKeysGroupAdded(publicKeysGroupAdded.getUid(), publicKeysGroupAdded.getPublicKeyGroup()); } else if (message instanceof PublicKeysGroupRemoved) { PublicKeysGroupRemoved publicKeysGroupRemoved = (PublicKeysGroupRemoved) message; onPublicKeysGroupRemoved(publicKeysGroupRemoved.getUid(), publicKeysGroupRemoved.getKeyGroupId()); } else { super.onReceive(message); } } @Override public Promise onAsk(Object message) throws Exception { if (message instanceof FetchOwnKey) { return fetchOwnIdentity(); } else if (message instanceof FetchOwnPreKeyByPublic) { return fetchPreKey(((FetchOwnPreKeyByPublic) message).getPublicKey()); } else if (message instanceof FetchOwnPreKeyById) { return fetchPreKey(((FetchOwnPreKeyById) message).getKeyId()); } else if (message instanceof FetchUserKeys) { return fetchUserGroups(((FetchUserKeys) message).getUid()); } else if (message instanceof FetchUserPreKey) { return fetchUserPreKey(((FetchUserPreKey) message).getUid(), ((FetchUserPreKey) message).getKeyGroup(), ((FetchUserPreKey) message).getKeyId()); } else if (message instanceof FetchUserPreKeyRandom) { return fetchUserPreKey(((FetchUserPreKeyRandom) message).getUid(), ((FetchUserPreKeyRandom) message).getKeyGroup()); } else if (message instanceof FetchOwnRandomPreKey) { return fetchPreKey(); } else { return super.onAsk(message); } } // // Own Keys // public static class FetchOwnKey implements AskMessage<OwnIdentity> { } public static class OwnIdentity extends AskResult { private int keyGroup; private PrivateKey identityKey; public OwnIdentity(int keyGroup, PrivateKey identityKey) { this.keyGroup = keyGroup; this.identityKey = identityKey; } public int getKeyGroup() { return keyGroup; } public PrivateKey getIdentityKey() { return identityKey; } } public static class FetchOwnRandomPreKey implements AskMessage<PrivateKey> { } public static class FetchOwnPreKeyByPublic implements AskMessage<PrivateKey> { private byte[] publicKey; public FetchOwnPreKeyByPublic(byte[] publicKey) { this.publicKey = publicKey; } public byte[] getPublicKey() { return publicKey; } } public static class FetchOwnPreKeyById implements AskMessage<PrivateKey> { private long keyId; public FetchOwnPreKeyById(long keyId) { this.keyId = keyId; } public long getKeyId() { return keyId; } } // // Users Keys // public static class FetchUserKeys implements AskMessage<UserKeys> { private int uid; public FetchUserKeys(int uid) { this.uid = uid; } public int getUid() { return uid; } } public static class FetchUserPreKey implements AskMessage<PublicKey> { private int uid; private int keyGroup; private long keyId; public FetchUserPreKey(int uid, int keyGroup, long keyId) { this.keyGroup = keyGroup; this.uid = uid; this.keyId = keyId; } public int getUid() { return uid; } public long getKeyId() { return keyId; } public int getKeyGroup() { return keyGroup; } } public static class FetchUserPreKeyRandom implements AskMessage<PublicKey> { private int uid; private int keyGroup; public FetchUserPreKeyRandom(int uid, int keyGroup) { this.keyGroup = keyGroup; this.uid = uid; } public int getUid() { return uid; } public int getKeyGroup() { return keyGroup; } } // // Updates handling // public static class PublicKeysGroupAdded { private int uid; private ApiEncryptionKeyGroup publicKeyGroup; public PublicKeysGroupAdded(int uid, ApiEncryptionKeyGroup publicKeyGroup) { this.uid = uid; this.publicKeyGroup = publicKeyGroup; } public int getUid() { return uid; } public ApiEncryptionKeyGroup getPublicKeyGroup() { return publicKeyGroup; } } public static class PublicKeysGroupRemoved { private int uid; private int keyGroupId; public PublicKeysGroupRemoved(int uid, int keyGroupId) { this.uid = uid; this.keyGroupId = keyGroupId; } public int getUid() { return uid; } public int getKeyGroupId() { return keyGroupId; } } }