Skip to content

Commit

Permalink
Make Java API for username hashing more idiomatic
Browse files Browse the repository at this point in the history
  • Loading branch information
moiseev-signal committed Feb 9, 2023
1 parent 9bd2edc commit f1b6c63
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,27 @@

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<String> usernames = Username.generateCandidates(nickname, 3, 32);
List<Username> 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));
}
}

public void testInvalidNicknameValidation() throws BaseUsernameException {
List<String> 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
Expand All @@ -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) {
Expand All @@ -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"));
}
Expand All @@ -79,15 +74,15 @@ public void testInvalidUsernames() throws BaseUsernameException {
List<String> 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
}
}
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
Expand Down
46 changes: 46 additions & 0 deletions java/shared/java/org/signal/libsignal/usernames/Username.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,70 @@

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<Username> candidatesFrom(String nickname, int minNicknameLength, int maxNicknameLength) throws BaseUsernameException {
String names = Native.Username_CandidatesFrom(nickname, minNicknameLength, maxNicknameLength);
ArrayList<Username> 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<String> 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);
}

public static void verifyProof(byte[] proof, byte[] hash) throws BaseUsernameException {
Native.Username_Verify(proof, hash);
}

@Override
public String toString() {
return this.value;
}
}
4 changes: 2 additions & 2 deletions swift/Tests/LibSignalClientTests/UsernameTests.swift
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down

0 comments on commit f1b6c63

Please sign in to comment.