1
0
mirror of https://github.com/google/nomulus synced 2026-05-20 06:41:51 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
dependabot[bot]
f13fda2c15 Bump ip from 2.0.0 to 2.0.1 in /console-webapp (#2331)
Bumps [ip](https://github.com/indutny/node-ip) from 2.0.0 to 2.0.1.
- [Commits](https://github.com/indutny/node-ip/compare/v2.0.0...v2.0.1)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-26 23:03:36 +00:00
Lai Jiang
f72a0d2f16 Remove SHA256 as a supported password hashing algorithm (#2310)
We introduced Scrypt as the default password hashing algorithm in
November 2023 and have been auto-converting saved hashes whenever a
successful EPP login or registry lock/unlock request is processed.

We will send comms to registrars to inform them the upcoming removal of
SHA256 support and urge them to log in at least once before the change.
Otherwise, they will need to contact support to reset the password out of
band after the change.

This PR will NOT be submitted until comms are out and the effective date
is immediate.

Co-authored-by: Weimin Yu <weiminyu@google.com>
2024-02-26 15:28:12 +00:00
Ben McIlwain
1eef260da9 Convert some more @AutoValues to records (#2334) 2024-02-23 18:56:40 +00:00
23 changed files with 86 additions and 321 deletions

View File

@@ -9678,9 +9678,9 @@
}
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==",
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.1.tgz",
"integrity": "sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==",
"dev": true
},
"node_modules/ipaddr.js": {

View File

@@ -21,7 +21,6 @@ import static org.apache.http.HttpStatus.SC_INTERNAL_SERVER_ERROR;
import static org.apache.http.HttpStatus.SC_OK;
import static org.joda.time.DateTimeZone.UTC;
import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
@@ -125,7 +124,7 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
return Streams.stream(Registrar.loadAllCached())
.map(
registrar ->
RegistrarInfo.create(
new RegistrarInfo(
registrar,
registrar.getClientCertificate().isPresent()
&& certificateChecker.shouldReceiveExpiringNotification(
@@ -333,19 +332,6 @@ public class SendExpiringCertificateNotificationEmailAction implements Runnable
}
}
@AutoValue
public abstract static class RegistrarInfo {
static RegistrarInfo create(
Registrar registrar, boolean isCertExpiring, boolean isFailOverCertExpiring) {
return new AutoValue_SendExpiringCertificateNotificationEmailAction_RegistrarInfo(
registrar, isCertExpiring, isFailOverCertExpiring);
}
public abstract Registrar registrar();
public abstract boolean isCertExpiring();
public abstract boolean isFailOverCertExpiring();
}
record RegistrarInfo(
Registrar registrar, boolean isCertExpiring, boolean isFailOverCertExpiring) {}
}

View File

@@ -17,7 +17,6 @@ package google.registry.beam.spec11;
import static com.google.common.base.Preconditions.checkArgument;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableSet;
import dagger.Component;
import dagger.Module;
@@ -199,7 +198,7 @@ public class Spec11Pipeline implements Serializable {
(KV<DomainNameInfo, ThreatMatch> kv) ->
KV.of(
kv.getKey().registrarId(),
EmailAndThreatMatch.create(
new EmailAndThreatMatch(
kv.getKey().registrarEmailAddress(), kv.getValue()))))
.apply("Group by registrar client ID", GroupByKey.create())
.apply(
@@ -281,15 +280,5 @@ public class Spec11Pipeline implements Serializable {
Spec11Pipeline spec11Pipeline();
}
@AutoValue
abstract static class EmailAndThreatMatch implements Serializable {
abstract String email();
abstract ThreatMatch threatMatch();
static EmailAndThreatMatch create(String email, ThreatMatch threatMatch) {
return new AutoValue_Spec11Pipeline_EmailAndThreatMatch(email, threatMatch);
}
}
record EmailAndThreatMatch(String email, ThreatMatch threatMatch) implements Serializable {}
}

View File

@@ -18,19 +18,13 @@ import static google.registry.bsa.BsaStringUtils.DOMAIN_JOINER;
import static google.registry.bsa.BsaStringUtils.PROPERTY_JOINER;
import static google.registry.bsa.BsaStringUtils.PROPERTY_SPLITTER;
import com.google.auto.value.AutoValue;
import java.util.List;
/**
* A domain name whose second-level domain (SLD) matches a BSA label but is not blocked. It may be
* already registered, or on the TLD's reserve list.
*/
// TODO(1/15/2024): rename to UnblockableDomain.
@AutoValue
public abstract class UnblockableDomain {
public abstract String domainName();
public abstract Reason reason();
public record UnblockableDomain(String domainName, Reason reason) {
/** Reasons why a valid domain name cannot be blocked. */
public enum Reason {
@@ -45,14 +39,10 @@ public abstract class UnblockableDomain {
public static UnblockableDomain deserialize(String text) {
List<String> items = PROPERTY_SPLITTER.splitToList(text);
return of(items.get(0), Reason.valueOf(items.get(1)));
}
public static UnblockableDomain of(String domainName, Reason reason) {
return new AutoValue_UnblockableDomain(domainName, reason);
return new UnblockableDomain(items.get(0), Reason.valueOf(items.get(1)));
}
public static UnblockableDomain of(String label, String tld, Reason reason) {
return of(DOMAIN_JOINER.join(label, tld), reason);
return new UnblockableDomain(DOMAIN_JOINER.join(label, tld), reason);
}
}

View File

@@ -49,7 +49,7 @@ public abstract class UnblockableDomainChange {
@Memoized
public UnblockableDomain newValue() {
verify(newReason().isPresent(), "Removed unblockable does not have new value.");
return UnblockableDomain.of(unblockable().domainName(), newReason().get());
return new UnblockableDomain(unblockable().domainName(), newReason().get());
}
public boolean isNewOrChange() {
@@ -78,7 +78,7 @@ public abstract class UnblockableDomainChange {
public static UnblockableDomainChange deserialize(String text) {
List<String> items = BsaStringUtils.PROPERTY_SPLITTER.splitToList(text);
return of(
UnblockableDomain.of(items.get(0), Reason.valueOf(items.get(1))),
new UnblockableDomain(items.get(0), Reason.valueOf(items.get(1))),
Objects.equals(items.get(2), DELETE_REASON_PLACEHOLDER)
? Optional.empty()
: Optional.of(Reason.valueOf(items.get(2))));

View File

@@ -239,10 +239,10 @@ public final class DomainsRefresher {
Streams.concat(
newCreated.stream()
.map(name -> UnblockableDomain.of(name, Reason.REGISTERED))
.map(name -> new UnblockableDomain(name, Reason.REGISTERED))
.map(UnblockableDomainChange::ofNew),
reservedNotCreated.stream()
.map(name -> UnblockableDomain.of(name, Reason.RESERVED))
.map(name -> new UnblockableDomain(name, Reason.RESERVED))
.map(UnblockableDomainChange::ofNew))
.forEach(changes::add);
return changes.build();

View File

@@ -152,7 +152,7 @@ public final class LabelDiffUpdates {
ImmutableSet<String> registeredDomainNames =
ImmutableSet.copyOf(ForeignKeyUtils.load(Domain.class, validDomainNames, now).keySet());
for (String domain : registeredDomainNames) {
nonBlockedDomains.add(UnblockableDomain.of(domain, Reason.REGISTERED));
nonBlockedDomains.add(new UnblockableDomain(domain, Reason.REGISTERED));
tm().put(BsaUnblockableDomain.of(domain, BsaUnblockableDomain.Reason.REGISTERED));
}
@@ -161,7 +161,7 @@ public final class LabelDiffUpdates {
.filter(domain -> isReservedDomain(domain, now))
.collect(toImmutableSet());
for (String domain : reservedDomainNames) {
nonBlockedDomains.add(UnblockableDomain.of(domain, Reason.RESERVED));
nonBlockedDomains.add(new UnblockableDomain(domain, Reason.RESERVED));
tm().put(BsaUnblockableDomain.of(domain, BsaUnblockableDomain.Reason.RESERVED));
}
return nonBlockedDomains.build();

View File

@@ -14,25 +14,21 @@
package google.registry.flows;
import com.google.auto.value.AutoValue;
import google.registry.model.ImmutableObject;
import com.google.auto.value.AutoBuilder;
/** Object to hold metadata specific to a particular execution of a flow. */
@AutoValue
public abstract class FlowMetadata extends ImmutableObject {
public abstract boolean isSuperuser();
public record FlowMetadata(boolean isSuperuser) {
public static Builder newBuilder() {
return new AutoValue_FlowMetadata.Builder();
return new AutoBuilder_FlowMetadata_Builder();
}
/** Builder for {@link FlowMetadata} */
@AutoValue.Builder
public abstract static class Builder {
@AutoBuilder
public interface Builder {
public abstract Builder setSuperuser(boolean isSuperuser);
Builder setIsSuperuser(boolean isSuperuser);
public abstract FlowMetadata build();
FlowMetadata build();
}
}

View File

@@ -314,7 +314,7 @@ public class FlowModule {
@Provides
static FlowMetadata provideFlowMetadata(@Superuser boolean isSuperuser) {
return FlowMetadata.newBuilder().setSuperuser(isSuperuser).build();
return FlowMetadata.newBuilder().setIsSuperuser(isSuperuser).build();
}
/** Wrapper class to carry an {@link EppException} to the calling code. */

View File

@@ -47,7 +47,6 @@ import google.registry.model.eppinput.EppInput.Options;
import google.registry.model.eppinput.EppInput.Services;
import google.registry.model.eppoutput.EppResponse;
import google.registry.model.registrar.Registrar;
import google.registry.util.PasswordUtils.HashAlgorithm;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
@@ -142,17 +141,8 @@ public class LoginFlow implements MutatingFlow {
throw new RegistrarAccountNotActiveException();
}
if (login.getNewPassword().isPresent()
|| registrar.get().getCurrentHashAlgorithm(login.getPassword()).orElse(null)
!= HashAlgorithm.SCRYPT) {
String newPassword =
login
.getNewPassword()
.orElseGet(
() -> {
logger.atInfo().log("Rehashing existing registrar password with Scrypt");
return login.getPassword();
});
if (login.getNewPassword().isPresent()) {
String newPassword = login.getNewPassword().get();
// Load fresh from database (bypassing the cache) to ensure we don't save stale data.
Optional<Registrar> freshRegistrar = Registrar.loadByRegistrarId(login.getClientId());
if (freshRegistrar.isEmpty()) {

View File

@@ -86,8 +86,7 @@ public class User extends UpdateAutoTimestampEntity implements Buildable {
return false;
}
return PasswordUtils.verifyPassword(
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt)
.isPresent();
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
}
/**

View File

@@ -62,7 +62,6 @@ import google.registry.model.tld.Tld.TldType;
import google.registry.persistence.VKey;
import google.registry.util.CidrAddressBlock;
import google.registry.util.PasswordUtils;
import google.registry.util.PasswordUtils.HashAlgorithm;
import java.security.cert.CertificateParsingException;
import java.util.Comparator;
import java.util.List;
@@ -642,10 +641,6 @@ public class Registrar extends UpdateAutoTimestampEntity implements Buildable, J
}
public boolean verifyPassword(String password) {
return getCurrentHashAlgorithm(password).isPresent();
}
public Optional<HashAlgorithm> getCurrentHashAlgorithm(String password) {
return PasswordUtils.verifyPassword(password, passwordHash, salt);
}

View File

@@ -38,7 +38,6 @@ import google.registry.model.UnsafeSerializable;
import google.registry.model.registrar.RegistrarPoc.RegistrarPocId;
import google.registry.persistence.VKey;
import google.registry.util.PasswordUtils;
import google.registry.util.PasswordUtils.HashAlgorithm;
import java.io.Serializable;
import java.util.Map;
import java.util.Optional;
@@ -242,10 +241,6 @@ public class RegistrarPoc extends ImmutableObject implements Jsonifiable, Unsafe
|| isNullOrEmpty(registryLockPasswordHash)) {
return false;
}
return getCurrentHashAlgorithm(registryLockPassword).isPresent();
}
public Optional<HashAlgorithm> getCurrentHashAlgorithm(String registryLockPassword) {
return PasswordUtils.verifyPassword(
registryLockPassword, registryLockPasswordHash, registryLockPasswordSalt);
}

View File

@@ -47,7 +47,6 @@ import google.registry.request.auth.UserAuthInfo;
import google.registry.security.JsonResponseHelper;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.PasswordUtils.HashAlgorithm;
import java.net.URISyntaxException;
import java.util.Map;
import java.util.Optional;
@@ -223,19 +222,6 @@ public class RegistryLockPostAction implements Runnable, JsonActionRunner.JsonAc
checkArgument(
registrarPoc.verifyRegistryLockPassword(postInput.password),
"Incorrect registry lock password for contact");
if (registrarPoc.getCurrentHashAlgorithm(postInput.password).orElse(null)
!= HashAlgorithm.SCRYPT) {
logger.atInfo().log("Rehashing existing registry lock password with Scrypt.");
tm().transact(
() -> {
tm().update(
tm().loadByEntity(registrarPoc)
.asBuilder()
.setAllowedToSetRegistryLockPassword(true)
.setRegistryLockPassword(postInput.password)
.build());
});
}
return registrarPoc
.getRegistryLockEmailAddress()
.orElseThrow(

View File

@@ -139,7 +139,7 @@ class BsaRefreshFunctionalTest {
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.app", Reason.RESERVED);
UnblockableDomain newUnblockable = new UnblockableDomain("blocked1.app", Reason.RESERVED);
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
@@ -154,7 +154,7 @@ class BsaRefreshFunctionalTest {
persistActiveDomain("dummy.dev", fakeClock.nowUtc());
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.dev", Reason.REGISTERED);
UnblockableDomain newUnblockable = new UnblockableDomain("blocked1.dev", Reason.REGISTERED);
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
@@ -169,7 +169,7 @@ class BsaRefreshFunctionalTest {
Domain domain = persistActiveDomain("blocked1.dev", fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.dev", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.dev", Reason.REGISTERED));
fakeClock.advanceOneMilli();
deleteTestDomain(domain, fakeClock.nowUtc());
fakeClock.advanceOneMilli();
@@ -181,7 +181,7 @@ class BsaRefreshFunctionalTest {
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(
UnblockableDomainChange.ofDeleted(
UnblockableDomain.of("blocked1.dev", Reason.REGISTERED)));
new UnblockableDomain("blocked1.dev", Reason.REGISTERED)));
verify(bsaReportSender, never()).addUnblockableDomainsUpdates(anyString());
verify(bsaReportSender, times(1)).removeUnblockableDomainsUpdates("[\n \"blocked1.dev\"\n]");
@@ -193,7 +193,7 @@ class BsaRefreshFunctionalTest {
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.RESERVED));
fakeClock.advanceOneMilli();
removeReservedDomainFromList(RESERVED_LIST_NAME, ImmutableSet.of("blocked1"));
@@ -204,7 +204,7 @@ class BsaRefreshFunctionalTest {
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(
UnblockableDomainChange.ofDeleted(
UnblockableDomain.of("blocked1.app", Reason.RESERVED)));
new UnblockableDomain("blocked1.app", Reason.RESERVED)));
verify(bsaReportSender, never()).addUnblockableDomainsUpdates(anyString());
verify(bsaReportSender, times(1)).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
@@ -217,7 +217,7 @@ class BsaRefreshFunctionalTest {
Domain domain = persistActiveDomain("blocked1.app", fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.REGISTERED));
fakeClock.advanceOneMilli();
deleteTestDomain(domain, fakeClock.nowUtc());
fakeClock.advanceOneMilli();
@@ -226,11 +226,11 @@ class BsaRefreshFunctionalTest {
Mockito.reset(bsaReportSender);
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.RESERVED));
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(
UnblockableDomainChange.ofChanged(
UnblockableDomain.of("blocked1.app", Reason.REGISTERED), Reason.RESERVED));
new UnblockableDomain("blocked1.app", Reason.REGISTERED), Reason.RESERVED));
InOrder inOrder = Mockito.inOrder(bsaReportSender);
inOrder.verify(bsaReportSender).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
inOrder
@@ -244,7 +244,7 @@ class BsaRefreshFunctionalTest {
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.RESERVED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.RESERVED));
fakeClock.advanceOneMilli();
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
fakeClock.advanceOneMilli();
@@ -252,12 +252,12 @@ class BsaRefreshFunctionalTest {
Mockito.reset(bsaReportSender);
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
UnblockableDomain changed = UnblockableDomain.of("blocked1.app", Reason.REGISTERED);
UnblockableDomain changed = new UnblockableDomain("blocked1.app", Reason.REGISTERED);
assertThat(queryUnblockableDomains()).containsExactly(changed);
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(
UnblockableDomainChange.ofChanged(
UnblockableDomain.of("blocked1.app", Reason.RESERVED), Reason.REGISTERED));
new UnblockableDomain("blocked1.app", Reason.RESERVED), Reason.REGISTERED));
InOrder inOrder = Mockito.inOrder(bsaReportSender);
inOrder.verify(bsaReportSender).removeUnblockableDomainsUpdates("[\n \"blocked1.app\"\n]");
inOrder
@@ -272,7 +272,7 @@ class BsaRefreshFunctionalTest {
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
UnblockableDomain newUnblockable = UnblockableDomain.of("blocked1.app", Reason.REGISTERED);
UnblockableDomain newUnblockable = new UnblockableDomain("blocked1.app", Reason.REGISTERED);
assertThat(queryUnblockableDomains()).containsExactly(newUnblockable);
assertThat(gcsClient.readRefreshChanges(jobName))
.containsExactly(UnblockableDomainChange.ofNew(newUnblockable));
@@ -285,7 +285,7 @@ class BsaRefreshFunctionalTest {
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.REGISTERED));
fakeClock.advanceOneMilli();
removeReservedDomainFromList(RESERVED_LIST_NAME, ImmutableSet.of("blocked1"));
fakeClock.advanceOneMilli();
@@ -294,7 +294,7 @@ class BsaRefreshFunctionalTest {
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.REGISTERED));
// Verify that refresh change file does not exist (404 error) since there is no change.
assertThat(
assertThrows(
@@ -309,7 +309,7 @@ class BsaRefreshFunctionalTest {
persistActiveDomain("blocked1.app", fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.REGISTERED));
fakeClock.advanceOneMilli();
addReservedDomainToList(
RESERVED_LIST_NAME, ImmutableMap.of("blocked1", RESERVED_FOR_SPECIFIC_USE));
@@ -319,7 +319,7 @@ class BsaRefreshFunctionalTest {
String jobName = getRefreshJobName(fakeClock.nowUtc());
action.run();
assertThat(queryUnblockableDomains())
.containsExactly(UnblockableDomain.of("blocked1.app", Reason.REGISTERED));
.containsExactly(new UnblockableDomain("blocked1.app", Reason.REGISTERED));
// Verify that refresh change file does not exist (404 error) since there is no change.
assertThat(
assertThrows(

View File

@@ -134,10 +134,10 @@ class JsonSerializationsTest {
assertThat(
toUnblockableDomainsReport(
Stream.of(
UnblockableDomain.of("a.ing", Reason.REGISTERED),
UnblockableDomain.of("b.app", Reason.INVALID),
UnblockableDomain.of("c.dev", Reason.RESERVED),
UnblockableDomain.of("d.page", Reason.REGISTERED))))
new UnblockableDomain("a.ing", Reason.REGISTERED),
new UnblockableDomain("b.app", Reason.INVALID),
new UnblockableDomain("c.dev", Reason.RESERVED),
new UnblockableDomain("d.page", Reason.REGISTERED))))
.hasValue(expected);
}
}

View File

@@ -27,7 +27,7 @@ class UnblockableDomainTest {
@BeforeEach
void setup() {
unit = UnblockableDomain.of("buy.app", Reason.REGISTERED);
unit = new UnblockableDomain("buy.app", Reason.REGISTERED);
}
@Test

View File

@@ -69,7 +69,7 @@ public class DomainsRefresherTest {
assertThat(refresher.refreshStaleUnblockables())
.containsExactly(
UnblockableDomainChange.ofDeleted(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.REGISTERED)));
new UnblockableDomain("label.tld", UnblockableDomain.Reason.REGISTERED)));
}
@Test
@@ -79,7 +79,7 @@ public class DomainsRefresherTest {
assertThat(refresher.refreshStaleUnblockables())
.containsExactly(
UnblockableDomainChange.ofDeleted(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.RESERVED)));
new UnblockableDomain("label.tld", UnblockableDomain.Reason.RESERVED)));
}
@Test
@@ -89,7 +89,7 @@ public class DomainsRefresherTest {
assertThat(refresher.getNewUnblockables())
.containsExactly(
UnblockableDomainChange.ofNew(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.REGISTERED)));
new UnblockableDomain("label.tld", UnblockableDomain.Reason.REGISTERED)));
}
@Test
@@ -100,7 +100,7 @@ public class DomainsRefresherTest {
assertThat(refresher.getNewUnblockables())
.containsExactly(
UnblockableDomainChange.ofNew(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.RESERVED)));
new UnblockableDomain("label.tld", UnblockableDomain.Reason.RESERVED)));
}
@Test
@@ -113,7 +113,7 @@ public class DomainsRefresherTest {
assertThat(refresher.refreshStaleUnblockables())
.containsExactly(
UnblockableDomainChange.ofChanged(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.REGISTERED),
new UnblockableDomain("label.tld", UnblockableDomain.Reason.REGISTERED),
UnblockableDomain.Reason.RESERVED));
}
@@ -126,7 +126,7 @@ public class DomainsRefresherTest {
assertThat(refresher.refreshStaleUnblockables())
.containsExactly(
UnblockableDomainChange.ofChanged(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.RESERVED),
new UnblockableDomain("label.tld", UnblockableDomain.Reason.RESERVED),
UnblockableDomain.Reason.REGISTERED));
}
@@ -141,7 +141,7 @@ public class DomainsRefresherTest {
assertThat(refresher.refreshStaleUnblockables())
.containsExactly(
UnblockableDomainChange.ofChanged(
UnblockableDomain.of("label.tld", UnblockableDomain.Reason.RESERVED),
new UnblockableDomain("label.tld", UnblockableDomain.Reason.RESERVED),
UnblockableDomain.Reason.REGISTERED));
}
}

View File

@@ -126,8 +126,8 @@ class LabelDiffUpdatesTest {
fakeClock.nowUtc());
assertThat(unblockableDomains)
.containsExactly(
UnblockableDomain.of("label.app", UnblockableDomain.Reason.REGISTERED),
UnblockableDomain.of("label.dev", UnblockableDomain.Reason.INVALID));
new UnblockableDomain("label.app", UnblockableDomain.Reason.REGISTERED),
new UnblockableDomain("label.dev", UnblockableDomain.Reason.INVALID));
assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaLabel.vKey("label")))).isPresent();
assertThat(
tm().transact(() -> tm().loadByKeyIfPresent(BsaUnblockableDomain.vKey("label", "app"))))
@@ -153,9 +153,9 @@ class LabelDiffUpdatesTest {
fakeClock.nowUtc());
assertThat(unblockableDomains)
.containsExactly(
UnblockableDomain.of("label.app", UnblockableDomain.Reason.REGISTERED),
UnblockableDomain.of("label.page", UnblockableDomain.Reason.RESERVED),
UnblockableDomain.of("label.dev", UnblockableDomain.Reason.INVALID));
new UnblockableDomain("label.app", UnblockableDomain.Reason.REGISTERED),
new UnblockableDomain("label.page", UnblockableDomain.Reason.RESERVED),
new UnblockableDomain("label.dev", UnblockableDomain.Reason.INVALID));
assertThat(tm().transact(() -> tm().loadByKeyIfPresent(BsaLabel.vKey("label")))).isPresent();
assertThat(
tm().transact(() -> tm().loadByKey(BsaUnblockableDomain.vKey("label", "app")).reason))

View File

@@ -14,15 +14,11 @@
package google.registry.flows.session;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.deleteResource;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
import static google.registry.testing.EppExceptionSubject.assertAboutEppExceptions;
import static google.registry.util.PasswordUtils.HashAlgorithm.SCRYPT;
import static google.registry.util.PasswordUtils.HashAlgorithm.SHA256;
import static org.junit.jupiter.api.Assertions.assertThrows;
import com.google.common.collect.ImmutableMap;
@@ -42,7 +38,6 @@ import google.registry.model.eppoutput.EppOutput;
import google.registry.model.registrar.Registrar;
import google.registry.model.registrar.Registrar.State;
import google.registry.testing.DatabaseHelper;
import google.registry.util.PasswordUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -191,33 +186,6 @@ public abstract class LoginFlowTestCase extends FlowTestCase<LoginFlow> {
doFailingTest("login_valid.xml", RegistrarAccountNotActiveException.class);
}
@Test
void testSuccess_sha256Password() throws Exception {
String password = "foo-BAR2";
tm().transact(
() -> {
// The salt is not exposed by Registrar (nor should it be), so we query it
// directly.
String encodedSalt =
tm().query("SELECT salt FROM Registrar WHERE registrarId = :id", String.class)
.setParameter("id", registrar.getRegistrarId())
.getSingleResult();
byte[] salt = base64().decode(encodedSalt);
String newHash = PasswordUtils.hashPassword(password, salt, SHA256);
// Set password directly, as the Java method would have used Scrypt.
tm().query("UPDATE Registrar SET passwordHash = :hash WHERE registrarId = :id")
.setParameter("id", registrar.getRegistrarId())
.setParameter("hash", newHash)
.executeUpdate();
});
assertThat(loadRegistrar("NewRegistrar").getCurrentHashAlgorithm(password).get())
.isEqualTo(SHA256);
doSuccessfulTest("login_valid.xml");
// Verifies that after successfully login, the password is re-hased with Scrypt.
assertThat(loadRegistrar("NewRegistrar").getCurrentHashAlgorithm(password).get())
.isEqualTo(SCRYPT);
}
@Test
void testFailure_incorrectPassword() {
persistResource(getRegistrarBuilder().setPassword("diff password").build());

View File

@@ -15,10 +15,8 @@
package google.registry.ui.server.registrar;
import static com.google.common.collect.ImmutableSetMultimap.toImmutableSetMultimap;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.model.EppResourceUtils.loadByForeignKey;
import static google.registry.persistence.transaction.TransactionManagerFactory.tm;
import static google.registry.testing.DatabaseHelper.createTld;
import static google.registry.testing.DatabaseHelper.loadRegistrar;
import static google.registry.testing.DatabaseHelper.persistResource;
@@ -27,8 +25,6 @@ import static google.registry.testing.SqlHelper.getRegistryLockByVerificationCod
import static google.registry.testing.SqlHelper.saveRegistryLock;
import static google.registry.tools.LockOrUnlockDomainCommand.REGISTRY_LOCK_STATUSES;
import static google.registry.ui.server.registrar.RegistryLockGetActionTest.userFromRegistrarPoc;
import static google.registry.util.PasswordUtils.HashAlgorithm.SCRYPT;
import static google.registry.util.PasswordUtils.HashAlgorithm.SHA256;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -42,9 +38,6 @@ import google.registry.model.console.RegistrarRole;
import google.registry.model.console.UserRoles;
import google.registry.model.domain.Domain;
import google.registry.model.domain.RegistryLock;
import google.registry.model.registrar.RegistrarPoc;
import google.registry.model.registrar.RegistrarPoc.RegistrarPocId;
import google.registry.persistence.VKey;
import google.registry.persistence.transaction.JpaTestExtensions;
import google.registry.persistence.transaction.JpaTestExtensions.JpaIntegrationTestExtension;
import google.registry.persistence.transaction.JpaTransactionManagerExtension;
@@ -61,7 +54,6 @@ import google.registry.testing.DeterministicStringGenerator;
import google.registry.testing.FakeClock;
import google.registry.tools.DomainLockUtils;
import google.registry.util.EmailMessage;
import google.registry.util.PasswordUtils;
import google.registry.util.StringGenerator.Alphabets;
import java.util.Map;
import java.util.Optional;
@@ -131,48 +123,6 @@ final class RegistryLockPostActionTest {
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testSuccess_lock_sha256Password() throws Exception {
tm().transact(
() -> {
// The salt is not exposed by RegistrarPoc (nor should it be), so we query
// it directly.
String encodedSalt =
tm().query(
"SELECT registryLockPasswordSalt FROM RegistrarPoc "
+ "WHERE emailAddress = :email "
+ "AND registrarId = :registrarId",
String.class)
.setParameter("email", "Marla.Singer@crr.com")
.setParameter("registrarId", "TheRegistrar")
.getSingleResult();
byte[] salt = base64().decode(encodedSalt);
String newHash = PasswordUtils.hashPassword("hi", salt, SHA256);
// Set password directly, as the Java method would have used Scrypt.
tm().query("UPDATE RegistrarPoc SET registryLockPasswordHash = :hash")
.setParameter("hash", newHash)
.executeUpdate();
});
RegistrarPoc registrarPoc =
tm().transact(
() ->
tm().loadByKey(
VKey.create(
RegistrarPoc.class,
new RegistrarPocId("Marla.Singer@crr.com", "TheRegistrar"))));
assertThat(registrarPoc.getCurrentHashAlgorithm("hi").get()).isEqualTo(SHA256);
Map<String, ?> response = action.handleJsonRequest(lockRequest());
RegistrarPoc updatedRegistrarPoc =
tm().transact(
() ->
tm().loadByKey(
VKey.create(
RegistrarPoc.class,
new RegistrarPocId("Marla.Singer@crr.com", "TheRegistrar"))));
assertThat(updatedRegistrarPoc.getCurrentHashAlgorithm("hi").get()).isEqualTo(SCRYPT);
assertSuccess(response, "lock", "Marla.Singer.RegistryLock@crr.com");
}
@Test
void testSuccess_unlock() throws Exception {
saveRegistryLock(createLock().asBuilder().setLockCompletionTime(clock.nowUtc()).build());

View File

@@ -15,82 +15,29 @@
package google.registry.util;
import static com.google.common.io.BaseEncoding.base64;
import static google.registry.util.PasswordUtils.HashAlgorithm.SCRYPT;
import static google.registry.util.PasswordUtils.HashAlgorithm.SHA256;
import static java.nio.charset.StandardCharsets.US_ASCII;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.flogger.FluentLogger;
import com.google.common.primitives.Bytes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Optional;
import org.bouncycastle.crypto.generators.SCrypt;
/** Common utility class to handle password hashing and salting */
/**
* Common utility class to handle password hashing and salting /*
*
* <p>We use a memory-hard hashing algorithm (Scrypt) to prevent brute-force attacks on passwords.
*
* <p>Note that in tests, we simply concatenate the password and salt which is much faster and
* reduces the overall test run time by a half. Our tests are not verifying that SCRYPT is
* implemented correctly anyway.
*
* @see <a href="https://en.wikipedia.org/wiki/Scrypt">Scrypt</a>
*/
public final class PasswordUtils {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
private static final Supplier<MessageDigest> SHA256_DIGEST_SUPPLIER =
Suppliers.memoize(
() -> {
try {
return MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
// All implementations of MessageDigest are required to support SHA-256.
throw new RuntimeException(
"All MessageDigest implementations are required to support SHA-256 but this one"
+ " didn't",
e);
}
});
private PasswordUtils() {}
/**
* Password hashing algorithm that takes a password and a salt (both as {@code byte[]}) and
* returns a hash.
*/
public enum HashAlgorithm {
/**
* SHA-2 that returns a 256-bit digest.
*
* @see <a href="https://en.wikipedia.org/wiki/SHA-2">SHA-2</a>
*/
@Deprecated
SHA256 {
@Override
byte[] hash(byte[] password, byte[] salt) {
return SHA256_DIGEST_SUPPLIER
.get()
.digest((new String(password, US_ASCII) + base64().encode(salt)).getBytes(US_ASCII));
}
},
/**
* Memory-hard hashing algorithm, preferred over SHA-256.
*
* <p>Note that in tests, we simply concatenate the password and salt which is much faster and
* reduces the overall test run time by a half. Our tests are not verifying that SCRYPT is
* implemented correctly anyway.
*
* @see <a href="https://en.wikipedia.org/wiki/Scrypt">Scrypt</a>
*/
SCRYPT {
@Override
byte[] hash(byte[] password, byte[] salt) {
return RegistryEnvironment.get() == RegistryEnvironment.UNITTEST
? Bytes.concat(password, salt)
: SCrypt.generate(password, salt, 32768, 8, 1, 256);
}
};
abstract byte[] hash(byte[] password, byte[] salt);
}
public static final Supplier<byte[]> SALT_SUPPLIER =
() -> {
// The generated hashes are 256 bits, and the salt should generally be of the same size.
@@ -99,38 +46,25 @@ public final class PasswordUtils {
return salt;
};
public static String hashPassword(String password, byte[] salt) {
return hashPassword(password, salt, SCRYPT);
private static byte[] hashPassword(byte[] password, byte[] salt) {
return RegistryEnvironment.get() == RegistryEnvironment.UNITTEST
? Bytes.concat(password, salt)
: SCrypt.generate(password, salt, 32768, 8, 1, 256);
}
/** Returns the hash of the password using the provided salt and {@link HashAlgorithm}. */
public static String hashPassword(String password, byte[] salt, HashAlgorithm algorithm) {
return base64().encode(algorithm.hash(password.getBytes(US_ASCII), salt));
/** Returns the hash of the password using the provided salt. */
public static String hashPassword(String password, byte[] salt) {
return base64().encode(hashPassword(password.getBytes(US_ASCII), salt));
}
/**
* Verifies a password by regenerating the hash with the provided salt and comparing it to the
* provided hash.
*
* <p>This method will first try to use {@link HashAlgorithm#SCRYPT} to verify the password, and
* falls back to {@link HashAlgorithm#SHA256} if the former fails.
*
* @return the {@link HashAlgorithm} used to successfully verify the password, or {@link
* Optional#empty()} if neither works.
*/
public static Optional<HashAlgorithm> verifyPassword(String password, String hash, String salt) {
public static boolean verifyPassword(String password, String hash, String salt) {
byte[] decodedHash = base64().decode(hash);
byte[] decodedSalt = base64().decode(salt);
byte[] calculatedHash = SCRYPT.hash(password.getBytes(US_ASCII), decodedSalt);
if (Arrays.equals(decodedHash, calculatedHash)) {
logger.atInfo().log("Scrypt hash verified.");
return Optional.of(SCRYPT);
}
calculatedHash = SHA256.hash(password.getBytes(US_ASCII), decodedSalt);
if (Arrays.equals(decodedHash, calculatedHash)) {
logger.atInfo().log("SHA256 hash verified.");
return Optional.of(SHA256);
}
return Optional.empty();
byte[] calculatedHash = hashPassword(password.getBytes(US_ASCII), decodedSalt);
return Arrays.equals(decodedHash, calculatedHash);
}
}

View File

@@ -16,8 +16,6 @@ package google.registry.util;
import static com.google.common.io.BaseEncoding.base64;
import static com.google.common.truth.Truth.assertThat;
import static google.registry.util.PasswordUtils.HashAlgorithm.SCRYPT;
import static google.registry.util.PasswordUtils.HashAlgorithm.SHA256;
import static google.registry.util.PasswordUtils.SALT_SUPPLIER;
import static google.registry.util.PasswordUtils.hashPassword;
import static google.registry.util.PasswordUtils.verifyPassword;
@@ -53,18 +51,8 @@ final class PasswordUtilsTest {
byte[] salt = SALT_SUPPLIER.get();
String password = "mySuperSecurePassword";
String hashedPassword = hashPassword(password, salt);
assertThat(hashedPassword).isEqualTo(hashPassword(password, salt, SCRYPT));
assertThat(verifyPassword(password, hashedPassword, base64().encode(salt)).get())
.isEqualTo(SCRYPT);
}
@Test
void testVerify_sha256() {
byte[] salt = SALT_SUPPLIER.get();
String password = "mySuperSecurePassword";
String hashedPassword = hashPassword(password, salt, SHA256);
assertThat(verifyPassword(password, hashedPassword, base64().encode(salt)).get())
.isEqualTo(SHA256);
assertThat(hashedPassword).isEqualTo(hashPassword(password, salt));
assertThat(verifyPassword(password, hashedPassword, base64().encode(salt))).isTrue();
}
@Test
@@ -72,7 +60,6 @@ final class PasswordUtilsTest {
byte[] salt = SALT_SUPPLIER.get();
String password = "mySuperSecurePassword";
String hashedPassword = hashPassword(password, salt);
assertThat(verifyPassword(password + "a", hashedPassword, base64().encode(salt)).isPresent())
.isFalse();
assertThat(verifyPassword(password + "a", hashedPassword, base64().encode(salt))).isFalse();
}
}