Skip to content
Open
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
2 changes: 1 addition & 1 deletion .clusterfuzzlite/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ cp fernet-fuzzer/target/fernet-fuzzer-*.jar "${OUT}/fernet-fuzzer.jar"

RUNTIME_CLASSPATH="\${this_dir}/fernet-java8.jar:\${this_dir}/fernet-fuzzer.jar"

fuzzers="TokenEncryptDecryptFuzzer TokenDecryptFuzzer"
fuzzers="TokenEncryptDecryptFuzzer TokenDecryptFuzzer TokenReplayFuzzer PayloadPaddingFuzzer"
echo "$fuzzers" | tr ' ' '\n' | while read -r fuzzer
do
cp "${SRC}/default.options" "${OUT}/${fuzzer}.options"
Expand Down
29 changes: 27 additions & 2 deletions .clusterfuzzlite/fernet-fuzzer/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,44 @@
<!-- Override the Fernet Java version with the local snapshot version -->
<fernet.version>1.5.0</fernet.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-bom</artifactId>
<version>4.6.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.macasaet.fernet</groupId>
<artifactId>fernet-java8</artifactId>
<version>${fernet.version}</version>
<version>[${fernet.version}]</version>
</dependency>
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-api</artifactId>
<version>0.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
Copyright 2022 Carlos Macasaet

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Base64;
import java.util.HashMap;
import java.util.UUID;
import java.util.function.Predicate;
import javax.crypto.spec.IvParameterSpec;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
import com.macasaet.fernet.*;

