Skip to content

Commit

Permalink
Pull request #708: Adding fallback properties to allow old digests to…
Browse files Browse the repository at this point in the history
… be verified.

Merge in MC/connect from feature/ROCKSOLID-10902-update-password-digester-to-use-fallback-algorithm to development

* commit '377675dccad3ebbcb04d6db510ad4540c08d5e64':
  Adding comment to new properties on migration
  Adding fallback properties to allow old digests to be verified.
  • Loading branch information
narupley authored and jdonextgen committed Jul 10, 2023
2 parents bca27fc + 377675d commit 010f2a4
Show file tree
Hide file tree
Showing 5 changed files with 266 additions and 10 deletions.
90 changes: 90 additions & 0 deletions server/src/com/mirth/commons/encryption/Digester.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ public class Digester {
private boolean usePBE = true;
private int keySizeBits = DEFAULT_KEY_SIZE_BITS;

private String fallbackAlgorithm;
private int fallbackSaltSizeBytes = 8;
private int fallbackIterations = 1000;
private boolean fallbackUsePBE = false;
private int fallbackKeySizeBits = 256;
private Digester fallbackDigester;

// Typically shouldn't need to be changed
private Charset charset = Charset.forName(System.getProperty("mirth.digester.charset", StandardCharsets.UTF_8.name()));

Expand Down Expand Up @@ -62,6 +69,54 @@ public void setFormat(Output format) {
this.format = format;
}

public String getFallbackAlgorithm() {
return fallbackAlgorithm;
}

public void setFallbackAlgorithm(String fallbackAlgorithm) {
this.fallbackAlgorithm = fallbackAlgorithm;
}

public int getFallbackSaltSizeBytes() {
return fallbackSaltSizeBytes;
}

public void setFallbackSaltSizeBytes(Integer fallbackSaltSizeBytes) {
if (fallbackSaltSizeBytes != null) {
this.fallbackSaltSizeBytes = fallbackSaltSizeBytes;
}
}

public int getFallbackIterations() {
return fallbackIterations;
}

public void setFallbackIterations(Integer fallbackIterations) {
if (fallbackIterations != null) {
this.fallbackIterations = fallbackIterations;
}
}

public boolean isFallbackUsePBE() {
return fallbackUsePBE;
}

public void setFallbackUsePBE(Boolean fallbackUsePBE) {
if (fallbackUsePBE != null) {
this.fallbackUsePBE = fallbackUsePBE;
}
}

public int getFallbackKeySizeBits() {
return fallbackKeySizeBits;
}

public void setFallbackKeySizeBits(Integer fallbackKeySizeBits) {
if (fallbackKeySizeBits != null) {
this.fallbackKeySizeBits = fallbackKeySizeBits;
}
}

public SecureRandom getSaltGenerator() {
return saltGenerator;
}
Expand Down Expand Up @@ -191,6 +246,27 @@ private byte[] digest(final String message, final byte[] salt) throws Exception
}

public boolean matches(final String message, final String digest) throws EncryptionException {
boolean matches = false;
EncryptionException firstCause = null;
try {
matches = doMatches(message, digest);
} catch (EncryptionException e) {
firstCause = e;
}

if (!matches && getFallback() != null) {
// Try fallback algorithm
try {
matches = getFallback().matches(message, digest);
} catch (EncryptionException e) {
throw firstCause != null ? firstCause : e;
}
}

return matches;
}

private boolean doMatches(final String message, final String digest) throws EncryptionException {
try {
byte[] digestBytes = null;

Expand All @@ -211,4 +287,18 @@ private boolean matches(final String message, final byte[] digest) throws Except
System.arraycopy(digest, 0, salt, 0, saltSizeBytes);
return Arrays.equals(digest(message, salt), digest);
}

private Digester getFallback() {
if (fallbackDigester == null && fallbackAlgorithm != null) {
fallbackDigester = new Digester();
fallbackDigester.setProvider(provider);
fallbackDigester.setAlgorithm(fallbackAlgorithm);
fallbackDigester.setSaltSizeBytes(fallbackSaltSizeBytes);
fallbackDigester.setIterations(fallbackIterations);
fallbackDigester.setUsePBE(fallbackUsePBE);
fallbackDigester.setKeySizeBits(fallbackKeySizeBits);
fallbackDigester.setFormat(format);
}
return fallbackDigester;
}
}
76 changes: 76 additions & 0 deletions server/src/com/mirth/connect/model/EncryptionSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public class EncryptionSettings extends AbstractSettings implements Serializable
private static final String DIGEST_USE_PBE = "digest.usepbe";
private static final String DIGEST_KEY_SIZE = "digest.keysizeinbits";

