Skip to content

Commit 1b2be84

Browse files
committed
Allow fingerprint generation for collections of identity keys
// FREEBIE
1 parent 98e88b7 commit 1b2be84

File tree

8 files changed

+203
-64
lines changed

8 files changed

+203
-64
lines changed

Diff for: java/src/main/java/org/whispersystems/libsignal/devices/DeviceConsistencyCommitment.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package org.whispersystems.libsignal.devices;
22

33
import org.whispersystems.libsignal.IdentityKey;
4-
import org.whispersystems.libsignal.protocol.DeviceConsistencyMessage;
5-
import org.whispersystems.libsignal.util.ByteArrayComparator;
64
import org.whispersystems.libsignal.util.ByteUtil;
5+
import org.whispersystems.libsignal.util.IdentityKeyComparator;
76

87
import java.security.MessageDigest;
98
import java.security.NoSuchAlgorithmException;
109
import java.util.ArrayList;
1110
import java.util.Collections;
12-
import java.util.Comparator;
1311
import java.util.List;
1412

1513
public class DeviceConsistencyCommitment {
@@ -47,13 +45,5 @@ public int getGeneration() {
4745
return generation;
4846
}
4947

50-
private static class IdentityKeyComparator extends ByteArrayComparator implements Comparator<IdentityKey> {
51-
52-
@Override
53-
public int compare(IdentityKey first, IdentityKey second) {
54-
return compare(first.getPublicKey().serialize(), second.getPublicKey().serialize());
55-
}
56-
}
57-
5848

5949
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.whispersystems.libsignal.fingerprint;
2+
3+
4+
import org.whispersystems.libsignal.IdentityKey;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.util.List;
8+
9+
abstract class BaseFingerprintType {
10+
11+
protected byte[] getLogicalKeyBytes(List<IdentityKey> identityKeys) {
12+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
13+
14+
for (IdentityKey identityKey : identityKeys) {
15+
byte[] publicKeyBytes = identityKey.getPublicKey().serialize();
16+
baos.write(publicKeyBytes, 0, publicKeyBytes.length);
17+
}
18+
19+
return baos.toByteArray();
20+
}
21+
22+
}

Diff for: java/src/main/java/org/whispersystems/libsignal/fingerprint/DisplayableFingerprint.java

+67-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,45 @@
55
*/
66
package org.whispersystems.libsignal.fingerprint;
77

8-
public class DisplayableFingerprint {
8+
import org.whispersystems.libsignal.IdentityKey;
9+
import org.whispersystems.libsignal.util.ByteUtil;
10+
import org.whispersystems.libsignal.util.IdentityKeyComparator;
11+
12+
import java.io.ByteArrayOutputStream;
13+
import java.security.MessageDigest;
14+
import java.security.NoSuchAlgorithmException;
15+
import java.util.ArrayList;
16+
import java.util.Collections;
17+
import java.util.LinkedList;
18+
import java.util.List;
19+
20+
public class DisplayableFingerprint extends BaseFingerprintType {
21+
22+
private static final int VERSION = 0;
923

1024
private final String localFingerprint;
1125
private final String remoteFingerprint;
1226

13-
public DisplayableFingerprint(String localFingerprint, String remoteFingerprint) {
14-
this.localFingerprint = localFingerprint;
15-
this.remoteFingerprint = remoteFingerprint;
27+
DisplayableFingerprint(int iterations,
28+
String localStableIdentifier, final IdentityKey localIdentityKey,
29+
String remoteStableIdentifier, final IdentityKey remoteIdentityKey)
30+
{
31+
this(iterations, localStableIdentifier,
32+
new LinkedList<IdentityKey>(){{
33+
add(localIdentityKey);
34+
}},
35+
remoteStableIdentifier,
36+
new LinkedList<IdentityKey>() {{
37+
add(remoteIdentityKey);
38+
}});
39+
}
40+
41+
DisplayableFingerprint(int iterations,
42+
String localStableIdentifier, List<IdentityKey> localIdentityKeys,
43+
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
44+
{
45+
this.localFingerprint = getDisplayStringFor(iterations, localStableIdentifier, localIdentityKeys);
46+
this.remoteFingerprint = getDisplayStringFor(iterations, remoteStableIdentifier, remoteIdentityKeys);
1647
}
1748

1849
public String getDisplayText() {
@@ -22,4 +53,36 @@ public String getDisplayText() {
2253
return remoteFingerprint + localFingerprint;
2354
}
2455
}
56+
57+
private String getDisplayStringFor(int iterations, String stableIdentifier, List<IdentityKey> unsortedIdentityKeys) {
58+
try {
59+
ArrayList<IdentityKey> sortedIdentityKeys = new ArrayList<>(unsortedIdentityKeys);
60+
Collections.sort(sortedIdentityKeys, new IdentityKeyComparator());
61+
62+
MessageDigest digest = MessageDigest.getInstance("SHA-512");
63+
byte[] publicKey = getLogicalKeyBytes(sortedIdentityKeys);
64+
byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(VERSION),
65+
publicKey, stableIdentifier.getBytes());
66+
67+
for (int i=0;i<iterations;i++) {
68+
digest.update(hash);
69+
hash = digest.digest(publicKey);
70+
}
71+
72+
return getEncodedChunk(hash, 0) +
73+
getEncodedChunk(hash, 5) +
74+
getEncodedChunk(hash, 10) +
75+
getEncodedChunk(hash, 15) +
76+
getEncodedChunk(hash, 20) +
77+
getEncodedChunk(hash, 25);
78+
} catch (NoSuchAlgorithmException e) {
79+
throw new AssertionError(e);
80+
}
81+
}
82+
83+
private String getEncodedChunk(byte[] hash, int offset) {
84+
long chunk = ByteUtil.byteArray5ToLong(hash, offset) % 100000;
85+
return String.format("%05d", chunk);
86+
}
87+
2588
}

Diff for: java/src/main/java/org/whispersystems/libsignal/fingerprint/FingerprintGenerator.java

+5
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
import org.whispersystems.libsignal.IdentityKey;
99

10+
import java.util.List;
11+
1012
public interface FingerprintGenerator {
1113
public Fingerprint createFor(String localStableIdentifier, IdentityKey localIdentityKey,
1214
String remoteStableIdentifier, IdentityKey remoteIdentityKey);
15+
16+
public Fingerprint createFor(String localStableIdentifier, List<IdentityKey> localIdentityKey,
17+
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKey);
1318
}

Diff for: java/src/main/java/org/whispersystems/libsignal/fingerprint/NumericFingerprintGenerator.java

+40-31
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@
66
package org.whispersystems.libsignal.fingerprint;
77

88
import org.whispersystems.libsignal.IdentityKey;
9+
import org.whispersystems.libsignal.devices.DeviceConsistencySignature;
10+
import org.whispersystems.libsignal.util.ByteArrayComparator;
911
import org.whispersystems.libsignal.util.ByteUtil;
12+
import org.whispersystems.libsignal.util.IdentityKeyComparator;
1013

14+
import java.io.ByteArrayOutputStream;
1115
import java.security.MessageDigest;
1216
import java.security.NoSuchAlgorithmException;
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.Comparator;
20+
import java.util.List;
1321

1422
public class NumericFingerprintGenerator implements FingerprintGenerator {
1523

16-
private static final int VERSION = 0;
17-
18-
private final long iterations;
24+
private final int iterations;
1925

2026
/**
2127
* Construct a fingerprint generator for 60 digit numerics.
@@ -30,7 +36,7 @@ public class NumericFingerprintGenerator implements FingerprintGenerator {
3036
* - 1400 > 110 bits
3137
* - 5200 > 112 bits
3238
*/
33-
public NumericFingerprintGenerator(long iterations) {
39+
public NumericFingerprintGenerator(int iterations) {
3440
this.iterations = iterations;
3541
}
3642

@@ -47,42 +53,45 @@ public NumericFingerprintGenerator(long iterations) {
4753
public Fingerprint createFor(String localStableIdentifier, IdentityKey localIdentityKey,
4854
String remoteStableIdentifier, IdentityKey remoteIdentityKey)
4955
{
50-
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(getDisplayStringFor(localStableIdentifier, localIdentityKey),
51-
getDisplayStringFor(remoteStableIdentifier, remoteIdentityKey));
56+
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(iterations,
57+
localStableIdentifier,
58+
localIdentityKey,
59+
remoteStableIdentifier,
60+
remoteIdentityKey);
5261

53-
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(VERSION,
54-
localStableIdentifier, localIdentityKey,
62+
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(localStableIdentifier, localIdentityKey,
5563
remoteStableIdentifier, remoteIdentityKey);
5664

5765
return new Fingerprint(displayableFingerprint, scannableFingerprint);
5866
}
5967

60-
private String getDisplayStringFor(String stableIdentifier, IdentityKey identityKey) {
61-
try {
62-
MessageDigest digest = MessageDigest.getInstance("SHA-512");
63-
byte[] publicKey = identityKey.getPublicKey().serialize();
64-
byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(VERSION),
65-
publicKey, stableIdentifier.getBytes());
68+
/**
69+
* Generate a scannable and displayble fingerprint for logical identities that have multiple
70+
* physical keys.
71+
*
72+
* Do not trust the output of this unless you've been through the device consistency process
73+
* for the provided localIdentityKeys.
74+
*
75+
* @param localStableIdentifier The client's "stable" identifier.
76+
* @param localIdentityKeys The client's collection of physical identity keys.
77+
* @param remoteStableIdentifier The remote party's "stable" identifier.
78+
* @param remoteIdentityKeys The remote party's collection of physical identity key.
79+
* @return A unique fingerprint for this conversation.
80+
*/
81+
public Fingerprint createFor(String localStableIdentifier, List<IdentityKey> localIdentityKeys,
82+
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
83+
{
84+
DisplayableFingerprint displayableFingerprint = new DisplayableFingerprint(iterations,
85+
localStableIdentifier,
86+
localIdentityKeys,
87+
remoteStableIdentifier,
88+
remoteIdentityKeys);
6689

67-
for (int i=0;i<iterations;i++) {
68-
digest.update(hash);
69-
hash = digest.digest(publicKey);
70-
}
90+
ScannableFingerprint scannableFingerprint = new ScannableFingerprint(localStableIdentifier, localIdentityKeys,
91+
remoteStableIdentifier, remoteIdentityKeys);
7192

72-
return getEncodedChunk(hash, 0) +
73-
getEncodedChunk(hash, 5) +
74-
getEncodedChunk(hash, 10) +
75-
getEncodedChunk(hash, 15) +
76-
getEncodedChunk(hash, 20) +
77-
getEncodedChunk(hash, 25);
78-
} catch (NoSuchAlgorithmException e) {
79-
throw new AssertionError(e);
80-
}
93+
return new Fingerprint(displayableFingerprint, scannableFingerprint);
8194
}
8295

83-
private String getEncodedChunk(byte[] hash, int offset) {
84-
long chunk = ByteUtil.byteArray5ToLong(hash, offset) % 100000;
85-
return String.format("%05d", chunk);
86-
}
8796

8897
}

Diff for: java/src/main/java/org/whispersystems/libsignal/fingerprint/ScannableFingerprint.java

+40-13
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,37 @@
1313
import org.whispersystems.libsignal.fingerprint.FingerprintProtos.FingerprintData;
1414

1515
import java.security.MessageDigest;
16+
import java.security.NoSuchAlgorithmException;
17+
import java.util.List;
1618

17-
public class ScannableFingerprint {
19+
public class ScannableFingerprint extends BaseFingerprintType {
20+
21+
private static final int VERSION = 0;
1822

1923
private final CombinedFingerprint combinedFingerprint;
2024

21-
public ScannableFingerprint(int version,
22-
String localStableIdentifier, IdentityKey localIdentityKey,
23-
String remoteStableIdentifier, IdentityKey remoteIdentityKey)
25+
ScannableFingerprint(String localStableIdentifier, IdentityKey localIdentityKey,
26+
String remoteStableIdentifier, IdentityKey remoteIdentityKey)
27+
{
28+
this.combinedFingerprint = initializeCombinedFingerprint(localStableIdentifier, localIdentityKey.serialize(),
29+
remoteStableIdentifier, remoteIdentityKey.serialize());
30+
}
31+
32+
ScannableFingerprint(String localStableIdentifier, List<IdentityKey> localIdentityKeys,
33+
String remoteStableIdentifier, List<IdentityKey> remoteIdentityKeys)
2434
{
25-
this.combinedFingerprint = CombinedFingerprint.newBuilder()
26-
.setVersion(version)
27-
.setLocalFingerprint(FingerprintData.newBuilder()
28-
.setIdentifier(ByteString.copyFrom(localStableIdentifier.getBytes()))
29-
.setPublicKey(ByteString.copyFrom(localIdentityKey.serialize())))
30-
.setRemoteFingerprint(FingerprintData.newBuilder()
31-
.setIdentifier(ByteString.copyFrom(remoteStableIdentifier.getBytes()))
32-
.setPublicKey(ByteString.copyFrom(remoteIdentityKey.serialize())))
33-
.build();
35+
try {
36+
MessageDigest messageDigest = MessageDigest.getInstance("SHA-512");
37+
38+
byte[] localIdentityLogicalKey = messageDigest.digest(getLogicalKeyBytes(localIdentityKeys));
39+
byte[] remoteIdentityLogicalKey = messageDigest.digest(getLogicalKeyBytes(remoteIdentityKeys));
40+
41+
42+
this.combinedFingerprint = initializeCombinedFingerprint(localStableIdentifier, localIdentityLogicalKey,
43+
remoteStableIdentifier, remoteIdentityLogicalKey);
44+
} catch (NoSuchAlgorithmException e) {
45+
throw new AssertionError(e);
46+
}
3447
}
3548

3649
/**
@@ -77,4 +90,18 @@ public boolean compareTo(byte[] scannedFingerprintData)
7790
throw new FingerprintParsingException(e);
7891
}
7992
}
93+
94+
private CombinedFingerprint initializeCombinedFingerprint(String localStableIdentifier, byte[] localIdentityKeyBytes,
95+
String remoteStableIdentifier, byte[] remoteIdentityKeyBytes)
96+
{
97+
return CombinedFingerprint.newBuilder()
98+
.setVersion(VERSION)
99+
.setLocalFingerprint(FingerprintData.newBuilder()
100+
.setIdentifier(ByteString.copyFrom(localStableIdentifier.getBytes()))
101+
.setPublicKey(ByteString.copyFrom(localIdentityKeyBytes)))
102+
.setRemoteFingerprint(FingerprintData.newBuilder()
103+
.setIdentifier(ByteString.copyFrom(remoteStableIdentifier.getBytes()))
104+
.setPublicKey(ByteString.copyFrom(remoteIdentityKeyBytes)))
105+
.build();
106+
}
80107
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.whispersystems.libsignal.util;
2+
3+
import org.whispersystems.libsignal.IdentityKey;
4+
5+
import java.util.Comparator;
6+
7+
public class IdentityKeyComparator extends ByteArrayComparator implements Comparator<IdentityKey> {
8+
9+
@Override
10+
public int compare(IdentityKey first, IdentityKey second) {
11+
return compare(first.getPublicKey().serialize(), second.getPublicKey().serialize());
12+
}
13+
}

0 commit comments

Comments
 (0)