mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-08-05 21:07:49 +01:00
Move PinValidityCheck to :core:util and kotlinize it.
This commit is contained in:
committed by
Alex Hart
parent
66ff18c310
commit
5d72248bb5
@@ -7,7 +7,7 @@ import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker;
|
||||
import org.signal.network.pin.PinValidityChecker;
|
||||
import org.signal.network.util.Preconditions;
|
||||
|
||||
public final class CreateSvrPinViewModel extends ViewModel implements BaseSvrPinViewModel {
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
package org.thoughtcrime.securesms.registration.v2;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.thoughtcrime.securesms.registration.testdata.PinValidityVector;
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker;
|
||||
import org.signal.network.util.JsonUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public final class PinValidityChecker_validity_Test {
|
||||
|
||||
@Test
|
||||
public void vectors_valid() throws IOException {
|
||||
for (PinValidityVector vector : getKbsPinValidityTestVectorList()) {
|
||||
boolean valid = PinValidityChecker.valid(vector.getPin());
|
||||
|
||||
assertEquals(String.format("%s [%s]", vector.getName(), vector.getPin()),
|
||||
vector.isValid(),
|
||||
valid);
|
||||
}
|
||||
}
|
||||
|
||||
private static PinValidityVector[] getKbsPinValidityTestVectorList() throws IOException {
|
||||
try (InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("data/kbs_pin_validity_vectors.json")) {
|
||||
|
||||
PinValidityVector[] data = JsonUtil.fromJson(StreamUtil.readFullyAsString(resourceAsStream), PinValidityVector[].class);
|
||||
|
||||
assertTrue(data.length > 0);
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
-27
@@ -1,27 +0,0 @@
|
||||
package org.thoughtcrime.securesms.registration.testdata;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public class PinValidityVector {
|
||||
|
||||
@JsonProperty("name")
|
||||
private String name;
|
||||
|
||||
@JsonProperty("pin")
|
||||
private String pin;
|
||||
|
||||
@JsonProperty("valid")
|
||||
private boolean valid;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getPin() {
|
||||
return pin;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -17,12 +17,12 @@ import kotlinx.coroutines.flow.onEach
|
||||
import org.signal.core.ui.compose.EventDrivenViewModel
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.net.RequestResult
|
||||
import org.signal.network.pin.PinValidityChecker
|
||||
import org.signal.registration.NetworkController
|
||||
import org.signal.registration.RegistrationFlowEvent
|
||||
import org.signal.registration.RegistrationFlowState
|
||||
import org.signal.registration.RegistrationRepository
|
||||
import org.signal.registration.RestoreDecision
|
||||
import org.whispersystems.signalservice.api.kbs.PinValidityChecker
|
||||
import kotlin.time.toKotlinDuration
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -5,9 +5,9 @@
|
||||
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
final class PinString {
|
||||
public final class PinString {
|
||||
|
||||
static boolean allNumeric(CharSequence pin) {
|
||||
public static boolean allNumeric(CharSequence pin) {
|
||||
for (int i = 0; i < pin.length(); i++) {
|
||||
if (!Character.isDigit(pin.charAt(i))) return false;
|
||||
}
|
||||
@@ -17,7 +17,7 @@ final class PinString {
|
||||
/**
|
||||
* Converts a string of not necessarily Arabic numerals to Arabic 0..9 characters.
|
||||
*/
|
||||
static String toArabic(CharSequence numerals) {
|
||||
public static String toArabic(CharSequence numerals) {
|
||||
int length = numerals.length();
|
||||
char[] arabic = new char[length];
|
||||
|
||||
|
||||
-78
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.kbs;
|
||||
|
||||
public final class PinValidityChecker {
|
||||
|
||||
public static boolean valid(String pin) {
|
||||
pin = pin.trim();
|
||||
|
||||
if (pin.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PinString.allNumeric(pin)) {
|
||||
pin = PinString.toArabic(pin);
|
||||
|
||||
return !sequential(pin) &&
|
||||
!sequential(reverse(pin)) &&
|
||||
!allTheSame(pin);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static String reverse(String string) {
|
||||
char[] chars = string.toCharArray();
|
||||
|
||||
for (int i = 0; i < chars.length / 2; i++) {
|
||||
char temp = chars[i];
|
||||
chars[i] = chars[chars.length - i - 1];
|
||||
chars[chars.length - i - 1] = temp;
|
||||
}
|
||||
|
||||
return new String(chars);
|
||||
}
|
||||
|
||||
private static boolean sequential(String pin) {
|
||||
int length = pin.length();
|
||||
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c = pin.charAt(0);
|
||||
|
||||
for (int i = 1; i < length; i++) {
|
||||
char n = pin.charAt(i);
|
||||
if (n != c + 1) {
|
||||
return false;
|
||||
}
|
||||
c = n;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean allTheSame(String pin) {
|
||||
int length = pin.length();
|
||||
|
||||
if (length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char c = pin.charAt(0);
|
||||
|
||||
for (int i = 1; i < length; i++) {
|
||||
char n = pin.charAt(i);
|
||||
if (n != c) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.network.pin
|
||||
|
||||
import org.whispersystems.signalservice.api.kbs.PinString
|
||||
|
||||
/**
|
||||
* Rejects PINs that are trivially guessable. A numeric PIN must not be empty, sequential in either
|
||||
* direction, or a single repeated digit. Non-numeric PINs are only checked for emptiness.
|
||||
*/
|
||||
object PinValidityChecker {
|
||||
|
||||
@JvmStatic
|
||||
fun valid(pin: String): Boolean {
|
||||
val trimmed = pin.trim()
|
||||
|
||||
if (trimmed.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!PinString.allNumeric(trimmed)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val arabic = PinString.toArabic(trimmed)
|
||||
|
||||
return !arabic.isSequential() && !arabic.reversed().isSequential() && !arabic.isSingleRepeatedChar()
|
||||
}
|
||||
|
||||
private fun String.isSequential(): Boolean {
|
||||
return zipWithNext().all { (previous, next) -> next == previous + 1 }
|
||||
}
|
||||
|
||||
private fun String.isSingleRepeatedChar(): Boolean {
|
||||
return all { it == this[0] }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.network.pin
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isNotEmpty
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Test
|
||||
|
||||
class PinValidityCheckerTest {
|
||||
|
||||
@Test
|
||||
fun `validity matches the shared test vectors`() {
|
||||
val vectors = loadVectors()
|
||||
|
||||
assertThat(vectors).isNotEmpty()
|
||||
|
||||
for (vector in vectors) {
|
||||
assertThat(PinValidityChecker.valid(vector.pin), "${vector.name} [${vector.pin}]").isEqualTo(vector.valid)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadVectors(): List<PinValidityVector> {
|
||||
val json = checkNotNull(javaClass.classLoader.getResourceAsStream(VECTOR_RESOURCE)) { "Missing $VECTOR_RESOURCE" }
|
||||
.bufferedReader()
|
||||
.use { it.readText() }
|
||||
|
||||
return Json.decodeFromString(json)
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class PinValidityVector(
|
||||
val name: String,
|
||||
val pin: String,
|
||||
val valid: Boolean
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val VECTOR_RESOURCE = "data/kbs_pin_validity_vectors.json"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user