From cad43f6fe392e860d3eb454f04fc5e8819f73651 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 25 Oct 2019 16:52:18 -0700 Subject: [PATCH] Add support for UUIDs. --- .../metadata/SealedSessionCipher.java | 80 +++- .../libsignal/metadata/SignalProtos.java | 369 +++++++++++++----- .../certificate/SenderCertificate.java | 24 +- protobuf/UnidentifiedDelivery.proto | 3 +- tests/build.gradle | 2 +- .../metadata/SealedSessionCipherTest.java | 41 +- .../certificate/SenderCertificateTest.java | 9 +- 7 files changed, 381 insertions(+), 147 deletions(-) diff --git a/java/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java b/java/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java index 8f5a77a..f607699 100644 --- a/java/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java +++ b/java/src/main/java/org/signal/libsignal/metadata/SealedSessionCipher.java @@ -28,12 +28,13 @@ import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.protocol.SignalMessage; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.ByteUtil; -import org.whispersystems.libsignal.util.Pair; +import org.whispersystems.libsignal.util.guava.Optional; import java.security.InvalidAlgorithmParameterException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.ParseException; +import java.util.UUID; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -45,14 +46,22 @@ import javax.crypto.spec.SecretKeySpec; public class SealedSessionCipher { - private final SignalProtocolStore signalProtocolStore; - private final SignalProtocolAddress localAddress; + private static final String TAG = SealedSessionCipher.class.getSimpleName(); + + private final SignalProtocolStore signalProtocolStore; + private final String localE164Address; + private final String localUuidAddress; + private final int localDeviceId; public SealedSessionCipher(SignalProtocolStore signalProtocolStore, - SignalProtocolAddress localAddress) + UUID localUuid, + String localE164Address, + int localDeviceId) { this.signalProtocolStore = signalProtocolStore; - this.localAddress = localAddress; + this.localUuidAddress = localUuid != null ? localUuid.toString() : null; + this.localE164Address = localE164Address; + this.localDeviceId = localDeviceId; } public byte[] encrypt(SignalProtocolAddress destinationAddress, SenderCertificate senderCertificate, byte[] paddedPlaintext) @@ -75,7 +84,7 @@ public class SealedSessionCipher { return new UnidentifiedSenderMessage(ephemeral.getPublicKey(), staticKeyCiphertext, messageBytes).getSerialized(); } - public Pair decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp) + public DecryptionResult decrypt(CertificateValidator validator, byte[] ciphertext, long timestamp) throws InvalidMetadataMessageException, InvalidMetadataVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyException, @@ -105,9 +114,10 @@ public class SealedSessionCipher { throw new InvalidKeyException("Sender's certificate key does not match key used in message"); } - if (content.getSenderCertificate().getSender().equals(localAddress.getName()) && - content.getSenderCertificate().getSenderDeviceId() == localAddress.getDeviceId()) - { + boolean isLocalE164 = localE164Address != null && localE164Address.equals(content.getSenderCertificate().getSenderE164().orNull()); + boolean isLocalUuid = localUuidAddress != null && localUuidAddress.equals(content.getSenderCertificate().getSenderUuid().orNull()); + + if ((isLocalE164 || isLocalUuid) && content.getSenderCertificate().getSenderDeviceId() == localDeviceId) { throw new SelfSendException(); } } catch (InvalidKeyException | InvalidMacException | InvalidCertificateException e) { @@ -115,9 +125,10 @@ public class SealedSessionCipher { } try { - return new Pair<>(new SignalProtocolAddress(content.getSenderCertificate().getSender(), - content.getSenderCertificate().getSenderDeviceId()), - decrypt(content)); + return new DecryptionResult(content.getSenderCertificate().getSenderUuid(), + content.getSenderCertificate().getSenderE164(), + content.getSenderCertificate().getSenderDeviceId(), + decrypt(content)); } catch (InvalidMessageException e) { throw new ProtocolInvalidMessageException(e, content.getSenderCertificate().getSender(), content.getSenderCertificate().getSenderDeviceId()); } catch (InvalidKeyException e) { @@ -172,8 +183,7 @@ public class SealedSessionCipher { private byte[] decrypt(UnidentifiedSenderMessageContent message) throws InvalidVersionException, InvalidMessageException, InvalidKeyException, DuplicateMessageException, InvalidKeyIdException, UntrustedIdentityException, LegacyMessageException, NoSessionException { - - SignalProtocolAddress sender = new SignalProtocolAddress(message.getSenderCertificate().getSender(), message.getSenderCertificate().getSenderDeviceId()); + SignalProtocolAddress sender = getPreferredAddress(signalProtocolStore, message.getSenderCertificate()); switch (message.getType()) { case CiphertextMessage.WHISPER_TYPE: return new SessionCipher(signalProtocolStore, sender).decrypt(new SignalMessage(message.getContent())); @@ -228,6 +238,48 @@ public class SealedSessionCipher { } } + private static SignalProtocolAddress getPreferredAddress(SignalProtocolStore store, SenderCertificate certificate) { + SignalProtocolAddress uuidAddress = certificate.getSenderUuid().isPresent() ? new SignalProtocolAddress(certificate.getSenderUuid().get(), certificate.getSenderDeviceId()) : null; + SignalProtocolAddress e164Address = certificate.getSenderE164().isPresent() ? new SignalProtocolAddress(certificate.getSenderE164().get(), certificate.getSenderDeviceId()) : null; + + if (uuidAddress != null && store.containsSession(uuidAddress)) { + return uuidAddress; + } else if (e164Address != null && store.containsSession(e164Address)) { + return e164Address; + } else { + return new SignalProtocolAddress(certificate.getSender(), certificate.getSenderDeviceId()); + } + } + + public static class DecryptionResult { + private final Optional senderUuid; + private final Optional senderE164; + private final int deviceId; + private final byte[] paddedMessage; + + private DecryptionResult(Optional senderUuid, Optional senderE164, int deviceId, byte[] paddedMessage) { + this.senderUuid = senderUuid; + this.senderE164 = senderE164; + this.deviceId = deviceId; + this.paddedMessage = paddedMessage; + } + + public Optional getSenderUuid() { + return senderUuid; + } + + public Optional getSenderE164() { + return senderE164; + } + + public int getDeviceId() { + return deviceId; + } + + public byte[] getPaddedMessage() { + return paddedMessage; + } + } private static class EphemeralKeys { private final byte[] chainKey; diff --git a/java/src/main/java/org/signal/libsignal/metadata/SignalProtos.java b/java/src/main/java/org/signal/libsignal/metadata/SignalProtos.java index 33c298f..b8d6512 100644 --- a/java/src/main/java/org/signal/libsignal/metadata/SignalProtos.java +++ b/java/src/main/java/org/signal/libsignal/metadata/SignalProtos.java @@ -1097,20 +1097,35 @@ public final class SignalProtos { public interface CertificateOrBuilder extends com.google.protobuf.MessageOrBuilder { - // optional string sender = 1; + // optional string senderE164 = 1; /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - boolean hasSender(); + boolean hasSenderE164(); /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - java.lang.String getSender(); + java.lang.String getSenderE164(); /** - * optional string sender = 1; + * optional string senderE164 = 1; */ com.google.protobuf.ByteString - getSenderBytes(); + getSenderE164Bytes(); + + // optional string senderUuid = 6; + /** + * optional string senderUuid = 6; + */ + boolean hasSenderUuid(); + /** + * optional string senderUuid = 6; + */ + java.lang.String getSenderUuid(); + /** + * optional string senderUuid = 6; + */ + com.google.protobuf.ByteString + getSenderUuidBytes(); // optional uint32 senderDevice = 2; /** @@ -1209,27 +1224,27 @@ public final class SignalProtos { } case 10: { bitField0_ |= 0x00000001; - sender_ = input.readBytes(); + senderE164_ = input.readBytes(); break; } case 16: { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; senderDevice_ = input.readUInt32(); break; } case 25: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; expires_ = input.readFixed64(); break; } case 34: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; identityKey_ = input.readBytes(); break; } case 42: { org.signal.libsignal.metadata.SignalProtos.ServerCertificate.Builder subBuilder = null; - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { subBuilder = signer_.toBuilder(); } signer_ = input.readMessage(org.signal.libsignal.metadata.SignalProtos.ServerCertificate.PARSER, extensionRegistry); @@ -1237,7 +1252,12 @@ public final class SignalProtos { subBuilder.mergeFrom(signer_); signer_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; + break; + } + case 50: { + bitField0_ |= 0x00000002; + senderUuid_ = input.readBytes(); break; } } @@ -1280,20 +1300,20 @@ public final class SignalProtos { } private int bitField0_; - // optional string sender = 1; - public static final int SENDER_FIELD_NUMBER = 1; - private java.lang.Object sender_; + // optional string senderE164 = 1; + public static final int SENDERE164_FIELD_NUMBER = 1; + private java.lang.Object senderE164_; /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public boolean hasSender() { + public boolean hasSenderE164() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public java.lang.String getSender() { - java.lang.Object ref = sender_; + public java.lang.String getSenderE164() { + java.lang.Object ref = senderE164_; if (ref instanceof java.lang.String) { return (java.lang.String) ref; } else { @@ -1301,22 +1321,65 @@ public final class SignalProtos { (com.google.protobuf.ByteString) ref; java.lang.String s = bs.toStringUtf8(); if (bs.isValidUtf8()) { - sender_ = s; + senderE164_ = s; } return s; } } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ public com.google.protobuf.ByteString - getSenderBytes() { - java.lang.Object ref = sender_; + getSenderE164Bytes() { + java.lang.Object ref = senderE164_; if (ref instanceof java.lang.String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - sender_ = b; + senderE164_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // optional string senderUuid = 6; + public static final int SENDERUUID_FIELD_NUMBER = 6; + private java.lang.Object senderUuid_; + /** + * optional string senderUuid = 6; + */ + public boolean hasSenderUuid() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string senderUuid = 6; + */ + public java.lang.String getSenderUuid() { + java.lang.Object ref = senderUuid_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + senderUuid_ = s; + } + return s; + } + } + /** + * optional string senderUuid = 6; + */ + public com.google.protobuf.ByteString + getSenderUuidBytes() { + java.lang.Object ref = senderUuid_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + senderUuid_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; @@ -1330,7 +1393,7 @@ public final class SignalProtos { * optional uint32 senderDevice = 2; */ public boolean hasSenderDevice() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 senderDevice = 2; @@ -1346,7 +1409,7 @@ public final class SignalProtos { * optional fixed64 expires = 3; */ public boolean hasExpires() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional fixed64 expires = 3; @@ -1362,7 +1425,7 @@ public final class SignalProtos { * optional bytes identityKey = 4; */ public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes identityKey = 4; @@ -1378,7 +1441,7 @@ public final class SignalProtos { * optional .signal.ServerCertificate signer = 5; */ public boolean hasSigner() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional .signal.ServerCertificate signer = 5; @@ -1394,7 +1457,8 @@ public final class SignalProtos { } private void initFields() { - sender_ = ""; + senderE164_ = ""; + senderUuid_ = ""; senderDevice_ = 0; expires_ = 0L; identityKey_ = com.google.protobuf.ByteString.EMPTY; @@ -1413,20 +1477,23 @@ public final class SignalProtos { throws java.io.IOException { getSerializedSize(); if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, getSenderBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeUInt32(2, senderDevice_); + output.writeBytes(1, getSenderE164Bytes()); } if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeFixed64(3, expires_); + output.writeUInt32(2, senderDevice_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeBytes(4, identityKey_); + output.writeFixed64(3, expires_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { + output.writeBytes(4, identityKey_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeMessage(5, signer_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(6, getSenderUuidBytes()); + } getUnknownFields().writeTo(output); } @@ -1438,24 +1505,28 @@ public final class SignalProtos { size = 0; if (((bitField0_ & 0x00000001) == 0x00000001)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, getSenderBytes()); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(2, senderDevice_); + .computeBytesSize(1, getSenderE164Bytes()); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream - .computeFixed64Size(3, expires_); + .computeUInt32Size(2, senderDevice_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeBytesSize(4, identityKey_); + .computeFixed64Size(3, expires_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(4, identityKey_); + } + if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(5, signer_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(6, getSenderUuidBytes()); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -1573,20 +1644,22 @@ public final class SignalProtos { public Builder clear() { super.clear(); - sender_ = ""; + senderE164_ = ""; bitField0_ = (bitField0_ & ~0x00000001); - senderDevice_ = 0; + senderUuid_ = ""; bitField0_ = (bitField0_ & ~0x00000002); - expires_ = 0L; + senderDevice_ = 0; bitField0_ = (bitField0_ & ~0x00000004); - identityKey_ = com.google.protobuf.ByteString.EMPTY; + expires_ = 0L; bitField0_ = (bitField0_ & ~0x00000008); + identityKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000010); if (signerBuilder_ == null) { signer_ = org.signal.libsignal.metadata.SignalProtos.ServerCertificate.getDefaultInstance(); } else { signerBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); return this; } @@ -1618,22 +1691,26 @@ public final class SignalProtos { if (((from_bitField0_ & 0x00000001) == 0x00000001)) { to_bitField0_ |= 0x00000001; } - result.sender_ = sender_; + result.senderE164_ = senderE164_; if (((from_bitField0_ & 0x00000002) == 0x00000002)) { to_bitField0_ |= 0x00000002; } - result.senderDevice_ = senderDevice_; + result.senderUuid_ = senderUuid_; if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000004; } - result.expires_ = expires_; + result.senderDevice_ = senderDevice_; if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000008; } - result.identityKey_ = identityKey_; + result.expires_ = expires_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000010; } + result.identityKey_ = identityKey_; + if (((from_bitField0_ & 0x00000020) == 0x00000020)) { + to_bitField0_ |= 0x00000020; + } if (signerBuilder_ == null) { result.signer_ = signer_; } else { @@ -1655,9 +1732,14 @@ public final class SignalProtos { public Builder mergeFrom(org.signal.libsignal.metadata.SignalProtos.SenderCertificate.Certificate other) { if (other == org.signal.libsignal.metadata.SignalProtos.SenderCertificate.Certificate.getDefaultInstance()) return this; - if (other.hasSender()) { + if (other.hasSenderE164()) { bitField0_ |= 0x00000001; - sender_ = other.sender_; + senderE164_ = other.senderE164_; + onChanged(); + } + if (other.hasSenderUuid()) { + bitField0_ |= 0x00000002; + senderUuid_ = other.senderUuid_; onChanged(); } if (other.hasSenderDevice()) { @@ -1699,76 +1781,150 @@ public final class SignalProtos { } private int bitField0_; - // optional string sender = 1; - private java.lang.Object sender_ = ""; + // optional string senderE164 = 1; + private java.lang.Object senderE164_ = ""; /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public boolean hasSender() { + public boolean hasSenderE164() { return ((bitField0_ & 0x00000001) == 0x00000001); } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public java.lang.String getSender() { - java.lang.Object ref = sender_; + public java.lang.String getSenderE164() { + java.lang.Object ref = senderE164_; if (!(ref instanceof java.lang.String)) { java.lang.String s = ((com.google.protobuf.ByteString) ref) .toStringUtf8(); - sender_ = s; + senderE164_ = s; return s; } else { return (java.lang.String) ref; } } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ public com.google.protobuf.ByteString - getSenderBytes() { - java.lang.Object ref = sender_; + getSenderE164Bytes() { + java.lang.Object ref = senderE164_; if (ref instanceof String) { com.google.protobuf.ByteString b = com.google.protobuf.ByteString.copyFromUtf8( (java.lang.String) ref); - sender_ = b; + senderE164_ = b; return b; } else { return (com.google.protobuf.ByteString) ref; } } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public Builder setSender( + public Builder setSenderE164( java.lang.String value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000001; - sender_ = value; + senderE164_ = value; onChanged(); return this; } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public Builder clearSender() { + public Builder clearSenderE164() { bitField0_ = (bitField0_ & ~0x00000001); - sender_ = getDefaultInstance().getSender(); + senderE164_ = getDefaultInstance().getSenderE164(); onChanged(); return this; } /** - * optional string sender = 1; + * optional string senderE164 = 1; */ - public Builder setSenderBytes( + public Builder setSenderE164Bytes( com.google.protobuf.ByteString value) { if (value == null) { throw new NullPointerException(); } bitField0_ |= 0x00000001; - sender_ = value; + senderE164_ = value; + onChanged(); + return this; + } + + // optional string senderUuid = 6; + private java.lang.Object senderUuid_ = ""; + /** + * optional string senderUuid = 6; + */ + public boolean hasSenderUuid() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional string senderUuid = 6; + */ + public java.lang.String getSenderUuid() { + java.lang.Object ref = senderUuid_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + senderUuid_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string senderUuid = 6; + */ + public com.google.protobuf.ByteString + getSenderUuidBytes() { + java.lang.Object ref = senderUuid_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + senderUuid_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string senderUuid = 6; + */ + public Builder setSenderUuid( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + senderUuid_ = value; + onChanged(); + return this; + } + /** + * optional string senderUuid = 6; + */ + public Builder clearSenderUuid() { + bitField0_ = (bitField0_ & ~0x00000002); + senderUuid_ = getDefaultInstance().getSenderUuid(); + onChanged(); + return this; + } + /** + * optional string senderUuid = 6; + */ + public Builder setSenderUuidBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + senderUuid_ = value; onChanged(); return this; } @@ -1779,7 +1935,7 @@ public final class SignalProtos { * optional uint32 senderDevice = 2; */ public boolean hasSenderDevice() { - return ((bitField0_ & 0x00000002) == 0x00000002); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 senderDevice = 2; @@ -1791,7 +1947,7 @@ public final class SignalProtos { * optional uint32 senderDevice = 2; */ public Builder setSenderDevice(int value) { - bitField0_ |= 0x00000002; + bitField0_ |= 0x00000004; senderDevice_ = value; onChanged(); return this; @@ -1800,7 +1956,7 @@ public final class SignalProtos { * optional uint32 senderDevice = 2; */ public Builder clearSenderDevice() { - bitField0_ = (bitField0_ & ~0x00000002); + bitField0_ = (bitField0_ & ~0x00000004); senderDevice_ = 0; onChanged(); return this; @@ -1812,7 +1968,7 @@ public final class SignalProtos { * optional fixed64 expires = 3; */ public boolean hasExpires() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional fixed64 expires = 3; @@ -1824,7 +1980,7 @@ public final class SignalProtos { * optional fixed64 expires = 3; */ public Builder setExpires(long value) { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000008; expires_ = value; onChanged(); return this; @@ -1833,7 +1989,7 @@ public final class SignalProtos { * optional fixed64 expires = 3; */ public Builder clearExpires() { - bitField0_ = (bitField0_ & ~0x00000004); + bitField0_ = (bitField0_ & ~0x00000008); expires_ = 0L; onChanged(); return this; @@ -1845,7 +2001,7 @@ public final class SignalProtos { * optional bytes identityKey = 4; */ public boolean hasIdentityKey() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes identityKey = 4; @@ -1860,7 +2016,7 @@ public final class SignalProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000010; identityKey_ = value; onChanged(); return this; @@ -1869,7 +2025,7 @@ public final class SignalProtos { * optional bytes identityKey = 4; */ public Builder clearIdentityKey() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000010); identityKey_ = getDefaultInstance().getIdentityKey(); onChanged(); return this; @@ -1883,7 +2039,7 @@ public final class SignalProtos { * optional .signal.ServerCertificate signer = 5; */ public boolean hasSigner() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional .signal.ServerCertificate signer = 5; @@ -1908,7 +2064,7 @@ public final class SignalProtos { } else { signerBuilder_.setMessage(value); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -1922,7 +2078,7 @@ public final class SignalProtos { } else { signerBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -1930,7 +2086,7 @@ public final class SignalProtos { */ public Builder mergeSigner(org.signal.libsignal.metadata.SignalProtos.ServerCertificate value) { if (signerBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010) && + if (((bitField0_ & 0x00000020) == 0x00000020) && signer_ != org.signal.libsignal.metadata.SignalProtos.ServerCertificate.getDefaultInstance()) { signer_ = org.signal.libsignal.metadata.SignalProtos.ServerCertificate.newBuilder(signer_).mergeFrom(value).buildPartial(); @@ -1941,7 +2097,7 @@ public final class SignalProtos { } else { signerBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; return this; } /** @@ -1954,14 +2110,14 @@ public final class SignalProtos { } else { signerBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000020); return this; } /** * optional .signal.ServerCertificate signer = 5; */ public org.signal.libsignal.metadata.SignalProtos.ServerCertificate.Builder getSignerBuilder() { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000020; onChanged(); return getSignerFieldBuilder().getBuilder(); } @@ -3735,20 +3891,21 @@ public final class SignalProtos { "\n\032UnidentifiedDelivery.proto\022\006signal\"c\n\021" + "ServerCertificate\022\023\n\013certificate\030\001 \001(\014\022\021" + "\n\tsignature\030\002 \001(\014\032&\n\013Certificate\022\n\n\002id\030\001" + - " \001(\r\022\013\n\003key\030\002 \001(\014\"\302\001\n\021SenderCertificate\022" + + " \001(\r\022\013\n\003key\030\002 \001(\014\"\332\001\n\021SenderCertificate\022" + "\023\n\013certificate\030\001 \001(\014\022\021\n\tsignature\030\002 \001(\014\032" + - "\204\001\n\013Certificate\022\016\n\006sender\030\001 \001(\t\022\024\n\014sende" + - "rDevice\030\002 \001(\r\022\017\n\007expires\030\003 \001(\006\022\023\n\013identi" + - "tyKey\030\004 \001(\014\022)\n\006signer\030\005 \001(\0132\031.signal.Ser" + - "verCertificate\"\241\002\n\031UnidentifiedSenderMes" + - "sage\022\027\n\017ephemeralPublic\030\001 \001(\014\022\027\n\017encrypt", - "edStatic\030\002 \001(\014\022\030\n\020encryptedMessage\030\003 \001(\014" + - "\032\267\001\n\007Message\022<\n\004type\030\001 \001(\0162..signal.Unid" + - "entifiedSenderMessage.Message.Type\0224\n\021se" + - "nderCertificate\030\002 \001(\0132\031.signal.SenderCer" + - "tificate\022\017\n\007content\030\003 \001(\014\"\'\n\004Type\022\022\n\016PRE" + - "KEY_MESSAGE\020\001\022\013\n\007MESSAGE\020\002B-\n\035org.signal" + - ".libsignal.metadataB\014SignalProtos" + "\234\001\n\013Certificate\022\022\n\nsenderE164\030\001 \001(\t\022\022\n\ns" + + "enderUuid\030\006 \001(\t\022\024\n\014senderDevice\030\002 \001(\r\022\017\n" + + "\007expires\030\003 \001(\006\022\023\n\013identityKey\030\004 \001(\014\022)\n\006s" + + "igner\030\005 \001(\0132\031.signal.ServerCertificate\"\241" + + "\002\n\031UnidentifiedSenderMessage\022\027\n\017ephemera", + "lPublic\030\001 \001(\014\022\027\n\017encryptedStatic\030\002 \001(\014\022\030" + + "\n\020encryptedMessage\030\003 \001(\014\032\267\001\n\007Message\022<\n\004" + + "type\030\001 \001(\0162..signal.UnidentifiedSenderMe" + + "ssage.Message.Type\0224\n\021senderCertificate\030" + + "\002 \001(\0132\031.signal.SenderCertificate\022\017\n\007cont" + + "ent\030\003 \001(\014\"\'\n\004Type\022\022\n\016PREKEY_MESSAGE\020\001\022\013\n" + + "\007MESSAGE\020\002B-\n\035org.signal.libsignal.metad" + + "ataB\014SignalProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -3778,7 +3935,7 @@ public final class SignalProtos { internal_static_signal_SenderCertificate_Certificate_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signal_SenderCertificate_Certificate_descriptor, - new java.lang.String[] { "Sender", "SenderDevice", "Expires", "IdentityKey", "Signer", }); + new java.lang.String[] { "SenderE164", "SenderUuid", "SenderDevice", "Expires", "IdentityKey", "Signer", }); internal_static_signal_UnidentifiedSenderMessage_descriptor = getDescriptor().getMessageTypes().get(2); internal_static_signal_UnidentifiedSenderMessage_fieldAccessorTable = new diff --git a/java/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java b/java/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java index 54cba19..cec663a 100644 --- a/java/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java +++ b/java/src/main/java/org/signal/libsignal/metadata/certificate/SenderCertificate.java @@ -7,6 +7,7 @@ import org.signal.libsignal.metadata.SignalProtos; import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.ECPublicKey; +import org.whispersystems.libsignal.util.guava.Optional; public class SenderCertificate { @@ -14,7 +15,8 @@ public class SenderCertificate { private final ServerCertificate signer; private final ECPublicKey key; private final int senderDeviceId; - private final String sender; + private final Optional senderUuid; + private final Optional senderE164; private final long expiration; private final byte[] serialized; @@ -31,13 +33,19 @@ public class SenderCertificate { SignalProtos.SenderCertificate.Certificate certificate = SignalProtos.SenderCertificate.Certificate.parseFrom(wrapper.getCertificate()); - if (!certificate.hasSigner() || !certificate.hasIdentityKey() || !certificate.hasSenderDevice() || !certificate.hasExpires() || !certificate.hasSender()) { + if (!certificate.hasSigner() || + !certificate.hasIdentityKey() || + !certificate.hasSenderDevice() || + !certificate.hasExpires() || + (!certificate.hasSenderUuid() && !certificate.hasSenderE164())) + { throw new InvalidCertificateException("Missing fields"); } this.signer = new ServerCertificate(certificate.getSigner().toByteArray()); this.key = Curve.decodePoint(certificate.getIdentityKey().toByteArray(), 0); - this.sender = certificate.getSender(); + this.senderUuid = certificate.hasSenderUuid() ? Optional.of(certificate.getSenderUuid()) : Optional.absent(); + this.senderE164 = certificate.hasSenderE164() ? Optional.of(certificate.getSenderE164()) : Optional.absent(); this.senderDeviceId = certificate.getSenderDevice(); this.expiration = certificate.getExpires(); @@ -62,8 +70,16 @@ public class SenderCertificate { return senderDeviceId; } + public Optional getSenderUuid() { + return senderUuid; + } + + public Optional getSenderE164() { + return senderE164; + } + public String getSender() { - return sender; + return senderUuid.or(senderE164).orNull(); } public long getExpiration() { diff --git a/protobuf/UnidentifiedDelivery.proto b/protobuf/UnidentifiedDelivery.proto index 6aad325..d5708bc 100644 --- a/protobuf/UnidentifiedDelivery.proto +++ b/protobuf/UnidentifiedDelivery.proto @@ -15,7 +15,8 @@ message ServerCertificate { message SenderCertificate { message Certificate { - optional string sender = 1; + optional string senderE164 = 1; + optional string senderUuid = 6; optional uint32 senderDevice = 2; optional fixed64 expires = 3; optional bytes identityKey = 4; diff --git a/tests/build.gradle b/tests/build.gradle index 013779e..76d6da2 100644 --- a/tests/build.gradle +++ b/tests/build.gradle @@ -6,7 +6,7 @@ repositories { } dependencies { - testCompile 'junit:junit:3.8.2' + testCompile 'junit:junit:4.12' compile project(':java') } \ No newline at end of file diff --git a/tests/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java b/tests/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java index f64b079..121d102 100644 --- a/tests/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java +++ b/tests/src/test/java/org/signal/libsignal/metadata/SealedSessionCipherTest.java @@ -5,6 +5,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import junit.framework.TestCase; +import org.signal.libsignal.metadata.SealedSessionCipher.DecryptionResult; import org.signal.libsignal.metadata.certificate.CertificateValidator; import org.signal.libsignal.metadata.certificate.InvalidCertificateException; import org.signal.libsignal.metadata.certificate.SenderCertificate; @@ -23,6 +24,8 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.Pair; +import java.util.UUID; + public class SealedSessionCipherTest extends TestCase { public void testEncryptDecrypt() throws UntrustedIdentityException, InvalidKeyException, InvalidCertificateException, InvalidProtocolBufferException, InvalidMetadataMessageException, ProtocolDuplicateMessageException, ProtocolUntrustedIdentityException, ProtocolLegacyMessageException, ProtocolInvalidKeyException, InvalidMetadataVersionException, ProtocolInvalidVersionException, ProtocolInvalidMessageException, ProtocolInvalidKeyIdException, ProtocolNoSessionException, SelfSendException { @@ -32,20 +35,21 @@ public class SealedSessionCipherTest extends TestCase { initializeSessions(aliceStore, bobStore); ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, new SignalProtocolAddress("+14151111111", 1)); + SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); + SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1), senderCertificate, "smert za smert".getBytes()); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1)); + SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); - Pair plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); + DecryptionResult plaintext = bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); - assertEquals(new String(plaintext.second()), "smert za smert"); - assertEquals(plaintext.first().getName(), "+14151111111"); - assertEquals(plaintext.first().getDeviceId(), 1); + assertEquals(new String(plaintext.getPaddedMessage()), "smert za smert"); + assertEquals(plaintext.getSenderUuid().get(), "9d0652a3-dcc3-4d11-975f-74d61598733f"); + assertEquals(plaintext.getSenderE164().get(), "+14151111111"); + assertEquals(plaintext.getDeviceId(), 1); } public void testEncryptDecryptUntrusted() throws Exception { @@ -56,13 +60,13 @@ public class SealedSessionCipherTest extends TestCase { ECKeyPair trustRoot = Curve.generateKeyPair(); ECKeyPair falseTrustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(falseTrustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, new SignalProtocolAddress("+14151111111", 1)); + SenderCertificate senderCertificate = createCertificateFor(falseTrustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); + SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1), senderCertificate, "и вот я".getBytes()); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222",1)); + SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); @@ -79,13 +83,13 @@ public class SealedSessionCipherTest extends TestCase { initializeSessions(aliceStore, bobStore); ECKeyPair trustRoot = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, new SignalProtocolAddress("+14151111111", 1)); + SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, aliceStore.getIdentityKeyPair().getPublicKey().getPublicKey(), 31337); + SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1), senderCertificate, "и вот я".getBytes()); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1)); + SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31338); @@ -103,14 +107,14 @@ public class SealedSessionCipherTest extends TestCase { ECKeyPair trustRoot = Curve.generateKeyPair(); ECKeyPair randomKeyPair = Curve.generateKeyPair(); - SenderCertificate senderCertificate = createCertificateFor(trustRoot, "+14151111111", 1, randomKeyPair.getPublicKey(), 31337); - SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, new SignalProtocolAddress("+14151111111", 1)); + SenderCertificate senderCertificate = createCertificateFor(trustRoot, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1, randomKeyPair.getPublicKey(), 31337); + SealedSessionCipher aliceCipher = new SealedSessionCipher(aliceStore, UUID.fromString("9d0652a3-dcc3-4d11-975f-74d61598733f"), "+14151111111", 1); byte[] ciphertext = aliceCipher.encrypt(new SignalProtocolAddress("+14152222222", 1), senderCertificate, "smert za smert".getBytes()); - SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, new SignalProtocolAddress("+14152222222", 1)); + SealedSessionCipher bobCipher = new SealedSessionCipher(bobStore, UUID.fromString("e80f7bbe-5b94-471e-bd8c-2173654ea3d1"), "+14152222222", 1); try { bobCipher.decrypt(new CertificateValidator(trustRoot.getPublicKey()), ciphertext, 31335); @@ -121,7 +125,7 @@ public class SealedSessionCipherTest extends TestCase { - private SenderCertificate createCertificateFor(ECKeyPair trustRoot, String sender, int deviceId, ECPublicKey identityKey, long expires) + private SenderCertificate createCertificateFor(ECKeyPair trustRoot, UUID uuid, String e164, int deviceId, ECPublicKey identityKey, long expires) throws InvalidKeyException, InvalidCertificateException, InvalidProtocolBufferException { ECKeyPair serverKey = Curve.generateKeyPair(); @@ -140,7 +144,8 @@ public class SealedSessionCipherTest extends TestCase { .toByteArray()); byte[] senderCertificateBytes = SignalProtos.SenderCertificate.Certificate.newBuilder() - .setSender(sender) + .setSenderUuid(uuid.toString()) + .setSenderE164(e164) .setSenderDevice(deviceId) .setIdentityKey(ByteString.copyFrom(identityKey.serialize())) .setExpires(expires) diff --git a/tests/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java b/tests/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java index 1b710d3..fc35b52 100644 --- a/tests/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java +++ b/tests/src/test/java/org/signal/libsignal/metadata/certificate/SenderCertificateTest.java @@ -19,7 +19,8 @@ public class SenderCertificateTest extends TestCase { ECKeyPair key = Curve.generateKeyPair(); byte[] certificateBytes = SignalProtos.SenderCertificate.Certificate.newBuilder() - .setSender("+14152222222") + .setSenderUuid("9d0652a3-dcc3-4d11-975f-74d61598733f") + .setSenderE164("+14152222222") .setSenderDevice(1) .setExpires(31337) .setIdentityKey(ByteString.copyFrom(key.getPublicKey().serialize())) @@ -43,7 +44,8 @@ public class SenderCertificateTest extends TestCase { ECKeyPair key = Curve.generateKeyPair(); byte[] certificateBytes = SignalProtos.SenderCertificate.Certificate.newBuilder() - .setSender("+14152222222") + .setSenderUuid("9d0652a3-dcc3-4d11-975f-74d61598733f") + .setSenderE164("+14152222222") .setSenderDevice(1) .setExpires(31337) .setIdentityKey(ByteString.copyFrom(key.getPublicKey().serialize())) @@ -72,7 +74,8 @@ public class SenderCertificateTest extends TestCase { ECKeyPair key = Curve.generateKeyPair(); byte[] certificateBytes = SignalProtos.SenderCertificate.Certificate.newBuilder() - .setSender("+14152222222") + .setSenderUuid("9d0652a3-dcc3-4d11-975f-74d61598733f") + .setSenderE164("+14152222222") .setSenderDevice(1) .setExpires(31337) .setIdentityKey(ByteString.copyFrom(key.getPublicKey().serialize()))