Skip to content

Commit

Permalink
Padded data should be at least 32 bytes (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
marchof authored Nov 17, 2023
1 parent 63b70f1 commit b87051d
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 15 deletions.
61 changes: 61 additions & 0 deletions src/main/java/com/mountainminds/three4j/PaddedBuffer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*******************************************************************************
* Copyright (c) 2023 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*******************************************************************************/
package com.mountainminds.three4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.Random;

/**
* Internal utility for PKCS#7 padding.
*/
class PaddedBuffer extends DataOutputStream {

private static ThreadLocal<Random> RANDOM = ThreadLocal.withInitial(SecureRandom::new);

private static final int MSG_LEN_MIN = 32;
private static final int PAD_LEN_MIN = 1;
private static final int PAD_LEN_MAX = 255;

private final Random random;

PaddedBuffer(Random random) {
super(new ByteArrayOutputStream());
this.random = random;
}

PaddedBuffer() {
this(RANDOM.get());
}

byte[] withPadding() throws IOException {
var buffer = (ByteArrayOutputStream) out;
int panLenMin = Math.max(PAD_LEN_MIN, MSG_LEN_MIN - buffer.size());
int padding = random.nextInt(PAD_LEN_MAX - panLenMin + 1) + panLenMin;
for (int i = 0; i < padding; i++) {
write(padding);
}
return buffer.toByteArray();
}

static DataInputStream removePadding(byte[] buffer) {
int len = 0xff & buffer[buffer.length - 1];
return new DataInputStream(new ByteArrayInputStream(buffer, 0, buffer.length - len));
}

}
21 changes: 6 additions & 15 deletions src/main/java/com/mountainminds/three4j/PlainMessage.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2022 Mountainminds GmbH & Co. KG
* Copyright (c) 2023 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
Expand All @@ -15,8 +15,6 @@

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
Expand All @@ -30,7 +28,6 @@

import com.google.gson.Gson;

import software.pando.crypto.nacl.Bytes;
import software.pando.crypto.nacl.CryptoBox;

/**
Expand All @@ -51,15 +48,10 @@ public EncryptedMessage encrypt(PrivateKey privateKey, PublicKey publicKey) {
}

private final byte[] encode() {
try (var buffer = new ByteArrayOutputStream(); var out = new DataOutputStream(buffer)) {
out.write(getType());
encode(out);
// add random padding of 1 - 255 bytes (PKCS#7 style)
int padding = Math.max(1, 0xFF & Bytes.secureRandom(1)[0]);
for (int i = 0; i < padding; i++) {
out.write(padding);
}
return buffer.toByteArray();
try (var buffer = new PaddedBuffer()) {
buffer.write(getType());
encode(buffer);
return buffer.withPadding();
} catch (IOException e) {
// Must not happen with ByteArrayOutputStream
throw new RuntimeException("Unexpected IOException", e);
Expand All @@ -75,8 +67,7 @@ private final byte[] encode() {
* @return decoded message of the respective subtype
*/
public static PlainMessage decode(byte[] bytes) throws IllegalArgumentException {
int padding = 0xFF & bytes[bytes.length - 1];
try (var in = new DataInputStream(new ByteArrayInputStream(bytes, 0, bytes.length - padding))) {
try (var in = PaddedBuffer.removePadding(bytes)) {
int type = in.read();
switch (type) {
case Text.TYPE:
Expand Down
105 changes: 105 additions & 0 deletions src/test/java/com/mountainminds/three4j/PaddedBufferTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*******************************************************************************
* Copyright (c) 2021 Mountainminds GmbH & Co. KG
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*******************************************************************************/
package com.mountainminds.three4j;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Random;

import org.junit.jupiter.api.Test;

public class PaddedBufferTest {

private final Random minRandom = new Random() {
private static final long serialVersionUID = 1L;

@Override
public int nextInt(int bound) {
return 0;
}
};

private final Random maxRandom = new Random() {
private static final long serialVersionUID = 1L;

@Override
public int nextInt(int bound) {
return bound - 1;
}
};

@Test
void finish_should_expand_message_to_at_least_32_bytes() throws IOException {
try (var buffer = new PaddedBuffer(minRandom)) {
buffer.write(0x12);
buffer.write(0x15);
var msg = buffer.withPadding();
var expected = bytes(0x12, 0x15).nbytes(30, 30).toByteArray();
assertArrayEquals(expected, msg);
}
}

@Test
void finish_should_add_at_least_1_byte() throws IOException {
try (var buffer = new PaddedBuffer(minRandom)) {
buffer.write(bytes().nbytes(64, 42).toByteArray());
var msg = buffer.withPadding();
var expected = bytes().nbytes(64, 42).nbytes(1, 1).toByteArray();
assertArrayEquals(expected, msg);
}
}

@Test
void finish_should_add_at_most_255_bytes() throws IOException {
try (var buffer = new PaddedBuffer(maxRandom)) {
buffer.write(0x42);
buffer.write(0x43);
var msg = buffer.withPadding();
var expected = bytes(0x42, 0x43).nbytes(255, 255).toByteArray();
assertArrayEquals(expected, msg);
}
}

@Test
void removePadding_should_remove_padding_of_length_1() throws IOException {
var data = PaddedBuffer.removePadding(bytes(1, 2, 3).nbytes(1, 1).toByteArray());
assertArrayEquals(bytes(1, 2, 3).toByteArray(), data.readAllBytes());
}

@Test
void removePadding_should_remove_padding_of_length_255() throws IOException {
var data = PaddedBuffer.removePadding(bytes(1, 2, 3).nbytes(255, 255).toByteArray());
assertArrayEquals(bytes(1, 2, 3).toByteArray(), data.readAllBytes());
}

private static Bytes bytes(int... bytes) {
var out = new Bytes();
for (int b : bytes) {
out.write(0xff & b);
}
return out;
}

private static class Bytes extends ByteArrayOutputStream {
Bytes nbytes(int count, int value) {
for (int i = 0; i < count; i++) {
write(0xff & value);
}
return this;
}
}

}

0 comments on commit b87051d

Please sign in to comment.