0
0
mirror of https://github.com/signalapp/libsignal.git synced 2024-09-20 12:02:18 +02:00

Merge pull request #417 from signalapp/feature/zkgroup

Add zkgroup to libsignal-client
This commit is contained in:
Jordan Rose 2021-11-08 11:30:16 -08:00 committed by GitHub
commit 3647f2501b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
148 changed files with 6237 additions and 19 deletions

4
Cargo.lock generated
View File

@ -788,6 +788,7 @@ version = "0.1.0"
dependencies = [
"aes-gcm-siv",
"async-trait",
"bincode",
"device-transfer",
"futures-util",
"hkdf",
@ -802,11 +803,13 @@ dependencies = [
"paste",
"rand 0.7.3",
"scopeguard",
"serde",
"sha2",
"signal-crypto",
"signal-neon-futures",
"static_assertions",
"uuid",
"zkgroup",
]
[[package]]
@ -837,6 +840,7 @@ dependencies = [
"log-panics",
"rand 0.7.3",
"signal-crypto",
"zkgroup",
]
[[package]]

View File

@ -33,7 +33,7 @@ sourceSets {
}
dependencies {
testCompile ('junit:junit:3.8.2')
testCompile ('junit:junit:4.12')
}
test {

View File

@ -104,6 +104,14 @@ public final class Native {
public static native byte[] Aes256GcmSiv_Encrypt(long aesGcmSivObj, byte[] ptext, byte[] nonce, byte[] associatedData);
public static native long Aes256GcmSiv_New(byte[] key);
public static native void AuthCredentialPresentation_CheckValidContents(byte[] obj);
public static native int AuthCredentialPresentation_GetRedemptionTime(byte[] presentation);
public static native byte[] AuthCredentialPresentation_GetUuidCiphertext(byte[] presentation);
public static native void AuthCredentialResponse_CheckValidContents(byte[] obj);
public static native void AuthCredential_CheckValidContents(byte[] obj);
public static native void CryptographicHash_Destroy(long handle);
public static native byte[] CryptographicHash_Finalize(long hash);
public static native long CryptographicHash_New(String algo);
@ -146,6 +154,23 @@ public final class Native {
public static native byte[] GroupCipher_DecryptMessage(long sender, byte[] message, SenderKeyStore store, Object ctx);
public static native CiphertextMessage GroupCipher_EncryptMessage(long sender, UUID distributionId, byte[] message, SenderKeyStore store, Object ctx);
public static native void GroupMasterKey_CheckValidContents(byte[] obj);
public static native void GroupPublicParams_CheckValidContents(byte[] obj);
public static native byte[] GroupPublicParams_GetGroupIdentifier(byte[] groupPublicParams);
public static native void GroupSecretParams_CheckValidContents(byte[] obj);
public static native byte[] GroupSecretParams_DecryptBlobWithPadding(byte[] params, byte[] ciphertext);
public static native byte[] GroupSecretParams_DecryptProfileKey(byte[] params, byte[] profileKey, UUID uuid);
public static native UUID GroupSecretParams_DecryptUuid(byte[] params, byte[] uuid);
public static native byte[] GroupSecretParams_DeriveFromMasterKey(byte[] masterKey);
public static native byte[] GroupSecretParams_EncryptBlobWithPaddingDeterministic(byte[] params, byte[] randomness, byte[] plaintext, int paddingLen);
public static native byte[] GroupSecretParams_EncryptProfileKey(byte[] params, byte[] profileKey, UUID uuid);
public static native byte[] GroupSecretParams_EncryptUuid(byte[] params, UUID uuid);
public static native byte[] GroupSecretParams_GenerateDeterministic(byte[] randomness);
public static native byte[] GroupSecretParams_GetMasterKey(byte[] params);
public static native byte[] GroupSecretParams_GetPublicParams(byte[] params);
public static native long GroupSessionBuilder_CreateSenderKeyDistributionMessage(long sender, UUID distributionId, SenderKeyStore store, Object ctx);
public static native void GroupSessionBuilder_ProcessSenderKeyDistributionMessage(long sender, long senderKeyDistributionMessage, SenderKeyStore store, Object ctx);
@ -207,11 +232,48 @@ public final class Native {
public static native int PreKeySignalMessage_GetVersion(long obj);
public static native long PreKeySignalMessage_New(int messageVersion, int registrationId, int preKeyId, int signedPreKeyId, long baseKey, long identityKey, long signalMessage);
public static native void ProfileKeyCiphertext_CheckValidContents(byte[] obj);
public static native void ProfileKeyCommitment_CheckValidContents(byte[] obj);
public static native void ProfileKeyCredentialPresentation_CheckValidContents(byte[] obj);
public static native byte[] ProfileKeyCredentialPresentation_GetProfileKeyCiphertext(byte[] presentation);
public static native byte[] ProfileKeyCredentialPresentation_GetUuidCiphertext(byte[] presentation);
public static native void ProfileKeyCredentialRequestContext_CheckValidContents(byte[] obj);
public static native byte[] ProfileKeyCredentialRequestContext_GetRequest(byte[] context);
public static native void ProfileKeyCredentialRequest_CheckValidContents(byte[] obj);
public static native void ProfileKeyCredentialResponse_CheckValidContents(byte[] obj);
public static native void ProfileKeyCredential_CheckValidContents(byte[] obj);
public static native void ProfileKey_CheckValidContents(byte[] obj);
public static native byte[] ProfileKey_GetCommitment(byte[] profileKey, UUID uuid);
public static native byte[] ProfileKey_GetProfileKeyVersion(byte[] profileKey, UUID uuid);
public static native void ProtocolAddress_Destroy(long handle);
public static native int ProtocolAddress_DeviceId(long obj);
public static native String ProtocolAddress_Name(long obj);
public static native long ProtocolAddress_New(String name, int deviceId);
public static native void ReceiptCredentialPresentation_CheckValidContents(byte[] obj);
public static native long ReceiptCredentialPresentation_GetReceiptExpirationTime(byte[] presentation);
public static native long ReceiptCredentialPresentation_GetReceiptLevel(byte[] presentation);
public static native byte[] ReceiptCredentialPresentation_GetReceiptSerial(byte[] presentation);
public static native void ReceiptCredentialRequestContext_CheckValidContents(byte[] obj);
public static native byte[] ReceiptCredentialRequestContext_GetRequest(byte[] requestContext);
public static native void ReceiptCredentialRequest_CheckValidContents(byte[] obj);
public static native void ReceiptCredentialResponse_CheckValidContents(byte[] obj);
public static native void ReceiptCredential_CheckValidContents(byte[] obj);
public static native long ReceiptCredential_GetReceiptExpirationTime(byte[] receiptCredential);
public static native long ReceiptCredential_GetReceiptLevel(byte[] receiptCredential);
public static native boolean ScannableFingerprint_Compare(byte[] fprint1, byte[] fprint2);
public static native long SealedSessionCipher_DecryptToUsmc(byte[] ctext, IdentityKeyStore identityStore, Object ctx);
@ -267,6 +329,28 @@ public final class Native {
public static native byte[] ServerCertificate_GetSignature(long obj);
public static native long ServerCertificate_New(int keyId, long serverKey, long trustRoot);
public static native void ServerPublicParams_CheckValidContents(byte[] obj);
public static native byte[] ServerPublicParams_CreateAuthCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] authCredential);
public static native byte[] ServerPublicParams_CreateProfileKeyCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] profileKeyCredential);
public static native byte[] ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic(byte[] serverPublicParams, byte[] randomness, UUID uuid, byte[] profileKey);
public static native byte[] ServerPublicParams_CreateReceiptCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] receiptCredential);
public static native byte[] ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] receiptSerial);
public static native byte[] ServerPublicParams_ReceiveAuthCredential(byte[] params, UUID uuid, int redemptionTime, byte[] response);
public static native byte[] ServerPublicParams_ReceiveProfileKeyCredential(byte[] serverPublicParams, byte[] requestContext, byte[] response);
public static native byte[] ServerPublicParams_ReceiveReceiptCredential(byte[] serverPublicParams, byte[] requestContext, byte[] response);
public static native void ServerPublicParams_VerifySignature(byte[] serverPublicParams, byte[] message, byte[] notarySignature);
public static native void ServerSecretParams_CheckValidContents(byte[] obj);
public static native byte[] ServerSecretParams_GenerateDeterministic(byte[] randomness);
public static native byte[] ServerSecretParams_GetPublicParams(byte[] params);
public static native byte[] ServerSecretParams_IssueAuthCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, UUID uuid, int redemptionTime);
public static native byte[] ServerSecretParams_IssueProfileKeyCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, byte[] request, UUID uuid, byte[] commitment);
public static native byte[] ServerSecretParams_IssueReceiptCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, byte[] request, long receiptExpirationTime, long receiptLevel);
public static native byte[] ServerSecretParams_SignDeterministic(byte[] params, byte[] randomness, byte[] message);
public static native void ServerSecretParams_VerifyAuthCredentialPresentation(byte[] serverSecretParams, byte[] groupPublicParams, byte[] presentation);
public static native void ServerSecretParams_VerifyProfileKeyCredentialPresentation(byte[] serverSecretParams, byte[] groupPublicParams, byte[] presentation);
public static native void ServerSecretParams_VerifyReceiptCredentialPresentation(byte[] serverSecretParams, byte[] presentation);
public static native void SessionBuilder_ProcessPreKeyBundle(long bundle, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, Object ctx);
public static native byte[] SessionCipher_DecryptPreKeySignalMessage(long message, long protocolAddress, SessionStore sessionStore, IdentityKeyStore identityKeyStore, PreKeyStore prekeyStore, SignedPreKeyStore signedPrekeyStore, Object ctx);
@ -321,4 +405,6 @@ public final class Native {
public static native long UnidentifiedSenderMessageContent_GetSenderCert(long m);
public static native byte[] UnidentifiedSenderMessageContent_GetSerialized(long obj);
public static native long UnidentifiedSenderMessageContent_New(CiphertextMessage message, long sender, int contentHint, byte[] groupId);
public static native void UuidCiphertext_CheckValidContents(byte[] obj);
}

View File

@ -0,0 +1,18 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
public class InvalidInputException extends Exception {
public InvalidInputException() {
}
public InvalidInputException(String message) {
super(message);
}
}

View File

@ -0,0 +1,9 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
public class InvalidRedemptionTimeException extends Exception {
}

View File