private static final String DIGEST_FALLBACK_ALGORITHM = "digest.fallback.algorithm";
private static final String DIGEST_FALLBACK_SALT_SIZE = "digest.fallback.saltsizeinbytes";
private static final String DIGEST_FALLBACK_ITERATIONS = "digest.fallback.iterations";
private static final String DIGEST_FALLBACK_USE_PBE = "digest.fallback.usepbe";
private static final String DIGEST_FALLBACK_KEY_SIZE = "digest.fallback.keysizeinbits";

private static final String SECURITY_PROVIDER = "security.provider";

private Boolean encryptExport;
Expand All @@ -65,6 +71,11 @@ public class EncryptionSettings extends AbstractSettings implements Serializable
private Integer digestIterations;
private Boolean digestUsePBE;
private Integer digestKeySize;
private String digestFallbackAlgorithm;
private Integer digestFallbackSaltSize;
private Integer digestFallbackIterations;
private Boolean digestFallbackUsePBE;
private Integer digestFallbackKeySize;
private String securityProvider;
private byte[] secretKey;

Expand Down Expand Up @@ -182,6 +193,46 @@ public void setDigestKeySize(Integer digestKeySize) {
this.digestKeySize = digestKeySize;
}

public String getDigestFallbackAlgorithm() {
return digestFallbackAlgorithm;
}

public void setDigestFallbackAlgorithm(String digestFallbackAlgorithm) {
this.digestFallbackAlgorithm = digestFallbackAlgorithm;
}

public Integer getDigestFallbackSaltSize() {
return digestFallbackSaltSize;
}

public void setDigestFallbackSaltSize(Integer digestFallbackSaltSize) {
this.digestFallbackSaltSize = digestFallbackSaltSize;
}

public Integer getDigestFallbackIterations() {
return digestFallbackIterations;
}

public void setDigestFallbackIterations(Integer digestFallbackIterations) {
this.digestFallbackIterations = digestFallbackIterations;
}

public Boolean getDigestFallbackUsePBE() {
return digestFallbackUsePBE;
}

public void setDigestFallbackUsePBE(Boolean digestFallbackUsePBE) {
this.digestFallbackUsePBE = digestFallbackUsePBE;
}

public Integer getDigestFallbackKeySize() {
return digestFallbackKeySize;
}

public void setDigestFallbackKeySize(Integer digestFallbackKeySize) {
this.digestFallbackKeySize = digestFallbackKeySize;
}

public String getSecurityProvider() {
return securityProvider;
}
Expand Down Expand Up @@ -212,6 +263,11 @@ public void setProperties(Properties properties) {
setDigestIterations(toIntegerObject(properties.getProperty(DIGEST_ITERATIONS), DEFAULT_DIGEST_ITERATIONS));
setDigestUsePBE(intToBooleanObject(properties.getProperty(DIGEST_USE_PBE), DEFAULT_DIGEST_USE_PBE));
setDigestKeySize(toIntegerObject(properties.getProperty(DIGEST_KEY_SIZE), DEFAULT_DIGEST_KEY_SIZE));
setDigestFallbackAlgorithm(properties.getProperty(DIGEST_FALLBACK_ALGORITHM, "SHA256"));
setDigestFallbackSaltSize(toIntegerObject(properties.getProperty(DIGEST_FALLBACK_SALT_SIZE), 8));
setDigestFallbackIterations(toIntegerObject(properties.getProperty(DIGEST_FALLBACK_ITERATIONS), 1000));
setDigestFallbackUsePBE(intToBooleanObject(properties.getProperty(DIGEST_FALLBACK_USE_PBE), false));
setDigestFallbackKeySize(toIntegerObject(properties.getProperty(DIGEST_FALLBACK_KEY_SIZE), 256));
setSecurityProvider(properties.getProperty(SECURITY_PROVIDER, DEFAULT_SECURITY_PROVIDER));
}

