Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds some of the first steps towards implementing identities: * A type system on top of UnknownMessage * IdentityAnnouncementMessage for announcing identities * libsignal compatibility stubs * Initial onboarding UI to create an identity A few things are known to be broken: * libsignal integration is not fully implemented and has not been tested * Onboarding UI does not close once the identity has been created * Onboarding UI does not prevent creating a username that conflicts with an existing one
- Loading branch information
Showing
28 changed files
with
919 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
app/src/main/java/com/alternativeinfrastructures/noise/models/LocalIdentity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package com.alternativeinfrastructures.noise.models; | ||
|
||
import com.alternativeinfrastructures.noise.NoiseDatabase; | ||
import com.alternativeinfrastructures.noise.models.signal.TypeConverters; | ||
import com.alternativeinfrastructures.noise.storage.IdentityAnnouncementMessage; | ||
import com.alternativeinfrastructures.noise.storage.UnknownMessage; | ||
import com.raizlabs.android.dbflow.annotation.Column; | ||
import com.raizlabs.android.dbflow.annotation.ForeignKey; | ||
import com.raizlabs.android.dbflow.annotation.Index; | ||
import com.raizlabs.android.dbflow.annotation.PrimaryKey; | ||
import com.raizlabs.android.dbflow.annotation.Table; | ||
import com.raizlabs.android.dbflow.rx2.structure.BaseRXModel; | ||
|
||
import org.whispersystems.libsignal.IdentityKey; | ||
import org.whispersystems.libsignal.IdentityKeyPair; | ||
import org.whispersystems.libsignal.InvalidKeyException; | ||
import org.whispersystems.libsignal.state.SignedPreKeyRecord; | ||
import org.whispersystems.libsignal.util.KeyHelper; | ||
|
||
import java.io.IOException; | ||
|
||
import io.reactivex.Single; | ||
|
||
@Table(database = NoiseDatabase.class) | ||
public class LocalIdentity extends BaseRXModel { | ||
public static boolean validUsername(String username) { | ||
// TODO: Other constraints? | ||
return username != null && username.length() >= 5 && username.length() <= 31; | ||
} | ||
|
||
public static Single<Boolean> createNew(String username) throws IOException, InvalidKeyException, UnknownMessage.PayloadTooLargeException { | ||
LocalIdentity identity = new LocalIdentity(); | ||
identity.username = username; | ||
identity.registrationId = KeyHelper.generateRegistrationId(true /*extendedRange*/); | ||
identity.identityKeyPair = KeyHelper.generateIdentityKeyPair(); | ||
// TODO: Once there's support for multiple identities, increment the signedPreKeyId | ||
identity.signedPreKey = KeyHelper.generateSignedPreKey( | ||
identity.identityKeyPair, 0 /*signedPreKeyId*/); | ||
identity.remoteIdentity = identity.createRemoteIdentity(); | ||
|
||
byte zeroBits = 20; // XXX | ||
|
||
return IdentityAnnouncementMessage.createAndSignAsync(identity.remoteIdentity, zeroBits) | ||
.flatMap((UnknownMessage) -> identity.save()); | ||
} | ||
|
||
private RemoteIdentity createRemoteIdentity() { | ||
// TODO: Is using the registrationId as the deviceId okay? | ||
return new RemoteIdentity(username, registrationId, identityKeyPair.getPublicKey()); | ||
} | ||
|
||
@Index | ||
protected String username; | ||
public String getUsername() { return username; } | ||
|
||
protected int registrationId; | ||
public int getRegistrationId() { return registrationId; } | ||
|
||
@PrimaryKey | ||
@Column(typeConverter = TypeConverters.IdentityKeyPairConverter.class) | ||
protected IdentityKeyPair identityKeyPair; | ||
public IdentityKeyPair getIdentityKeyPair() { return identityKeyPair; } | ||
|
||
@Column(typeConverter = TypeConverters.SignedPreKeyRecordConverter.class) | ||
protected SignedPreKeyRecord signedPreKey; | ||
|
||
@ForeignKey | ||
protected RemoteIdentity remoteIdentity; | ||
public RemoteIdentity getRemoteIdentity() { return remoteIdentity; } | ||
} |
64 changes: 64 additions & 0 deletions
64
app/src/main/java/com/alternativeinfrastructures/noise/models/RemoteIdentity.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
package com.alternativeinfrastructures.noise.models; | ||
|
||
import com.alternativeinfrastructures.noise.NoiseDatabase; | ||
import com.alternativeinfrastructures.noise.models.signal.TypeConverters; | ||
import com.alternativeinfrastructures.noise.storage.IdentityAnnouncementMessage; | ||
import com.alternativeinfrastructures.noise.storage.UnknownMessage; | ||
import com.raizlabs.android.dbflow.annotation.Column; | ||
import com.raizlabs.android.dbflow.annotation.Index; | ||
import com.raizlabs.android.dbflow.annotation.PrimaryKey; | ||
import com.raizlabs.android.dbflow.annotation.Table; | ||
import com.raizlabs.android.dbflow.rx2.structure.BaseRXModel; | ||
|
||
import org.whispersystems.libsignal.IdentityKey; | ||
|
||
import java.io.IOException; | ||
|
||
import io.reactivex.Single; | ||
|
||
@Table(database = NoiseDatabase.class) | ||
public class RemoteIdentity extends BaseRXModel { | ||
// public static Single<RemoteIdentity> createIdentityAndAnnouncementAsync( | ||
// String username, int deviceId, IdentityKey identityKey) throws UnknownMessage.PayloadTooLargeException, IOException { | ||
// RemoteIdentity identity = new RemoteIdentity(); | ||
// identity.username = username; | ||
// identity.deviceId = deviceId; | ||
// identity.identityKey = identityKey; | ||
// return IdentityAnnouncementMessage.createAndSignAsync(identity) | ||
// .map((UnknownMessage message) -> identity); | ||
// } | ||
|
||
public RemoteIdentity(String username, int deviceId, IdentityKey identityKey) { | ||
this.username = username; | ||
this.deviceId = deviceId; | ||
this.identityKey = identityKey; | ||
} | ||
|
||
public RemoteIdentity() {} | ||
|
||
@Override | ||
public boolean equals(Object object) { | ||
RemoteIdentity other; | ||
if (!(object instanceof RemoteIdentity)) | ||
return false; | ||
|
||
other = (RemoteIdentity) object; | ||
return (username.equals(other.username) && | ||
deviceId == other.deviceId && | ||
identityKey.equals(other.identityKey)); | ||
} | ||
|
||
@Column | ||
@Index | ||
protected String username; | ||
public String getUsername() { return username; } | ||
|
||
@Column | ||
protected int deviceId; | ||
public int getDeviceId() { return deviceId; } | ||
|
||
@PrimaryKey | ||
@Column(typeConverter = TypeConverters.IdentityKeyConverter.class) | ||
protected IdentityKey identityKey; | ||
public IdentityKey getIdentityKey() { return identityKey; } | ||
} |
25 changes: 25 additions & 0 deletions
25
app/src/main/java/com/alternativeinfrastructures/noise/models/signal/DummyPreKeyStore.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.alternativeinfrastructures.noise.models.signal; | ||
|
||
import org.whispersystems.libsignal.state.PreKeyRecord; | ||
import org.whispersystems.libsignal.state.PreKeyStore; | ||
|
||
// Ensuring prekeys are used exactly once is nontrivial without a central service. Fortunately, they're optional. | ||
public class DummyPreKeyStore implements PreKeyStore { | ||
@Override | ||
public PreKeyRecord loadPreKey(int preKeyId) { | ||
return null; | ||
} | ||
|
||
@Override | ||
public void storePreKey(int preKeyId, PreKeyRecord record) { | ||
} | ||
|
||
@Override | ||
public boolean containsPreKey(int preKeyId) { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void removePreKey(int preKeyId) { | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
...c/main/java/com/alternativeinfrastructures/noise/models/signal/NoiseIdentityKeyStore.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.alternativeinfrastructures.noise.models.signal; | ||
|
||
import com.alternativeinfrastructures.noise.models.LocalIdentity; | ||
import com.alternativeinfrastructures.noise.models.RemoteIdentity; | ||
import com.alternativeinfrastructures.noise.models.RemoteIdentity_Table; | ||
import com.raizlabs.android.dbflow.sql.language.SQLite; | ||
|
||
import org.whispersystems.libsignal.IdentityKey; | ||
import org.whispersystems.libsignal.IdentityKeyPair; | ||
import org.whispersystems.libsignal.SignalProtocolAddress; | ||
import org.whispersystems.libsignal.state.IdentityKeyStore; | ||
|
||
public class NoiseIdentityKeyStore implements IdentityKeyStore { | ||
// TODO: Cache the LocalIdentity | ||
// Constructor grabs a copy of the Identity from the database | ||
// Register for changes to the Identity | ||
// This way we can avoid querying the database every time | ||
|
||
// TODO: Support multiple local identities? | ||
// Constructor can accept arguments to pick one | ||
|
||
// TODO: Use Rx | ||
|
||
// TODO: Use SQLCipher or similar to avoid putting private keys in the database in cleartext | ||
|
||
@Override | ||
public IdentityKeyPair getIdentityKeyPair() { | ||
LocalIdentity identity = SQLite.select().from(LocalIdentity.class).querySingle(); | ||
return identity == null ? null : identity.getIdentityKeyPair(); | ||
} | ||
|
||
@Override | ||
public int getLocalRegistrationId() { | ||
LocalIdentity identity = SQLite.select().from(LocalIdentity.class).querySingle(); | ||
return identity == null ? null : identity.getRegistrationId(); | ||
} | ||
|
||
@Override | ||
public void saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) { | ||
// RemoteIdentity identity = new RemoteIdentity(address.getName(), address.getDeviceId(), | ||
// identityKey); | ||
// identity.save().subscribe(); | ||
// TODO: Do we actually need this? Identity discovery should happen automatically via IdentityAnnouncementMessage | ||
// Once we have something running, see who calls this | ||
// TODO: Identity lifetime management | ||
// At what point is it okay to update an identity? | ||
} | ||
|
||
@Override | ||
public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey) { | ||
RemoteIdentity identity = SQLite.select().from(RemoteIdentity.class).where( | ||
RemoteIdentity_Table.username.eq(address.getName()), | ||
RemoteIdentity_Table.deviceId.eq(address.getDeviceId())).querySingle(); | ||
return identity != null && identityKey.equals(identity.getIdentityKey()); | ||
} | ||
} |
Oops, something went wrong.