Skip to content

Commit

Permalink
chore: add more support for signatures (#249)
Browse files Browse the repository at this point in the history
Signed-off-by: Lazar Petrovic <lpetrovic05@gmail.com>
  • Loading branch information
lpetrovic05 committed May 7, 2024
1 parent 94dc554 commit 80df99b
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -305,14 +305,54 @@ public void writeTo(@NonNull final MessageDigest digest, final int offset, final
digest.update(buffer, Math.toIntExact(start + offset), length);
}

/**
* Same as {@link #updateSignature(Signature, int, int)} with offset 0 and length equal to the length of this
* {@link Bytes} object.
*/
public void updateSignature(@NonNull final Signature signature) throws SignatureException {
signature.update(buffer, start, length);
}

/**
* A helper method for efficient copy of our data into a Signature without creating a defensive copy of the data.
* The implementation relies on a well-behaved Signature that doesn't modify the buffer data.
* The implementation relies on a well-behaved Signature that doesn't modify the buffer data. Calls the
* {@link Signature#update(byte[], int, int)} method with all the data in this {@link Bytes} object. This method
* should be used when the data in the buffer should be validated or signed.
*
* @param signature the Signature to copy into
* @param signature The Signature to update
* @param offset The offset from the start of this {@link Bytes} object to get the bytes from
* @param length The number of bytes to extract
* @throws SignatureException If the Signature instance throws this exception
*/
public void writeTo(@NonNull final Signature signature) throws SignatureException {
signature.update(buffer, start, length);
public void updateSignature(@NonNull final Signature signature, final int offset, final int length)
throws SignatureException {
validateOffsetLength(offset, length);
signature.update(buffer, calculateOffset(offset), length);
}

/**
* Same as {@link #verifySignature(Signature, int, int)} with offset 0 and length equal to the length of this
* {@link Bytes} object.
*/
public void verifySignature(@NonNull final Signature signature) throws SignatureException {
signature.verify(buffer, start, length);
}

/**
* A helper method for efficient copy of our data into a Signature without creating a defensive copy of the data.
* The implementation relies on a well-behaved Signature that doesn't modify the buffer data. Calls the
* {@link Signature#verify(byte[], int, int)} method with all the data in this {@link Bytes} object. This method
* should be used when the data in the buffer is a signature that should be verified.
*
* @param signature the Signature to use to verify
* @param offset The offset from the start of this {@link Bytes} object to get the bytes from
* @param length The number of bytes to extract
* @throws SignatureException If the Signature instance throws this exception
*/
public void verifySignature(@NonNull final Signature signature, final int offset, final int length)
throws SignatureException {
validateOffsetLength(offset, length);
signature.verify(buffer, calculateOffset(offset), length);
}

/**
Expand Down Expand Up @@ -593,6 +633,34 @@ private void validateOffset(final long offset) {
}
}

/**
* Validates whether the offset and length supplied to a method are within the bounds of the Bytes object.
*
* @param suppliedOffset the offset supplied
* @param suppliedLength the length supplied
*/
private void validateOffsetLength(final long suppliedOffset, final long suppliedLength) {
if (suppliedOffset < 0 || suppliedLength < 0) {
throw new IllegalArgumentException("Negative length or offset not allowed");
}
if (suppliedOffset + suppliedLength > length) {
throw new IndexOutOfBoundsException(
"The offset(%d) and length(%d) provided are out of bounds for this Bytes object, which has a length of %d"
.formatted(suppliedOffset, suppliedLength, length)
);
}
}

/**
* Calculates the offset from the start for the given supplied offset.
*
* @param suppliedOffset the offset supplied
* @return the calculated offset
*/
private int calculateOffset(final long suppliedOffset) {
return Math.toIntExact(start + suppliedOffset);
}

/** Sorts {@link Bytes} according to their byte values, lower valued bytes first.
* Bytes are compared using the passed in Byte Comparator
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,45 +1,47 @@
package com.hedera.pbj.runtime.io.buffer;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.hedera.pbj.runtime.io.DataEncodingException;
import com.hedera.pbj.runtime.io.ReadableSequentialData;
import com.hedera.pbj.runtime.io.WritableSequentialData;
import com.hedera.pbj.runtime.io.stream.WritableStreamingData;
import edu.umd.cs.findbugs.annotations.NonNull;

import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Stream;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;

final class BytesTest {

// ================================================================================================================
Expand Down Expand Up @@ -428,36 +430,125 @@ void writeToMessageDigestNo0OffsetPartial() throws NoSuchAlgorithmException {
assertArrayEquals(exp, res);
}

/**
* Mock a Signature object for testing.
*
* @return a mock Signature object
*/
private static Signature mockSignature() throws InvalidKeyException {
final Signature signature = Mockito.mock(Signature.class);
// Signature.update() throws unless we call initVerify() first
signature.initVerify(Mockito.mock(PublicKey.class));
Mockito.verify(signature).initVerify(Mockito.any(PublicKey.class));
return signature;
}

@Test
@DisplayName("Write to Signature")
void writeToSignature() throws SignatureException, InvalidKeyException {
@DisplayName("Update Signature")
void updateSignature() throws SignatureException, InvalidKeyException {
byte[] byteArray = {0, 1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray);

final Signature signature = Mockito.mock(Signature.class);
// Signature.writeTo() throws unless we call initVerify() first
signature.initVerify(Mockito.mock(PublicKey.class));
bytes.writeTo(signature);
Mockito.verify(signature).initVerify(Mockito.any(PublicKey.class));
final Signature signature = mockSignature();
bytes.updateSignature(signature);
Mockito.verify(signature).update(byteArray, 0, 6);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Write to Signature no 0 Offset")
void writeToSignatureNo0Offset() throws InvalidKeyException, SignatureException {
@DisplayName("Update Signature no 0 Offset")
void updateSignatureNo0Offset() throws InvalidKeyException, SignatureException {
final byte[] byteArray = {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray, 10, 6);

final Signature signature = Mockito.mock(Signature.class);
// Signature.writeTo() throws unless we call initVerify() first
signature.initVerify(Mockito.mock(PublicKey.class));
bytes.writeTo(signature);
Mockito.verify(signature).initVerify(Mockito.any(PublicKey.class));
final Signature signature = mockSignature();
bytes.updateSignature(signature);
Mockito.verify(signature).update(byteArray, 10, 6);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Update Signature no 0 Offset, partial")
void updateSignatureNo0OffsetPartial() throws InvalidKeyException, SignatureException {
final byte[] byteArray = {9, 9, 9, 9, 9, 0, 1, 2, 3, 4, 5, 9, 9, 9, 9, 9};
final Bytes bytes = Bytes.wrap(byteArray, 5, 6);

final Signature signature = mockSignature();
bytes.updateSignature(signature, 1, 4);
Mockito.verify(signature).update(byteArray, 6, 4);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Check updateSignature() index bounds")
void updateSignatureBoundsChecks() throws InvalidKeyException {
byte[] byteArray = {1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray);

final Signature signature = mockSignature();
assertThrows(IndexOutOfBoundsException.class, () -> bytes.updateSignature(signature, 3, 10));
assertThrows(IndexOutOfBoundsException.class, () -> bytes.updateSignature(signature, 0, 6));
assertThrows(IndexOutOfBoundsException.class, () -> bytes.updateSignature(signature, 1, 5));
assertThrows(IllegalArgumentException.class, () -> bytes.updateSignature(signature, 0, -5));
assertThrows(IllegalArgumentException.class, () -> bytes.updateSignature(signature, -1, 2));
Mockito.verifyNoMoreInteractions(signature);
assertDoesNotThrow(() -> bytes.updateSignature(signature, 0, 5));
assertDoesNotThrow(() -> bytes.updateSignature(signature, 5, 0));
}

@Test
@DisplayName("Verify Signature")
void verifySignature() throws SignatureException, InvalidKeyException {
byte[] byteArray = {0, 1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray);

final Signature signature = mockSignature();
bytes.verifySignature(signature);
Mockito.verify(signature).verify(byteArray, 0, 6);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Verify Signature no 0 Offset")
void verifySignatureNo0Offset() throws InvalidKeyException, SignatureException {
final byte[] byteArray = {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray, 10, 6);

final Signature signature = mockSignature();
bytes.verifySignature(signature);
Mockito.verify(signature).verify(byteArray, 10, 6);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Verify Signature no 0 Offset, partial")
void verifySignatureNo0OffsetPartial() throws InvalidKeyException, SignatureException {
final byte[] byteArray = {9, 9, 9, 9, 9, 0, 1, 2, 3, 4, 5, 9, 9, 9, 9, 9};
final Bytes bytes = Bytes.wrap(byteArray, 5, 6);

final Signature signature = mockSignature();
bytes.verifySignature(signature, 1, 4);
Mockito.verify(signature).verify(byteArray, 6, 4);
Mockito.verifyNoMoreInteractions(signature);
}

@Test
@DisplayName("Check verifySignature() index bounds")
void verifySignatureBoundsChecks() throws InvalidKeyException {
byte[] byteArray = {1, 2, 3, 4, 5};
final Bytes bytes = Bytes.wrap(byteArray);

final Signature signature = mockSignature();
assertThrows(IndexOutOfBoundsException.class, () -> bytes.verifySignature(signature, 3, 10));
assertThrows(IndexOutOfBoundsException.class, () -> bytes.verifySignature(signature, 0, 6));
assertThrows(IndexOutOfBoundsException.class, () -> bytes.verifySignature(signature, 1, 5));
assertThrows(IllegalArgumentException.class, () -> bytes.verifySignature(signature, 0, -5));
assertThrows(IllegalArgumentException.class, () -> bytes.verifySignature(signature, -1, 2));
Mockito.verifyNoMoreInteractions(signature);
assertDoesNotThrow(() -> bytes.verifySignature(signature, 0, 5));
assertDoesNotThrow(() -> bytes.verifySignature(signature, 5, 0));
}

// asUtf8String throws with null (no offset here? That's wierd. Should have offset, or we should have non-offset
// versions of everything else Or at least "getBytes").

Expand Down

0 comments on commit 80df99b

Please sign in to comment.