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:
Jordan Rose
2022-06-21 15:11:57 -07:00
committed by GitHub
parent cc217911a9
commit 70ec1ca26f
35 changed files with 1725 additions and 168 deletions

View File

@@ -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);

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}