refactoring of ExternalServiceCredentialGenerator

This commit is contained in:
Sergey Skrobotov
2023-01-25 15:15:46 -08:00
parent dd98f7f043
commit eb499833c6
32 changed files with 594 additions and 415 deletions

View File

@@ -1,106 +0,0 @@
/*
* Copyright 2013-2021 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import com.google.common.annotations.VisibleForTesting;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Clock;
import java.util.HexFormat;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.whispersystems.textsecuregcm.util.Util;
public class ExternalServiceCredentialGenerator {
private final byte[] key;
private final byte[] userIdKey;
private final boolean usernameDerivation;
private final boolean prependUsername;
private final boolean truncateKey;
private final Clock clock;
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey) {
this(key, userIdKey, true, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername) {
this(key, prependUsername, true);
}
public ExternalServiceCredentialGenerator(byte[] key, boolean prependUsername, boolean truncateKey) {
this(key, new byte[0], false, prependUsername, truncateKey);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation) {
this(key, userIdKey, usernameDerivation, true, true);
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername) {
this(key, userIdKey, usernameDerivation, prependUsername, true, Clock.systemUTC());
}
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey) {
this(key, userIdKey, usernameDerivation, prependUsername, truncateKey, Clock.systemUTC());
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, Clock clock) {
this(key, userIdKey, usernameDerivation, prependUsername, true, clock);
}
@VisibleForTesting
public ExternalServiceCredentialGenerator(byte[] key, byte[] userIdKey, boolean usernameDerivation,
boolean prependUsername, boolean truncateKey, Clock clock) {
this.key = key;
this.userIdKey = userIdKey;
this.usernameDerivation = usernameDerivation;
this.prependUsername = prependUsername;
this.truncateKey = truncateKey;
this.clock = clock;
}
public ExternalServiceCredentials generateFor(String identity) {
Mac mac = getMacInstance();
String username = getUserId(identity, mac, usernameDerivation);
long currentTimeSeconds = clock.millis() / 1000;
String prefix = username + ":" + currentTimeSeconds;
byte[] prefixMac = getHmac(key, prefix.getBytes(), mac);
final HexFormat hex = HexFormat.of();
String output = hex.formatHex(truncateKey ? Util.truncate(prefixMac, 10) : prefixMac);
String token = (prependUsername ? prefix : currentTimeSeconds) + ":" + output;
return new ExternalServiceCredentials(username, token);
}
private String getUserId(String number, Mac mac, boolean usernameDerivation) {
final HexFormat hex = HexFormat.of();
if (usernameDerivation) return hex.formatHex(Util.truncate(getHmac(userIdKey, number.getBytes(), mac), 10));
else return number;
}
private Mac getMacInstance() {
try {
return Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
private byte[] getHmac(byte[] key, byte[] input, Mac mac) {
try {
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(input);
} catch (InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* Copyright 2013 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.auth;
import static java.util.Objects.requireNonNull;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256ToHexString;
import static org.whispersystems.textsecuregcm.util.HmacUtils.hmac256TruncatedToHexString;
import java.time.Clock;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.Validate;
public class ExternalServiceCredentialsGenerator {
private static final int TRUNCATE_LENGTH = 10;
private final byte[] key;
private final byte[] userDerivationKey;
private final boolean prependUsername;
private final boolean truncateSignature;
private final Clock clock;
public static ExternalServiceCredentialsGenerator.Builder builder(final byte[] key) {
return new Builder(key);
}
private ExternalServiceCredentialsGenerator(
final byte[] key,
final byte[] userDerivationKey,
final boolean prependUsername,
final boolean truncateSignature,
final Clock clock) {
this.key = requireNonNull(key);
this.userDerivationKey = requireNonNull(userDerivationKey);
this.prependUsername = prependUsername;
this.truncateSignature = truncateSignature;
this.clock = requireNonNull(clock);
}
public ExternalServiceCredentials generateForUuid(final UUID uuid) {
return generateFor(uuid.toString());
}
public ExternalServiceCredentials generateFor(final String identity) {
final String username = userDerivationKey.length > 0
? hmac256TruncatedToHexString(userDerivationKey, identity, TRUNCATE_LENGTH)
: identity;
final long currentTimeSeconds = TimeUnit.MILLISECONDS.toSeconds(clock.millis());
final String dataToSign = username + ":" + currentTimeSeconds;
final String signature = truncateSignature
? hmac256TruncatedToHexString(key, dataToSign, TRUNCATE_LENGTH)
: hmac256ToHexString(key, dataToSign);
final String token = (prependUsername ? dataToSign : currentTimeSeconds) + ":" + signature;
return new ExternalServiceCredentials(username, token);
}
public static class Builder {
private final byte[] key;
private byte[] userDerivationKey = new byte[0];
private boolean prependUsername = true;
private boolean truncateSignature = true;
private Clock clock = Clock.systemUTC();
private Builder(final byte[] key) {
this.key = requireNonNull(key);
}
public Builder withUserDerivationKey(final byte[] userDerivationKey) {
Validate.isTrue(requireNonNull(userDerivationKey).length > 0, "userDerivationKey must not be empty");
this.userDerivationKey = userDerivationKey;
return this;
}
public Builder withClock(final Clock clock) {
this.clock = requireNonNull(clock);
return this;
}
public Builder prependUsername(final boolean prependUsername) {
this.prependUsername = prependUsername;
return this;
}
public Builder truncateSignature(final boolean truncateSignature) {
this.truncateSignature = truncateSignature;
return this;
}
public ExternalServiceCredentialsGenerator build() {
return new ExternalServiceCredentialsGenerator(
key, userDerivationKey, prependUsername, truncateSignature, clock);
}
}
}