mirror of
https://github.com/google/nomulus
synced 2026-09-06 08:07:03 +00:00
Add a registry lock password to contacts (#226)
* Add a registry lock password to contacts * enabled -> allowed * Simple CR responses, still need to add tests * Add a very simple hashing test file * Allow setting of RL password rather than directly setting it * Round out pw tests * Include 'allowedToSet...' in registrar contact JSON * Responses to CR * fix the hardcoded tests * Use null or empty rather than just null
This commit is contained in:
@@ -23,7 +23,7 @@ import google.registry.model.registrar.Registrar;
|
||||
public class PasswordOnlyTransportCredentials implements TransportCredentials {
|
||||
@Override
|
||||
public void validate(Registrar r, String password) throws AuthenticationErrorException {
|
||||
if (!r.testPassword(password)) {
|
||||
if (!r.verifyPassword(password)) {
|
||||
throw new BadRegistrarPasswordException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ public class TlsCredentials implements TransportCredentials {
|
||||
|
||||
private void validatePassword(Registrar registrar, String password)
|
||||
throws BadRegistrarPasswordException {
|
||||
if (!registrar.testPassword(password)) {
|
||||
if (!registrar.verifyPassword(password)) {
|
||||
throw new BadRegistrarPasswordException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,10 +34,11 @@ import static google.registry.model.registry.Registries.assertTldsExist;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableCopy;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static google.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
import static google.registry.util.X509Utils.getCertificateHash;
|
||||
import static google.registry.util.X509Utils.loadCertificate;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static java.util.function.Predicate.isEqual;
|
||||
|
||||
@@ -73,10 +74,6 @@ import google.registry.model.common.EntityGroupRoot;
|
||||
import google.registry.model.registrar.Registrar.BillingAccountEntry.CurrencyMapper;
|
||||
import google.registry.model.registry.Registry;
|
||||
import google.registry.util.CidrAddressBlock;
|
||||
import google.registry.util.NonFinalForTesting;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
@@ -412,15 +409,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
|
||||
/** Whether or not registry lock is allowed for this registrar. */
|
||||
boolean registryLockAllowed = false;
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Supplier<byte[]> saltSupplier =
|
||||
() -> {
|
||||
// There are 32 bytes in a sha-256 hash, and the salt should generally be the same size.
|
||||
byte[] salt = new byte[32];
|
||||
new SecureRandom().nextBytes(salt);
|
||||
return salt;
|
||||
};
|
||||
|
||||
public String getClientId() {
|
||||
return clientIdentifier;
|
||||
}
|
||||
@@ -634,16 +622,6 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
|
||||
.build();
|
||||
}
|
||||
|
||||
private String hashPassword(String password) {
|
||||
try {
|
||||
return base64()
|
||||
.encode(MessageDigest.getInstance("SHA-256").digest((password + salt).getBytes(UTF_8)));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// All implementations of MessageDigest are required to support SHA-256.
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String checkValidPhoneNumber(String phoneNumber) {
|
||||
checkArgument(
|
||||
E164_PATTERN.matcher(phoneNumber).matches(),
|
||||
@@ -652,8 +630,8 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
|
||||
return phoneNumber;
|
||||
}
|
||||
|
||||
public boolean testPassword(String password) {
|
||||
return hashPassword(password).equals(passwordHash);
|
||||
public boolean verifyPassword(String password) {
|
||||
return hashPassword(password, salt).equals(passwordHash);
|
||||
}
|
||||
|
||||
public String getPhonePasscode() {
|
||||
@@ -888,8 +866,8 @@ public class Registrar extends ImmutableObject implements Buildable, Jsonifiable
|
||||
checkArgument(
|
||||
Range.closed(6, 16).contains(nullToEmpty(password).length()),
|
||||
"Password must be 6-16 characters long.");
|
||||
getInstance().salt = base64().encode(saltSupplier.get());
|
||||
getInstance().passwordHash = getInstance().hashPassword(password);
|
||||
getInstance().salt = base64().encode(SALT_SUPPLIER.get());
|
||||
getInstance().passwordHash = hashPassword(password, getInstance().salt);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,13 +14,18 @@
|
||||
|
||||
package google.registry.model.registrar;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
import static com.google.common.collect.ImmutableSet.toImmutableSet;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static google.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static google.registry.model.registrar.Registrar.checkValidEmail;
|
||||
import static google.registry.model.transaction.TransactionManagerFactory.tm;
|
||||
import static google.registry.util.CollectionUtils.nullToEmptyImmutableSortedCopy;
|
||||
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
|
||||
import static google.registry.util.PasswordUtils.hashPassword;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
import com.google.common.base.Enums;
|
||||
@@ -135,6 +140,21 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
|
||||
*/
|
||||
boolean visibleInDomainWhoisAsAbuse = false;
|
||||
|
||||
/**
|
||||
* Whether or not the contact is allowed to set their registry lock password through the registrar
|
||||
* console. This will be set to false on contact creation and when the user sets a password.
|
||||
*/
|
||||
boolean allowedToSetRegistryLockPassword = false;
|
||||
|
||||
/**
|
||||
* A hashed password that exists iff this contact is registry-lock-enabled. The hash is a base64
|
||||
* encoded SHA256 string.
|
||||
*/
|
||||
String registryLockPasswordHash;
|
||||
|
||||
/** Randomly generated hash salt. */
|
||||
String registryLockPasswordSalt;
|
||||
|
||||
public static ImmutableSet<Type> typesFromCSV(String csv) {
|
||||
return typesFromStrings(Arrays.asList(csv.split(",")));
|
||||
}
|
||||
@@ -213,19 +233,39 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
|
||||
return new Builder(clone(this));
|
||||
}
|
||||
|
||||
public boolean isAllowedToSetRegistryLockPassword() {
|
||||
return allowedToSetRegistryLockPassword;
|
||||
}
|
||||
|
||||
public boolean isRegistryLockAllowed() {
|
||||
return !isNullOrEmpty(registryLockPasswordHash) && !isNullOrEmpty(registryLockPasswordSalt);
|
||||
}
|
||||
|
||||
public boolean verifyRegistryLockPassword(String registryLockPassword) {
|
||||
if (isNullOrEmpty(registryLockPassword)
|
||||
|| isNullOrEmpty(registryLockPasswordSalt)
|
||||
|| isNullOrEmpty(registryLockPasswordHash)) {
|
||||
return false;
|
||||
}
|
||||
return hashPassword(registryLockPassword, registryLockPasswordSalt)
|
||||
.equals(registryLockPasswordHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation that's human friendly.
|
||||
*
|
||||
* <p>The output will look something like this:<pre> {@code
|
||||
* <p>The output will look something like this:
|
||||
*
|
||||
* Some Person
|
||||
* person@example.com
|
||||
* Tel: +1.2125650666
|
||||
* Types: [ADMIN, WHOIS]
|
||||
* Visible in WHOIS as Admin contact: Yes
|
||||
* Visible in WHOIS as Technical contact: No
|
||||
* GAE-UserID: 1234567890
|
||||
* Registrar-Console access: Yes}</pre>
|
||||
* <pre>{@code
|
||||
* Some Person
|
||||
* person@example.com
|
||||
* Tel: +1.2125650666
|
||||
* Types: [ADMIN, WHOIS]
|
||||
* Visible in WHOIS as Admin contact: Yes
|
||||
* Visible in WHOIS as Technical contact: No
|
||||
* GAE-UserID: 1234567890
|
||||
* Registrar-Console access: Yes
|
||||
* }</pre>
|
||||
*/
|
||||
public String toStringMultilinePlainText() {
|
||||
StringBuilder result = new StringBuilder(256);
|
||||
@@ -273,6 +313,7 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
|
||||
.put("visibleInWhoisAsAdmin", visibleInWhoisAsAdmin)
|
||||
.put("visibleInWhoisAsTech", visibleInWhoisAsTech)
|
||||
.put("visibleInDomainWhoisAsAbuse", visibleInDomainWhoisAsAbuse)
|
||||
.put("allowedToSetRegistryLockPassword", allowedToSetRegistryLockPassword)
|
||||
.put("gaeUserId", gaeUserId)
|
||||
.build();
|
||||
}
|
||||
@@ -346,5 +387,27 @@ public class RegistrarContact extends ImmutableObject implements Jsonifiable {
|
||||
getInstance().gaeUserId = gaeUserId;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAllowedToSetRegistryLockPassword(boolean allowedToSetRegistryLockPassword) {
|
||||
if (allowedToSetRegistryLockPassword) {
|
||||
getInstance().registryLockPasswordSalt = null;
|
||||
getInstance().registryLockPasswordHash = null;
|
||||
}
|
||||
getInstance().allowedToSetRegistryLockPassword = allowedToSetRegistryLockPassword;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setRegistryLockPassword(String registryLockPassword) {
|
||||
checkArgument(
|
||||
getInstance().allowedToSetRegistryLockPassword,
|
||||
"Not allowed to set registry lock password for this contact");
|
||||
checkArgument(
|
||||
!isNullOrEmpty(registryLockPassword), "Registry lock password was null or empty");
|
||||
getInstance().registryLockPasswordSalt = base64().encode(SALT_SUPPLIER.get());
|
||||
getInstance().registryLockPasswordHash =
|
||||
hashPassword(registryLockPassword, getInstance().registryLockPasswordSalt);
|
||||
getInstance().allowedToSetRegistryLockPassword = false;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,15 @@ final class RegistrarContactCommand extends MutatingCommand {
|
||||
arity = 1)
|
||||
private Boolean visibleInDomainWhoisAsAbuse;
|
||||
|
||||
@Nullable
|
||||
@Parameter(
|
||||
names = "--allowed_to_set_registry_lock_password",
|
||||
description =
|
||||
"Allow this contact to set their registry lock password in the console,"
|
||||
+ " enabling registry lock",
|
||||
arity = 1)
|
||||
private Boolean allowedToSetRegistryLockPassword;
|
||||
|
||||
@Parameter(
|
||||
names = {"-o", "--output"},
|
||||
description = "Output file when --mode=LIST",
|
||||
@@ -260,6 +269,9 @@ final class RegistrarContactCommand extends MutatingCommand {
|
||||
if (visibleInDomainWhoisAsAbuse != null) {
|
||||
builder.setVisibleInDomainWhoisAsAbuse(visibleInDomainWhoisAsAbuse);
|
||||
}
|
||||
if (allowedToSetRegistryLockPassword != null) {
|
||||
builder.setAllowedToSetRegistryLockPassword(allowedToSetRegistryLockPassword);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@@ -301,6 +313,9 @@ final class RegistrarContactCommand extends MutatingCommand {
|
||||
builder.setGaeUserId(null);
|
||||
}
|
||||
}
|
||||
if (allowedToSetRegistryLockPassword != null) {
|
||||
builder.setAllowedToSetRegistryLockPassword(allowedToSetRegistryLockPassword);
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user