Remove identifiers from ScannableFingerprint
Although it helps to eliminate confusion when people inadvertantly scan the wrong code, people might share the QR code image publicly and inadvertantly publish their identifier. // FREEBIE
This commit is contained in:
parent
1b2be846be
commit
dbbdb1f5d8
@ -1,22 +0,0 @@
|
||||
package org.whispersystems.libsignal.fingerprint;
|
||||
|
||||
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.List;
|
||||
|
||||
abstract class BaseFingerprintType {
|
||||
|
||||
protected byte[] getLogicalKeyBytes(List<IdentityKey> identityKeys) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
for (IdentityKey identityKey : identityKeys) {
|
||||
byte[] publicKeyBytes = identityKey.getPublicKey().serialize();
|
||||
baos.write(publicKeyBytes, 0, publicKeyBytes.length);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,79 +5,34 @@
|
||||
*/
|
||||
package org.whispersystems.libsignal.fingerprint;
|
||||
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.whispersystems.libsignal.util.IdentityKeyComparator;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
public class DisplayableFingerprint {
|
||||
|
||||
public class DisplayableFingerprint extends BaseFingerprintType {
|
||||
private final String localFingerprintNumbers;
|
||||
private final String remoteFingerprintNumbers;
|
||||
|
||||
private static final int VERSION = 0;
|
||||
|
||||
private final String localFingerprint;
|
||||
private final String remoteFingerprint;
|
||||
|
||||
DisplayableFingerprint(int iterations,
|
||||
String localStableIdentifier, final IdentityKey localIdentityKey,
|
||||
String remoteStableIdentifier, final IdentityKey remoteIdentityKey)
|
||||
DisplayableFingerprint(byte[] localFingerprint, byte[] remoteFingerprint)
|
||||
{
|
||||
this(iterations, localStableIdentifier,
|
||||
new LinkedList<IdentityKey>(){{
|
||||
add(localIdentityKey);
|
||||
}},
|
||||
remoteStableIdentifier,
|
||||
new LinkedList<IdentityKey>() {{
|
||||
add(remoteIdentityKey);
|
||||
}});
|
||||
}
|
||||
|
||||
DisplayableFingerprint(int iterations,
|
||||
String localStableIdentifier, List<IdentityKey> localIdentityKeys,
|
||||
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
|
||||
{
|
||||
this.localFingerprint = getDisplayStringFor(iterations, localStableIdentifier, localIdentityKeys);
|
||||
this.remoteFingerprint = getDisplayStringFor(iterations, remoteStableIdentifier, remoteIdentityKeys);
|
||||
this.localFingerprintNumbers = getDisplayStringFor(localFingerprint);
|
||||
this.remoteFingerprintNumbers = getDisplayStringFor(remoteFingerprint);
|
||||
}
|
||||
|
||||
public String getDisplayText() {
|
||||
if (localFingerprint.compareTo(remoteFingerprint) <= 0) {
|
||||
return localFingerprint + remoteFingerprint;
|
||||
if (localFingerprintNumbers.compareTo(remoteFingerprintNumbers) <= 0) {
|
||||
return localFingerprintNumbers + remoteFingerprintNumbers;
|
||||
} else {
|
||||
return remoteFingerprint + localFingerprint;
|
||||
return remoteFingerprintNumbers + localFingerprintNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
private String getDisplayStringFor(int iterations, String stableIdentifier, List<IdentityKey> unsortedIdentityKeys) {
|
||||
try {
|
||||
ArrayList<IdentityKey> sortedIdentityKeys = new ArrayList<>(unsortedIdentityKeys);
|
||||
Collections.sort(sortedIdentityKeys, new IdentityKeyComparator());
|
||||
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] publicKey = getLogicalKeyBytes(sortedIdentityKeys);
|
||||
byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(VERSION),
|
||||
publicKey, stableIdentifier.getBytes());
|
||||
|
||||
for (int i=0;i<iterations;i++) {
|
||||
digest.update(hash);
|
||||
hash = digest.digest(publicKey);
|
||||
}
|
||||
|
||||
return getEncodedChunk(hash, 0) +
|
||||
getEncodedChunk(hash, 5) +
|
||||
getEncodedChunk(hash, 10) +
|
||||
getEncodedChunk(hash, 15) +
|
||||
getEncodedChunk(hash, 20) +
|
||||
getEncodedChunk(hash, 25);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
private String getDisplayStringFor(byte[] fingerprint) {
|
||||
return getEncodedChunk(fingerprint, 0) +
|
||||
getEncodedChunk(fingerprint, 5) +
|
||||
getEncodedChunk(fingerprint, 10) +
|
||||
getEncodedChunk(fingerprint, 15) +
|
||||
getEncodedChunk(fingerprint, 20) +
|
||||
getEncodedChunk(fingerprint, 25);
|
||||
}
|
||||
|
||||
private String getEncodedChunk(byte[] hash, int offset) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -7,11 +7,20 @@ package org.whispersystems.libsignal.fingerprint;
|
||||
|
||||
public class FingerprintVersionMismatchException extends Exception {
|
||||
|
||||
public FingerprintVersionMismatchException() {
|
||||
private final int theirVersion;
|
||||
private final int ourVersion;
|
||||
|
||||
public FingerprintVersionMismatchException(int theirVersion, int ourVersion) {
|
||||
super();
|
||||
this.theirVersion = theirVersion;
|
||||
this.ourVersion = ourVersion;
|
||||
}
|
||||
|
||||
public FingerprintVersionMismatchException(Exception e) {
|
||||
super(e);
|
||||
public int getTheirVersion() {
|
||||
return theirVersion;
|
||||
}
|
||||
|
||||
public int getOurVersion() {
|
||||
return ourVersion;
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,8 +6,6 @@
|
||||
package org.whispersystems.libsignal.fingerprint;
|
||||
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.devices.DeviceConsistencySignature;
|
||||
import org.whispersystems.libsignal.util.ByteArrayComparator;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
import org.whispersystems.libsignal.util.IdentityKeyComparator;
|
||||
|
||||
@ -16,11 +14,13 @@ import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class NumericFingerprintGenerator implements FingerprintGenerator {
|
||||
|
||||
private static final int FINGERPRINT_VERSION = 0;
|
||||
|
||||
private final int iterations;
|
||||
|
||||
/**
|
||||
@ -50,19 +50,17 @@ public class NumericFingerprintGenerator implements FingerprintGenerator {
|
||||
* @return A unique fingerprint for this conversation.
|
||||
*/
|
||||
@Override
|
||||
public Fingerprint createFor(String localStableIdentifier, IdentityKey localIdentityKey,
|
||||
String remoteStableIdentifier, IdentityKey remoteIdentityKey)
|
||||
public Fingerprint createFor(String localStableIdentifier, final IdentityKey localIdentityKey,
|
||||
String remoteStableIdentifier, final IdentityKey remoteIdentityKey)
|
||||
{
|
||||
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(iterations,
|
||||
localStableIdentifier,
|
||||
localIdentityKey,
|
||||
remoteStableIdentifier,
|
||||
remoteIdentityKey);
|
||||
|
||||
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(localStableIdentifier, localIdentityKey,
|
||||
remoteStableIdentifier, remoteIdentityKey);
|
||||
|
||||
return new Fingerprint(displayableFingerprint, scannableFingerprint);
|
||||
return createFor(localStableIdentifier,
|
||||
new LinkedList<IdentityKey>() {{
|
||||
add(localIdentityKey);
|
||||
}},
|
||||
remoteStableIdentifier,
|
||||
new LinkedList<IdentityKey>() {{
|
||||
add(remoteIdentityKey);
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,17 +79,49 @@ public class NumericFingerprintGenerator implements FingerprintGenerator {
|
||||
public Fingerprint createFor(String localStableIdentifier, List<IdentityKey> localIdentityKeys,
|
||||
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
|
||||
{
|
||||
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(iterations,
|
||||
localStableIdentifier,
|
||||
localIdentityKeys,
|
||||
remoteStableIdentifier,
|
||||
remoteIdentityKeys);
|
||||
byte[] localFingerprint = getFingerprint(iterations, localStableIdentifier, localIdentityKeys);
|
||||
byte[] remoteFingerprint = getFingerprint(iterations, remoteStableIdentifier, remoteIdentityKeys);
|
||||
|
||||
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(localStableIdentifier, localIdentityKeys,
|
||||
remoteStableIdentifier, remoteIdentityKeys);
|
||||
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(localFingerprint,
|
||||
remoteFingerprint);
|
||||
|
||||
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(localFingerprint,
|
||||
remoteFingerprint);
|
||||
|
||||
return new Fingerprint(displayableFingerprint, scannableFingerprint);
|
||||
}
|
||||
|
||||
private byte[] getFingerprint(int iterations, String stableIdentifier, List<IdentityKey> unsortedIdentityKeys) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-512");
|
||||
byte[] publicKey = getLogicalKeyBytes(unsortedIdentityKeys);
|
||||
byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(FINGERPRINT_VERSION),
|
||||
publicKey, stableIdentifier.getBytes());
|
||||
|
||||
for (int i=0;i<iterations;i++) {
|
||||
digest.update(hash);
|
||||
hash = digest.digest(publicKey);
|
||||
}
|
||||
|
||||
return hash;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getLogicalKeyBytes(List<IdentityKey> identityKeys) {
|
||||
ArrayList<IdentityKey> sortedIdentityKeys = new ArrayList<>(identityKeys);
|
||||
Collections.sort(sortedIdentityKeys, new IdentityKeyComparator());
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
for (IdentityKey identityKey : sortedIdentityKeys) {
|
||||
byte[] publicKeyBytes = identityKey.getPublicKey().serialize();
|
||||
baos.write(publicKeyBytes, 0, publicKeyBytes.length);
|
||||
}
|
||||
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -8,49 +8,40 @@ package org.whispersystems.libsignal.fingerprint;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.CombinedFingerprint;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.FingerprintData;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.CombinedFingerprints;
|
||||
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.LogicalFingerprint;
|
||||
import org.whispersystems.libsignal.util.ByteUtil;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
|
||||
public class ScannableFingerprint extends BaseFingerprintType {
|
||||
public class ScannableFingerprint {
|
||||
|
||||
private static final int VERSION = 0;
|
||||
private static final int VERSION = 1;
|
||||
|
||||
private final CombinedFingerprint combinedFingerprint;
|
||||
private final CombinedFingerprints fingerprints;
|
||||
|
||||
ScannableFingerprint(String localStableIdentifier, IdentityKey localIdentityKey,
|
||||
String remoteStableIdentifier, IdentityKey remoteIdentityKey)
|
||||
ScannableFingerprint(byte[] localFingerprintData, byte[] remoteFingerprintData)
|
||||
{
|
||||
this.combinedFingerprint = initializeCombinedFingerprint(localStableIdentifier, localIdentityKey.serialize(),
|
||||
remoteStableIdentifier, remoteIdentityKey.serialize());
|
||||
}
|
||||
LogicalFingerprint localFingerprint = LogicalFingerprint.newBuilder()
|
||||
.setContent(ByteString.copyFrom(ByteUtil.trim(localFingerprintData, 32)))
|
||||
.build();
|
||||
|
||||
ScannableFingerprint(String localStableIdentifier, List<IdentityKey> localIdentityKeys,
|
||||
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
|
||||
{
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
|
||||
LogicalFingerprint remoteFingerprint = LogicalFingerprint.newBuilder()
|
||||
.setContent(ByteString.copyFrom(ByteUtil.trim(remoteFingerprintData, 32)))
|
||||
.build();
|
||||
|
||||
byte[] localIdentityLogicalKey = messageDigest.digest(getLogicalKeyBytes(localIdentityKeys));
|
||||
byte[] remoteIdentityLogicalKey = messageDigest.digest(getLogicalKeyBytes(remoteIdentityKeys));
|
||||
|
||||
|
||||
this.combinedFingerprint = initializeCombinedFingerprint(localStableIdentifier, localIdentityLogicalKey,
|
||||
remoteStableIdentifier, remoteIdentityLogicalKey);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
this.fingerprints = CombinedFingerprints.newBuilder()
|
||||
.setVersion(VERSION)
|
||||
.setLocalFingerprint(localFingerprint)
|
||||
.setRemoteFingerprint(remoteFingerprint)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A byte string to be displayed in a QR code.
|
||||
*/
|
||||
public byte[] getSerialized() {
|
||||
return combinedFingerprint.toByteArray();
|
||||
return fingerprints.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -59,49 +50,24 @@ public class ScannableFingerprint extends BaseFingerprintType {
|
||||
* @param scannedFingerprintData The scanned data
|
||||
* @return True if matching, otehrwise false.
|
||||
* @throws FingerprintVersionMismatchException if the scanned fingerprint is the wrong version.
|
||||
* @throws FingerprintIdentifierMismatchException if the scanned fingerprint is for the wrong stable identifier.
|
||||
*/
|
||||
public boolean compareTo(byte[] scannedFingerprintData)
|
||||
throws FingerprintVersionMismatchException,
|
||||
FingerprintIdentifierMismatchException,
|
||||
FingerprintParsingException
|
||||
{
|
||||
try {
|
||||
CombinedFingerprint scannedFingerprint = CombinedFingerprint.parseFrom(scannedFingerprintData);
|
||||
CombinedFingerprints scanned = CombinedFingerprints.parseFrom(scannedFingerprintData);
|
||||
|
||||
if (!scannedFingerprint.hasRemoteFingerprint() || !scannedFingerprint.hasLocalFingerprint() ||
|
||||
!scannedFingerprint.hasVersion() || scannedFingerprint.getVersion() != combinedFingerprint.getVersion())
|
||||
if (!scanned.hasRemoteFingerprint() || !scanned.hasLocalFingerprint() ||
|
||||
!scanned.hasVersion() || scanned.getVersion() != VERSION)
|
||||
{
|
||||
throw new FingerprintVersionMismatchException();
|
||||
throw new FingerprintVersionMismatchException(scanned.getVersion(), VERSION);
|
||||
}
|
||||
|
||||
if (!combinedFingerprint.getLocalFingerprint().getIdentifier().equals(scannedFingerprint.getRemoteFingerprint().getIdentifier()) ||
|
||||
!combinedFingerprint.getRemoteFingerprint().getIdentifier().equals(scannedFingerprint.getLocalFingerprint().getIdentifier()))
|
||||
{
|
||||
throw new FingerprintIdentifierMismatchException(new String(combinedFingerprint.getLocalFingerprint().getIdentifier().toByteArray()),
|
||||
new String(combinedFingerprint.getRemoteFingerprint().getIdentifier().toByteArray()),
|
||||
new String(scannedFingerprint.getLocalFingerprint().getIdentifier().toByteArray()),
|
||||
new String(scannedFingerprint.getRemoteFingerprint().getIdentifier().toByteArray()));
|
||||
}
|
||||
|
||||
return MessageDigest.isEqual(combinedFingerprint.getLocalFingerprint().toByteArray(), scannedFingerprint.getRemoteFingerprint().toByteArray()) &&
|
||||
MessageDigest.isEqual(combinedFingerprint.getRemoteFingerprint().toByteArray(), scannedFingerprint.getLocalFingerprint().toByteArray());
|
||||
return MessageDigest.isEqual(fingerprints.getLocalFingerprint().getContent().toByteArray(), scanned.getRemoteFingerprint().getContent().toByteArray()) &&
|
||||
MessageDigest.isEqual(fingerprints.getRemoteFingerprint().getContent().toByteArray(), scanned.getLocalFingerprint().getContent().toByteArray());
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
throw new FingerprintParsingException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private CombinedFingerprint initializeCombinedFingerprint(String localStableIdentifier, byte[] localIdentityKeyBytes,
|
||||
String remoteStableIdentifier, byte[] remoteIdentityKeyBytes)
|
||||
{
|
||||
return CombinedFingerprint.newBuilder()
|
||||
.setVersion(VERSION)
|
||||
.setLocalFingerprint(FingerprintData.newBuilder()
|
||||
.setIdentifier(ByteString.copyFrom(localStableIdentifier.getBytes()))
|
||||
.setPublicKey(ByteString.copyFrom(localIdentityKeyBytes)))
|
||||
.setRemoteFingerprint(FingerprintData.newBuilder()
|
||||
.setIdentifier(ByteString.copyFrom(remoteStableIdentifier.getBytes()))
|
||||
.setPublicKey(ByteString.copyFrom(remoteIdentityKeyBytes)))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -711,7 +711,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
boolean hasMessage();
|
||||
@ -719,7 +719,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
com.google.protobuf.ByteString getMessage();
|
||||
@ -932,7 +932,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasMessage() {
|
||||
@ -942,7 +942,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
@ -1438,7 +1438,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public boolean hasMessage() {
|
||||
@ -1448,7 +1448,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public com.google.protobuf.ByteString getMessage() {
|
||||
@ -1458,7 +1458,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public Builder setMessage(com.google.protobuf.ByteString value) {
|
||||
@ -1474,7 +1474,7 @@ public final class SignalProtos {
|
||||
* <code>optional bytes message = 4;</code>
|
||||
*
|
||||
* <pre>
|
||||
* WhisperMessage
|
||||
* SignalMessage
|
||||
* </pre>
|
||||
*/
|
||||
public Builder clearMessage() {
|
||||
|
||||
@ -3,13 +3,13 @@ package textsecure;
|
||||
option java_package = "org.whispersystems.libsignal.fingerprint";
|
||||
option java_outer_classname = "FingerprintProtos";
|
||||
|
||||
message FingerprintData {
|
||||
optional bytes publicKey = 1;
|
||||
optional bytes identifier = 2;
|
||||
message LogicalFingerprint {
|
||||
optional bytes content = 1;
|
||||
// optional bytes identifier = 2;
|
||||
}
|
||||
|
||||
message CombinedFingerprint {
|
||||
optional uint32 version = 1;
|
||||
optional FingerprintData localFingerprint = 2;
|
||||
optional FingerprintData remoteFingerprint = 3;
|
||||
message CombinedFingerprints {
|
||||
optional uint32 version = 1;
|
||||
optional LogicalFingerprint localFingerprint = 2;
|
||||
optional LogicalFingerprint remoteFingerprint = 3;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user