mirror of
https://github.com/signalapp/libsignal.git
synced 2026-05-13 10:26:47 +02:00
zkgroup: Add AuthCredentialWithPni
This is a variant of AuthCredential that carries two UUIDs, intended to be a user's ACI and PNI. Why? Because when you've been invited to a group, you may have been invited by your ACI or by your PNI, or by both, and it's easier for clients to treat all those states the same by having a credential that covers both identities. The downside is that it's larger (both the data, obviously, but also the zkgroup proof of validity, unsurprisingly). AnyAuthCredentialPresentation gains a 'get_pni_ciphertext' method, which will return `None` for the existing presentations and `Some(encrypted_pni)` for the new credential. Having a separate credential type but a common presentation type makes it easier for the server to handle all possible credentials uniformly.
This commit is contained in:
@@ -105,11 +105,16 @@ public final class Native {
|
||||
public static native long Aes256GcmSiv_New(byte[] key);
|
||||
|
||||
public static native void AuthCredentialPresentation_CheckValidContents(byte[] presentationBytes);
|
||||
public static native int AuthCredentialPresentation_GetRedemptionTime(byte[] presentationBytes);
|
||||
public static native byte[] AuthCredentialPresentation_GetPniCiphertext(byte[] presentationBytes);
|
||||
public static native long AuthCredentialPresentation_GetRedemptionTime(byte[] presentationBytes);
|
||||
public static native byte[] AuthCredentialPresentation_GetUuidCiphertext(byte[] presentationBytes);
|
||||
|
||||
public static native void AuthCredentialResponse_CheckValidContents(byte[] buffer);
|
||||
|
||||
public static native void AuthCredentialWithPniResponse_CheckValidContents(byte[] buffer);
|
||||
|
||||
public static native void AuthCredentialWithPni_CheckValidContents(byte[] buffer);
|
||||
|
||||
public static native void AuthCredential_CheckValidContents(byte[] buffer);
|
||||
|
||||
public static native void Cds2ClientState_CompleteHandshake(long cli, byte[] handshakeReceived);
|
||||
@@ -358,6 +363,7 @@ public final class Native {
|
||||
|
||||
public static native void ServerPublicParams_CheckValidContents(byte[] buffer);
|
||||
public static native byte[] ServerPublicParams_CreateAuthCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] authCredential);
|
||||
public static native byte[] ServerPublicParams_CreateAuthCredentialWithPniPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] authCredential);
|
||||
public static native byte[] ServerPublicParams_CreateExpiringProfileKeyCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] profileKeyCredential);
|
||||
public static native byte[] ServerPublicParams_CreatePniCredentialPresentationDeterministic(byte[] serverPublicParams, byte[] randomness, byte[] groupSecretParams, byte[] pniCredential);
|
||||
public static native byte[] ServerPublicParams_CreatePniCredentialRequestContextDeterministic(byte[] serverPublicParams, byte[] randomness, UUID aci, UUID pni, byte[] profileKey);
|
||||
@@ -366,6 +372,7 @@ public final class Native {
|
||||
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_ReceiveAuthCredentialWithPni(byte[] params, UUID aci, UUID pni, long redemptionTime, byte[] response);
|
||||
public static native byte[] ServerPublicParams_ReceiveExpiringProfileKeyCredential(byte[] serverPublicParams, byte[] requestContext, byte[] response, long currentTimeInSeconds);
|
||||
public static native byte[] ServerPublicParams_ReceivePniCredential(byte[] serverPublicParams, byte[] requestContext, byte[] response);
|
||||
public static native byte[] ServerPublicParams_ReceiveProfileKeyCredential(byte[] serverPublicParams, byte[] requestContext, byte[] response);
|
||||
@@ -376,6 +383,7 @@ public final class Native {
|
||||
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_IssueAuthCredentialWithPniDeterministic(byte[] serverSecretParams, byte[] randomness, UUID aci, UUID pni, long redemptionTime);
|
||||
public static native byte[] ServerSecretParams_IssueExpiringProfileKeyCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, byte[] request, UUID uuid, byte[] commitment, long expirationInSeconds);
|
||||
public static native byte[] ServerSecretParams_IssuePniCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, byte[] request, UUID aci, UUID pni, byte[] commitment);
|
||||
public static native byte[] ServerSecretParams_IssueProfileKeyCredentialDeterministic(byte[] serverSecretParams, byte[] randomness, byte[] request, UUID uuid, byte[] commitment);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//
|
||||
// Copyright 2020-2021 Signal Messenger, LLC.
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
package org.signal.libsignal.zkgroup.auth;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.time.Instant;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.groups.UuidCiphertext;
|
||||
import org.signal.libsignal.zkgroup.internal.ByteArray;
|
||||
@@ -13,7 +14,7 @@ import org.signal.libsignal.internal.Native;
|
||||
|
||||
public final class AuthCredentialPresentation extends ByteArray {
|
||||
|
||||
public enum Version {V1, V2, UNKNOWN};
|
||||
public enum Version {V1, V2, V3, UNKNOWN};
|
||||
|
||||
public AuthCredentialPresentation(byte[] contents) throws InvalidInputException {
|
||||
super(contents);
|
||||
@@ -30,18 +31,33 @@ public final class AuthCredentialPresentation extends ByteArray {
|
||||
}
|
||||
}
|
||||
|
||||
public int getRedemptionTime() {
|
||||
return Native.AuthCredentialPresentation_GetRedemptionTime(contents);
|
||||
/**
|
||||
* Returns the PNI ciphertext for this credential. May be {@code null}.
|
||||
*/
|
||||
public UuidCiphertext getPniCiphertext() {
|
||||
byte[] newContents = Native.AuthCredentialPresentation_GetPniCiphertext(contents);
|
||||
if (newContents == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return new UuidCiphertext(newContents);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Instant getRedemptionTime() {
|
||||
return Instant.ofEpochSecond(Native.AuthCredentialPresentation_GetRedemptionTime(contents));
|
||||
}
|
||||
|
||||
public Version getVersion() {
|
||||
if (this.contents[0] == 0) {
|
||||
return Version.V1;
|
||||
} else if (this.contents[0] == 1) {
|
||||
return Version.V2;
|
||||
} else {
|
||||
return Version.UNKNOWN;
|
||||
}
|
||||
switch (this.contents[0]) {
|
||||
case 0: return Version.V1;
|
||||
case 1: return Version.V2;
|
||||
case 2: return Version.V3;
|
||||
default: return Version.UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright 2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
package org.signal.libsignal.zkgroup.auth;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.internal.ByteArray;
|
||||
import org.signal.libsignal.internal.Native;
|
||||
|
||||
public final class AuthCredentialWithPni extends ByteArray {
|
||||
public AuthCredentialWithPni(byte[] contents) throws InvalidInputException {
|
||||
super(contents);
|
||||
Native.AuthCredentialWithPni_CheckValidContents(contents);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Copyright 2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
package org.signal.libsignal.zkgroup.auth;
|
||||
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.internal.ByteArray;
|
||||
import org.signal.libsignal.internal.Native;
|
||||
|
||||
public final class AuthCredentialWithPniResponse extends ByteArray {
|
||||
public AuthCredentialWithPniResponse(byte[] contents) throws InvalidInputException {
|
||||
super(contents);
|
||||
Native.AuthCredentialWithPniResponse_CheckValidContents(contents);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Copyright 2020-2021 Signal Messenger, LLC.
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
@@ -33,6 +33,21 @@ public class ClientZkAuthOperations {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces the AuthCredentialWithPni from a server-generated AuthCredentialWithPniResponse.
|
||||
*
|
||||
* @param redemptionTime This is provided by the server as an integer, and should be passed through directly.
|
||||
*/
|
||||
public AuthCredentialWithPni receiveAuthCredentialWithPni(UUID aci, UUID pni, long redemptionTime, AuthCredentialWithPniResponse authCredentialResponse) throws VerificationFailedException {
|
||||
byte[] newContents = Native.ServerPublicParams_ReceiveAuthCredentialWithPni(serverPublicParams.getInternalContentsForJNI(), aci, pni, redemptionTime, authCredentialResponse.getInternalContentsForJNI());
|
||||
|
||||
try {
|
||||
return new AuthCredentialWithPni(newContents);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public AuthCredentialPresentation createAuthCredentialPresentation(GroupSecretParams groupSecretParams, AuthCredential authCredential) {
|
||||
return createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential);
|
||||
}
|
||||
@@ -50,4 +65,20 @@ public class ClientZkAuthOperations {
|
||||
}
|
||||
}
|
||||
|
||||
public AuthCredentialPresentation createAuthCredentialPresentation(GroupSecretParams groupSecretParams, AuthCredentialWithPni authCredential) {
|
||||
return createAuthCredentialPresentation(new SecureRandom(), groupSecretParams, authCredential);
|
||||
}
|
||||
|
||||
public AuthCredentialPresentation createAuthCredentialPresentation(SecureRandom secureRandom, GroupSecretParams groupSecretParams, AuthCredentialWithPni authCredential) {
|
||||
byte[] random = new byte[RANDOM_LENGTH];
|
||||
secureRandom.nextBytes(random);
|
||||
|
||||
byte[] newContents = Native.ServerPublicParams_CreateAuthCredentialWithPniPresentationDeterministic(serverPublicParams.getInternalContentsForJNI(), random, groupSecretParams.getInternalContentsForJNI(), authCredential.getInternalContentsForJNI());
|
||||
|
||||
try {
|
||||
return new AuthCredentialPresentation(newContents);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
//
|
||||
// Copyright 2020-2021 Signal Messenger, LLC.
|
||||
// Copyright 2020-2022 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
package org.signal.libsignal.zkgroup.auth;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
import org.signal.libsignal.zkgroup.ServerSecretParams;
|
||||
import org.signal.libsignal.zkgroup.VerificationFailedException;
|
||||
@@ -43,15 +44,34 @@ public class ServerZkAuthOperations {
|
||||
}
|
||||
}
|
||||
|
||||
public AuthCredentialWithPniResponse issueAuthCredentialWithPni(UUID aci, UUID pni, Instant redemptionTime) {
|
||||
return issueAuthCredentialWithPni(new SecureRandom(), aci, pni, redemptionTime);
|
||||
}
|
||||
|
||||
public AuthCredentialWithPniResponse issueAuthCredentialWithPni(SecureRandom secureRandom, UUID aci, UUID pni, Instant redemptionTime) {
|
||||
byte[] random = new byte[RANDOM_LENGTH];
|
||||
|
||||
secureRandom.nextBytes(random);
|
||||
|
||||
byte[] newContents = Native.ServerSecretParams_IssueAuthCredentialWithPniDeterministic(serverSecretParams.getInternalContentsForJNI(), random, aci, pni, redemptionTime.getEpochSecond());
|
||||
|
||||
try {
|
||||
return new AuthCredentialWithPniResponse(newContents);
|
||||
} catch (InvalidInputException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation) throws VerificationFailedException, InvalidRedemptionTimeException {
|
||||
verifyAuthCredentialPresentation(groupPublicParams, authCredentialPresentation, System.currentTimeMillis());
|
||||
verifyAuthCredentialPresentation(groupPublicParams, authCredentialPresentation, Instant.now());
|
||||
}
|
||||
|
||||
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);
|
||||
public void verifyAuthCredentialPresentation(GroupPublicParams groupPublicParams, AuthCredentialPresentation authCredentialPresentation, Instant currentTime) throws VerificationFailedException, InvalidRedemptionTimeException {
|
||||
// TODO: Move this check down to Rust.
|
||||
Instant acceptableStartTime = authCredentialPresentation.getRedemptionTime().minus(1, ChronoUnit.DAYS);
|
||||
Instant acceptableEndTime = acceptableStartTime.plus(3, ChronoUnit.DAYS);
|
||||
|
||||
if (currentTimeMillis < acceptableStartTime || currentTimeMillis > acceptableEndTime) {
|
||||
if (currentTime.isBefore(acceptableStartTime) || currentTime.isAfter(acceptableEndTime)) {
|
||||
throw new InvalidRedemptionTimeException();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user