/**
* This fuzzer simulates an attacker padding the content of a token
*/
public class PayloadPaddingFuzzer {

/**
* Freeze time for the fuzzer. In practice, the clock will return a different instant every second.
*/
final static Clock clock = Clock.fixed(Instant.ofEpochSecond(581182474), ZoneId.of("UTC"));
/**
* Run each fuzz input against the same key. Note that in practice, the key is likely rotated on a regular basis.
*/
final static Key key = new Key("UrNImCIJQuYODgrBU5NgH5rpTc7l52IS5ELuhwF4RHU=");
final static Validator<String> validator = new StringValidator() {
public Charset getCharset() {
return StandardCharsets.UTF_8;
}

public Clock getClock() {
return clock;
}

public Predicate<String> getObjectValidator() {
return candidate -> {
final var components = candidate.split("&");
final var map = new HashMap<String, String>(components.length);
for (final var component : components) {
final var pair = component.split("=");
map.put(pair[0], pair[1]);
}
return map.containsKey("id") && map.containsKey("username");
};
}
};
final static Utility utility = new Utility();

public static void fuzzerTestOneInput(final FuzzedDataProvider data) {
// retrieve a valid token from the server
final var version = (byte) 0x80;
final var timestamp = clock.instant();
final var initializationVector = new IvParameterSpec(Base64.getUrlDecoder().decode("7x-FMghmHjn-6lVUKCsN-A=="));
final var id = UUID.fromString("5c8293ac-6c70-4be0-823b-6ec391fc164b");
final var username = "alice";
final var plain = "id=" + id + "&username=" + username;
final var cipherText = key.encrypt(plain.getBytes(StandardCharsets.UTF_8), initializationVector);
final var signature = key.sign(version, timestamp, initializationVector, cipherText);
final var validToken = new Token(version, timestamp, initializationVector, cipherText, signature) {
};
validator.validateAndDecrypt(key, validToken);

// tamper with the token
final var forgedTimestamp = timestamp.plusSeconds(data.consumeLong(0, 60));
final var forgedInitializationVector = new IvParameterSpec(utility.consumeBytes(data, 16));
final var forgedCipherText = data.consumeBytes(cipherText.length * 2);
final var forgedSignature = utility.consumeBytes(data, signature.length);
final var forgedToken = new Token(version, forgedTimestamp, forgedInitializationVector, forgedCipherText, forgedSignature) {
};
try {
final var result = validator.validateAndDecrypt(key, forgedToken);
if (result.length() > plain.length() && result.startsWith(plain)) {
throw new FuzzerSecurityIssueHigh("Able to pad a token with a malicious payload");
}
throw new FuzzerSecurityIssueHigh("Able to forge a token");
} catch (final TokenValidationException ignored) {
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
limitations under the License.
*/

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.function.Function;
import java.time.ZoneId;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.spec.IvParameterSpec;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
import com.macasaet.fernet.Key;
import com.macasaet.fernet.Token;
import com.macasaet.fernet.TokenValidationException;
Expand All @@ -32,31 +35,36 @@
*/
public class TokenDecryptFuzzer {

/**
* Freeze time for the fuzzer. In practice, the clock will return a different instant every second.
*/
final static Clock clock = Clock.fixed(Instant.ofEpochSecond(581182474), ZoneId.of("UTC"));
/*
* Run each fuzz input against the same key. Note that in practice, the key is likely rotated on a regular basis.
*/
final static Key key = new Key("UrNImCIJQuYODgrBU5NgH5rpTc7l52IS5ELuhwF4RHU=");
final static Validator<byte[]> validator = () -> Function.identity();
final static Validator<UUID> validator = new UuidValidator(clock);
final static Utility utility = new Utility();

public static void fuzzerTestOneInput(final FuzzedDataProvider data) {
final var ivBytes = new byte[16];
for (int i = ivBytes.length; --i >= 0; ivBytes[i] = data.consumeByte()) ;
final var initializationVector = new IvParameterSpec(ivBytes);
final var cipherTextLength = data.consumeInt(1, 4096) * 16;
final var cipherText = new byte[cipherTextLength];
for (int i = cipherTextLength; --i >= 0; cipherText[i] = data.consumeByte()) ;
final var signature = new byte[32];
for (int i = signature.length; --i >= 0; signature[i] = data.consumeByte()) ;
final var timestamp = Instant.now().plus(Duration.ofSeconds(data.consumeLong(-60, 60)));
final var token = new Token((byte) -128, timestamp, initializationVector, cipherText, signature) {
final var initializationVector = new IvParameterSpec(utility.consumeBytes(data, 16));
final var cipherText = utility.consumeBytes(data, 32); // random payload the size of an encrypted UUID
final var signature = utility.consumeBytes(data, 32); // random signature of the right size
final var timestamp = clock.instant().plus(Duration.ofSeconds(data.consumeLong(-60, 60)));
// generate the shape of a valid token without knowing the encryption or signing key
final var token = new Token((byte) 0x80, timestamp, initializationVector, cipherText, signature) {
};

try {
token.validateAndDecrypt(key, validator);
throw new IllegalStateException("Random input passed validation");
final var result = token.validateAndDecrypt(key, validator);
throw new FuzzerSecurityIssueHigh("Random input passed validation and generated UUID: " + result.toString());
} catch (final TokenValidationException tve) {
if(tve.getCause() instanceof BadPaddingException) {
throw new IllegalStateException("Random input forged signature");
if (tve.getCause() instanceof BadPaddingException) {
throw new FuzzerSecurityIssueHigh("Random input forged signature: " + tve.getCause().getMessage(), tve.getCause());
}
}
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@
See the License for the specific language governing permissions and
limitations under the License.
*/
import java.util.Arrays;
import java.util.function.Function;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.time.ZoneId;
import java.util.UUID;
import javax.crypto.spec.IvParameterSpec;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
import com.macasaet.fernet.Key;
import com.macasaet.fernet.Token;
import com.macasaet.fernet.Validator;
Expand All @@ -26,20 +32,38 @@
*/
public class TokenEncryptDecryptFuzzer {

/**
* Freeze time for the fuzzer. In practice, the clock will return a different instant every second.
*/
final static Clock clock = Clock.fixed(Instant.ofEpochSecond(581182474), ZoneId.of("UTC"));
/*
* Run each fuzz input against the same key. Note that in practice, the key is likely rotated on a regular basis.
*/
final static Key key = new Key("UrNImCIJQuYODgrBU5NgH5rpTc7l52IS5ELuhwF4RHU=");
final static Validator<byte[]> validator = () -> Function.identity();
final static Validator<UUID> validator = new UuidValidator(clock);
final static Utility utility = new Utility();

public static void fuzzerTestOneInput(final FuzzedDataProvider data) {
final var payload = data.consumeBytes(4096);
final var token = Token.generate(key, payload);
final var version = (byte) 0x80;
final var timestamp = clock.instant().plus(Duration.ofSeconds(data.consumeLong(-60, 60)));
final var initializationVector = new IvParameterSpec(utility.consumeBytes(data, 16));
final var idBytes = utility.consumeBytes(data, 16);
// copied from JDK
idBytes[6] &= 0x0f; /* clear version */
idBytes[6] |= 0x40; /* set to version 4 */
idBytes[8] &= 0x3f; /* clear variant */
idBytes[8] |= 0x80; /* set to IETF variant */
final var cipherText = key.encrypt(idBytes, initializationVector);
final var signature = key.sign(version, timestamp, initializationVector, cipherText);

final var token = new Token(version, timestamp, initializationVector, cipherText, signature) {
};
final var serialised = token.serialise();
final var deserialised = Token.fromString(serialised);
final var decrypted = deserialised.validateAndDecrypt(key, validator);
if (!Arrays.equals(payload, decrypted)) {
throw new IllegalStateException("Encryption/decryption fault");
if (!validator.getTransformer().apply(idBytes).equals(decrypted)) {
throw new FuzzerSecurityIssueHigh("Encryption/decryption fault");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
Copyright 2022 Carlos Macasaet

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
import java.util.UUID;
import javax.crypto.spec.IvParameterSpec;

import com.code_intelligence.jazzer.api.FuzzedDataProvider;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueHigh;
import com.code_intelligence.jazzer.api.FuzzerSecurityIssueMedium;
import com.macasaet.fernet.Key;
import com.macasaet.fernet.Token;
import com.macasaet.fernet.TokenValidationException;
import com.macasaet.fernet.Validator;

/**
* This fuzzer simulates a replay attack
*/
public class TokenReplayFuzzer {

/**
* Freeze time for the fuzzer. In practice, the clock will return a different instant every second.
*/
static Clock clock = Clock.fixed(Instant.ofEpochSecond(581182474), ZoneId.of("UTC"));
/*
* Run each fuzz input against the same key. Note that in practice, the key is likely rotated on a regular basis.
*/
final static Key key = new Key("UrNImCIJQuYODgrBU5NgH5rpTc7l52IS5ELuhwF4RHU=");
final static Validator<UUID> validator = new UuidValidator(clock) {

public Clock getClock() {
return clock;
}
};
final static Utility utility = new Utility();

public static void fuzzerTestOneInput(final FuzzedDataProvider data) {
// retrieve a valid token from the server
final var version = (byte) 0x80;
final var timestamp = clock.instant();
final var initializationVector = new IvParameterSpec(Base64.getUrlDecoder().decode("7x-FMghmHjn-6lVUKCsN-A=="));
final var id = UUID.fromString("5c8293ac-6c70-4be0-823b-6ec391fc164b");
final var idBytes = utility.toBytes(id);
final var cipherText = key.encrypt(idBytes, initializationVector);
final var signature = key.sign(version, timestamp, initializationVector, cipherText);
final var validToken = new Token(version, timestamp, initializationVector, cipherText, signature) {
};
validator.validateAndDecrypt(key, validToken);

// fast-forward time
clock = Clock.fixed(clock.instant().plus(4, ChronoUnit.HOURS), ZoneId.of("UTC"));

// replay the expired token
final var forgedTimestamp = timestamp.plus(4, ChronoUnit.HOURS).plusSeconds(data.consumeLong(-60, 60));
final var forgedSignature = utility.consumeBytes(data, 32);
final var forgedToken = new Token(version, forgedTimestamp, initializationVector, cipherText, forgedSignature) {
};

try {
final var forgedId = validator.validateAndDecrypt(key, forgedToken);
if (forgedId.equals(id)) {
throw new FuzzerSecurityIssueHigh("Fuzz input replayed a token");
}
throw new FuzzerSecurityIssueMedium("Fuzz input forged a signature with a different timestamp");
} catch (final TokenValidationException ignored) {
}
}

}
Loading