*/
package org.asamk.signal.manager;
-import org.apache.http.util.TextUtils;
import org.asamk.Signal;
import org.asamk.signal.*;
import org.asamk.signal.storage.SignalAccount;
import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.*;
-import org.signal.libsignal.metadata.certificate.CertificateValidator;
import org.whispersystems.libsignal.*;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.fingerprint.Fingerprint;
-import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.KeyHelper;
import org.whispersystems.signalservice.api.push.exceptions.NetworkFailureException;
import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
-import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
import org.whispersystems.signalservice.api.util.SleepTimer;
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
import org.whispersystems.signalservice.internal.push.SignalServiceProtos;
import java.io.*;
import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
}
- private static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
- List<SignalServiceAttachment> SignalServiceAttachments = null;
- if (attachments != null) {
- SignalServiceAttachments = new ArrayList<>(attachments.size());
- for (String attachment : attachments) {
- try {
- SignalServiceAttachments.add(createAttachment(new File(attachment)));
- } catch (IOException e) {
- throw new AttachmentInvalidException(attachment, e);
- }
- }
- }
- return SignalServiceAttachments;
- }
-
- private static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
- InputStream attachmentStream = new FileInputStream(attachmentFile);
- final long attachmentSize = attachmentFile.length();
- String mime = Files.probeContentType(attachmentFile.toPath());
- if (mime == null) {
- mime = "application/octet-stream";
- }
- // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
- Optional<byte[]> preview = Optional.absent();
- Optional<String> caption = Optional.absent();
- return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
- }
-
- private static CertificateValidator getCertificateValidator() {
- try {
- ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
- return new CertificateValidator(unidentifiedSenderTrustRoot);
- } catch (InvalidKeyException | IOException e) {
- throw new AssertionError(e);
- }
- }
-
public String getUsername() {
return username;
}
}
public boolean userHasKeys() {
- return account.getSignalProtocolStore() != null;
+ return account != null && account.getSignalProtocolStore() != null;
}
public void init() throws IOException {
accountManager.setGcmId(Optional.<String>absent());
}
- public URI getDeviceLinkUri() throws TimeoutException, IOException {
+ public String getDeviceLinkUri() throws TimeoutException, IOException {
if (account == null) {
createNewIdentity();
}
accountManager = new SignalServiceAccountManager(BaseConfig.serviceConfiguration, username, account.getPassword(), BaseConfig.USER_AGENT, timer);
String uuid = accountManager.getNewDeviceUuid();
- try {
- return new URI("tsdevice:/?uuid=" + URLEncoder.encode(uuid, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(getIdentity().serialize()), "utf-8"));
- } catch (URISyntaxException e) {
- // Shouldn't happen
- return null;
- }
+ return Utils.createDeviceLinkUri(new Utils.DeviceLinkInfo(uuid, getIdentity().getPublicKey()));
}
public void finishDeviceLink(String deviceName) throws IOException, InvalidKeyException, TimeoutException, UserAlreadyExists {
requestSyncGroups();
requestSyncContacts();
+ requestSyncBlocked();
+ requestSyncConfiguration();
account.save();
}
}
public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
- Map<String, String> query = Util.getQueryMap(linkUri.getRawQuery());
- String deviceIdentifier = query.get("uuid");
- String publicKeyEncoded = query.get("pub_key");
+ Utils.DeviceLinkInfo info = Utils.parseDeviceLinkUri(linkUri);
- if (TextUtils.isEmpty(deviceIdentifier) || TextUtils.isEmpty(publicKeyEncoded)) {
- throw new RuntimeException("Invalid device link uri");
- }
-
- ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
-
- addDevice(deviceIdentifier, deviceKey);
+ addDevice(info.deviceIdentifier, info.deviceKey);
}
private void addDevice(String deviceIdentifier, ECPublicKey deviceKey) throws IOException, InvalidKeyException {
public void verifyAccount(String verificationCode, String pin) throws IOException {
verificationCode = verificationCode.replace("-", "");
account.setSignalingKey(KeyUtils.createSignalingKey());
+ // TODO make unrestricted unidentified access configurable
accountManager.verifyAccountWithCode(verificationCode, account.getSignalingKey(), account.getSignalProtocolStore().getLocalRegistrationId(), true, pin, getSelfUnidentifiedAccessKey(), false);
//accountManager.setGcmId(Optional.of(GoogleCloudMessaging.getInstance(this).register(REGISTRATION_ID)));
} else {
account.setRegistrationLockPin(null);
}
+ account.save();
}
private void refreshPreKeys() throws IOException {
return Optional.absent();
}
- return Optional.of(createAttachment(file));
+ return Optional.of(Utils.createAttachment(file));
}
private Optional<SignalServiceAttachmentStream> createContactAvatarAttachment(String number) throws IOException {
return Optional.absent();
}
- return Optional.of(createAttachment(file));
+ return Optional.of(Utils.createAttachment(file));
}
private GroupInfo getGroupForSending(byte[] groupId) throws GroupNotFoundException, NotAGroupMemberException {
throws IOException, EncapsulatedExceptions, GroupNotFoundException, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) {
- messageBuilder.withAttachments(getSignalServiceAttachments(attachments));
+ messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
}
if (groupId != null) {
SignalServiceGroup group = SignalServiceGroup.newBuilder(SignalServiceGroup.Type.DELIVER)
Set<String> newMembers = new HashSet<>();
for (String member : members) {
try {
- member = canonicalizeNumber(member);
+ member = Utils.canonicalizeNumber(member, username);
} catch (InvalidNumberException e) {
System.err.println("Failed to add member \"" + member + "\" to group: " + e.getMessage());
System.err.println("Aborting…");
File aFile = getGroupAvatarFile(g.groupId);
if (aFile.exists()) {
try {
- group.withAvatar(createAttachment(aFile));
+ group.withAvatar(Utils.createAttachment(aFile));
} catch (IOException e) {
throw new AttachmentInvalidException(aFile.toString(), e);
}
throws IOException, EncapsulatedExceptions, AttachmentInvalidException {
final SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder().withBody(messageText);
if (attachments != null) {
- messageBuilder.withAttachments(getSignalServiceAttachments(attachments));
+ messageBuilder.withAttachments(Utils.getSignalServiceAttachments(attachments));
}
sendMessageLegacy(messageBuilder, recipients);
}
private List<SendMessageResult> sendMessage(SignalServiceDataMessage.Builder messageBuilder, Collection<String> recipients)
throws IOException {
- Set<SignalServiceAddress> recipientsTS = getSignalServiceAddresses(recipients);
- if (recipientsTS == null) return Collections.emptyList();
+ Set<SignalServiceAddress> recipientsTS = Utils.getSignalServiceAddresses(recipients, username);
+ if (recipientsTS == null) {
+ account.save();
+ return Collections.emptyList();
+ }
SignalServiceDataMessage message = null;
try {
}
}
- private Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients) {
- Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
- for (String recipient : recipients) {
- try {
- recipientsTS.add(getPushAddress(recipient));
- } catch (InvalidNumberException e) {
- System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
- System.err.println("Aborting sending.");
- account.save();
- return null;
- }
- }
- return recipientsTS;
- }
-
private SignalServiceContent decryptMessage(SignalServiceEnvelope envelope) throws InvalidMetadataMessageException, ProtocolInvalidMessageException, ProtocolDuplicateMessageException, ProtocolLegacyMessageException, ProtocolInvalidKeyIdException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolNoSessionException, ProtocolInvalidKeyException, ProtocolUntrustedIdentityException, SelfSendException {
- SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), account.getSignalProtocolStore(), getCertificateValidator());
+ SignalServiceCipher cipher = new SignalServiceCipher(new SignalServiceAddress(username), account.getSignalProtocolStore(), Utils.getCertificateValidator());
try {
return cipher.decrypt(envelope);
} catch (ProtocolUntrustedIdentityException e) {
}
}
if (message.getProfileKey().isPresent() && message.getProfileKey().get().length == 32) {
+ if (source.equals(username)) {
+ this.account.setProfileKey(message.getProfileKey().get());
+ }
ContactInfo contact = account.getContactStore().getContact(source);
if (contact == null) {
contact = new ContactInfo();
}
SignalServiceEnvelope envelope;
try {
- envelope = loadEnvelope(fileEntry);
+ envelope = Utils.loadEnvelope(fileEntry);
if (envelope == null) {
continue;
}
// store message on disk, before acknowledging receipt to the server
try {
File cacheFile = getMessageCacheFile(envelope.getSource(), now, envelope.getTimestamp());
- storeEnvelope(envelope, cacheFile);
+ Utils.storeEnvelope(envelope, cacheFile);
} catch (IOException e) {
System.err.println("Failed to store encrypted message in disk cache, ignoring: " + e.getMessage());
}
}
}
- private SignalServiceEnvelope loadEnvelope(File file) throws IOException {
- try (FileInputStream f = new FileInputStream(file)) {
- DataInputStream in = new DataInputStream(f);
- int version = in.readInt();
- if (version > 2) {
- return null;
- }
- int type = in.readInt();
- String source = in.readUTF();
- int sourceDevice = in.readInt();
- if (version == 1) {
- // read legacy relay field
- in.readUTF();
- }
- long timestamp = in.readLong();
- byte[] content = null;
- int contentLen = in.readInt();
- if (contentLen > 0) {
- content = new byte[contentLen];
- in.readFully(content);
- }
- byte[] legacyMessage = null;
- int legacyMessageLen = in.readInt();
- if (legacyMessageLen > 0) {
- legacyMessage = new byte[legacyMessageLen];
- in.readFully(legacyMessage);
- }
- long serverTimestamp = 0;
- String uuid = null;
- if (version == 2) {
- serverTimestamp = in.readLong();
- uuid = in.readUTF();
- if ("".equals(uuid)) {
- uuid = null;
- }
- }
- return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
- }
- }
-
- private void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
- try (FileOutputStream f = new FileOutputStream(file)) {
- try (DataOutputStream out = new DataOutputStream(f)) {
- out.writeInt(2); // version
- out.writeInt(envelope.getType());
- out.writeUTF(envelope.getSource());
- out.writeInt(envelope.getSourceDevice());
- out.writeLong(envelope.getTimestamp());
- if (envelope.hasContent()) {
- out.writeInt(envelope.getContent().length);
- out.write(envelope.getContent());
- } else {
- out.writeInt(0);
- }
- if (envelope.hasLegacyMessage()) {
- out.writeInt(envelope.getLegacyMessage().length);
- out.write(envelope.getLegacyMessage());
- } else {
- out.writeInt(0);
- }
- out.writeLong(envelope.getServerTimestamp());
- String uuid = envelope.getUuid();
- out.writeUTF(uuid == null ? "" : uuid);
- }
- }
- }
-
private File getContactAvatarFile(String number) {
return new File(avatarsPath, "contact-" + number);
}
return retrieveAttachment(pointer, getContactAvatarFile(number), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return retrieveAttachment(stream, getContactAvatarFile(number));
+ return Utils.retrieveAttachment(stream, getContactAvatarFile(number));
}
}
return retrieveAttachment(pointer, getGroupAvatarFile(groupId), false);
} else {
SignalServiceAttachmentStream stream = attachment.asStream();
- return retrieveAttachment(stream, getGroupAvatarFile(groupId));
+ return Utils.retrieveAttachment(stream, getGroupAvatarFile(groupId));
}
}
return retrieveAttachment(pointer, getAttachmentFile(pointer.getId()), true);
}
- private File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
- InputStream input = stream.getInputStream();
-
- try (OutputStream output = new FileOutputStream(outputFile)) {
- byte[] buffer = new byte[4096];
- int read;
-
- while ((read = input.read(buffer)) != -1) {
- output.write(buffer, 0, read);
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- return null;
- }
- return outputFile;
- }
-
private File retrieveAttachment(SignalServiceAttachmentPointer pointer, File outputFile, boolean storePreview) throws IOException, InvalidMessageException {
if (storePreview && pointer.getPreview().isPresent()) {
File previewFile = new File(outputFile + ".preview");
return messageReceiver.retrieveAttachment(pointer, tmpFile, BaseConfig.MAX_ATTACHMENT_SIZE);
}
- private String canonicalizeNumber(String number) throws InvalidNumberException {
- String localNumber = username;
- return PhoneNumberFormatter.formatNumber(number, localNumber);
- }
-
- private SignalServiceAddress getPushAddress(String number) throws InvalidNumberException {
- String e164number = canonicalizeNumber(number);
- return new SignalServiceAddress(e164number);
- }
-
@Override
public boolean isRemote() {
return false;
}
public String computeSafetyNumber(String theirUsername, IdentityKey theirIdentityKey) {
- Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(username, getIdentity(), theirUsername, theirIdentityKey);
- return fingerprint.getDisplayableFingerprint().getDisplayText();
+ return Utils.computeSafetyNumber(username, getIdentity(), theirUsername, theirIdentityKey);
}
public interface ReceiveMessageHandler {
--- /dev/null
+package org.asamk.signal.manager;
+
+import org.apache.http.util.TextUtils;
+import org.asamk.signal.AttachmentInvalidException;
+import org.signal.libsignal.metadata.certificate.CertificateValidator;
+import org.whispersystems.libsignal.IdentityKey;
+import org.whispersystems.libsignal.InvalidKeyException;
+import org.whispersystems.libsignal.ecc.Curve;
+import org.whispersystems.libsignal.ecc.ECPublicKey;
+import org.whispersystems.libsignal.fingerprint.Fingerprint;
+import org.whispersystems.libsignal.fingerprint.NumericFingerprintGenerator;
+import org.whispersystems.libsignal.util.guava.Optional;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachment;
+import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentStream;
+import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.InvalidNumberException;
+import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
+import org.whispersystems.signalservice.internal.util.Base64;
+
+import java.io.*;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.util.*;
+
+class Utils {
+
+ static List<SignalServiceAttachment> getSignalServiceAttachments(List<String> attachments) throws AttachmentInvalidException {
+ List<SignalServiceAttachment> SignalServiceAttachments = null;
+ if (attachments != null) {
+ SignalServiceAttachments = new ArrayList<>(attachments.size());
+ for (String attachment : attachments) {
+ try {
+ SignalServiceAttachments.add(createAttachment(new File(attachment)));
+ } catch (IOException e) {
+ throw new AttachmentInvalidException(attachment, e);
+ }
+ }
+ }
+ return SignalServiceAttachments;
+ }
+
+ static SignalServiceAttachmentStream createAttachment(File attachmentFile) throws IOException {
+ InputStream attachmentStream = new FileInputStream(attachmentFile);
+ final long attachmentSize = attachmentFile.length();
+ String mime = Files.probeContentType(attachmentFile.toPath());
+ if (mime == null) {
+ mime = "application/octet-stream";
+ }
+ // TODO mabybe add a parameter to set the voiceNote, preview, width, height and caption option
+ Optional<byte[]> preview = Optional.absent();
+ Optional<String> caption = Optional.absent();
+ return new SignalServiceAttachmentStream(attachmentStream, mime, attachmentSize, Optional.of(attachmentFile.getName()), false, preview, 0, 0, caption, null);
+ }
+
+ static CertificateValidator getCertificateValidator() {
+ try {
+ ECPublicKey unidentifiedSenderTrustRoot = Curve.decodePoint(Base64.decode(BaseConfig.UNIDENTIFIED_SENDER_TRUST_ROOT), 0);
+ return new CertificateValidator(unidentifiedSenderTrustRoot);
+ } catch (InvalidKeyException | IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private static Map<String, String> getQueryMap(String query) {
+ String[] params = query.split("&");
+ Map<String, String> map = new HashMap<>();
+ for (String param : params) {
+ String name = null;
+ final String[] paramParts = param.split("=");
+ try {
+ name = URLDecoder.decode(paramParts[0], "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // Impossible
+ }
+ String value = null;
+ try {
+ value = URLDecoder.decode(paramParts[1], "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // Impossible
+ }
+ map.put(name, value);
+ }
+ return map;
+ }
+
+ static String createDeviceLinkUri(DeviceLinkInfo info) {
+ try {
+ return "tsdevice:/?uuid=" + URLEncoder.encode(info.deviceIdentifier, "utf-8") + "&pub_key=" + URLEncoder.encode(Base64.encodeBytesWithoutPadding(info.deviceKey.serialize()), "utf-8");
+ } catch (UnsupportedEncodingException e) {
+ // Shouldn't happen
+ return null;
+ }
+ }
+
+ static DeviceLinkInfo parseDeviceLinkUri(URI linkUri) throws IOException, InvalidKeyException {
+ Map<String, String> query = getQueryMap(linkUri.getRawQuery());
+ String deviceIdentifier = query.get("uuid");
+ String publicKeyEncoded = query.get("pub_key");
+
+ if (TextUtils.isEmpty(deviceIdentifier) || TextUtils.isEmpty(publicKeyEncoded)) {
+ throw new RuntimeException("Invalid device link uri");
+ }
+
+ ECPublicKey deviceKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
+
+ return new DeviceLinkInfo(deviceIdentifier, deviceKey);
+ }
+
+ static Set<SignalServiceAddress> getSignalServiceAddresses(Collection<String> recipients, String localNumber) {
+ Set<SignalServiceAddress> recipientsTS = new HashSet<>(recipients.size());
+ for (String recipient : recipients) {
+ try {
+ recipientsTS.add(getPushAddress(recipient, localNumber));
+ } catch (InvalidNumberException e) {
+ System.err.println("Failed to add recipient \"" + recipient + "\": " + e.getMessage());
+ System.err.println("Aborting sending.");
+ return null;
+ }
+ }
+ return recipientsTS;
+ }
+
+ static String canonicalizeNumber(String number, String localNumber) throws InvalidNumberException {
+ return PhoneNumberFormatter.formatNumber(number, localNumber);
+ }
+
+ private static SignalServiceAddress getPushAddress(String number, String localNumber) throws InvalidNumberException {
+ String e164number = canonicalizeNumber(number, localNumber);
+ return new SignalServiceAddress(e164number);
+ }
+
+ static SignalServiceEnvelope loadEnvelope(File file) throws IOException {
+ try (FileInputStream f = new FileInputStream(file)) {
+ DataInputStream in = new DataInputStream(f);
+ int version = in.readInt();
+ if (version > 2) {
+ return null;
+ }
+ int type = in.readInt();
+ String source = in.readUTF();
+ int sourceDevice = in.readInt();
+ if (version == 1) {
+ // read legacy relay field
+ in.readUTF();
+ }
+ long timestamp = in.readLong();
+ byte[] content = null;
+ int contentLen = in.readInt();
+ if (contentLen > 0) {
+ content = new byte[contentLen];
+ in.readFully(content);
+ }
+ byte[] legacyMessage = null;
+ int legacyMessageLen = in.readInt();
+ if (legacyMessageLen > 0) {
+ legacyMessage = new byte[legacyMessageLen];
+ in.readFully(legacyMessage);
+ }
+ long serverTimestamp = 0;
+ String uuid = null;
+ if (version == 2) {
+ serverTimestamp = in.readLong();
+ uuid = in.readUTF();
+ if ("".equals(uuid)) {
+ uuid = null;
+ }
+ }
+ return new SignalServiceEnvelope(type, source, sourceDevice, timestamp, legacyMessage, content, serverTimestamp, uuid);
+ }
+ }
+
+ static void storeEnvelope(SignalServiceEnvelope envelope, File file) throws IOException {
+ try (FileOutputStream f = new FileOutputStream(file)) {
+ try (DataOutputStream out = new DataOutputStream(f)) {
+ out.writeInt(2); // version
+ out.writeInt(envelope.getType());
+ out.writeUTF(envelope.getSource());
+ out.writeInt(envelope.getSourceDevice());
+ out.writeLong(envelope.getTimestamp());
+ if (envelope.hasContent()) {
+ out.writeInt(envelope.getContent().length);
+ out.write(envelope.getContent());
+ } else {
+ out.writeInt(0);
+ }
+ if (envelope.hasLegacyMessage()) {
+ out.writeInt(envelope.getLegacyMessage().length);
+ out.write(envelope.getLegacyMessage());
+ } else {
+ out.writeInt(0);
+ }
+ out.writeLong(envelope.getServerTimestamp());
+ String uuid = envelope.getUuid();
+ out.writeUTF(uuid == null ? "" : uuid);
+ }
+ }
+ }
+
+ static File retrieveAttachment(SignalServiceAttachmentStream stream, File outputFile) throws IOException {
+ InputStream input = stream.getInputStream();
+
+ try (OutputStream output = new FileOutputStream(outputFile)) {
+ byte[] buffer = new byte[4096];
+ int read;
+
+ while ((read = input.read(buffer)) != -1) {
+ output.write(buffer, 0, read);
+ }
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return outputFile;
+ }
+
+ static String computeSafetyNumber(String ownUsername, IdentityKey ownIdentityKey, String theirUsername, IdentityKey theirIdentityKey) {
+ Fingerprint fingerprint = new NumericFingerprintGenerator(5200).createFor(ownUsername, ownIdentityKey, theirUsername, theirIdentityKey);
+ return fingerprint.getDisplayableFingerprint().getDisplayText();
+ }
+
+ static class DeviceLinkInfo {
+
+ String deviceIdentifier;
+ ECPublicKey deviceKey;
+
+ DeviceLinkInfo(final String deviceIdentifier, final ECPublicKey deviceKey) {
+ this.deviceIdentifier = deviceIdentifier;
+ this.deviceKey = deviceKey;
+ }
+ }
+}