Expand Down Expand Up @@ -267,6 +323,26 @@ public Properties getProperties() {
properties.put(DIGEST_KEY_SIZE, getDigestKeySize());
}

if (getDigestFallbackAlgorithm() != null) {
properties.put(DIGEST_FALLBACK_ALGORITHM, getDigestFallbackAlgorithm());
}

if (getDigestFallbackSaltSize() != null) {
properties.put(DIGEST_FALLBACK_SALT_SIZE, getDigestFallbackSaltSize());
}

if (getDigestFallbackIterations() != null) {
properties.put(DIGEST_FALLBACK_ITERATIONS, getDigestFallbackIterations());
}

if (getDigestFallbackUsePBE() != null) {
properties.put(DIGEST_FALLBACK_USE_PBE, getDigestFallbackUsePBE());
}

if (getDigestFallbackKeySize() != null) {
properties.put(DIGEST_FALLBACK_KEY_SIZE, getDigestFallbackKeySize());
}

if (getSecurityProvider() != null) {
properties.put(SECURITY_PROVIDER, getSecurityProvider());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1450,6 +1450,11 @@ private void configureEncryption(Provider provider, KeyStore keyStore, char[] ke
digester.setIterations(encryptionConfig.getDigestIterations());
digester.setUsePBE(encryptionConfig.getDigestUsePBE());
digester.setKeySizeBits(encryptionConfig.getDigestKeySize());
digester.setFallbackAlgorithm(encryptionConfig.getDigestFallbackAlgorithm());
digester.setFallbackSaltSizeBytes(encryptionConfig.getDigestFallbackSaltSize());
digester.setFallbackIterations(encryptionConfig.getDigestFallbackIterations());
digester.setFallbackUsePBE(encryptionConfig.getDigestFallbackUsePBE());
digester.setFallbackKeySizeBits(encryptionConfig.getDigestFallbackKeySize());
digester.setFormat(Output.BASE64);

if (StringUtils.equalsAnyIgnoreCase(encryptionConfig.getEncryptionAlgorithm(), "AES", "DES", "DESede")) {
Expand Down
18 changes: 11 additions & 7 deletions server/src/com/mirth/connect/server/migration/Migrate4_4_0.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,18 @@ public void updateConfiguration(PropertiesConfiguration configuration) {
if (getStartingVersion() == null || getStartingVersion().ordinal() < Version.v4_4_0.ordinal()) {
String digestAlgorithm = configuration.getString("digest.algorithm");

// If no explicit digest algorithm was set, then default to the old SHA256 on upgrade.
if (StringUtils.isBlank(digestAlgorithm)) {
configuration.setProperty("digest.algorithm", "SHA256");
if (StringUtils.isNotBlank(digestAlgorithm)) {
// Keep the current algorithm, and set these other properties since the defaults changed.
configuration.setProperty("digest.iterations", "1000");
configuration.setProperty("digest.usepbe", "0");
} else {
// Use the new algorithm, but set the old default as the fallback
configuration.setProperty("digest.fallback.algorithm", "SHA256");
configuration.getLayout().setBlancLinesBefore("digest.fallback.algorithm", 1);
configuration.getLayout().setComment("digest.fallback.algorithm", "Allows old digest values to be verified");
configuration.setProperty("digest.fallback.iterations", "1000");
configuration.setProperty("digest.fallback.usepbe", "0");
}

// Always set these properties on upgrade since the defaults changed.
configuration.setProperty("digest.iterations", "1000");
configuration.setProperty("digest.usepbe", "0");
}
}

Expand Down
87 changes: 84 additions & 3 deletions server/test/com/mirth/commons/encryption/test/DigesterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void testPBKDF2() throws Exception {
// Hardcoded hash used 600000 iterations
assertFalse(digester.matches("admin", HASH_PBKDF2_ADMIN));
}

@Test
public void testArgon2id() throws Exception {
Digester digester = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "Argon2", 3);
Expand Down Expand Up @@ -112,7 +112,7 @@ public void testArgon2id() throws Exception {
assertFalse(digester.matches("admin", HASH_ARGON2D_ADMIN));
assertFalse(digester.matches("admin", HASH_ARGON2I_ADMIN));
}

@Test
public void testArgon2d() throws Exception {
Digester digester = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "Argon2d", 4);
Expand Down Expand Up @@ -146,7 +146,7 @@ public void testArgon2d() throws Exception {
assertFalse(digester.matches("admin", HASH_ARGON2I_ADMIN));
assertFalse(digester.matches("admin", HASH_ARGON2ID_ADMIN));
}

@Test
public void testArgon2i() throws Exception {
Digester digester = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "Argon2i", 5);
Expand Down Expand Up @@ -181,6 +181,87 @@ public void testArgon2i() throws Exception {
assertFalse(digester.matches("admin", HASH_ARGON2ID_ADMIN));
}

@Test
public void testFallback1() throws Exception {
Digester digester = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "PBKDF2WithHmacSHA256", 600000, true, 256);

assertFalse(digester.matches("admin", HASH_SHA256_ADMIN));

digester.setFallbackAlgorithm("SHA256");

assertTrue(digester.matches("admin", HASH_SHA256_ADMIN));
}

@Test
public void testFallback2() throws Exception {
Digester digester1 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "Argon2", 3);

String input1 = "admin";
String digest1 = digester1.digest(input1);
assertFalse(StringUtils.isBlank(digest1));
assertFalse(input1.equals(digest1));
assertTrue(digester1.matches(input1, digest1));

Digester digester2 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "PBKDF2WithHmacSHA256", 600000, true, 256);

assertFalse(digester2.matches(input1, digest1));

digester2.setFallbackAlgorithm(digester1.getAlgorithm());
digester2.setFallbackSaltSizeBytes(digester1.getSaltSizeBytes());
digester2.setFallbackIterations(digester1.getIterations());
digester2.setFallbackUsePBE(digester1.isUsePBE());
digester2.setFallbackKeySizeBits(digester1.getKeySizeBits());

assertTrue(digester2.matches(input1, digest1));
}

@Test
public void testFallback3() throws Exception {
Digester digester1 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "PBKDF2WithHmacSHA256", 600000, true, 256);

String input1 = "admin";
String digest1 = digester1.digest(input1);
assertFalse(StringUtils.isBlank(digest1));
assertFalse(input1.equals(digest1));
assertTrue(digester1.matches(input1, digest1));

Digester digester2 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "Argon2", 3);

