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:
Moxie Marlinspike 2016-11-12 15:00:54 -08:00
parent 1b2be846be
commit dbbdb1f5d8
8 changed files with 388 additions and 502 deletions

View File

@ -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();
}
}

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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() {

View File

@ -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;
}