diff --git a/java/server/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java b/java/server/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java index 6be853511b..9ee75dd3f8 100644 --- a/java/server/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java +++ b/java/server/src/test/java/org/signal/libsignal/usernames/UsernamesTest.java @@ -5,21 +5,19 @@ package org.signal.libsignal.usernames; -import org.signal.libsignal.usernames.Username; - -import java.util.List; - import junit.framework.TestCase; import org.signal.libsignal.protocol.util.Hex; +import java.util.List; + public class UsernamesTest extends TestCase { public void testUsernameGeneration() throws BaseUsernameException { String nickname = "SiGNAl"; - List usernames = Username.generateCandidates(nickname, 3, 32); + List usernames = Username.candidatesFrom(nickname, 3, 32); assertFalse("Non-zero number of usernames expected", usernames.size() == 0); - for (String name : usernames) { - assertTrue(String.format("%s does not start with %s", name, nickname), name.startsWith(nickname)); + for (Username name : usernames) { + assertTrue(String.format("%s does not start with %s", name, nickname), name.getUsername().startsWith(nickname)); } } @@ -27,7 +25,7 @@ public void testInvalidNicknameValidation() throws BaseUsernameException { List invalidNicknames = List.of("hi", "way_too_long_to_be_a_reasonable_nickname", "Iā°Unicode", "s p a c e s", "0zerostart"); for (String nickname : invalidNicknames) { try { - Username.generateCandidates(nickname, 3, 32); + Username.candidatesFrom(nickname, 3, 32); fail(String.format("'%s' should not be considered valid", nickname)); } catch (BaseUsernameException ex) { // this is fine @@ -38,28 +36,25 @@ public void testInvalidNicknameValidation() throws BaseUsernameException { public void testValidUsernameHashing() throws BaseUsernameException { String username = "he110.42"; - byte[] hash = Username.hash(username); + byte[] hash = new Username(username).getHash(); assertEquals(32, hash.length); assertEquals("f63f0521eb3adfe1d936f4b626b89558483507fbdb838fc554af059111cf322e", Hex.toStringCondensed(hash)); } public void testToTheProofAndBack() throws BaseUsernameException { - String username = "hello_signal.42"; - byte[] hash = Username.hash(username); - assertNotNull(hash); - byte[] randomness = new byte[32]; - byte[] proof = Username.generateProof(username, randomness); + Username username = new Username("hello_signal.42"); + assertNotNull(username.getHash()); + byte[] proof = username.generateProof(); assertNotNull(proof); assertEquals(128, proof.length); - Username.verifyProof(proof, hash); + Username.verifyProof(proof, username.getHash()); } public void testInvalidHash() throws BaseUsernameException { - String username = "hello_signal.42"; - byte[] hash = Username.hash(username); - byte[] proof = Username.generateProof(username, new byte[32]); + Username username = new Username("hello_signal.42"); + byte[] hash = username.getHash(); + byte[] proof = username.generateProof(); hash[0] = 0; - try { Username.verifyProof(proof, hash); } catch (BaseUsernameException ex) { @@ -69,7 +64,7 @@ public void testInvalidHash() throws BaseUsernameException { public void testInvalidRandomness() throws BaseUsernameException { try { - Username.generateProof("valid_name.01", new byte[31]); + new Username("valid_name.01").generateProofWithRandomness(new byte[31]); } catch (Error err) { assertTrue(err.getMessage().contains("Failed to create proof")); } @@ -79,7 +74,7 @@ public void testInvalidUsernames() throws BaseUsernameException { List usernames = List.of("0zerostart.01", "zero.00", "short_zero.0", "short_one.1"); for (String name : usernames) { try { - Username.hash(name); + new Username(name); fail(String.format("'%s' should not be valid", name)); } catch (BaseUsernameException ex) { // this is fine @@ -87,7 +82,7 @@ public void testInvalidUsernames() throws BaseUsernameException { } for (String name : usernames) { try { - Username.generateProof(name, new byte[32]); + new Username(name).generateProof(); fail(String.format("'%s' should not be valid", name)); } catch (BaseUsernameException ex) { // this is fine diff --git a/java/shared/java/org/signal/libsignal/usernames/Username.java b/java/shared/java/org/signal/libsignal/usernames/Username.java index 250f616abc..b67f719878 100644 --- a/java/shared/java/org/signal/libsignal/usernames/Username.java +++ b/java/shared/java/org/signal/libsignal/usernames/Username.java @@ -6,19 +6,60 @@ import org.signal.libsignal.internal.Native; +import java.security.SecureRandom; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; public final class Username { + private String value; + private byte[] hash; + + public Username(String username) throws BaseUsernameException { + this.value = username; + this.hash = hash(username); + } + + public String getUsername() { + return this.value; + } + + public byte[] getHash() { + return this.hash; + } + + public static List candidatesFrom(String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { + String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); + ArrayList result = new ArrayList<>(); + for (String name : names.split(",")) { + result.add(new Username(name)); + } + return result; + } + + public byte[] generateProof() throws BaseUsernameException { + byte[] randomness = new byte[32]; + SecureRandom r = new SecureRandom(); + r.nextBytes(randomness); + return generateProofWithRandomness(randomness); + } + + public byte[] generateProofWithRandomness(byte[] randomness) throws BaseUsernameException { + return Native.Username_Proof(this.value, randomness); + } + + @Deprecated public static List generateCandidates(String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException { String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength); return Arrays.asList(names.split(",")); } + @Deprecated public static byte[] hash(String username) throws BaseUsernameException { return Native.Username_Hash(username); } + @Deprecated public static byte[] generateProof(String username, byte[] randomness) throws BaseUsernameException { return Native.Username_Proof(username, randomness); } @@ -26,4 +67,9 @@ public static byte[] generateProof(String username, byte[] randomness) throws Ba public static void verifyProof(byte[] proof, byte[] hash) throws BaseUsernameException { Native.Username_Verify(proof, hash); } + + @Override + public String toString() { + return this.value; + } } diff --git a/swift/Tests/LibSignalClientTests/UsernameTests.swift b/swift/Tests/LibSignalClientTests/UsernameTests.swift index 2e5e559a51..d1a8156933 100644 --- a/swift/Tests/LibSignalClientTests/UsernameTests.swift +++ b/swift/Tests/LibSignalClientTests/UsernameTests.swift @@ -1,10 +1,10 @@ // -// Copyright 2021 Signal Messenger, LLC. +// Copyright 2023 Signal Messenger, LLC. // SPDX-License-Identifier: AGPL-3.0-only // import XCTest -@testable import LibSignalClient +import LibSignalClient class UsernameTests: TestCaseBase {