assertFalse(digester2.matches(input1, digest1));

digester2.setFallbackAlgorithm(digester1.getAlgorithm());
digester2.setFallbackSaltSizeBytes(digester1.getSaltSizeBytes());
digester2.setFallbackIterations(digester1.getIterations());
digester2.setFallbackUsePBE(digester1.isUsePBE());
digester2.setFallbackKeySizeBits(digester1.getKeySizeBits());

assertTrue(digester2.matches(input1, digest1));
}

@Test
public void testFallback4() throws Exception {
Digester digester1 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "PBKDF2WithHmacSHA256", 2222, true, 128);
digester1.setSaltSizeBytes(10);

String input1 = "admin";
String digest1 = digester1.digest(input1);
assertFalse(StringUtils.isBlank(digest1));
assertFalse(input1.equals(digest1));
assertTrue(digester1.matches(input1, digest1));

Digester digester2 = createDigester(new org.bouncycastle.jce.provider.BouncyCastleProvider(), "PBKDF2WithHmacSHA256", 600000, true, 256);

assertFalse(digester2.matches(input1, digest1));

digester2.setFallbackAlgorithm(digester1.getAlgorithm());
digester2.setFallbackSaltSizeBytes(digester1.getSaltSizeBytes());
digester2.setFallbackIterations(digester1.getIterations());
digester2.setFallbackUsePBE(digester1.isUsePBE());
digester2.setFallbackKeySizeBits(digester1.getKeySizeBits());

assertTrue(digester2.matches(input1, digest1));
}

private Digester createDigester(Provider provider, String algorithm, int iterations) {
return createDigester(provider, algorithm, iterations, false, 256);
}
Expand Down

0 comments on commit 010f2a4

Please sign in to comment.