Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ public NinjaSessionConverter(NinjaProperties ninjaProperties) {
});

byte[] decodedKey = Base64.getDecoder().decode(encodedSecret);

// HS256 requires a key of at least 256 bits (32 bytes). A shorter key (e.g. the
// 'changeme' demo default) would still "work" with this custom Jwts implementation but
// produces weak, forgeable tokens. Fail fast at startup instead of silently accepting it.
int MINIMUM_SECRET_LENGTH_IN_BYTES = 32;
if (decodedKey.length < MINIMUM_SECRET_LENGTH_IN_BYTES) {
throw new RuntimeException(String.format(
"The secret '%s' in 'conf/application.conf' is too weak. HS256 requires at least %d bytes (256 bits) after Base64-decoding, but the configured secret decodes to %d bytes. Please generate a strong secret using 'mvn ninja:generateSecret'.",
NinjaConstants.NINJA_APPLICATION_SECRET_KEY, MINIMUM_SECRET_LENGTH_IN_BYTES, decodedKey.length));
}

this.secretKeyForSessionEncryption = new SecretKeySpec(decodedKey, 0, decodedKey.length, "HmacSHA256");

this.sessionExpiryTimeInSeconds = ninjaProperties.get("application.session.expire_time_in_seconds").map(v -> Long.valueOf(v));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.r10r.ninjax.core;

import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.r10r.ninjax.core.properties.NinjaProperties;

public class NinjaSessionConverterTest {

/**
* Test double for NinjaProperties that serves a fixed map of properties instead of
* loading from conf/application.conf.
*/
private static class FixedNinjaProperties extends NinjaProperties {
private final Map<String, String> values;

FixedNinjaProperties(Map<String, String> values) {
this.values = values;
}

@Override
public Optional<String> get(String propertyName) {
return Optional.ofNullable(values.get(propertyName));
}
}

private static String base64SecretOfLength(int numberOfBytes) {
return Base64.getEncoder().encodeToString(new byte[numberOfBytes]);
}

@Test
public void shouldRejectSecretShorterThan32Bytes() {
// given
NinjaProperties properties = new FixedNinjaProperties(
Map.of(NinjaConstants.NINJA_APPLICATION_SECRET_KEY, base64SecretOfLength(31)));

// when
RuntimeException exception = assertThrows(RuntimeException.class,
() -> new NinjaSessionConverter(properties));

// then
assertThat(exception.getMessage()).contains("too weak");
}

@Test
public void shouldRejectWeakChangemeDemoDefault() {
// given
NinjaProperties properties = new FixedNinjaProperties(
Map.of(NinjaConstants.NINJA_APPLICATION_SECRET_KEY, "changeme"));

// when
RuntimeException exception = assertThrows(RuntimeException.class,
() -> new NinjaSessionConverter(properties));

// then
assertThat(exception.getMessage()).contains("too weak");
}

@Test
public void shouldAcceptSecretOfAtLeast32Bytes() {
// given
NinjaProperties properties = new FixedNinjaProperties(
Map.of(NinjaConstants.NINJA_APPLICATION_SECRET_KEY, base64SecretOfLength(32)));

// when
NinjaSessionConverter converter = new NinjaSessionConverter(properties);

// then
assertThat(converter).isNotNull();
}

@Test
public void shouldFailWhenSecretIsMissing() {
// given
NinjaProperties properties = new FixedNinjaProperties(Map.of());

// when
RuntimeException exception = assertThrows(RuntimeException.class,
() -> new NinjaSessionConverter(properties));

// then
assertThat(exception.getMessage()).contains("Missing key");
}
}
3 changes: 2 additions & 1 deletion ninjax-demo-todo/src/main/java/conf/application.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ninja.port=8081
application.secret=changeme
# Demo secret only. Generate your own for production with 'mvn ninja:generateSecret'.
application.secret=RHwx0fWz73AlJx1fulfkrYKL5Yo7t8F1H8xUajByE28=
application.session.expire_time_in_seconds=3600
application.session.cookie.secure=false

Expand Down
2 changes: 1 addition & 1 deletion ninjax-demo-todo/src/test/resources/conf/application.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

# Set in test: ninja.port=
application.secret=changeme
application.secret=RHwx0fWz73AlJx1fulfkrYKL5Yo7t8F1H8xUajByE28=
application.session.expire_time_in_seconds=3600
application.session.cookie.secure=false

Expand Down
3 changes: 2 additions & 1 deletion ninjax-jetty-demo-todo/src/main/java/conf/application.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
ninja.port=8081
application.secret=changeme
# Demo secret only. Generate your own for production with 'mvn ninja:generateSecret'.
application.secret=RHwx0fWz73AlJx1fulfkrYKL5Yo7t8F1H8xUajByE28=
application.session.expire_time_in_seconds=3600
application.session.cookie.secure=false

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

# Set in test: ninja.port=
application.secret=changeme
application.secret=RHwx0fWz73AlJx1fulfkrYKL5Yo7t8F1H8xUajByE28=
application.session.expire_time_in_seconds=3600
application.session.cookie.secure=false

Expand Down