Compare commits

...

1 Commits

Author SHA1 Message Date
Moxie Marlinspike
bc0acb2705 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
2016-11-12 15:00:54 -08:00
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.LogicalFingerprint;
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.LogicalFingerprints;
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 LogicalFingerprints 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()
.setData(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()
.setData(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 = LogicalFingerprints.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);
LogicalFingerprints scanned = LogicalFingerprints.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().getData().toByteArray(), scanned.getRemoteFingerprint().getData().toByteArray()) &&
MessageDigest.isEqual(fingerprints.getRemoteFingerprint().getData().toByteArray(), scanned.getLocalFingerprint().getData().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 data = 1;
// optional bytes identifier = 2;
}
message CombinedFingerprint {
optional uint32 version = 1;
optional FingerprintData localFingerprint = 2;
optional FingerprintData remoteFingerprint = 3;
message LogicalFingerprints {
optional uint32 version = 1;
optional LogicalFingerprint localFingerprint = 2;
optional LogicalFingerprint remoteFingerprint = 3;
}