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:
commit
3647f2501b
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -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]]
|
||||
|
@ -33,7 +33,7 @@ sourceSets {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile ('junit:junit:3.8.2')
|
||||
testCompile ('junit:junit:4.12')
|
||||
}
|
||||
|
||||
test {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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); }
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
@ -6,7 +6,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:3.8.2'
|
||||
testCompile 'junit:junit:4.12'
|
||||
|
||||
compile project(':java')
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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
89
node/Native.d.ts
vendored
@ -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
423
node/test/ZKGroup-test.ts
Normal 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);
|
||||
});
|
||||
});
|
14
node/zkgroup/NotarySignature.ts
Normal file
14
node/zkgroup/NotarySignature.ts
Normal 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));
|
||||
}
|
||||
}
|
22
node/zkgroup/ServerPublicParams.ts
Normal file
22
node/zkgroup/ServerPublicParams.ts
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
52
node/zkgroup/ServerSecretParams.ts
Normal file
52
node/zkgroup/ServerSecretParams.ts
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/auth/AuthCredential.ts
Normal file
13
node/zkgroup/auth/AuthCredential.ts
Normal 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);
|
||||
}
|
||||
}
|
26
node/zkgroup/auth/AuthCredentialPresentation.ts
Normal file
26
node/zkgroup/auth/AuthCredentialPresentation.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
12
node/zkgroup/auth/AuthCredentialResponse.ts
Normal file
12
node/zkgroup/auth/AuthCredentialResponse.ts
Normal 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);
|
||||
}
|
||||
}
|
67
node/zkgroup/auth/ClientZkAuthOperations.ts
Normal file
67
node/zkgroup/auth/ClientZkAuthOperations.ts
Normal 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
57
node/zkgroup/auth/ServerZkAuthOperations.ts
Normal file
57
node/zkgroup/auth/ServerZkAuthOperations.ts
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
89
node/zkgroup/groups/ClientZkGroupCipher.ts
Normal file
89
node/zkgroup/groups/ClientZkGroupCipher.ts
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
14
node/zkgroup/groups/GroupIdentifier.ts
Normal file
14
node/zkgroup/groups/GroupIdentifier.ts
Normal 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));
|
||||
}
|
||||
}
|
14
node/zkgroup/groups/GroupMasterKey.ts
Normal file
14
node/zkgroup/groups/GroupMasterKey.ts
Normal 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));
|
||||
}
|
||||
}
|
20
node/zkgroup/groups/GroupPublicParams.ts
Normal file
20
node/zkgroup/groups/GroupPublicParams.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
52
node/zkgroup/groups/GroupSecretParams.ts
Normal file
52
node/zkgroup/groups/GroupSecretParams.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/groups/ProfileKeyCiphertext.ts
Normal file
13
node/zkgroup/groups/ProfileKeyCiphertext.ts
Normal 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);
|
||||
}
|
||||
}
|
13
node/zkgroup/groups/UuidCiphertext.ts
Normal file
13
node/zkgroup/groups/UuidCiphertext.ts
Normal 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
52
node/zkgroup/index.ts
Normal 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';
|
15
node/zkgroup/internal/BigIntUtil.ts
Normal file
15
node/zkgroup/internal/BigIntUtil.ts
Normal 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;
|
||||
}
|
37
node/zkgroup/internal/ByteArray.ts
Normal file
37
node/zkgroup/internal/ByteArray.ts
Normal 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);
|
||||
}
|
||||
}
|
10
node/zkgroup/internal/Constants.ts
Normal file
10
node/zkgroup/internal/Constants.ts
Normal 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;
|
29
node/zkgroup/internal/UUIDUtil.ts
Normal file
29
node/zkgroup/internal/UUIDUtil.ts
Normal 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;
|
||||
}
|
96
node/zkgroup/profiles/ClientZkProfileOperations.ts
Normal file
96
node/zkgroup/profiles/ClientZkProfileOperations.ts
Normal 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
30
node/zkgroup/profiles/ProfileKey.ts
Normal file
30
node/zkgroup/profiles/ProfileKey.ts
Normal 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))
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/profiles/ProfileKeyCommitment.ts
Normal file
13
node/zkgroup/profiles/ProfileKeyCommitment.ts
Normal 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);
|
||||
}
|
||||
}
|
13
node/zkgroup/profiles/ProfileKeyCredential.ts
Normal file
13
node/zkgroup/profiles/ProfileKeyCredential.ts
Normal 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);
|
||||
}
|
||||
}
|
34
node/zkgroup/profiles/ProfileKeyCredentialPresentation.ts
Normal file
34
node/zkgroup/profiles/ProfileKeyCredentialPresentation.ts
Normal 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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/profiles/ProfileKeyCredentialRequest.ts
Normal file
13
node/zkgroup/profiles/ProfileKeyCredentialRequest.ts
Normal 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);
|
||||
}
|
||||
}
|
23
node/zkgroup/profiles/ProfileKeyCredentialRequestContext.ts
Normal file
23
node/zkgroup/profiles/ProfileKeyCredentialRequestContext.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/profiles/ProfileKeyCredentialResponse.ts
Normal file
13
node/zkgroup/profiles/ProfileKeyCredentialResponse.ts
Normal 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);
|
||||
}
|
||||
}
|
21
node/zkgroup/profiles/ProfileKeyVersion.ts
Normal file
21
node/zkgroup/profiles/ProfileKeyVersion.ts
Normal 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');
|
||||
}
|
||||
}
|
69
node/zkgroup/profiles/ServerZkProfileOperations.ts
Normal file
69
node/zkgroup/profiles/ServerZkProfileOperations.ts
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
81
node/zkgroup/receipts/ClientZkReceiptOperations.ts
Normal file
81
node/zkgroup/receipts/ClientZkReceiptOperations.ts
Normal 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()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
25
node/zkgroup/receipts/ReceiptCredential.ts
Normal file
25
node/zkgroup/receipts/ReceiptCredential.ts
Normal 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();
|
||||
}
|
||||
}
|
37
node/zkgroup/receipts/ReceiptCredentialPresentation.ts
Normal file
37
node/zkgroup/receipts/ReceiptCredentialPresentation.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/receipts/ReceiptCredentialRequest.ts
Normal file
13
node/zkgroup/receipts/ReceiptCredentialRequest.ts
Normal 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);
|
||||
}
|
||||
}
|
25
node/zkgroup/receipts/ReceiptCredentialRequestContext.ts
Normal file
25
node/zkgroup/receipts/ReceiptCredentialRequestContext.ts
Normal 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)
|
||||
);
|
||||
}
|
||||
}
|
13
node/zkgroup/receipts/ReceiptCredentialResponse.ts
Normal file
13
node/zkgroup/receipts/ReceiptCredentialResponse.ts
Normal 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);
|
||||
}
|
||||
}
|
14
node/zkgroup/receipts/ReceiptSerial.ts
Normal file
14
node/zkgroup/receipts/ReceiptSerial.ts
Normal 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));
|
||||
}
|
||||
}
|
61
node/zkgroup/receipts/ServerZkReceiptOperations.ts
Normal file
61
node/zkgroup/receipts/ServerZkReceiptOperations.ts
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
@ -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"]
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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:]
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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".
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user