@ -0,0 +1,18 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
import org.signal.zkgroup.internal.ByteArray;
public final class NotarySignature extends ByteArray {
public static final int SIZE = 64;
public NotarySignature(byte[] contents) throws InvalidInputException {
super(contents, SIZE);
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ServerPublicParams extends ByteArray {
public ServerPublicParams(byte[] contents) {
super(contents);
Native.ServerPublicParams_CheckValidContents(contents);
}
public void verifySignature(byte[] message, NotarySignature notarySignature) throws VerificationFailedException {
Native.ServerPublicParams_VerifySignature(contents, message, notarySignature.getInternalContentsForJNI());
}
}

View File

@ -0,0 +1,60 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
import java.security.SecureRandom;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public final class ServerSecretParams extends ByteArray {
public static ServerSecretParams generate() {
return generate(new SecureRandom());
}
public static ServerSecretParams generate(SecureRandom secureRandom) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerSecretParams_GenerateDeterministic(random);
try {
return new ServerSecretParams(newContents);
} catch (IllegalArgumentException e) {
throw new AssertionError(e);
}
}
public ServerSecretParams(byte[] contents) {
super(contents);
Native.ServerSecretParams_CheckValidContents(contents);
}
public ServerPublicParams getPublicParams() {
byte[] newContents = Native.ServerSecretParams_GetPublicParams(contents);
return new ServerPublicParams(newContents);
}
public NotarySignature sign(byte[] message) {
return sign(new SecureRandom(), message);
}
public NotarySignature sign(SecureRandom secureRandom, byte[] message) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerSecretParams_SignDeterministic(contents, random, message);
try {
return new NotarySignature(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,11 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
public class VerificationFailedException extends Exception {
public VerificationFailedException() { super(); }
public VerificationFailedException(String msg) { super(msg); }
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.auth;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class AuthCredential extends ByteArray {
public AuthCredential(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.AuthCredential_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,39 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.auth;
import java.nio.ByteBuffer;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class AuthCredentialPresentation extends ByteArray {
public AuthCredentialPresentation(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.AuthCredentialPresentation_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
public UuidCiphertext getUuidCiphertext() {
byte[] newContents = Native.AuthCredentialPresentation_GetUuidCiphertext(contents);
try {
return new UuidCiphertext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public int getRedemptionTime() {
return Native.AuthCredentialPresentation_GetRedemptionTime(contents);
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.auth;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class AuthCredentialResponse extends ByteArray {
public AuthCredentialResponse(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.AuthCredentialResponse_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,53 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.auth;
import java.security.SecureRandom;
import java.util.UUID;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerPublicParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ClientZkAuthOperations {
private final ServerPublicParams serverPublicParams;
public ClientZkAuthOperations(ServerPublicParams serverPublicParams) {
this.serverPublicParams = serverPublicParams;
}
public AuthCredential receiveAuthCredential(UUID uuid, int redemptionTime, AuthCredentialResponse authCredentialResponse) throws VerificationFailedException {
byte[] newContents = Native.ServerPublicParams_ReceiveAuthCredential(serverPublicParams.getInternalContentsForJNI(), uuid, redemptionTime, authCredentialResponse.getInternalContentsForJNI());
try {
return new AuthCredential(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public AuthCredentialPresentation createAuthCredentialPresentation(GroupSecretParams groupSecretParams, AuthCredential authCredential) {
return createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential);
}
public AuthCredentialPresentation createAuthCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, AuthCredential authCredential) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerPublicParams_CreateAuthCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), authCredential.getInternalContentsForJNI());
try {
return new AuthCredentialPresentation(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.auth;
import java.security.SecureRandom;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerSecretParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.InvalidRedemptionTimeException;
import org.signal.zkgroup.groups.GroupPublicParams;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ServerZkAuthOperations {
private final ServerSecretParams serverSecretParams;
public ServerZkAuthOperations(ServerSecretParams serverSecretParams) {
this.serverSecretParams = serverSecretParams;
}
public AuthCredentialResponse issueAuthCredential(UUID uuid, int redemptionTime) {
return issueAuthCredential(new SecureRandom(), uuid, redemptionTime);
}
public AuthCredentialResponse issueAuthCredential(SecureRandom secureRandom, UUID uuid, int redemptionTime) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerSecretParams_IssueAuthCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, uuid, redemptionTime);
try {
return new AuthCredentialResponse(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation) throws VerificationFailedException, InvalidRedemptionTimeException {
verifyAuthCredentialPresentation(groupPublicParams, authCredentialPresentation, System.currentTimeMillis());
}
public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation, long currentTimeMillis) throws VerificationFailedException, InvalidRedemptionTimeException {
long acceptableStartTime = TimeUnit.MILLISECONDS.convert(authCredentialPresentation.getRedemptionTime()-1, TimeUnit.DAYS);
long acceptableEndTime = TimeUnit.MILLISECONDS.convert(authCredentialPresentation.getRedemptionTime()+2, TimeUnit.DAYS);
if (currentTimeMillis < acceptableStartTime || currentTimeMillis > acceptableEndTime) {
throw new InvalidRedemptionTimeException();
}
Native.ServerSecretParams_VerifyAuthCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), groupPublicParams.getInternalContentsForJNI(), authCredentialPresentation.getInternalContentsForJNI());
}
}

View File

@ -0,0 +1,74 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import java.nio.ByteBuffer;
import java.security.SecureRandom;
import java.util.UUID;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.client.internal.Native;
import org.signal.zkgroup.profiles.ProfileKey;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ClientZkGroupCipher {
private final GroupSecretParams groupSecretParams;
public ClientZkGroupCipher(GroupSecretParams groupSecretParams) {
this.groupSecretParams = groupSecretParams;
}
public UuidCiphertext encryptUuid(UUID uuid) {
byte[] newContents = Native.GroupSecretParams_EncryptUuid(groupSecretParams.getInternalContentsForJNI(), uuid);
try {
return new UuidCiphertext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public UUID decryptUuid(UuidCiphertext uuidCiphertext) throws VerificationFailedException {
return Native.GroupSecretParams_DecryptUuid(groupSecretParams.getInternalContentsForJNI(), uuidCiphertext.getInternalContentsForJNI());
}
public ProfileKeyCiphertext encryptProfileKey(ProfileKey profileKey, UUID uuid) {
byte[] newContents = Native.GroupSecretParams_EncryptProfileKey(groupSecretParams.getInternalContentsForJNI(), profileKey.getInternalContentsForJNI(), uuid);
try {
return new ProfileKeyCiphertext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ProfileKey decryptProfileKey(ProfileKeyCiphertext profileKeyCiphertext, UUID uuid) throws VerificationFailedException {
byte[] newContents = Native.GroupSecretParams_DecryptProfileKey(groupSecretParams.getInternalContentsForJNI(), profileKeyCiphertext.getInternalContentsForJNI(), uuid);
try {
return new ProfileKey(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public byte[] encryptBlob(byte[] plaintext) throws VerificationFailedException {
return encryptBlob(new SecureRandom(), plaintext);
}
public byte[] encryptBlob(SecureRandom secureRandom, byte[] plaintext) throws VerificationFailedException {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
return Native.GroupSecretParams_EncryptBlobWithPaddingDeterministic(groupSecretParams.getInternalContentsForJNI(), random, plaintext, 0);
}
public byte[] decryptBlob(byte[] blobCiphertext) throws VerificationFailedException {
return Native.GroupSecretParams_DecryptBlobWithPadding(groupSecretParams.getInternalContentsForJNI(), blobCiphertext);
}
}

View File

@ -0,0 +1,19 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
public final class GroupIdentifier extends ByteArray {
public static final int SIZE = 32;
public GroupIdentifier(byte[] contents) throws InvalidInputException {
super(contents, SIZE);
}
}

View File

@ -0,0 +1,19 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
public final class GroupMasterKey extends ByteArray {
public static final int SIZE = 32;
public GroupMasterKey(byte[] contents) throws InvalidInputException {
super(contents, SIZE);
}
}

View File

@ -0,0 +1,33 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class GroupPublicParams extends ByteArray {
public GroupPublicParams(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.GroupPublicParams_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
public GroupIdentifier getGroupIdentifier() {
byte[] newContents = Native.GroupPublicParams_GetGroupIdentifier(contents);
try {
return new GroupIdentifier(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,73 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import java.security.SecureRandom;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public final class GroupSecretParams extends ByteArray {
public static GroupSecretParams generate() {
return generate(new SecureRandom());
}
public static GroupSecretParams generate(SecureRandom secureRandom) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.GroupSecretParams_GenerateDeterministic(random);
try {
return new GroupSecretParams(newContents);
} catch (IllegalArgumentException e) {
throw new AssertionError(e);
}
}
public static GroupSecretParams deriveFromMasterKey(GroupMasterKey groupMasterKey) {
byte[] newContents = Native.GroupSecretParams_DeriveFromMasterKey(groupMasterKey.getInternalContentsForJNI());
try {
return new GroupSecretParams(newContents);
} catch (IllegalArgumentException e) {
throw new AssertionError(e);
}
}
public GroupSecretParams(byte[] contents) {
super(contents);
Native.GroupSecretParams_CheckValidContents(contents);
}
public GroupMasterKey getMasterKey() {
byte[] newContents = Native.GroupSecretParams_GetMasterKey(contents);
try {
return new GroupMasterKey(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public GroupPublicParams getPublicParams() {
byte[] newContents = Native.GroupSecretParams_GetPublicParams(contents);
try {
return new GroupPublicParams(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public byte[] serialize() {
return contents.clone();
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCiphertext extends ByteArray {
public ProfileKeyCiphertext(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCiphertext_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.groups;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class UuidCiphertext extends ByteArray {
public UuidCiphertext(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.UuidCiphertext_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.internal;
import org.signal.zkgroup.InvalidInputException;
import java.util.Arrays;
import java.util.Locale;
public abstract class ByteArray {
protected final byte[] contents;
protected ByteArray(byte[] contents) {
this.contents = contents.clone();
}
protected ByteArray(byte[] contents, int expectedLength) throws InvalidInputException {
this.contents = cloneArrayOfLength(contents, expectedLength);
}
private static byte[] cloneArrayOfLength(byte[] bytes, int expectedLength) throws InvalidInputException {
if (bytes.length != expectedLength) {
throw new InvalidInputException(String.format(Locale.US, "Length of array supplied was %d expected %d", bytes.length, expectedLength));
}
return bytes.clone();
}
public byte[] getInternalContentsForJNI() {
return contents;
}
public byte[] serialize() {
return contents.clone();
}
@Override
public int hashCode() {
return getClass().hashCode() * 31 + Arrays.hashCode(contents);
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ByteArray other = (ByteArray) o;
if (contents == other.getInternalContentsForJNI()) return true;
if (contents.length != other.getInternalContentsForJNI().length) return false;
int result = 0;
for (int i = 0; i < contents.length; i++) {
result |= contents[i] ^ other.getInternalContentsForJNI()[i];
}
return result == 0;
}
}

View File

@ -0,0 +1,10 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.internal;
public class Constants {
public static final int RANDOM_LENGTH = 32;
}

View File

@ -0,0 +1,74 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import java.security.SecureRandom;
import java.util.UUID;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerPublicParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ClientZkProfileOperations {
private final ServerPublicParams serverPublicParams;
public ClientZkProfileOperations(ServerPublicParams serverPublicParams) {
this.serverPublicParams = serverPublicParams;
}
public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext(UUID uuid, ProfileKey profileKey) {
return createProfileKeyCredentialRequestContext(new SecureRandom(), uuid, profileKey);
}
public ProfileKeyCredentialRequestContext createProfileKeyCredentialRequestContext(SecureRandom secureRandom, UUID uuid, ProfileKey profileKey) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic(serverPublicParams.getInternalContentsForJNI(), random, uuid, profileKey.getInternalContentsForJNI());
try {
return new ProfileKeyCredentialRequestContext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ProfileKeyCredential receiveProfileKeyCredential(ProfileKeyCredentialRequestContext profileKeyCredentialRequestContext, ProfileKeyCredentialResponse profileKeyCredentialResponse) throws VerificationFailedException {
if (profileKeyCredentialResponse == null) {
throw new VerificationFailedException();
}
byte[] newContents = Native.ServerPublicParams_ReceiveProfileKeyCredential(serverPublicParams.getInternalContentsForJNI(), profileKeyCredentialRequestContext.getInternalContentsForJNI(), profileKeyCredentialResponse.getInternalContentsForJNI());
try {
return new ProfileKeyCredential(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation(GroupSecretParams groupSecretParams, ProfileKeyCredential profileKeyCredential) {
return createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, profileKeyCredential);
}
public ProfileKeyCredentialPresentation createProfileKeyCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, ProfileKeyCredential profileKeyCredential) {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerPublicParams_CreateProfileKeyCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), profileKeyCredential.getInternalContentsForJNI());
try {
return new ProfileKeyCredentialPresentation(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,45 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import java.util.UUID;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKey extends ByteArray {
public ProfileKey(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKey_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
public ProfileKeyCommitment getCommitment(UUID uuid) {
byte[] newContents = Native.ProfileKey_GetCommitment(contents, uuid);
try {
return new ProfileKeyCommitment(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ProfileKeyVersion getProfileKeyVersion(UUID uuid) {
byte[] newContents = Native.ProfileKey_GetProfileKeyVersion(contents, uuid);
try {
return new ProfileKeyVersion(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCommitment extends ByteArray {
public ProfileKeyCommitment(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCommitment_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCredential extends ByteArray {
public ProfileKeyCredential(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCredential_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,44 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.ProfileKeyCiphertext;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCredentialPresentation extends ByteArray {
public ProfileKeyCredentialPresentation(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCredentialPresentation_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
public UuidCiphertext getUuidCiphertext() {
byte[] newContents = Native.ProfileKeyCredentialPresentation_GetUuidCiphertext(contents);
try {
return new UuidCiphertext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ProfileKeyCiphertext getProfileKeyCiphertext() {
byte[] newContents = Native.ProfileKeyCredentialPresentation_GetProfileKeyCiphertext(contents);
try {
return new ProfileKeyCiphertext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCredentialRequest extends ByteArray {
public ProfileKeyCredentialRequest(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCredentialRequest_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,31 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCredentialRequestContext extends ByteArray {
public ProfileKeyCredentialRequestContext(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCredentialRequestContext_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
public ProfileKeyCredentialRequest getRequest() {
byte[] newContents = Native.ProfileKeyCredentialRequestContext_GetRequest(contents);
try {
return new ProfileKeyCredentialRequest(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ProfileKeyCredentialResponse extends ByteArray {
public ProfileKeyCredentialResponse(byte[] contents) throws InvalidInputException {
super(contents);
try {
Native.ProfileKeyCredentialResponse_CheckValidContents(contents);
} catch (IllegalArgumentException e) {
throw new InvalidInputException(e.getMessage());
}
}
}

View File

@ -0,0 +1,35 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import java.io.UnsupportedEncodingException;
public final class ProfileKeyVersion {
private byte[] contents;
public ProfileKeyVersion(byte[] contents) throws InvalidInputException {
if (contents.length != 64) {
throw new InvalidInputException("bad length");
}
this.contents = contents.clone();
}
public ProfileKeyVersion(String contents) throws InvalidInputException, UnsupportedEncodingException {
this(contents.getBytes("UTF-8"));
}
public String serialize() {
try {
return new String(contents, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new AssertionError();
}
}
}

View File

@ -0,0 +1,47 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.profiles;
import java.security.SecureRandom;
import java.util.UUID;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerSecretParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupPublicParams;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ServerZkProfileOperations {
private final ServerSecretParams serverSecretParams;
public ServerZkProfileOperations(ServerSecretParams serverSecretParams) {
this.serverSecretParams = serverSecretParams;
}
public ProfileKeyCredentialResponse issueProfileKeyCredential(ProfileKeyCredentialRequest profileKeyCredentialRequest, UUID uuid, ProfileKeyCommitment profileKeyCommitment) throws VerificationFailedException {
return issueProfileKeyCredential(new SecureRandom(), profileKeyCredentialRequest, uuid, profileKeyCommitment);
}
public ProfileKeyCredentialResponse issueProfileKeyCredential(SecureRandom secureRandom, ProfileKeyCredentialRequest profileKeyCredentialRequest, UUID uuid, ProfileKeyCommitment profileKeyCommitment) throws VerificationFailedException {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerSecretParams_IssueProfileKeyCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, profileKeyCredentialRequest.getInternalContentsForJNI(), uuid, profileKeyCommitment.getInternalContentsForJNI());
try {
return new ProfileKeyCredentialResponse(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public void verifyProfileKeyCredentialPresentation(GroupPublicParams groupPublicParams, ProfileKeyCredentialPresentation profileKeyCredentialPresentation) throws VerificationFailedException {
Native.ServerSecretParams_VerifyProfileKeyCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), groupPublicParams.getInternalContentsForJNI(), profileKeyCredentialPresentation.getInternalContentsForJNI());
}
}

View File

@ -0,0 +1,68 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import java.security.SecureRandom;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerPublicParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ClientZkReceiptOperations {
private final ServerPublicParams serverPublicParams;
public ClientZkReceiptOperations(ServerPublicParams serverPublicParams) {
this.serverPublicParams = serverPublicParams;
}
public ReceiptCredentialRequestContext createReceiptCredentialRequestContext(ReceiptSerial receiptSerial) throws VerificationFailedException {
return createReceiptCredentialRequestContext(new SecureRandom(), receiptSerial);
}
public ReceiptCredentialRequestContext createReceiptCredentialRequestContext(SecureRandom secureRandom, ReceiptSerial receiptSerial) throws VerificationFailedException {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic(serverPublicParams.getInternalContentsForJNI(), random, receiptSerial.getInternalContentsForJNI());
try {
return new ReceiptCredentialRequestContext(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ReceiptCredential receiveReceiptCredential(ReceiptCredentialRequestContext receiptCredentialRequestContext, ReceiptCredentialResponse receiptCredentialResponse) throws VerificationFailedException {
byte[] newContents = Native.ServerPublicParams_ReceiveReceiptCredential(serverPublicParams.getInternalContentsForJNI(), receiptCredentialRequestContext.getInternalContentsForJNI(), receiptCredentialResponse.getInternalContentsForJNI());
try {
return new ReceiptCredential(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public ReceiptCredentialPresentation createReceiptCredentialPresentation(ReceiptCredential receiptCredential) throws VerificationFailedException {
return createReceiptCredentialPresentation(new SecureRandom(), receiptCredential);
}
public ReceiptCredentialPresentation createReceiptCredentialPresentation(SecureRandom secureRandom, ReceiptCredential receiptCredential) throws VerificationFailedException {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerPublicParams_CreateReceiptCredentialPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, receiptCredential.getInternalContentsForJNI());
try {
return new ReceiptCredentialPresentation(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,28 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import java.nio.ByteBuffer;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ReceiptCredential extends ByteArray {
public ReceiptCredential(byte[] contents) throws InvalidInputException {
super(contents);
Native.ReceiptCredential_CheckValidContents(contents);
}
public long getReceiptExpirationTime() {
return Native.ReceiptCredential_GetReceiptExpirationTime(contents);
}
public long getReceiptLevel() {
return Native.ReceiptCredential_GetReceiptLevel(contents);
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import java.nio.ByteBuffer;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ReceiptCredentialPresentation extends ByteArray {
public ReceiptCredentialPresentation(byte[] contents) throws InvalidInputException {
super(contents);
Native.ReceiptCredentialPresentation_CheckValidContents(contents);
}
public long getReceiptExpirationTime() {
return Native.ReceiptCredentialPresentation_GetReceiptExpirationTime(contents);
}
public long getReceiptLevel() {
return Native.ReceiptCredentialPresentation_GetReceiptLevel(contents);
}
public ReceiptSerial getReceiptSerial() {
byte[] newContents = Native.ReceiptCredentialPresentation_GetReceiptSerial(contents);
try {
return new ReceiptSerial(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
}

View File

@ -0,0 +1,17 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ReceiptCredentialRequest extends ByteArray {
public ReceiptCredentialRequest(byte[] contents) throws InvalidInputException {
super(contents);
Native.ReceiptCredentialRequest_CheckValidContents(contents);
}
}

View File

@ -0,0 +1,35 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ReceiptCredentialRequestContext extends ByteArray {
public static final int SIZE = 177;
public ReceiptCredentialRequestContext(byte[] contents) throws InvalidInputException {
super(contents, SIZE);
Native.ReceiptCredentialRequestContext_CheckValidContents(contents);
}
public ReceiptCredentialRequest getRequest() {
byte[] newContents = Native.ReceiptCredentialRequestContext_GetRequest(contents);
try {
return new ReceiptCredentialRequest(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public byte[] serialize() {
return contents.clone();
}
}

View File

@ -0,0 +1,17 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
import org.signal.client.internal.Native;
public final class ReceiptCredentialResponse extends ByteArray {
public ReceiptCredentialResponse(byte[] contents) throws InvalidInputException {
super(contents);
Native.ReceiptCredentialResponse_CheckValidContents(contents);
}
}

View File

@ -0,0 +1,19 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.internal.ByteArray;
public final class ReceiptSerial extends ByteArray {
public static final int SIZE = 16;
public ReceiptSerial(byte[] contents) throws InvalidInputException {
super(contents, SIZE);
}
}

View File

@ -0,0 +1,45 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.receipts;
import java.security.SecureRandom;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.ServerSecretParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.client.internal.Native;
import static org.signal.zkgroup.internal.Constants.RANDOM_LENGTH;
public class ServerZkReceiptOperations {
private final ServerSecretParams serverSecretParams;
public ServerZkReceiptOperations(ServerSecretParams serverSecretParams) {
this.serverSecretParams = serverSecretParams;
}
public ReceiptCredentialResponse issueReceiptCredential(ReceiptCredentialRequest receiptCredentialRequest, long receiptExpirationTime, long receiptLevel) throws VerificationFailedException {
return issueReceiptCredential(new SecureRandom(), receiptCredentialRequest, receiptExpirationTime, receiptLevel);
}
public ReceiptCredentialResponse issueReceiptCredential(SecureRandom secureRandom, ReceiptCredentialRequest receiptCredentialRequest, long receiptExpirationTime, long receiptLevel) throws VerificationFailedException {
byte[] random = new byte[RANDOM_LENGTH];
secureRandom.nextBytes(random);
byte[] newContents = Native.ServerSecretParams_IssueReceiptCredentialDeterministic(serverSecretParams.getInternalContentsForJNI(), random, receiptCredentialRequest.getInternalContentsForJNI(), receiptExpirationTime, receiptLevel);
try {
return new ReceiptCredentialResponse(newContents);
} catch (InvalidInputException e) {
throw new AssertionError(e);
}
}
public void verifyReceiptCredentialPresentation(ReceiptCredentialPresentation receiptCredentialPresentation) throws VerificationFailedException {
Native.ServerSecretParams_VerifyReceiptCredentialPresentation(serverSecretParams.getInternalContentsForJNI(), receiptCredentialPresentation.getInternalContentsForJNI());
}
}

View File

@ -58,6 +58,14 @@ public class Hex {
return out;
}
public static byte[] fromStringCondensedAssert(String encoded) {
try {
return fromStringCondensed(encoded);
} catch (IOException e) {
throw new AssertionError(e);
}
}
private static void appendHexCharWithPrefix(StringBuffer buf, int b) {
buf.append("(byte)0x");
appendHexChar(buf, b);

View File

@ -6,7 +6,7 @@ repositories {
}
dependencies {
testCompile 'junit:junit:3.8.2'
testCompile 'junit:junit:4.12'
compile project(':java')
}

View File

@ -0,0 +1,39 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
import org.junit.Test;
import org.signal.zkgroup.internal.*;
import org.whispersystems.libsignal.util.Hex;
import java.io.IOException;
import java.security.SecureRandom;
import static org.junit.Assert.assertArrayEquals;
public final class RandomnessTest extends SecureRandomTest {
@Test
public void generate_usesSecureRandom() throws IOException {
byte[] array = Hex.fromStringCondensed("e18de7dfe7195f0b9320e309cd3ed3765dcf54a09be57813ee69f5ea35867689");
SecureRandom secureRandom = createSecureRandom(array);
byte[] random = new byte[array.length];
secureRandom.nextBytes(random);
assertArrayEquals(array, random);
}
@Test
public void generate_usesSecureRandom_alternativeValues() throws IOException {
byte[] array = Hex.fromStringCondensed("ba8a89a05eaf51cac3ce35256199b38a18e0e1fa16f1443db8e34b0489739b80");
SecureRandom secureRandom = createSecureRandom(array);
byte[] random = new byte[array.length];
secureRandom.nextBytes(random);
assertArrayEquals(array, random);
}
}

View File

@ -0,0 +1,49 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup;
import java.security.SecureRandom;
import java.security.SecureRandomSpi;
import java.util.Arrays;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
public abstract class SecureRandomTest {
private static class MockRandomSpi extends SecureRandomSpi {
private byte[] bytes;
private MockRandomSpi(byte[] bytes) {
this.bytes = bytes;
}
protected byte[] engineGenerateSeed(int numBytes) {
throw new AssertionError("should only use nextBytes()");
}
protected void engineNextBytes(byte[] outBytes) {
assertNotNull("Bytes have been used", bytes);
assertEquals("createSecureRandom was setup with wrong number of bytes", bytes.length, outBytes.length);
System.arraycopy(bytes, 0, outBytes, 0, bytes.length);
bytes = null;
}
protected void engineSetSeed(byte[] seed) {
throw new AssertionError("should only use nextBytes()");
}
}
private static class MockRandom extends SecureRandom {
private MockRandom(byte[] bytes) {
super(new MockRandomSpi(bytes), new SecureRandom().getProvider());
}
}
public static SecureRandom createSecureRandom(final byte[] nextRandom) {
return new MockRandom(nextRandom);
}
}

View File

@ -0,0 +1,370 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
package org.signal.zkgroup.integrationtests;
import java.io.UnsupportedEncodingException;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.NotarySignature;
import org.signal.zkgroup.SecureRandomTest;
import org.signal.zkgroup.ServerPublicParams;
import org.signal.zkgroup.ServerSecretParams;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.InvalidRedemptionTimeException;
import org.signal.zkgroup.auth.AuthCredential;
import org.signal.zkgroup.auth.AuthCredentialPresentation;
import org.signal.zkgroup.auth.AuthCredentialResponse;
import org.signal.zkgroup.auth.ClientZkAuthOperations;
import org.signal.zkgroup.auth.ServerZkAuthOperations;
import org.signal.zkgroup.groups.ClientZkGroupCipher;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.GroupPublicParams;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.signal.zkgroup.groups.ProfileKeyCiphertext;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.signal.zkgroup.profiles.ClientZkProfileOperations;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCommitment;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.signal.zkgroup.profiles.ProfileKeyCredentialPresentation;
import org.signal.zkgroup.profiles.ProfileKeyCredentialRequest;
import org.signal.zkgroup.profiles.ProfileKeyCredentialRequestContext;
import org.signal.zkgroup.profiles.ProfileKeyCredentialResponse;
import org.signal.zkgroup.profiles.ProfileKeyVersion;
import org.signal.zkgroup.profiles.ServerZkProfileOperations;
import org.whispersystems.libsignal.util.Hex;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.UUID;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
public final class ZkGroupTest extends SecureRandomTest {
private static final UUID TEST_UUID = UUID.fromString("00010203-0405-0607-0809-0a0b0c0d0e0f");
private static final byte[] TEST_ARRAY_32 = Hex.fromStringCondensedAssert("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
private static final byte[] TEST_ARRAY_32_1 = Hex.fromStringCondensedAssert("6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283");
private static final byte[] TEST_ARRAY_32_2 = Hex.fromStringCondensedAssert("c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7");
private static final byte[] TEST_ARRAY_32_3 = { 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32 };
private static final byte[] TEST_ARRAY_32_4 = {
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
28, 29, 30, 31, 32, 33};
private static final byte[] TEST_ARRAY_32_5 = Hex.fromStringCondensedAssert("030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122");
private static final byte[] authPresentationResult = Hex.fromStringCondensedAssert(
"000cde979737ed30bbeb16362e4e076945ce02069f727b0ed4c3c33c011e82546e1cdf081fbdf37c03a851ad060bdcbf6378cb4cb16dc3154d08de5439b5323203729d1841b517033af2fd177d30491c138ae723655734f6e5cc01c00696f4e92096d8c33df26ba2a820d42e9735d30f8eeef96d399079073c099f7035523bfe716638659319d3c36ad34c00ef8850f663c4d93030235074312a8878b6a5c5df4fbc7d32935278bfa5996b44ab75d6f06f4c30b98640ad5de74742656c8977567de000000000000000fde69f82ad2dcb4909650ac6b2573841af568fef822b32b45f625a764691a704d11b6f385261468117ead57fa623338e21c66ed846ab65809fcac158066d8e0e444077b99540d886e7dc09555dd6faea2cd3697f1e089f82d54e5d0fe4a185008b5cbc3979391ad71686bc03be7b00ea7e42c08d9f1d75c3a56c27ae2467b80636c0b5343eda7cd578ba88ddb7a0766568477fed63cf531862122c6c15b4a707973d41782cfc0ef4fe6c3115988a2e339015938d2df0a5d30237a2592cc10c05a9e4ef6b695bca99736b1a49ea39606a381ecfb05efe60d28b54823ec5a3680c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100");
private static final byte[] profileKeyPresentationResult = Hex.fromStringCondensedAssert(
"00c4d19bca1ae844585168869da4133e0e0bb59f2ce17b7ac65bff5da9610eca103429d8022a94bae2b5b1057b5595b8ad70bfc2d0e1ad662cb75e6bae0782be6f00e3db793bc28561f0196c2e74da6f303fa8bcb70c94096671b73f7b3a95fb002200d5b9180fa0ef7d3014d01344145b4d38480d72ff25c24294e305e5705072e0d32cc4e84f5caf31486089a4b934c80c92eba43472ff23a5af93c397535d33801f0e6fc6eb2ee0d117f03bb4fd38a8b9c88d94708131f38742ca804a3cfc4f9476bc2d03f53d17001c36478afbe9cc535a224b2df6b2b08bef06cbc7d4dc42ccfc3459f7ac5c4419ae9f3c8a161d554d047778943216240858da3b1101984c40010000000000007a01eea6b2adad14d71ab8b8e411bef3c596e954b70e4031570cb1abd7e932083241f1caca3116708fa4319fbbdfe351376c23644ae09a42f0155db4996c9d0c7ffc8521c1914c0e1a20ae51e65df64dd5e6e5985b3d9d31732046d2d77f9c08aaccf056b84026073976eec6164cbdaee5d9e76e497f0c290af681cabd5c5101282abb26c3680d6087ce053310fe8a94f59d8ae23caac5fc0ed0c379888abf028a6f29f89d4fe2acc1706341b2245ba1885bca57e1e27ccf7ed79371500965009f960c2ba00fad3e93383b87ce119cac0b3360eb99284ce78e2cbed680f7960373e0ab75c190254160c2353614109489e653c9b2e1c93f92c7c5ad583d987a04bd3541b24485c33ea49bac43c87c4ab3efde2e2d7ec10a40be544199f925b20b2c55542bc56410571e41cd8e0286f609a66768b5061ccb4777af32309928dd09765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547448c03ab4afbf6b8fb0e126c037a0ad4094600dd0e0634d76f88c21087f3cfb485a89bc1e3abc4c95041d1d170eccf02933ec5393d4be1dc573f83c33d3b9a746");
@Test
public void testAuthIntegration() throws VerificationFailedException, InvalidInputException, InvalidRedemptionTimeException {
UUID uuid = TEST_UUID;
int redemptionTime = 123456;
// Generate keys (client's are per-group, server's are not)
// ---
// SERVER
ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32));
ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams);
// CLIENT
GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize());
GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams();
// SERVER
// Issue credential
AuthCredentialResponse authCredentialResponse = serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), uuid, redemptionTime);
// CLIENT
// Receive credential
ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams);
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams );
AuthCredential authCredential = clientZkAuthCipher.receiveAuthCredential(uuid, redemptionTime, authCredentialResponse);
// Create and decrypt user entry
UuidCiphertext uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid);
UUID plaintext = clientZkGroupCipher.decryptUuid(uuidCiphertext);
assertEquals(uuid, plaintext);
// Create presentation
AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential);
// Verify presentation, using times at the edge of the acceptable window
UuidCiphertext uuidCiphertextRecv = presentation.getUuidCiphertext();
assertArrayEquals(uuidCiphertext.serialize(), uuidCiphertextRecv.serialize());
assertEquals(presentation.getRedemptionTime(), redemptionTime);
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(123455L, TimeUnit.DAYS));
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(123458L, TimeUnit.DAYS));
try {
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(123455L, TimeUnit.DAYS) - 1L);
throw new AssertionError("verifyAuthCredentialPresentation should fail #1!");
} catch(InvalidRedemptionTimeException e) {
// good
}
try {
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(123458L, TimeUnit.DAYS) + 1L);
throw new AssertionError("verifyAuthCredentialPresentation should fail #2!");
} catch(InvalidRedemptionTimeException e) {
// good
}
assertArrayEquals(presentation.serialize(), authPresentationResult);
}
@Test
public void testAuthIntegrationCurrentTime() throws VerificationFailedException, InvalidInputException, InvalidRedemptionTimeException {
// This test is mostly the same as testAuthIntegration() except instead of using a hardcoded
// redemption date to compare against test vectors, it uses the current time
UUID uuid = TEST_UUID;
int redemptionTime = (int)TimeUnit.DAYS.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS);
// Generate keys (client's are per-group, server's are not)
// ---
// SERVER
ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32));
ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
ServerZkAuthOperations serverZkAuth = new ServerZkAuthOperations(serverSecretParams);
// CLIENT
GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize());
GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams();
// SERVER
// Issue credential
AuthCredentialResponse authCredentialResponse = serverZkAuth.issueAuthCredential(createSecureRandom(TEST_ARRAY_32_2), uuid, redemptionTime);
// CLIENT
// Receive credential
ClientZkAuthOperations clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams);
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher (groupSecretParams );
AuthCredential authCredential = clientZkAuthCipher.receiveAuthCredential(uuid, redemptionTime, authCredentialResponse);
// Create and decrypt user entry
UuidCiphertext uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid);
UUID plaintext = clientZkGroupCipher.decryptUuid(uuidCiphertext);
assertEquals(uuid, plaintext);
// Create presentation
AuthCredentialPresentation presentation = clientZkAuthCipher.createAuthCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, authCredential);
// Verify presentation, using times at the edge of the acceptable window
UuidCiphertext uuidCiphertextRecv = presentation.getUuidCiphertext();
assertArrayEquals(uuidCiphertext.serialize(), uuidCiphertextRecv.serialize());
assertEquals(presentation.getRedemptionTime(), redemptionTime);
// By default the library uses the current time
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation);
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(redemptionTime - 1L, TimeUnit.DAYS));
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(redemptionTime + 2L, TimeUnit.DAYS));
try {
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(redemptionTime - 1L, TimeUnit.DAYS) - 1L);
throw new AssertionError("verifyAuthCredentialPresentation (current time) should fail #1!");
} catch(InvalidRedemptionTimeException e) {
// good
}
try {
serverZkAuth.verifyAuthCredentialPresentation(groupPublicParams, presentation, TimeUnit.MILLISECONDS.convert(redemptionTime + 2L, TimeUnit.DAYS) + 1L);
throw new AssertionError("verifyAuthCredentialPresentation (current time) should fail #2!");
} catch(InvalidRedemptionTimeException e) {
// good
}
}
@Test
public void testProfileKeyIntegration() throws VerificationFailedException, InvalidInputException, UnsupportedEncodingException {
UUID uuid = TEST_UUID;
int redemptionTime = 1234567;
// Generate keys (client's are per-group, server's are not)
// ---
// SERVER
ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32));
ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
ServerZkProfileOperations serverZkProfile = new ServerZkProfileOperations(serverSecretParams);
// CLIENT
GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
assertArrayEquals(groupSecretParams.getMasterKey().serialize(), masterKey.serialize());
GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams();
ClientZkProfileOperations clientZkProfileCipher = new ClientZkProfileOperations(serverPublicParams);
ProfileKey profileKey = new ProfileKey(TEST_ARRAY_32_1);
ProfileKeyCommitment profileKeyCommitment = profileKey.getCommitment(uuid);
// Create context and request
ProfileKeyCredentialRequestContext context = clientZkProfileCipher.createProfileKeyCredentialRequestContext(createSecureRandom(TEST_ARRAY_32_3), uuid, profileKey);
ProfileKeyCredentialRequest request = context.getRequest();
// SERVER
ProfileKeyCredentialResponse response = serverZkProfile.issueProfileKeyCredential(createSecureRandom(TEST_ARRAY_32_4), request, uuid, profileKeyCommitment);
// CLIENT
// Gets stored profile credential
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
ProfileKeyCredential profileKeyCredential = clientZkProfileCipher.receiveProfileKeyCredential(context, response);
// Create encrypted UID and profile key
UuidCiphertext uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid);
UUID plaintext = clientZkGroupCipher.decryptUuid(uuidCiphertext);
assertEquals(plaintext, uuid);
ProfileKeyCiphertext profileKeyCiphertext = clientZkGroupCipher.encryptProfileKey(profileKey, uuid);
ProfileKey decryptedProfileKey = clientZkGroupCipher.decryptProfileKey(profileKeyCiphertext, uuid);
assertArrayEquals(profileKey.serialize(), decryptedProfileKey.serialize());
ProfileKeyCredentialPresentation presentation = clientZkProfileCipher.createProfileKeyCredentialPresentation(createSecureRandom(TEST_ARRAY_32_5), groupSecretParams, profileKeyCredential);
assertArrayEquals(presentation.serialize(), profileKeyPresentationResult);
// Verify presentation
serverZkProfile.verifyProfileKeyCredentialPresentation(groupPublicParams, presentation);
UuidCiphertext uuidCiphertextRecv = presentation.getUuidCiphertext();
assertArrayEquals(uuidCiphertext.serialize(), uuidCiphertextRecv.serialize());
ProfileKeyVersion pkvB = profileKey.getProfileKeyVersion(uuid);
ProfileKeyVersion pkvC = new ProfileKeyVersion(pkvB.serialize());
if (!pkvB.serialize().equals(pkvC.serialize()))
throw new AssertionError();
}
@Test
public void testServerSignatures() throws VerificationFailedException {
ServerSecretParams serverSecretParams = ServerSecretParams.generate(createSecureRandom(TEST_ARRAY_32));
ServerPublicParams serverPublicParams = serverSecretParams.getPublicParams();
byte[] message = TEST_ARRAY_32_1;
NotarySignature signature = serverSecretParams.sign(createSecureRandom(TEST_ARRAY_32_2), message);
serverPublicParams.verifySignature(message, signature);
assertByteArray(
"87d354564d35ef91edba851e0815612e864c227a0471d50c270698604406d003a55473f576cf241fc6b41c6b16e5e63b333c02fe4a33858022fdd7a4ab367b06", signature.serialize());
byte[] alteredMessage = message.clone();
alteredMessage[0] ^= 1;
try {
serverPublicParams.verifySignature(alteredMessage, signature);
throw new AssertionError("signature validation should have failed!");
} catch (VerificationFailedException e) {
// good
}
}
@Test
public void testGroupIdentifier() throws VerificationFailedException {
GroupSecretParams groupSecretParams = GroupSecretParams.generate(createSecureRandom(TEST_ARRAY_32));
GroupPublicParams groupPublicParams = groupSecretParams.getPublicParams();
//assertByteArray("31f2c60f86f4c5996e9e2568355591d9", groupPublicParams.getGroupIdentifier().serialize());
}
@Test(expected = InvalidInputException.class)
public void testInvalidSerialized() throws InvalidInputException {
byte[] ckp = new byte[97]; // right size, wrong contents
Arrays.fill(ckp, (byte) -127);
GroupPublicParams groupSecretParams = new GroupPublicParams(ckp);
}
@Test(expected = IllegalArgumentException.class)
public void testInvalidSerializedInfallible() {
byte[] ckp = new byte[289]; // right size, wrong contents
Arrays.fill(ckp, (byte) -127);
GroupSecretParams groupSecretParams = new GroupSecretParams(ckp);
}
@Test(expected = InvalidInputException.class)
public void testWrongSizeSerialized() throws InvalidInputException {
byte[] ckp = new byte[5]; // right size, wrong contents
Arrays.fill(ckp, (byte) -127);
GroupPublicParams groupSecretParams = new GroupPublicParams(ckp);
}
@Test(expected = IllegalArgumentException.class)
public void testWrongSizeSerializedInfallible() {
byte[] ckp = new byte[5]; // right size, wrong contents
Arrays.fill(ckp, (byte) -127);
GroupSecretParams groupSecretParams = new GroupSecretParams(ckp);
}
@Test
public void testBlobEncryption() throws InvalidInputException, VerificationFailedException {
GroupMasterKey masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
ClientZkGroupCipher clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
byte[] plaintext = Hex.fromStringCondensedAssert("0102030405060708111213141516171819");
byte[] ciphertext = Hex.fromStringCondensedAssert("dd4d032ca9bb75a4a78541b90cb4e95743f3b0dabfc7e11101b098e34f6cf6513940a04c1f20a302692afdc7087f10196000");
byte[] ciphertextPaddedWith257 = Hex.fromStringCondensedAssert("5cb5b7bff06e85d929f3511fd194e638cf32a47663868bc8e64d98fb1bbe435ebd21c763ce2d42e85a1b2c169f12f9818ddadcf4b491398b7c5d46a224e1582749f5e2a4a2294caaaaab843a1b7cf6426fd543d09ff32a4ba5f319ca4442b4da34b3e2b5b4f8a52fdc4b484ea86b33db3ebb758dbd9614178f0e4e1f9b2b914f1e786936b62ed2b58b7ae3cb3e7ae0835b9516959837406662b85eac740cef83b60b5aaeaaab95643c2bef8ce87358fabff9d690052beb9e52d0c947e7c986b2f3ce3b7161cec72c08e2c4ade3debe3792d736c0457bc352afb8b6caa48a5b92c1ec05ba808ba8f94c6572ebbf29818912344987573de419dbcc7f1ea0e4b2dd4077b76b381819747ac332e46fa23abfc3338e2f4b081a8a53cba0988eef116764d944f1ce3f20a302692afdc7087f10196000");
byte[] ciphertext2 = clientZkGroupCipher.encryptBlob(createSecureRandom(TEST_ARRAY_32_2), plaintext);
byte[] plaintext2 = clientZkGroupCipher.decryptBlob(ciphertext2);
assertArrayEquals(plaintext, plaintext2);
assertArrayEquals(ciphertext, ciphertext2);
byte[] plaintext257 = clientZkGroupCipher.decryptBlob(ciphertextPaddedWith257);
assertArrayEquals(plaintext, plaintext257);
}
private void assertByteArray(String expectedAsHex, byte[] actual) {
byte[] expectedBytes = Hex.fromStringCondensedAssert(expectedAsHex);
assertArrayEquals(expectedBytes, actual);
}
}

View File

@ -59,7 +59,10 @@ module.exports = {
'no-underscore-dangle': 'off',
// useful for unused parameters
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-unused-vars': [
'error',
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
],
// though we have a logger, we still remap console to log to disk
'no-console': 'error',

View File

@ -13,6 +13,7 @@ export enum ErrorCode {
SealedSenderSelfSend,
UntrustedIdentity,
InvalidRegistrationId,
VerificationFailed,
}
export class SignalClientErrorBase extends Error {
@ -85,9 +86,14 @@ export type InvalidRegistrationIdError = SignalClientErrorCommon & {
addr: ProtocolAddress;
};
export type VerificationFailedError = SignalClientErrorCommon & {
code: ErrorCode.VerificationFailed;
};
export type SignalClientError =
| GenericError
| DuplicatedMessageError
| SealedSenderSelfSendError
| UntrustedIdentityError
| InvalidRegistrationIdError;
| InvalidRegistrationIdError
| VerificationFailedError;

89
node/Native.d.ts vendored
View File

@ -41,12 +41,20 @@ interface Wrapper<T> {
readonly _nativeHandle: T
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Serialized<T> = Buffer;
export function registerErrors(errorsModule: Record<string, unknown>): void;
export const enum LogLevel { Error = 1, Warn, Info, Debug, Trace }
export function Aes256GcmSiv_Decrypt(aesGcmSiv: Wrapper<Aes256GcmSiv>, ctext: Buffer, nonce: Buffer, associatedData: Buffer): Buffer;
export function Aes256GcmSiv_Encrypt(aesGcmSivObj: Wrapper<Aes256GcmSiv>, ptext: Buffer, nonce: Buffer, associatedData: Buffer): Buffer;
export function Aes256GcmSiv_New(key: Buffer): Aes256GcmSiv;
export function AuthCredentialPresentation_CheckValidContents(Obj: Serialized<AuthCredentialPresentation>): void;
export function AuthCredentialPresentation_GetRedemptionTime(presentation: Serialized<AuthCredentialPresentation>): number;
export function AuthCredentialPresentation_GetUuidCiphertext(presentation: Serialized<AuthCredentialPresentation>): Serialized<UuidCiphertext>;
export function AuthCredentialResponse_CheckValidContents(Obj: Serialized<AuthCredentialResponse>): void;
export function AuthCredential_CheckValidContents(Obj: Serialized<AuthCredential>): void;
export function CiphertextMessage_FromPlaintextContent(m: Wrapper<PlaintextContent>): CiphertextMessage;
export function CiphertextMessage_Serialize(obj: Wrapper<CiphertextMessage>): Buffer;
export function CiphertextMessage_Type(msg: Wrapper<CiphertextMessage>): number;
@ -62,6 +70,20 @@ export function Fingerprint_New(iterations: number, version: number, localIdenti
export function Fingerprint_ScannableEncoding(obj: Wrapper<Fingerprint>): Buffer;
export function GroupCipher_DecryptMessage(sender: Wrapper<ProtocolAddress>, message: Buffer, store: SenderKeyStore, ctx: null): Promise<Buffer>;
export function GroupCipher_EncryptMessage(sender: Wrapper<ProtocolAddress>, distributionId: Uuid, message: Buffer, store: SenderKeyStore, ctx: null): Promise<CiphertextMessage>;
export function GroupMasterKey_CheckValidContents(Obj: Serialized<GroupMasterKey>): void;
export function GroupPublicParams_CheckValidContents(Obj: Serialized<GroupPublicParams>): void;
export function GroupPublicParams_GetGroupIdentifier(groupPublicParams: Serialized<GroupPublicParams>): Buffer;
export function GroupSecretParams_CheckValidContents(Obj: Serialized<GroupSecretParams>): void;
export function GroupSecretParams_DecryptBlobWithPadding(params: Serialized<GroupSecretParams>, ciphertext: Buffer): Buffer;
export function GroupSecretParams_DecryptProfileKey(params: Serialized<GroupSecretParams>, profileKey: Serialized<ProfileKeyCiphertext>, uuid: Uuid): Serialized<ProfileKey>;
export function GroupSecretParams_DecryptUuid(params: Serialized<GroupSecretParams>, uuid: Serialized<UuidCiphertext>): Uuid;
export function GroupSecretParams_DeriveFromMasterKey(masterKey: Serialized<GroupMasterKey>): Serialized<GroupSecretParams>;
export function GroupSecretParams_EncryptBlobWithPaddingDeterministic(params: Serialized<GroupSecretParams>, randomness: Buffer, plaintext: Buffer, paddingLen: number): Buffer;
export function GroupSecretParams_EncryptProfileKey(params: Serialized<GroupSecretParams>, profileKey: Serialized<ProfileKey>, uuid: Uuid): Serialized<ProfileKeyCiphertext>;
export function GroupSecretParams_EncryptUuid(params: Serialized<GroupSecretParams>, uuid: Uuid): Serialized<UuidCiphertext>;
export function GroupSecretParams_GenerateDeterministic(randomness: Buffer): Serialized<GroupSecretParams>;
export function GroupSecretParams_GetMasterKey(params: Serialized<GroupSecretParams>): Serialized<GroupMasterKey>;
export function GroupSecretParams_GetPublicParams(params: Serialized<GroupSecretParams>): Serialized<GroupPublicParams>;
export function HKDF_DeriveSecrets(outputLength: number, ikm: Buffer, label: Buffer | null, salt: Buffer | null): Buffer;
export function HsmEnclaveClient_CompleteHandshake(cli: Wrapper<HsmEnclaveClient>, handshakeReceived: Buffer): void;
export function HsmEnclaveClient_EstablishedRecv(cli: Wrapper<HsmEnclaveClient>, receivedCiphertext: Buffer): Buffer;
@ -101,6 +123,19 @@ export function PrivateKey_Generate(): PrivateKey;
export function PrivateKey_GetPublicKey(k: Wrapper<PrivateKey>): PublicKey;
export function PrivateKey_Serialize(obj: Wrapper<PrivateKey>): Buffer;
export function PrivateKey_Sign(key: Wrapper<PrivateKey>, message: Buffer): Buffer;
export function ProfileKeyCiphertext_CheckValidContents(Obj: Serialized<ProfileKeyCiphertext>): void;
export function ProfileKeyCommitment_CheckValidContents(Obj: Serialized<ProfileKeyCommitment>): void;
export function ProfileKeyCredentialPresentation_CheckValidContents(Obj: Serialized<ProfileKeyCredentialPresentation>): void;
export function ProfileKeyCredentialPresentation_GetProfileKeyCiphertext(presentation: Serialized<ProfileKeyCredentialPresentation>): Serialized<ProfileKeyCiphertext>;
export function ProfileKeyCredentialPresentation_GetUuidCiphertext(presentation: Serialized<ProfileKeyCredentialPresentation>): Serialized<UuidCiphertext>;
export function ProfileKeyCredentialRequestContext_CheckValidContents(Obj: Serialized<ProfileKeyCredentialRequestContext>): void;
export function ProfileKeyCredentialRequestContext_GetRequest(context: Serialized<ProfileKeyCredentialRequestContext>): Serialized<ProfileKeyCredentialRequest>;
export function ProfileKeyCredentialRequest_CheckValidContents(Obj: Serialized<ProfileKeyCredentialRequest>): void;
export function ProfileKeyCredentialResponse_CheckValidContents(Obj: Serialized<ProfileKeyCredentialResponse>): void;
export function ProfileKeyCredential_CheckValidContents(Obj: Serialized<ProfileKeyCredential>): void;
export function ProfileKey_CheckValidContents(Obj: Serialized<ProfileKey>): void;
export function ProfileKey_GetCommitment(profileKey: Serialized<ProfileKey>, uuid: Uuid): Serialized<ProfileKeyCommitment>;
export function ProfileKey_GetProfileKeyVersion(profileKey: Serialized<ProfileKey>, uuid: Uuid): Buffer;
export function ProtocolAddress_DeviceId(obj: Wrapper<ProtocolAddress>): number;
export function ProtocolAddress_Name(obj: Wrapper<ProtocolAddress>): string;
export function ProtocolAddress_New(name: string, deviceId: number): ProtocolAddress;
@ -109,6 +144,17 @@ export function PublicKey_Deserialize(data: Buffer): PublicKey;
export function PublicKey_GetPublicKeyBytes(obj: Wrapper<PublicKey>): Buffer;
export function PublicKey_Serialize(obj: Wrapper<PublicKey>): Buffer;
export function PublicKey_Verify(key: Wrapper<PublicKey>, message: Buffer, signature: Buffer): boolean;
export function ReceiptCredentialPresentation_CheckValidContents(Obj: Serialized<ReceiptCredentialPresentation>): void;
export function ReceiptCredentialPresentation_GetReceiptExpirationTime(presentation: Serialized<ReceiptCredentialPresentation>): Buffer;
export function ReceiptCredentialPresentation_GetReceiptLevel(presentation: Serialized<ReceiptCredentialPresentation>): Buffer;
export function ReceiptCredentialPresentation_GetReceiptSerial(presentation: Serialized<ReceiptCredentialPresentation>): Buffer;
export function ReceiptCredentialRequestContext_CheckValidContents(Obj: Serialized<ReceiptCredentialRequestContext>): void;
export function ReceiptCredentialRequestContext_GetRequest(requestContext: Serialized<ReceiptCredentialRequestContext>): Serialized<ReceiptCredentialRequest>;
export function ReceiptCredentialRequest_CheckValidContents(Obj: Serialized<ReceiptCredentialRequest>): void;
export function ReceiptCredentialResponse_CheckValidContents(Obj: Serialized<ReceiptCredentialResponse>): void;
export function ReceiptCredential_CheckValidContents(Obj: Serialized<ReceiptCredential>): void;
export function ReceiptCredential_GetReceiptExpirationTime(receiptCredential: Serialized<ReceiptCredential>): Buffer;
export function ReceiptCredential_GetReceiptLevel(receiptCredential: Serialized<ReceiptCredential>): Buffer;
export function ScannableFingerprint_Compare(fprint1: Buffer, fprint2: Buffer): boolean;
export function SealedSenderDecryptionResult_GetDeviceId(obj: Wrapper<SealedSenderDecryptionResult>): number;
export function SealedSenderDecryptionResult_GetSenderE164(obj: Wrapper<SealedSenderDecryptionResult>): string | null;
@ -158,6 +204,26 @@ export function ServerCertificate_GetKeyId(obj: Wrapper<ServerCertificate>): num
export function ServerCertificate_GetSerialized(obj: Wrapper<ServerCertificate>): Buffer;
export function ServerCertificate_GetSignature(obj: Wrapper<ServerCertificate>): Buffer;
export function ServerCertificate_New(keyId: number, serverKey: Wrapper<PublicKey>, trustRoot: Wrapper<PrivateKey>): ServerCertificate;
export function ServerPublicParams_CheckValidContents(Obj: Serialized<ServerPublicParams>): void;
export function ServerPublicParams_CreateAuthCredentialPresentationDeterministic(serverPublicParams: Serialized<ServerPublicParams>, randomness: Buffer, groupSecretParams: Serialized<GroupSecretParams>, authCredential: Serialized<AuthCredential>): Serialized<AuthCredentialPresentation>;
export function ServerPublicParams_CreateProfileKeyCredentialPresentationDeterministic(serverPublicParams: Serialized<ServerPublicParams>, randomness: Buffer, groupSecretParams: Serialized<GroupSecretParams>, profileKeyCredential: Serialized<ProfileKeyCredential>): Serialized<ProfileKeyCredentialPresentation>;
export function ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic(serverPublicParams: Serialized<ServerPublicParams>, randomness: Buffer, uuid: Uuid, profileKey: Serialized<ProfileKey>): Serialized<ProfileKeyCredentialRequestContext>;
export function ServerPublicParams_CreateReceiptCredentialPresentationDeterministic(serverPublicParams: Serialized<ServerPublicParams>, randomness: Buffer, receiptCredential: Serialized<ReceiptCredential>): Serialized<ReceiptCredentialPresentation>;
export function ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic(serverPublicParams: Serialized<ServerPublicParams>, randomness: Buffer, receiptSerial: Buffer): Serialized<ReceiptCredentialRequestContext>;
export function ServerPublicParams_ReceiveAuthCredential(params: Serialized<ServerPublicParams>, uuid: Uuid, redemptionTime: number, response: Serialized<AuthCredentialResponse>): Serialized<AuthCredential>;
export function ServerPublicParams_ReceiveProfileKeyCredential(serverPublicParams: Serialized<ServerPublicParams>, requestContext: Serialized<ProfileKeyCredentialRequestContext>, response: Serialized<ProfileKeyCredentialResponse>): Serialized<ProfileKeyCredential>;
export function ServerPublicParams_ReceiveReceiptCredential(serverPublicParams: Serialized<ServerPublicParams>, requestContext: Serialized<ReceiptCredentialRequestContext>, response: Serialized<ReceiptCredentialResponse>): Serialized<ReceiptCredential>;
export function ServerPublicParams_VerifySignature(serverPublicParams: Serialized<ServerPublicParams>, message: Buffer, notarySignature: Buffer): void;
export function ServerSecretParams_CheckValidContents(Obj: Serialized<ServerSecretParams>): void;
export function ServerSecretParams_GenerateDeterministic(randomness: Buffer): Serialized<ServerSecretParams>;
export function ServerSecretParams_GetPublicParams(params: Serialized<ServerSecretParams>): Serialized<ServerPublicParams>;
export function ServerSecretParams_IssueAuthCredentialDeterministic(serverSecretParams: Serialized<ServerSecretParams>, randomness: Buffer, uuid: Uuid, redemptionTime: number): Serialized<AuthCredentialResponse>;
export function ServerSecretParams_IssueProfileKeyCredentialDeterministic(serverSecretParams: Serialized<ServerSecretParams>, randomness: Buffer, request: Serialized<ProfileKeyCredentialRequest>, uuid: Uuid, commitment: Serialized<ProfileKeyCommitment>): Serialized<ProfileKeyCredentialResponse>;
export function ServerSecretParams_IssueReceiptCredentialDeterministic(serverSecretParams: Serialized<ServerSecretParams>, randomness: Buffer, request: Serialized<ReceiptCredentialRequest>, receiptExpirationTime: Buffer, receiptLevel: Buffer): Serialized<ReceiptCredentialResponse>;
export function ServerSecretParams_SignDeterministic(params: Serialized<ServerSecretParams>, randomness: Buffer, message: Buffer): Buffer;
export function ServerSecretParams_VerifyAuthCredentialPresentation(serverSecretParams: Serialized<ServerSecretParams>, groupPublicParams: Serialized<GroupPublicParams>, presentation: Serialized<AuthCredentialPresentation>): void;
export function ServerSecretParams_VerifyProfileKeyCredentialPresentation(serverSecretParams: Serialized<ServerSecretParams>, groupPublicParams: Serialized<GroupPublicParams>, presentation: Serialized<ProfileKeyCredentialPresentation>): void;
export function ServerSecretParams_VerifyReceiptCredentialPresentation(serverSecretParams: Serialized<ServerSecretParams>, presentation: Serialized<ReceiptCredentialPresentation>): void;
export function SessionBuilder_ProcessPreKeyBundle(bundle: Wrapper<PreKeyBundle>, protocolAddress: Wrapper<ProtocolAddress>, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, ctx: null): Promise<void>;
export function SessionCipher_DecryptPreKeySignalMessage(message: Wrapper<PreKeySignalMessage>, protocolAddress: Wrapper<ProtocolAddress>, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, prekeyStore: PreKeyStore, signedPrekeyStore: SignedPreKeyStore, ctx: null): Promise<Buffer>;
export function SessionCipher_DecryptSignalMessage(message: Wrapper<SignalMessage>, protocolAddress: Wrapper<ProtocolAddress>, sessionStore: SessionStore, identityKeyStore: IdentityKeyStore, ctx: null): Promise<Buffer>;
@ -192,26 +258,49 @@ export function UnidentifiedSenderMessageContent_GetMsgType(m: Wrapper<Unidentif
export function UnidentifiedSenderMessageContent_GetSenderCert(m: Wrapper<UnidentifiedSenderMessageContent>): SenderCertificate;
export function UnidentifiedSenderMessageContent_New(message: Wrapper<CiphertextMessage>, sender: Wrapper<SenderCertificate>, contentHint: number, groupId: Buffer | null): UnidentifiedSenderMessageContent;
export function UnidentifiedSenderMessageContent_Serialize(obj: Wrapper<UnidentifiedSenderMessageContent>): Buffer;
export function UuidCiphertext_CheckValidContents(Obj: Serialized<UuidCiphertext>): void;
export function initLogger(maxLevel: LogLevel, callback: (level: LogLevel, target: string, file: string | null, line: number | null, message: string) => void): void
interface Aes256GcmSiv { readonly __type: unique symbol; }
interface AuthCredential { readonly __type: unique symbol; }
interface AuthCredentialPresentation { readonly __type: unique symbol; }
interface AuthCredentialResponse { readonly __type: unique symbol; }
interface CiphertextMessage { readonly __type: unique symbol; }
interface DecryptionErrorMessage { readonly __type: unique symbol; }
interface Fingerprint { readonly __type: unique symbol; }
interface GroupMasterKey { readonly __type: unique symbol; }
interface GroupPublicParams { readonly __type: unique symbol; }
interface GroupSecretParams { readonly __type: unique symbol; }
interface HsmEnclaveClient { readonly __type: unique symbol; }
interface PlaintextContent { readonly __type: unique symbol; }
interface PreKeyBundle { readonly __type: unique symbol; }
interface PreKeyRecord { readonly __type: unique symbol; }
interface PreKeySignalMessage { readonly __type: unique symbol; }
interface PrivateKey { readonly __type: unique symbol; }
interface ProfileKey { readonly __type: unique symbol; }
interface ProfileKeyCiphertext { readonly __type: unique symbol; }
interface ProfileKeyCommitment { readonly __type: unique symbol; }
interface ProfileKeyCredential { readonly __type: unique symbol; }
interface ProfileKeyCredentialPresentation { readonly __type: unique symbol; }
interface ProfileKeyCredentialRequest { readonly __type: unique symbol; }
interface ProfileKeyCredentialRequestContext { readonly __type: unique symbol; }
interface ProfileKeyCredentialResponse { readonly __type: unique symbol; }
interface ProtocolAddress { readonly __type: unique symbol; }
interface PublicKey { readonly __type: unique symbol; }
interface ReceiptCredential { readonly __type: unique symbol; }
interface ReceiptCredentialPresentation { readonly __type: unique symbol; }
interface ReceiptCredentialRequest { readonly __type: unique symbol; }
interface ReceiptCredentialRequestContext { readonly __type: unique symbol; }
interface ReceiptCredentialResponse { readonly __type: unique symbol; }
interface SealedSenderDecryptionResult { readonly __type: unique symbol; }
interface SenderCertificate { readonly __type: unique symbol; }
interface SenderKeyDistributionMessage { readonly __type: unique symbol; }
interface SenderKeyMessage { readonly __type: unique symbol; }
interface SenderKeyRecord { readonly __type: unique symbol; }
interface ServerCertificate { readonly __type: unique symbol; }
interface ServerPublicParams { readonly __type: unique symbol; }
interface ServerSecretParams { readonly __type: unique symbol; }
interface SessionRecord { readonly __type: unique symbol; }
interface SignalMessage { readonly __type: unique symbol; }
interface SignedPreKeyRecord { readonly __type: unique symbol; }
interface UnidentifiedSenderMessageContent { readonly __type: unique symbol; }
interface UuidCiphertext { readonly __type: unique symbol; }

423
node/test/ZKGroup-test.ts Normal file
View File

@ -0,0 +1,423 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { assert } from 'chai';
import { toUUID } from '../zkgroup/internal/UUIDUtil';
import ServerSecretParams from '../zkgroup/ServerSecretParams';
import ServerZkAuthOperations from '../zkgroup/auth/ServerZkAuthOperations';
import GroupMasterKey from '../zkgroup/groups/GroupMasterKey';
import GroupSecretParams from '../zkgroup/groups/GroupSecretParams';
import ClientZkAuthOperations from '../zkgroup/auth/ClientZkAuthOperations';
import ClientZkGroupCipher from '../zkgroup/groups/ClientZkGroupCipher';
import ServerZkProfileOperations from '../zkgroup/profiles/ServerZkProfileOperations';
import ClientZkProfileOperations from '../zkgroup/profiles/ClientZkProfileOperations';
import ProfileKey from '../zkgroup/profiles/ProfileKey';
import ProfileKeyVersion from '../zkgroup/profiles/ProfileKeyVersion';
import ClientZkReceiptOperations from '../zkgroup/receipts/ClientZkReceiptOperations';
import ServerZkReceiptOperations from '../zkgroup/receipts/ServerZkReceiptOperations';
import ReceiptSerial from '../zkgroup/receipts/ReceiptSerial';
function hexToBuffer(hex: string) {
return Buffer.from(hex, 'hex');
}
function assertByteArray(hex: string, actual: Buffer) {
const actualHex = actual.toString('hex');
assert.strictEqual(hex, actualHex);
}
function assertArrayEquals(expected: Buffer, actual: Buffer) {
const expectedHex = expected.toString('hex');
const actualHex = actual.toString('hex');
assert.strictEqual(expectedHex, actualHex);
}
function assertArrayNotEquals(expected: Buffer, actual: Buffer) {
const expectedHex = expected.toString('hex');
const actualHex = actual.toString('hex');
assert.notEqual(expectedHex, actualHex);
}
describe('ZKGroup', () => {
const TEST_ARRAY_16 = hexToBuffer('000102030405060708090a0b0c0d0e0f');
const TEST_ARRAY_32 = hexToBuffer(
'000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'
);
const TEST_ARRAY_32_1 = hexToBuffer(
'6465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f80818283'
);
const TEST_ARRAY_32_2 = hexToBuffer(
'c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7'
);
const TEST_ARRAY_32_3 = Buffer.from([
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
]);
const TEST_ARRAY_32_4 = Buffer.from([
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
]);
const TEST_ARRAY_32_5 = hexToBuffer(
'030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122'
);
const authPresentationResult = hexToBuffer(
'000cde979737ed30bbeb16362e4e076945ce02069f727b0ed4c3c33c011e82546e1cdf081fbdf37c03a851ad060bdcbf6378cb4cb16dc3154d08de5439b5323203729d1841b517033af2fd177d30491c138ae723655734f6e5cc01c00696f4e92096d8c33df26ba2a820d42e9735d30f8eeef96d399079073c099f7035523bfe716638659319d3c36ad34c00ef8850f663c4d93030235074312a8878b6a5c5df4fbc7d32935278bfa5996b44ab75d6f06f4c30b98640ad5de74742656c8977567de000000000000000fde69f82ad2dcb4909650ac6b2573841af568fef822b32b45f625a764691a704d11b6f385261468117ead57fa623338e21c66ed846ab65809fcac158066d8e0e444077b99540d886e7dc09555dd6faea2cd3697f1e089f82d54e5d0fe4a185008b5cbc3979391ad71686bc03be7b00ea7e42c08d9f1d75c3a56c27ae2467b80636c0b5343eda7cd578ba88ddb7a0766568477fed63cf531862122c6c15b4a707973d41782cfc0ef4fe6c3115988a2e339015938d2df0a5d30237a2592cc10c05a9e4ef6b695bca99736b1a49ea39606a381ecfb05efe60d28b54823ec5a3680c765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547440e20100'
);
const profileKeyPresentationResult = hexToBuffer(
'00c4d19bca1ae844585168869da4133e0e0bb59f2ce17b7ac65bff5da9610eca103429d8022a94bae2b5b1057b5595b8ad70bfc2d0e1ad662cb75e6bae0782be6f00e3db793bc28561f0196c2e74da6f303fa8bcb70c94096671b73f7b3a95fb002200d5b9180fa0ef7d3014d01344145b4d38480d72ff25c24294e305e5705072e0d32cc4e84f5caf31486089a4b934c80c92eba43472ff23a5af93c397535d33801f0e6fc6eb2ee0d117f03bb4fd38a8b9c88d94708131f38742ca804a3cfc4f9476bc2d03f53d17001c36478afbe9cc535a224b2df6b2b08bef06cbc7d4dc42ccfc3459f7ac5c4419ae9f3c8a161d554d047778943216240858da3b1101984c40010000000000007a01eea6b2adad14d71ab8b8e411bef3c596e954b70e4031570cb1abd7e932083241f1caca3116708fa4319fbbdfe351376c23644ae09a42f0155db4996c9d0c7ffc8521c1914c0e1a20ae51e65df64dd5e6e5985b3d9d31732046d2d77f9c08aaccf056b84026073976eec6164cbdaee5d9e76e497f0c290af681cabd5c5101282abb26c3680d6087ce053310fe8a94f59d8ae23caac5fc0ed0c379888abf028a6f29f89d4fe2acc1706341b2245ba1885bca57e1e27ccf7ed79371500965009f960c2ba00fad3e93383b87ce119cac0b3360eb99284ce78e2cbed680f7960373e0ab75c190254160c2353614109489e653c9b2e1c93f92c7c5ad583d987a04bd3541b24485c33ea49bac43c87c4ab3efde2e2d7ec10a40be544199f925b20b2c55542bc56410571e41cd8e0286f609a66768b5061ccb4777af32309928dd09765de9df4cfa5487f360e29e99343e91811baec331c4680985e608ca5d408e21725c6aa1b61d5a8b48d75f4aaa9a3cbe88d3e0f1a54319081f77c72c8f52547448c03ab4afbf6b8fb0e126c037a0ad4094600dd0e0634d76f88c21087f3cfb485a89bc1e3abc4c95041d1d170eccf02933ec5393d4be1dc573f83c33d3b9a746'
);
it('testAuthIntegration', () => {
const uuid = toUUID(TEST_ARRAY_16);
const redemptionTime = 123456;
// Generate keys (client's are per-group, server's are not)
// ---
// SERVER
const serverSecretParams = ServerSecretParams.generateWithRandom(
TEST_ARRAY_32
);
const serverPublicParams = serverSecretParams.getPublicParams();
const serverZkAuth = new ServerZkAuthOperations(serverSecretParams);
// CLIENT
const masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
const groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
assertArrayEquals(
groupSecretParams.getMasterKey().serialize(),
masterKey.serialize()
);
const groupPublicParams = groupSecretParams.getPublicParams();
// SERVER
// Issue credential
const authCredentialResponse = serverZkAuth.issueAuthCredentialWithRandom(
TEST_ARRAY_32_2,
uuid,
redemptionTime
);
// CLIENT
// Receive credential
const clientZkAuthCipher = new ClientZkAuthOperations(serverPublicParams);
const clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
const authCredential = clientZkAuthCipher.receiveAuthCredential(
uuid,
redemptionTime,
authCredentialResponse
);
// Create and decrypt user entry
const uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid);
const plaintext = clientZkGroupCipher.decryptUuid(uuidCiphertext);
assert.strictEqual(uuid, plaintext);
// Create presentation
const presentation = clientZkAuthCipher.createAuthCredentialPresentationWithRandom(
TEST_ARRAY_32_5,
groupSecretParams,
authCredential
);
// Verify presentation
const uuidCiphertextRecv = presentation.getUuidCiphertext();
assertArrayEquals(
uuidCiphertext.serialize(),
uuidCiphertextRecv.serialize()
);
assert.strictEqual(presentation.getRedemptionTime(), redemptionTime);
serverZkAuth.verifyAuthCredentialPresentation(
groupPublicParams,
presentation
);
assertArrayEquals(presentation.serialize(), authPresentationResult);
});
it('testProfileKeyIntegration', () => {
const uuid = toUUID(TEST_ARRAY_16);
// Generate keys (client's are per-group, server's are not)
// ---
// SERVER
const serverSecretParams = ServerSecretParams.generateWithRandom(
TEST_ARRAY_32
);
const serverPublicParams = serverSecretParams.getPublicParams();
const serverZkProfile = new ServerZkProfileOperations(serverSecretParams);
// CLIENT
const masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
const groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
assertArrayEquals(
groupSecretParams.getMasterKey().serialize(),
masterKey.serialize()
);
const groupPublicParams = groupSecretParams.getPublicParams();
const clientZkProfileCipher = new ClientZkProfileOperations(
serverPublicParams
);
const profileKey = new ProfileKey(TEST_ARRAY_32_1);
const profileKeyCommitment = profileKey.getCommitment(uuid);
// Create context and request
const context = clientZkProfileCipher.createProfileKeyCredentialRequestContextWithRandom(
TEST_ARRAY_32_3,
uuid,
profileKey
);
const request = context.getRequest();
// SERVER
const response = serverZkProfile.issueProfileKeyCredentialWithRandom(
TEST_ARRAY_32_4,
request,
uuid,
profileKeyCommitment
);
// CLIENT
// Gets stored profile credential
const clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
const profileKeyCredential = clientZkProfileCipher.receiveProfileKeyCredential(
context,
response
);
// Create encrypted UID and profile key
const uuidCiphertext = clientZkGroupCipher.encryptUuid(uuid);
const plaintext = clientZkGroupCipher.decryptUuid(uuidCiphertext);
assert.strictEqual(plaintext, uuid);
const profileKeyCiphertext = clientZkGroupCipher.encryptProfileKey(
profileKey,
uuid
);
const decryptedProfileKey = clientZkGroupCipher.decryptProfileKey(
profileKeyCiphertext,
uuid
);
assertArrayEquals(profileKey.serialize(), decryptedProfileKey.serialize());
const presentation = clientZkProfileCipher.createProfileKeyCredentialPresentationWithRandom(
TEST_ARRAY_32_5,
groupSecretParams,
profileKeyCredential
);
assertArrayEquals(presentation.serialize(), profileKeyPresentationResult);
// Verify presentation
serverZkProfile.verifyProfileKeyCredentialPresentation(
groupPublicParams,
presentation
);
const uuidCiphertextRecv = presentation.getUuidCiphertext();
assertArrayEquals(
uuidCiphertext.serialize(),
uuidCiphertextRecv.serialize()
);
const pkvB = profileKey.getProfileKeyVersion(uuid);
const pkvC = new ProfileKeyVersion(pkvB.serialize());
assertArrayEquals(pkvB.serialize(), pkvC.serialize());
});
it('testServerSignatures', () => {
const serverSecretParams = ServerSecretParams.generateWithRandom(
TEST_ARRAY_32
);
const serverPublicParams = serverSecretParams.getPublicParams();
const message = TEST_ARRAY_32_1;
const signature = serverSecretParams.signWithRandom(
TEST_ARRAY_32_2,
message
);
serverPublicParams.verifySignature(message, signature);
assertByteArray(
'87d354564d35ef91edba851e0815612e864c227a0471d50c270698604406d003a55473f576cf241fc6b41c6b16e5e63b333c02fe4a33858022fdd7a4ab367b06',
signature.serialize()
);
const alteredMessage = Buffer.from(message);
alteredMessage[0] ^= 1;
assertArrayNotEquals(message, alteredMessage);
try {
serverPublicParams.verifySignature(alteredMessage, signature);
assert.fail('signature validation should have failed!');
} catch (error) {
// good
}
});
it('testGroupIdentifier', () => {
const groupSecretParams = GroupSecretParams.generateWithRandom(
TEST_ARRAY_32
);
const _groupPublicParams = groupSecretParams.getPublicParams();
// assertByteArray('31f2c60f86f4c5996e9e2568355591d9', groupPublicParams.getGroupIdentifier().serialize());
});
it('testInvalidSerialized', () => {
const ckp = Buffer.alloc(289);
ckp.fill(-127);
assert.throws(() => new GroupSecretParams(ckp));
});
it('testWrongSizeSerialized', () => {
const ckp = Buffer.alloc(5);
ckp.fill(-127);
assert.throws(() => new GroupSecretParams(ckp));
});
it('testBlobEncryption', () => {
const groupSecretParams = GroupSecretParams.generate();
const clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
const plaintext = Buffer.from([0, 1, 2, 3, 4]);
const ciphertext = clientZkGroupCipher.encryptBlob(plaintext);
const plaintext2 = clientZkGroupCipher.decryptBlob(ciphertext);
assertArrayEquals(plaintext, plaintext2);
});
it('testBlobEncryptionWithRandom', () => {
const masterKey = new GroupMasterKey(TEST_ARRAY_32_1);
const groupSecretParams = GroupSecretParams.deriveFromMasterKey(masterKey);
const clientZkGroupCipher = new ClientZkGroupCipher(groupSecretParams);
const plaintext = hexToBuffer('0102030405060708111213141516171819');
const ciphertext = hexToBuffer(
'dd4d032ca9bb75a4a78541b90cb4e95743f3b0dabfc7e11101b098e34f6cf6513940a04c1f20a302692afdc7087f10196000'
);
const ciphertextPaddedWith257 = hexToBuffer(
'5cb5b7bff06e85d929f3511fd194e638cf32a47663868bc8e64d98fb1bbe435ebd21c763ce2d42e85a1b2c169f12f9818ddadcf4b491398b7c5d46a224e1582749f5e2a4a2294caaaaab843a1b7cf6426fd543d09ff32a4ba5f319ca4442b4da34b3e2b5b4f8a52fdc4b484ea86b33db3ebb758dbd9614178f0e4e1f9b2b914f1e786936b62ed2b58b7ae3cb3e7ae0835b9516959837406662b85eac740cef83b60b5aaeaaab95643c2bef8ce87358fabff9d690052beb9e52d0c947e7c986b2f3ce3b7161cec72c08e2c4ade3debe3792d736c0457bc352afb8b6caa48a5b92c1ec05ba808ba8f94c6572ebbf29818912344987573de419dbcc7f1ea0e4b2dd4077b76b381819747ac332e46fa23abfc3338e2f4b081a8a53cba0988eef116764d944f1ce3f20a302692afdc7087f10196000'
);
const ciphertext2 = clientZkGroupCipher.encryptBlobWithRandom(
TEST_ARRAY_32_2,
plaintext
);
const plaintext2 = clientZkGroupCipher.decryptBlob(ciphertext2);
assertArrayEquals(plaintext, plaintext2);
assertArrayEquals(ciphertext, ciphertext2);
const plaintext257 = clientZkGroupCipher.decryptBlob(
ciphertextPaddedWith257
);
assertArrayEquals(plaintext, plaintext257);
});
it('testReceiptFlow', () => {
const serverSecretParams = ServerSecretParams.generateWithRandom(
TEST_ARRAY_32
);
const serverPublicParams = serverSecretParams.getPublicParams();
const serverOps = new ServerZkReceiptOperations(serverSecretParams);
const clientOps = new ClientZkReceiptOperations(serverPublicParams);
const receiptSerial = new ReceiptSerial(
hexToBuffer('00112233445566778899aabbccddeeff')
);
// client
const context = clientOps.createReceiptCredentialRequestContext(
receiptSerial
);
const request = context.getRequest();
// issuance server
const receiptExpirationTime = BigInt('31337');
const receiptLevel = BigInt('3');
const response = serverOps.issueReceiptCredential(
request,
receiptExpirationTime,
receiptLevel
);
// client
const credential = clientOps.receiveReceiptCredential(context, response);
assert(receiptExpirationTime == credential.getReceiptExpirationTime());
assert(receiptLevel == credential.getReceiptLevel());
const presentation = clientOps.createReceiptCredentialPresentation(
credential
);
// redemption server
serverOps.verifyReceiptCredentialPresentation(presentation);
});
});

View File

@ -0,0 +1,14 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from './internal/ByteArray';
export default class NotarySignature extends ByteArray {
static SIZE = 64;
constructor(contents: Buffer) {
super(contents, NotarySignature.checkLength(NotarySignature.SIZE));
}
}

View File

@ -0,0 +1,22 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from './internal/ByteArray';
import NativeImpl from '../NativeImpl';
import NotarySignature from './NotarySignature';
export default class ServerPublicParams extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ServerPublicParams_CheckValidContents);
}
verifySignature(message: Buffer, notarySignature: NotarySignature): void {
NativeImpl.ServerPublicParams_VerifySignature(
this.contents,
message,
notarySignature.getContents()
);
}
}

View File

@ -0,0 +1,52 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import NativeImpl from '../NativeImpl';
import ByteArray from './internal/ByteArray';
import { RANDOM_LENGTH } from './internal/Constants';
import ServerPublicParams from './ServerPublicParams';
import NotarySignature from './NotarySignature';
export default class ServerSecretParams extends ByteArray {
static generate(): ServerSecretParams {
const random = randomBytes(RANDOM_LENGTH);
return ServerSecretParams.generateWithRandom(random);
}
static generateWithRandom(random: Buffer): ServerSecretParams {
return new ServerSecretParams(
NativeImpl.ServerSecretParams_GenerateDeterministic(random)
);
}
constructor(contents: Buffer) {
super(contents, NativeImpl.ServerSecretParams_CheckValidContents);
}
getPublicParams(): ServerPublicParams {
return new ServerPublicParams(
NativeImpl.ServerSecretParams_GetPublicParams(this.contents)
);
}
sign(message: Buffer): NotarySignature {
const random = randomBytes(RANDOM_LENGTH);
return this.signWithRandom(random, message);
}
signWithRandom(random: Buffer, message: Buffer): NotarySignature {
return new NotarySignature(
NativeImpl.ServerSecretParams_SignDeterministic(
this.contents,
random,
message
)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class AuthCredential extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.AuthCredential_CheckValidContents);
}
}

View File

@ -0,0 +1,26 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import UuidCiphertext from '../groups/UuidCiphertext';
export default class AuthCredentialPresentation extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.AuthCredentialPresentation_CheckValidContents);
}
getUuidCiphertext(): UuidCiphertext {
return new UuidCiphertext(
NativeImpl.AuthCredentialPresentation_GetUuidCiphertext(this.contents)
);
}
getRedemptionTime(): number {
return NativeImpl.AuthCredentialPresentation_GetRedemptionTime(
this.contents
);
}
}

View File

@ -0,0 +1,12 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class AuthCredentialResponse extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.AuthCredentialResponse_CheckValidContents);
}
}

View File

@ -0,0 +1,67 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import NativeImpl from '../../NativeImpl';
import { RANDOM_LENGTH } from '../internal/Constants';
import ServerPublicParams from '../ServerPublicParams';
import AuthCredential from './AuthCredential';
import AuthCredentialPresentation from './AuthCredentialPresentation';
import AuthCredentialResponse from './AuthCredentialResponse';
import GroupSecretParams from '../groups/GroupSecretParams';
import { UUIDType, fromUUID } from '../internal/UUIDUtil';
export default class ClientZkAuthOperations {
serverPublicParams: ServerPublicParams;
constructor(serverPublicParams: ServerPublicParams) {
this.serverPublicParams = serverPublicParams;
}
receiveAuthCredential(
uuid: UUIDType,
redemptionTime: number,
authCredentialResponse: AuthCredentialResponse
): AuthCredential {
return new AuthCredential(
NativeImpl.ServerPublicParams_ReceiveAuthCredential(
this.serverPublicParams.getContents(),
fromUUID(uuid),
redemptionTime,
authCredentialResponse.getContents()
)
);
}
createAuthCredentialPresentation(
groupSecretParams: GroupSecretParams,
authCredential: AuthCredential
): AuthCredentialPresentation {
const random = randomBytes(RANDOM_LENGTH);
return this.createAuthCredentialPresentationWithRandom(
random,
groupSecretParams,
authCredential
);
}
createAuthCredentialPresentationWithRandom(
random: Buffer,
groupSecretParams: GroupSecretParams,
authCredential: AuthCredential
): AuthCredentialPresentation {
return new AuthCredentialPresentation(
NativeImpl.ServerPublicParams_CreateAuthCredentialPresentationDeterministic(
this.serverPublicParams.getContents(),
random,
groupSecretParams.getContents(),
authCredential.getContents()
)
);
}
}

View File

@ -0,0 +1,57 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import { RANDOM_LENGTH } from '../internal/Constants';
import NativeImpl from '../../NativeImpl';
import ServerSecretParams from '../ServerSecretParams';
import AuthCredentialResponse from './AuthCredentialResponse';
import AuthCredentialPresentation from './AuthCredentialPresentation';
import GroupPublicParams from '../groups/GroupPublicParams';
import { UUIDType, fromUUID } from '../internal/UUIDUtil';
export default class ServerZkAuthOperations {
serverSecretParams: ServerSecretParams;
constructor(serverSecretParams: ServerSecretParams) {
this.serverSecretParams = serverSecretParams;
}
issueAuthCredential(
uuid: UUIDType,
redemptionTime: number
): AuthCredentialResponse {
const random = randomBytes(RANDOM_LENGTH);
return this.issueAuthCredentialWithRandom(random, uuid, redemptionTime);
}
issueAuthCredentialWithRandom(
random: Buffer,
uuid: UUIDType,
redemptionTime: number
): AuthCredentialResponse {
return new AuthCredentialResponse(
NativeImpl.ServerSecretParams_IssueAuthCredentialDeterministic(
this.serverSecretParams.getContents(),
random,
fromUUID(uuid),
redemptionTime
)
);
}
verifyAuthCredentialPresentation(
groupPublicParams: GroupPublicParams,
authCredentialPresentation: AuthCredentialPresentation
): void {
NativeImpl.ServerSecretParams_VerifyAuthCredentialPresentation(
this.serverSecretParams.getContents(),
groupPublicParams.getContents(),
authCredentialPresentation.getContents()
);
}
}

View File

@ -0,0 +1,89 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import { RANDOM_LENGTH } from '../internal/Constants';
import NativeImpl from '../../NativeImpl';
import UuidCiphertext from './UuidCiphertext';
import ProfileKeyCiphertext from './ProfileKeyCiphertext';
import ProfileKey from '../profiles/ProfileKey';
import GroupSecretParams from './GroupSecretParams';
import { UUIDType, fromUUID, toUUID } from '../internal/UUIDUtil';
export default class ClientZkGroupCipher {
groupSecretParams: GroupSecretParams;
constructor(groupSecretParams: GroupSecretParams) {
this.groupSecretParams = groupSecretParams;
}
encryptUuid(uuid: UUIDType): UuidCiphertext {
return new UuidCiphertext(
NativeImpl.GroupSecretParams_EncryptUuid(
this.groupSecretParams.getContents(),
fromUUID(uuid)
)
);
}
decryptUuid(uuidCiphertext: UuidCiphertext): UUIDType {
return toUUID(
NativeImpl.GroupSecretParams_DecryptUuid(
this.groupSecretParams.getContents(),
uuidCiphertext.getContents()
)
);
}
encryptProfileKey(
profileKey: ProfileKey,
uuid: UUIDType
): ProfileKeyCiphertext {
return new ProfileKeyCiphertext(
NativeImpl.GroupSecretParams_EncryptProfileKey(
this.groupSecretParams.getContents(),
profileKey.getContents(),
fromUUID(uuid)
)
);
}
decryptProfileKey(
profileKeyCiphertext: ProfileKeyCiphertext,
uuid: UUIDType
): ProfileKey {
return new ProfileKey(
NativeImpl.GroupSecretParams_DecryptProfileKey(
this.groupSecretParams.getContents(),
profileKeyCiphertext.getContents(),
fromUUID(uuid)
)
);
}
encryptBlob(plaintext: Buffer): Buffer {
const random = randomBytes(RANDOM_LENGTH);
return this.encryptBlobWithRandom(random, plaintext);
}
encryptBlobWithRandom(random: Buffer, plaintext: Buffer): Buffer {
return NativeImpl.GroupSecretParams_EncryptBlobWithPaddingDeterministic(
this.groupSecretParams.getContents(),
random,
plaintext,
0
);
}
decryptBlob(blobCiphertext: Buffer): Buffer {
return NativeImpl.GroupSecretParams_DecryptBlobWithPadding(
this.groupSecretParams.getContents(),
blobCiphertext
);
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
export default class GroupIdentifier extends ByteArray {
static SIZE = 32;
constructor(contents: Buffer) {
super(contents, GroupIdentifier.checkLength(GroupIdentifier.SIZE));
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
export default class GroupMasterKey extends ByteArray {
static SIZE = 32;
constructor(contents: Buffer) {
super(contents, GroupMasterKey.checkLength(GroupMasterKey.SIZE));
}
}

View File

@ -0,0 +1,20 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import GroupIdentifier from './GroupIdentifier';
export default class GroupPublicParams extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.GroupPublicParams_CheckValidContents);
}
getGroupIdentifier(): GroupIdentifier {
return new GroupIdentifier(
NativeImpl.GroupPublicParams_GetGroupIdentifier(this.contents)
);
}
}

View File

@ -0,0 +1,52 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import { RANDOM_LENGTH } from '../internal/Constants';
import GroupMasterKey from './GroupMasterKey';
import GroupPublicParams from './GroupPublicParams';
export default class GroupSecretParams extends ByteArray {
static generate(): GroupSecretParams {
const random = randomBytes(RANDOM_LENGTH);
return GroupSecretParams.generateWithRandom(random);
}
static generateWithRandom(random: Buffer): GroupSecretParams {
return new GroupSecretParams(
NativeImpl.GroupSecretParams_GenerateDeterministic(random)
);
}
static deriveFromMasterKey(
groupMasterKey: GroupMasterKey
): GroupSecretParams {
return new GroupSecretParams(
NativeImpl.GroupSecretParams_DeriveFromMasterKey(
groupMasterKey.getContents()
)
);
}
constructor(contents: Buffer) {
super(contents, NativeImpl.GroupSecretParams_CheckValidContents);
}
getMasterKey(): GroupMasterKey {
return new GroupMasterKey(
NativeImpl.GroupSecretParams_GetMasterKey(this.contents)
);
}
getPublicParams(): GroupPublicParams {
return new GroupPublicParams(
NativeImpl.GroupSecretParams_GetPublicParams(this.contents)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ProfileKeyCiphertext extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ProfileKeyCiphertext_CheckValidContents);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class UuidCiphertext extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.UuidCiphertext_CheckValidContents);
}
}

52
node/zkgroup/index.ts Normal file
View File

@ -0,0 +1,52 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
// Root
export { default as ServerPublicParams } from './ServerPublicParams';
export { default as ServerSecretParams } from './ServerSecretParams';
export { default as NotarySignature } from './NotarySignature';
// Auth
export { default as ClientZkAuthOperations } from './auth/ClientZkAuthOperations';
export { default as ServerZkAuthOperations } from './auth/ServerZkAuthOperations';
export { default as AuthCredential } from './auth/AuthCredential';
export { default as AuthCredentialResponse } from './auth/AuthCredentialResponse';
export { default as AuthCredentialPresentation } from './auth/AuthCredentialPresentation';
// Groups
export { default as ClientZkGroupCipher } from './groups/ClientZkGroupCipher';
export { default as GroupIdentifier } from './groups/GroupIdentifier';
export { default as GroupMasterKey } from './groups/GroupMasterKey';
export { default as GroupPublicParams } from './groups/GroupPublicParams';
export { default as GroupSecretParams } from './groups/GroupSecretParams';
export { default as ProfileKeyCiphertext } from './groups/ProfileKeyCiphertext';
export { default as UuidCiphertext } from './groups/UuidCiphertext';
// Profiles
export { default as ClientZkProfileOperations } from './profiles/ClientZkProfileOperations';
export { default as ServerZkProfileOperations } from './profiles/ServerZkProfileOperations';
export { default as ProfileKey } from './profiles/ProfileKey';
export { default as ProfileKeyCommitment } from './profiles/ProfileKeyCommitment';
export { default as ProfileKeyCredential } from './profiles/ProfileKeyCredential';
export { default as ProfileKeyCredentialPresentation } from './profiles/ProfileKeyCredentialPresentation';
export { default as ProfileKeyCredentialRequest } from './profiles/ProfileKeyCredentialRequest';
export { default as ProfileKeyCredentialRequestContext } from './profiles/ProfileKeyCredentialRequestContext';
export { default as ProfileKeyCredentialResponse } from './profiles/ProfileKeyCredentialResponse';
export { default as ProfileKeyVersion } from './profiles/ProfileKeyVersion';
// Receipts
export { default as ClientZkReceiptOperations } from './receipts/ClientZkReceiptOperations';
export { default as ServerZkReceiptOperations } from './receipts/ServerZkReceiptOperations';
export { default as ReceiptCredential } from './receipts/ReceiptCredential';
export { default as ReceiptCredentialPresentation } from './receipts/ReceiptCredentialPresentation';
export { default as ReceiptCredentialRequest } from './receipts/ReceiptCredentialRequest';
export { default as ReceiptCredentialRequestContext } from './receipts/ReceiptCredentialRequestContext';
export { default as ReceiptCredentialResponse } from './receipts/ReceiptCredentialResponse';
export { default as ReceiptSerial } from './receipts/ReceiptSerial';

View File

@ -0,0 +1,15 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
const UINT64_MAX = BigInt('0xFFFFFFFFFFFFFFFF');
export function bufferFromBigUInt64BE(value: bigint): Buffer {
if (value < 0 || value > UINT64_MAX) {
throw new RangeError(`value ${value} isn't representable as a u64`);
}
const result = Buffer.alloc(8);
result.writeBigUInt64BE(value);
return result;
}

View File

@ -0,0 +1,37 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { SignalClientErrorBase } from '../../Errors';
export default class ByteArray {
contents: Buffer;
constructor(contents: Buffer, checkValid: (contents: Buffer) => void) {
checkValid(contents);
this.contents = Buffer.from(contents);
}
protected static checkLength(
expectedLength: number
): (contents: Buffer) => void {
return contents => {
if (contents.length !== expectedLength) {
throw new SignalClientErrorBase(
`Length of array supplied was ${contents.length} expected ${expectedLength}`,
undefined,
this.name
);
}
};
}
public getContents(): Buffer {
return this.contents;
}
public serialize(): Buffer {
return Buffer.from(this.contents);
}
}

View File

@ -0,0 +1,10 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
export const RANDOM_LENGTH = 32;
export const FFI_RETURN_OK = 0;
export const FFI_RETURN_INTERNAL_ERROR = 1;
export const FFI_RETURN_INPUT_ERROR = 2;

View File

@ -0,0 +1,29 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
// Ideally we would replace the operations in this file with the 'uuid' package,
// but the tests use an invalid UUID as a test string, and 'uuid' always validates.
export type UUIDType = string;
export function toUUID(array: Buffer): UUIDType {
const hex = array.toString('hex');
return `${hex.substring(0, 8)}-${hex.substring(8, 12)}-${hex.substring(
12,
16
)}-${hex.substring(16, 20)}-${hex.substring(20)}`;
}
export function fromUUID(uuid: UUIDType): Buffer {
let i = 0;
const array = Buffer.alloc(16);
uuid.replace(/[0-9A-F]{2}/gi, (oct: string): string => {
array[i++] = parseInt(oct, 16);
return '';
});
return array;
}

View File

@ -0,0 +1,96 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import { RANDOM_LENGTH } from '../internal/Constants';
import NativeImpl from '../../NativeImpl';
import ServerPublicParams from '../ServerPublicParams';
import ProfileKeyCredentialRequestContext from './ProfileKeyCredentialRequestContext';
import ProfileKey from './ProfileKey';
import ProfileKeyCredential from './ProfileKeyCredential';
import ProfileKeyCredentialPresentation from './ProfileKeyCredentialPresentation';
import GroupSecretParams from '../groups/GroupSecretParams';
import ProfileKeyCredentialResponse from './ProfileKeyCredentialResponse';
import { UUIDType, fromUUID } from '../internal/UUIDUtil';
export default class ClientZkProfileOperations {
serverPublicParams: ServerPublicParams;
constructor(serverPublicParams: ServerPublicParams) {
this.serverPublicParams = serverPublicParams;
}
createProfileKeyCredentialRequestContext(
uuid: UUIDType,
profileKey: ProfileKey
): ProfileKeyCredentialRequestContext {
const random = randomBytes(RANDOM_LENGTH);
return this.createProfileKeyCredentialRequestContextWithRandom(
random,
uuid,
profileKey
);
}
createProfileKeyCredentialRequestContextWithRandom(
random: Buffer,
uuid: UUIDType,
profileKey: ProfileKey
): ProfileKeyCredentialRequestContext {
return new ProfileKeyCredentialRequestContext(
NativeImpl.ServerPublicParams_CreateProfileKeyCredentialRequestContextDeterministic(
this.serverPublicParams.getContents(),
random,
fromUUID(uuid),
profileKey.getContents()
)
);
}
receiveProfileKeyCredential(
profileKeyCredentialRequestContext: ProfileKeyCredentialRequestContext,
profileKeyCredentialResponse: ProfileKeyCredentialResponse
): ProfileKeyCredential {
return new ProfileKeyCredential(
NativeImpl.ServerPublicParams_ReceiveProfileKeyCredential(
this.serverPublicParams.getContents(),
profileKeyCredentialRequestContext.getContents(),
profileKeyCredentialResponse.getContents()
)
);
}
createProfileKeyCredentialPresentation(
groupSecretParams: GroupSecretParams,
profileKeyCredential: ProfileKeyCredential
): ProfileKeyCredentialPresentation {
const random = randomBytes(RANDOM_LENGTH);
return this.createProfileKeyCredentialPresentationWithRandom(
random,
groupSecretParams,
profileKeyCredential
);
}
createProfileKeyCredentialPresentationWithRandom(
random: Buffer,
groupSecretParams: GroupSecretParams,
profileKeyCredential: ProfileKeyCredential
): ProfileKeyCredentialPresentation {
return new ProfileKeyCredentialPresentation(
NativeImpl.ServerPublicParams_CreateProfileKeyCredentialPresentationDeterministic(
this.serverPublicParams.getContents(),
random,
groupSecretParams.getContents(),
profileKeyCredential.getContents()
)
);
}
}

View File

@ -0,0 +1,30 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import ProfileKeyCommitment from './ProfileKeyCommitment';
import ProfileKeyVersion from './ProfileKeyVersion';
import { UUIDType, fromUUID } from '../internal/UUIDUtil';
export default class ProfileKey extends ByteArray {
static SIZE = 32;
constructor(contents: Buffer) {
super(contents, ProfileKey.checkLength(ProfileKey.SIZE));
}
getCommitment(uuid: UUIDType): ProfileKeyCommitment {
return new ProfileKeyCommitment(
NativeImpl.ProfileKey_GetCommitment(this.contents, fromUUID(uuid))
);
}
getProfileKeyVersion(uuid: UUIDType): ProfileKeyVersion {
return new ProfileKeyVersion(
NativeImpl.ProfileKey_GetProfileKeyVersion(this.contents, fromUUID(uuid))
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ProfileKeyCommitment extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ProfileKeyCommitment_CheckValidContents);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ProfileKeyCredential extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ProfileKeyCredential_CheckValidContents);
}
}

View File

@ -0,0 +1,34 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import UuidCiphertext from '../groups/UuidCiphertext';
import ProfileKeyCiphertext from '../groups/ProfileKeyCiphertext';
export default class ProfileKeyCredentialPresentation extends ByteArray {
constructor(contents: Buffer) {
super(
contents,
NativeImpl.ProfileKeyCredentialPresentation_CheckValidContents
);
}
getUuidCiphertext(): UuidCiphertext {
return new UuidCiphertext(
NativeImpl.ProfileKeyCredentialPresentation_GetUuidCiphertext(
this.contents
)
);
}
getProfileKeyCiphertext(): ProfileKeyCiphertext {
return new ProfileKeyCiphertext(
NativeImpl.ProfileKeyCredentialPresentation_GetProfileKeyCiphertext(
this.contents
)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ProfileKeyCredentialRequest extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ProfileKeyCredentialRequest_CheckValidContents);
}
}

View File

@ -0,0 +1,23 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import ProfileKeyCredentialRequest from './ProfileKeyCredentialRequest';
export default class ProfileKeyCredentialRequestContext extends ByteArray {
constructor(contents: Buffer) {
super(
contents,
NativeImpl.ProfileKeyCredentialRequestContext_CheckValidContents
);
}
getRequest(): ProfileKeyCredentialRequest {
return new ProfileKeyCredentialRequest(
NativeImpl.ProfileKeyCredentialRequestContext_GetRequest(this.contents)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ProfileKeyCredentialResponse extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ProfileKeyCredentialResponse_CheckValidContents);
}
}

View File

@ -0,0 +1,21 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
export default class ProfileKeyVersion extends ByteArray {
static SIZE = 64;
constructor(contents: Buffer | string) {
super(
typeof contents === 'string' ? Buffer.from(contents) : contents,
ProfileKeyVersion.checkLength(ProfileKeyVersion.SIZE)
);
}
toString(): string {
return this.contents.toString('utf8');
}
}

View File

@ -0,0 +1,69 @@
//
// Copyright 2020-2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import NativeImpl from '../../NativeImpl';
import { RANDOM_LENGTH } from '../internal/Constants';
import ServerSecretParams from '../ServerSecretParams';
import ProfileKeyCredentialResponse from './ProfileKeyCredentialResponse';
import ProfileKeyCredentialRequest from './ProfileKeyCredentialRequest';
import ProfileKeyCommitment from './ProfileKeyCommitment';
import GroupPublicParams from '../groups/GroupPublicParams';
import ProfileKeyCredentialPresentation from './ProfileKeyCredentialPresentation';
import { UUIDType, fromUUID } from '../internal/UUIDUtil';
export default class ServerZkProfileOperations {
serverSecretParams: ServerSecretParams;
constructor(serverSecretParams: ServerSecretParams) {
this.serverSecretParams = serverSecretParams;
}
issueProfileKeyCredential(
profileKeyCredentialRequest: ProfileKeyCredentialRequest,
uuid: UUIDType,
profileKeyCommitment: ProfileKeyCommitment
): ProfileKeyCredentialResponse {
const random = randomBytes(RANDOM_LENGTH);
return this.issueProfileKeyCredentialWithRandom(
random,
profileKeyCredentialRequest,
uuid,
profileKeyCommitment
);
}
issueProfileKeyCredentialWithRandom(
random: Buffer,
profileKeyCredentialRequest: ProfileKeyCredentialRequest,
uuid: UUIDType,
profileKeyCommitment: ProfileKeyCommitment
): ProfileKeyCredentialResponse {
return new ProfileKeyCredentialResponse(
NativeImpl.ServerSecretParams_IssueProfileKeyCredentialDeterministic(
this.serverSecretParams.getContents(),
random,
profileKeyCredentialRequest.getContents(),
fromUUID(uuid),
profileKeyCommitment.getContents()
)
);
}
verifyProfileKeyCredentialPresentation(
groupPublicParams: GroupPublicParams,
profileKeyCredentialPresentation: ProfileKeyCredentialPresentation
): void {
NativeImpl.ServerSecretParams_VerifyProfileKeyCredentialPresentation(
this.serverSecretParams.getContents(),
groupPublicParams.getContents(),
profileKeyCredentialPresentation.getContents()
);
}
}

View File

@ -0,0 +1,81 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import { RANDOM_LENGTH } from '../internal/Constants';
import NativeImpl from '../../NativeImpl';
import ServerPublicParams from '../ServerPublicParams';
import ReceiptCredential from './ReceiptCredential';
import ReceiptCredentialPresentation from './ReceiptCredentialPresentation';
import ReceiptCredentialRequestContext from './ReceiptCredentialRequestContext';
import ReceiptCredentialResponse from './ReceiptCredentialResponse';
import ReceiptSerial from './ReceiptSerial';
export default class ClientZkReceiptOperations {
serverPublicParams: ServerPublicParams;
constructor(serverPublicParams: ServerPublicParams) {
this.serverPublicParams = serverPublicParams;
}
createReceiptCredentialRequestContext(
receiptSerial: ReceiptSerial
): ReceiptCredentialRequestContext {
const random = randomBytes(RANDOM_LENGTH);
return this.createReceiptCredentialRequestContextWithRandom(
random,
receiptSerial
);
}
createReceiptCredentialRequestContextWithRandom(
random: Buffer,
receiptSerial: ReceiptSerial
): ReceiptCredentialRequestContext {
return new ReceiptCredentialRequestContext(
NativeImpl.ServerPublicParams_CreateReceiptCredentialRequestContextDeterministic(
this.serverPublicParams.getContents(),
random,
receiptSerial.getContents()
)
);
}
receiveReceiptCredential(
receiptCredentialRequestContext: ReceiptCredentialRequestContext,
receiptCredentialResponse: ReceiptCredentialResponse
): ReceiptCredential {
return new ReceiptCredential(
NativeImpl.ServerPublicParams_ReceiveReceiptCredential(
this.serverPublicParams.getContents(),
receiptCredentialRequestContext.getContents(),
receiptCredentialResponse.getContents()
)
);
}
createReceiptCredentialPresentation(
receiptCredential: ReceiptCredential
): ReceiptCredentialPresentation {
const random = randomBytes(RANDOM_LENGTH);
return this.createReceiptCredentialPresentationWithRandom(
random,
receiptCredential
);
}
createReceiptCredentialPresentationWithRandom(
random: Buffer,
receiptCredential: ReceiptCredential
): ReceiptCredentialPresentation {
return new ReceiptCredentialPresentation(
NativeImpl.ServerPublicParams_CreateReceiptCredentialPresentationDeterministic(
this.serverPublicParams.getContents(),
random,
receiptCredential.getContents()
)
);
}
}

View File

@ -0,0 +1,25 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ReceiptCredential extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ReceiptCredential_CheckValidContents);
}
getReceiptExpirationTime(): bigint {
return NativeImpl.ReceiptCredential_GetReceiptExpirationTime(
this.contents
).readBigUInt64BE();
}
getReceiptLevel(): bigint {
return NativeImpl.ReceiptCredential_GetReceiptLevel(
this.contents
).readBigUInt64BE();
}
}

View File

@ -0,0 +1,37 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import ReceiptSerial from './ReceiptSerial';
export default class ReceiptCredentialPresentation extends ByteArray {
static SIZE = 329;
constructor(contents: Buffer) {
super(
contents,
NativeImpl.ReceiptCredentialPresentation_CheckValidContents
);
}
getReceiptExpirationTime(): bigint {
return NativeImpl.ReceiptCredentialPresentation_GetReceiptExpirationTime(
this.contents
).readBigUInt64BE();
}
getReceiptLevel(): bigint {
return NativeImpl.ReceiptCredentialPresentation_GetReceiptLevel(
this.contents
).readBigUInt64BE();
}
getReceiptSerialBytes(): ReceiptSerial {
return new ReceiptSerial(
NativeImpl.ReceiptCredentialPresentation_GetReceiptSerial(this.contents)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ReceiptCredentialRequest extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ReceiptCredentialRequest_CheckValidContents);
}
}

View File

@ -0,0 +1,25 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
import ReceiptCredentialRequest from './ReceiptCredentialRequest';
export default class ReceiptCredentialRequestContext extends ByteArray {
static SIZE = 177;
constructor(contents: Buffer) {
super(
contents,
NativeImpl.ReceiptCredentialRequestContext_CheckValidContents
);
}
getRequest(): ReceiptCredentialRequest {
return new ReceiptCredentialRequest(
NativeImpl.ReceiptCredentialRequestContext_GetRequest(this.contents)
);
}
}

View File

@ -0,0 +1,13 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
import NativeImpl from '../../NativeImpl';
export default class ReceiptCredentialResponse extends ByteArray {
constructor(contents: Buffer) {
super(contents, NativeImpl.ReceiptCredentialResponse_CheckValidContents);
}
}

View File

@ -0,0 +1,14 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import ByteArray from '../internal/ByteArray';
export default class ReceiptSerial extends ByteArray {
static SIZE = 16;
constructor(contents: Buffer) {
super(contents, ReceiptSerial.checkLength(ReceiptSerial.SIZE));
}
}

View File

@ -0,0 +1,61 @@
//
// Copyright 2021 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { randomBytes } from 'crypto';
import NativeImpl from '../../NativeImpl';
import { RANDOM_LENGTH } from '../internal/Constants';
import { bufferFromBigUInt64BE } from '../internal/BigIntUtil';
import ServerSecretParams from '../ServerSecretParams';
import ReceiptCredentialRequest from './ReceiptCredentialRequest';
import ReceiptCredentialResponse from './ReceiptCredentialResponse';
import ReceiptCredentialPresentation from './ReceiptCredentialPresentation';
export default class ServerZkReceiptOperations {
serverSecretParams: ServerSecretParams;
constructor(serverSecretParams: ServerSecretParams) {
this.serverSecretParams = serverSecretParams;
}
issueReceiptCredential(
receiptCredentialRequest: ReceiptCredentialRequest,
receiptExpirationTime: bigint,
receiptLevel: bigint
): ReceiptCredentialResponse {
const random = randomBytes(RANDOM_LENGTH);
return this.issueReceiptCredentialWithRandom(
random,
receiptCredentialRequest,
receiptExpirationTime,
receiptLevel
);
}
issueReceiptCredentialWithRandom(
random: Buffer,
receiptCredentialRequest: ReceiptCredentialRequest,
receiptExpirationTime: bigint,
receiptLevel: bigint
): ReceiptCredentialResponse {
return new ReceiptCredentialResponse(
NativeImpl.ServerSecretParams_IssueReceiptCredentialDeterministic(
this.serverSecretParams.getContents(),
random,
receiptCredentialRequest.getContents(),
bufferFromBigUInt64BE(receiptExpirationTime),
bufferFromBigUInt64BE(receiptLevel)
)
);
}
verifyReceiptCredentialPresentation(
receiptCredentialPresentation: ReceiptCredentialPresentation
): void {
NativeImpl.ServerSecretParams_VerifyReceiptCredentialPresentation(
this.serverSecretParams.getContents(),
receiptCredentialPresentation.getContents()
);
}
}

View File

@ -23,6 +23,7 @@ libsignal-protocol = { path = "../../protocol" }
device-transfer = { path = "../../device-transfer" }
hsm-enclave = { path = "../../hsm-enclave" }
signal-crypto = { path = "../../crypto" }
zkgroup = { path = "../../zkgroup" }
libsignal-bridge = { path = "../shared", features = ["ffi"] }
async-trait = "0.1.41"
cpufeatures = "0.2.1" # Make sure iOS gets optimized crypto.

View File

@ -17,8 +17,10 @@ style = "type"
prefix_with_name = true
[export]
include = ["SignalErrorCode", "FfiDirection", "FfiCiphertextMessageType", "FfiContentHint"]
item_types = ["enums", "functions", "opaque", "structs", "typedefs"]
include = ["SignalErrorCode", "FfiDirection", "FfiCiphertextMessageType", "FfiContentHint", "RandomnessBytes"]
exclude = ["TAG_SIZE", "NONCE_SIZE"]
item_types = ["enums", "functions", "opaque", "structs", "typedefs", "constants"]
# FIXME: this doesn't work well with constants in SCREAMING_SNAKE_CASE
prefix = "Signal"
renaming_overrides_prefixing = true
@ -42,8 +44,8 @@ sort_by = "None"
[parse]
parse_deps = true
include = ["libsignal-protocol", "signal-crypto"]
extra_bindings = ["libsignal-bridge"]
include = ["libsignal-protocol", "signal-crypto", "zkgroup"]
extra_bindings = ["libsignal-bridge", "zkgroup"]
[parse.expand]
crates = ["libsignal-ffi", "libsignal-bridge"]

View File

@ -10,6 +10,7 @@ use libsignal_bridge::ffi::*;
use libsignal_protocol::*;
use signal_crypto::Error as SignalCryptoError;
use std::ffi::CString;
use zkgroup::ZkGroupError;
#[derive(Debug)]
#[repr(C)]
@ -50,6 +51,8 @@ pub enum SignalErrorCode {
DuplicatedMessage = 90,
CallbackError = 100,
VerificationFailure = 110,
}
impl From<&SignalFfiError> for SignalErrorCode {
@ -166,11 +169,19 @@ impl From<&SignalFfiError> for SignalErrorCode {
SignalFfiError::Signal(SignalProtocolError::InvalidArgument(_))
| SignalFfiError::HsmEnclave(HsmEnclaveError::InvalidCodeHashError)
| SignalFfiError::SignalCrypto(_) => SignalErrorCode::InvalidArgument,
| SignalFfiError::SignalCrypto(_)
| SignalFfiError::ZkGroup(ZkGroupError::BadArgs) => SignalErrorCode::InvalidArgument,
SignalFfiError::Signal(SignalProtocolError::ApplicationCallbackError(_, _)) => {
SignalErrorCode::CallbackError
}
SignalFfiError::ZkGroup(
ZkGroupError::DecryptionFailure
| ZkGroupError::MacVerificationFailure
| ZkGroupError::ProofVerificationFailure
| ZkGroupError::SignatureVerificationFailure,
) => SignalErrorCode::VerificationFailure,
}
}
}

View File

@ -39,7 +39,8 @@ ignore_this_warning = re.compile(
"("
r"WARN: Can't find .*\. This usually means that this type was incompatible or not found\.|"
r"WARN: Missing `\[defines\]` entry for `feature = \".*\"` in cbindgen config\.|"
r"WARN: Skip libsignal-bridge::_ - \(not `pub`\)\."
r"WARN: Skip libsignal-bridge::.+ - \(not `pub`\)\.|"
r"WARN: Couldn't find path for Array\(Path\(GenericPath \{ .+ \}\), Name\(\"LEN\"\)\), skipping associated constants"
")")
unknown_warning = False

View File

@ -41,4 +41,7 @@ interface Wrapper<T> {
readonly _nativeHandle: T
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type Serialized<T> = Buffer;
export function registerErrors(errorsModule: Record<string, unknown>): void;

View File

@ -28,7 +28,7 @@ def translate_to_ts(typ):
"i32": "number",
"u8": "number",
"u32": "number",
"u64": "number",
"u64": "Buffer", # FIXME: eventually this should be a bigint
"bool": "boolean",
"String": "string",
"&str": "string",
@ -39,6 +39,9 @@ def translate_to_ts(typ):
if typ in type_map:
return type_map[typ]
if typ.startswith('[u8;') or typ.startswith('&[u8;'):
return 'Buffer'
if typ.startswith('&mutdyn'):
return typ[7:]

View File

@ -15,15 +15,18 @@ libsignal-protocol = { path = "../../protocol" }
signal-crypto = { path = "../../crypto" }
device-transfer = { path = "../../device-transfer" }
hsm-enclave = { path = "../../hsm-enclave" }
zkgroup = { path = "../../zkgroup" }
libsignal-bridge-macros = { path = "macros" }
aes-gcm-siv = "0.10.1"
async-trait = "0.1.41"
bincode = "1.0"
futures-util = "0.3.7"
hkdf = "0.11"
log = "0.4"
paste = "1.0"
rand = "0.7.3"
scopeguard = "1.0"
serde = "1.0"
sha2 = "0.9"
static_assertions = "1.1"
uuid = "0.8"

View File

@ -6,9 +6,12 @@
use libc::{c_char, c_uchar, c_void};
use libsignal_protocol::*;
use paste::paste;
use std::convert::TryInto;
use std::ffi::CStr;
use std::ops::Deref;
use crate::support::{FixedLengthBincodeSerializable, Serialized};
use super::*;
/// Converts arguments from their FFI form to their Rust form.
@ -253,6 +256,21 @@ impl ResultTypeInfo for uuid::Uuid {
}
}
impl<const LEN: usize> SimpleArgTypeInfo for &'_ [u8; LEN] {
type ArgType = *const [u8; LEN];
#[allow(clippy::not_unsafe_ptr_arg_deref)]
fn convert_from(arg: *const [u8; LEN]) -> SignalFfiResult<Self> {
unsafe { arg.as_ref() }.ok_or(SignalFfiError::NullPointer)
}
}
impl<const LEN: usize> ResultTypeInfo for [u8; LEN] {
type ResultType = [u8; LEN];
fn convert_into(self) -> SignalFfiResult<Self::ResultType> {
Ok(self)
}
}
macro_rules! store {
($name:ident) => {
paste! {
@ -307,6 +325,13 @@ impl<T: ResultTypeInfo> ResultTypeInfo for Result<T, signal_crypto::Error> {
}
}
impl<T: ResultTypeInfo> ResultTypeInfo for Result<T, zkgroup::ZkGroupError> {
type ResultType = T::ResultType;
fn convert_into(self) -> SignalFfiResult<Self::ResultType> {
T::convert_into(self?)
}
}
/// Allocates and returns a new Rust-owned C string.
impl ResultTypeInfo for String {
type ResultType = *const libc::c_char;
@ -442,6 +467,32 @@ impl<T: BridgeHandle> ResultTypeInfo for Option<T> {
}
}
impl<T> SimpleArgTypeInfo for Serialized<T>
where
T: FixedLengthBincodeSerializable + for<'a> serde::Deserialize<'a>,
{
type ArgType = *const T::Array;
fn convert_from(foreign: Self::ArgType) -> SignalFfiResult<Self> {
let array = unsafe { foreign.as_ref() }.ok_or(SignalFfiError::NullPointer)?;
let result: T =
bincode::deserialize(array.as_ref()).map_err(|_| SignalFfiError::InvalidType)?;
Ok(Serialized::from(result))
}
}
impl<T> ResultTypeInfo for Serialized<T>
where
T: FixedLengthBincodeSerializable + serde::Serialize,
{
type ResultType = T::Array;
fn convert_into(self) -> SignalFfiResult<Self::ResultType> {
let result = bincode::serialize(self.deref()).expect("can always serialize a value");
Ok(result.as_slice().try_into().expect("wrong serialized size"))
}
}
/// Implementation of [`bridge_handle`](crate::support::bridge_handle) for FFI.
macro_rules! ffi_bridge_handle {
( $typ:ty as false $(, $($_:tt)*)? ) => {};
@ -519,11 +570,16 @@ macro_rules! ffi_arg_type {
(Context) => (*mut libc::c_void);
(Timestamp) => (u64);
(Uuid) => (*const [u8; 16]);
(&[u8; $len:expr]) => (*const [u8; $len]);
(&[& $typ:ty]) => (*const *const $typ);
(&mut dyn $typ:ty) => (*const paste!(ffi::[<Ffi $typ Struct>]));
(& $typ:ty) => (*const $typ);
(&mut $typ:ty) => (*mut $typ);
(Option<& $typ:ty>) => (*const $typ);
// In order to provide a fixed-sized array of the correct length,
// a serialized type FooBar must have a constant FOO_BAR_LEN that's in scope (and exposed to C).
(Serialized<$typ:ident>) => (*const [libc::c_uchar; paste!([<$typ:snake:upper _LEN>])]);
}
/// Syntactically translates `bridge_fn` result types to FFI types for `cbindgen`.
@ -554,5 +610,11 @@ macro_rules! ffi_result_type {
(Option<$typ:ty>) => (*mut $typ);
(Timestamp) => (u64);
(Uuid) => ([u8; 16]);
([u8; $len:expr]) => ([u8; $len]);
// In order to provide a fixed-sized array of the correct length,
// a serialized type FooBar must have a constant FOO_BAR_LEN that's in scope (and exposed to C).
(Serialized<$typ:ident>) => ([libc::c_uchar; paste!([<$typ:snake:upper _LEN>])]);
( $typ:ty ) => (*mut $typ);
}

View File

@ -10,6 +10,7 @@ use device_transfer::Error as DeviceTransferError;
use hsm_enclave::Error as HsmEnclaveError;
use libsignal_protocol::*;
use signal_crypto::Error as SignalCryptoError;
use zkgroup::ZkGroupError;
use crate::support::describe_panic;
@ -20,6 +21,7 @@ pub enum SignalFfiError {
DeviceTransfer(DeviceTransferError),
HsmEnclave(HsmEnclaveError),
SignalCrypto(SignalCryptoError),
ZkGroup(ZkGroupError),
InsufficientOutputSize(usize, usize),
NullPointer,
InvalidUtf8String,
@ -40,6 +42,7 @@ impl fmt::Display for SignalFfiError {
SignalFfiError::SignalCrypto(c) => {
write!(f, "Cryptographic operation failed: {}", c)
}
SignalFfiError::ZkGroup(e) => write!(f, "{}", e),
SignalFfiError::NullPointer => write!(f, "null pointer"),
SignalFfiError::InvalidType => write!(f, "invalid type"),
SignalFfiError::InvalidUtf8String => write!(f, "invalid UTF8 string"),
@ -77,6 +80,12 @@ impl From<SignalCryptoError> for SignalFfiError {
}
}
impl From<ZkGroupError> for SignalFfiError {
fn from(e: ZkGroupError) -> SignalFfiError {
SignalFfiError::ZkGroup(e)
}
}
pub type SignalFfiResult<T> = Result<T, SignalFfiError>;
/// Represents an error returned by a callback, following the C conventions that 0 means "success".

View File

@ -11,6 +11,8 @@ use paste::paste;
use std::convert::TryInto;
use std::ops::Deref;
use crate::support::{Array, FixedLengthBincodeSerializable, Serialized};
use super::*;
/// Converts arguments from their JNI form to their Rust form.
@ -162,6 +164,14 @@ impl<'a> SimpleArgTypeInfo<'a> for Option<u32> {
}
}
/// Reinterprets the bits of the Java `long` as a `u64`.
impl<'a> SimpleArgTypeInfo<'a> for u64 {
type ArgType = jlong;
fn convert_from(_env: &JNIEnv, foreign: jlong) -> SignalJniResult<Self> {
Ok(foreign as u64)
}
}
/// Supports values `0..=Long.MAX_VALUE`.
///
/// Negative `long` values are *not* reinterpreted as large `u64` values.
@ -436,6 +446,18 @@ impl ResultTypeInfo for Option<u32> {
}
}
/// Reinterprets the bits of the `u64` as a Java `long`.
impl ResultTypeInfo for u64 {
type ResultType = jlong;
fn convert_into(self, _env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
// Note that we don't check bounds here.
Ok(self as jlong)
}
fn convert_into_jobject(_signal_jni_result: &SignalJniResult<Self::ResultType>) -> JObject {
JObject::null()
}
}
/// Reinterprets the bits of the timestamp's `u64` as a Java `long`.
///
/// Note that this is different from the implementation of [`ArgTypeInfo`] for `Timestamp`.
@ -552,6 +574,36 @@ impl ResultTypeInfo for Option<Vec<u8>> {
}
}
impl<'storage, 'context: 'storage, const LEN: usize> ArgTypeInfo<'storage, 'context>
for &'storage [u8; LEN]
{
type ArgType = jbyteArray;
type StoredType = AutoArray<'context, 'context, jbyte>;
fn borrow(env: &'context JNIEnv, foreign: Self::ArgType) -> SignalJniResult<Self::StoredType> {
Ok(env.get_byte_array_elements(foreign, ReleaseMode::NoCopyBack)?)
}
fn load_from(
_env: &JNIEnv,
stored: &'storage mut Self::StoredType,
) -> SignalJniResult<&'storage [u8; LEN]> {
unsafe { std::slice::from_raw_parts(stored.as_ptr() as *const u8, stored.size()? as usize) }
.try_into()
.map_err(|_| SignalJniError::DeserializationFailed(std::any::type_name::<[u8; LEN]>()))
}
}
impl<const LEN: usize> ResultTypeInfo for [u8; LEN] {
type ResultType = jbyteArray;
fn convert_into(self, env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
self.as_ref().convert_into(env)
}
fn convert_into_jobject(signal_jni_result: &SignalJniResult<Self::ResultType>) -> JObject {
signal_jni_result
.as_ref()
.map_or(JObject::null(), |&jobj| JObject::from(jobj))
}
}
impl ResultTypeInfo for uuid::Uuid {
type ResultType = jobject;
fn convert_into(self, env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
@ -651,6 +703,16 @@ impl<T: ResultTypeInfo> ResultTypeInfo for Result<T, signal_crypto::Error> {
}
}
impl<T: ResultTypeInfo> ResultTypeInfo for Result<T, zkgroup::ZkGroupError> {
type ResultType = T::ResultType;
fn convert_into(self, env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
T::convert_into(self?, env)
}
fn convert_into_jobject(signal_jni_result: &SignalJniResult<Self::ResultType>) -> JObject {
<T as ResultTypeInfo>::convert_into_jobject(signal_jni_result)
}
}
impl<T: ResultTypeInfo> ResultTypeInfo for SignalJniResult<T> {
type ResultType = T::ResultType;
fn convert_into(self, env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
@ -764,6 +826,47 @@ impl<T: BridgeHandle> ResultTypeInfo for Option<T> {
}
}
impl<T> SimpleArgTypeInfo<'_> for Serialized<T>
where
T: FixedLengthBincodeSerializable + for<'a> serde::Deserialize<'a>,
{
type ArgType = jbyteArray;
fn convert_from(env: &jni_crate::JNIEnv, foreign: Self::ArgType) -> SignalJniResult<Self> {
let borrowed_array = env.get_byte_array_elements(foreign, ReleaseMode::NoCopyBack)?;
let len = borrowed_array.size()? as usize;
if len != T::Array::LEN {
return Err(SignalJniError::DeserializationFailed(
std::any::type_name::<T>(),
));
}
// Convert from i8 to u8.
let bytes =
unsafe { std::slice::from_raw_parts(borrowed_array.as_ptr() as *const u8, len) };
let result: T = bincode::deserialize(bytes)
.map_err(|_| SignalJniError::DeserializationFailed(std::any::type_name::<T>()))?;
Ok(Serialized::from(result))
}
}
impl<T> ResultTypeInfo for Serialized<T>
where
T: FixedLengthBincodeSerializable + serde::Serialize,
{
type ResultType = jbyteArray;
fn convert_into(self, env: &JNIEnv) -> SignalJniResult<Self::ResultType> {
let result = bincode::serialize(self.deref()).expect("can always serialize a value");
result.convert_into(env)
}
fn convert_into_jobject(
signal_jni_result: &SignalJniResult<Self::ResultType>,
) -> jni_crate::objects::JObject {
Vec::<u8>::convert_into_jobject(signal_jni_result)
}
}
/// Implementation of [`bridge_handle`](crate::support::bridge_handle) for JNI.
macro_rules! jni_bridge_handle {
( $typ:ty as false $(, $($_:tt)*)? ) => {};
@ -849,6 +952,9 @@ macro_rules! jni_arg_type {
(Option<u32>) => {
jni::jint
};
(u64) => {
jni::jlong
};
(String) => {
jni::JString
};
@ -864,6 +970,9 @@ macro_rules! jni_arg_type {
(&mut [u8]) => {
jni::jbyteArray
};
(&[u8; $len:expr]) => {
jni::jbyteArray
};
(Context) => {
jni::JObject
};
@ -891,6 +1000,9 @@ macro_rules! jni_arg_type {
(Option<& $typ:ty>) => {
jni::ObjectHandle
};
(Serialized<$typ:ident>) => {
jni::jbyteArray
};
}
/// Syntactically translates `bridge_fn` result types to JNI types for `cbindgen` and
@ -935,6 +1047,9 @@ macro_rules! jni_result_type {
(Option<u32>) => {
jni::jint
};
(u64) => {
jni::jlong
};
(&str) => {
jni::jstring
};
@ -953,12 +1068,18 @@ macro_rules! jni_result_type {
(Vec<u8>) => {
jni::jbyteArray
};
([u8; $len:expr]) => {
jni::jbyteArray
};
(Option<$typ:tt>) => {
jni_result_type!($typ)
};
(CiphertextMessage) => {
jni::JavaReturnCiphertextMessage
};
(Serialized<$typ:ident>) => {
jni::jbyteArray
};
( $handle:ident ) => {
jni::ObjectHandle
};

View File

@ -11,6 +11,7 @@ use device_transfer::Error as DeviceTransferError;
use hsm_enclave::Error as HsmEnclaveError;
use libsignal_protocol::*;
use signal_crypto::Error as SignalCryptoError;
use zkgroup::ZkGroupError;
use crate::support::describe_panic;
@ -22,12 +23,14 @@ pub enum SignalJniError {
Signal(SignalProtocolError),
DeviceTransfer(DeviceTransferError),
SignalCrypto(SignalCryptoError),
HsmEnclave(HsmEnclaveError),
ZkGroup(ZkGroupError),
Jni(jni::errors::Error),
BadJniParameter(&'static str),
DeserializationFailed(&'static str),
UnexpectedJniResultType(&'static str, &'static str),
NullHandle,
IntegerOverflow(String),
HsmEnclave(HsmEnclaveError),
UnexpectedPanic(std::boxed::Box<dyn std::any::Any + std::marker::Send>),
}
@ -36,7 +39,9 @@ impl fmt::Display for SignalJniError {
match self {
SignalJniError::Signal(s) => write!(f, "{}", s),
SignalJniError::DeviceTransfer(s) => write!(f, "{}", s),
SignalJniError::HsmEnclave(e) => write!(f, "{}", e),
SignalJniError::SignalCrypto(s) => write!(f, "{}", s),
SignalJniError::ZkGroup(e) => write!(f, "{}", e),
SignalJniError::Jni(s) => write!(f, "JNI error {}", s),
SignalJniError::NullHandle => write!(f, "null handle"),
SignalJniError::BadJniParameter(m) => write!(f, "bad parameter type {}", m),
@ -46,8 +51,8 @@ impl fmt::Display for SignalJniError {
SignalJniError::IntegerOverflow(m) => {
write!(f, "integer overflow during conversion of {}", m)
}
SignalJniError::HsmEnclave(e) => {
write!(f, "{}", e)
SignalJniError::DeserializationFailed(ty) => {
write!(f, "failed to deserialize {}", ty)
}
SignalJniError::UnexpectedPanic(e) => {
write!(f, "unexpected panic: {}", describe_panic(e))
@ -80,6 +85,12 @@ impl From<SignalCryptoError> for SignalJniError {
}
}
impl From<ZkGroupError> for SignalJniError {
fn from(e: ZkGroupError) -> SignalJniError {
SignalJniError::ZkGroup(e)
}
}
impl From<jni::errors::Error> for SignalJniError {
fn from(e: jni::errors::Error) -> SignalJniError {
SignalJniError::Jni(e)

View File

@ -14,6 +14,7 @@ use libsignal_protocol::*;
use signal_crypto::Error as SignalCryptoError;
use std::convert::{TryFrom, TryInto};
use std::error::Error;
use zkgroup::ZkGroupError;
pub(crate) use jni::objects::{AutoArray, JClass, JObject, JString, ReleaseMode};
pub(crate) use jni::sys::{jboolean, jbyteArray, jint, jlong, jlongArray, jstring};
@ -220,9 +221,8 @@ fn throw_error(env: &JNIEnv, error: SignalJniError) {
SignalJniError::Signal(SignalProtocolError::InvalidArgument(_))
| SignalJniError::SignalCrypto(SignalCryptoError::UnknownAlgorithm(_, _))
| SignalJniError::SignalCrypto(SignalCryptoError::InvalidInputSize)
| SignalJniError::SignalCrypto(SignalCryptoError::InvalidNonceSize) => {
"java/lang/IllegalArgumentException"
}
| SignalJniError::SignalCrypto(SignalCryptoError::InvalidNonceSize)
| SignalJniError::DeserializationFailed(_) => "java/lang/IllegalArgumentException",
SignalJniError::IntegerOverflow(_)
| SignalJniError::Jni(_)
@ -308,6 +308,16 @@ fn throw_error(env: &JNIEnv, error: SignalJniError) {
SignalJniError::HsmEnclave(HsmEnclaveError::InvalidBridgeStateError) => {
"java/lang/IllegalStateException"
}
SignalJniError::ZkGroup(ZkGroupError::BadArgs) => {
"org/signal/zkgroup/InvalidInputException"
}
SignalJniError::ZkGroup(
ZkGroupError::DecryptionFailure
| ZkGroupError::MacVerificationFailure
| ZkGroupError::ProofVerificationFailure
| ZkGroupError::SignatureVerificationFailure,
) => "org/signal/zkgroup/VerificationFailedException",
};
if let Err(e) = env.throw_new(exception_type, error.to_string()) {

Some files were not shown because too many files have changed in this diff Show More