Skip to content

Commit

Permalink
Return offsets instead of byte array copies during DER parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
emlun committed Dec 12, 2024
1 parent ba765e9 commit 0206707
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 43 deletions.
42 changes: 24 additions & 18 deletions yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,8 +261,8 @@ private static class ParseDerAnyResult {
DerTagClass tagClass;
boolean constructed;
byte tagValue;
byte[] content;
int nextOffset;
int valueStart;
int valueEnd;
}

@Value
Expand Down Expand Up @@ -342,14 +342,15 @@ private static ParseDerAnyResult parseDerAny(@NonNull byte[] der, int offset) {
DerTagClass.parse(tag),
(tag & 0x20) != 0,
(byte) (tag & 0x1f),
Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd),
contentLen.nextOffset,
contentEnd);
}
}

/**
* Parse a DER header with the given tag value, constructed bit and tag class, and return a copy
* of the value octets. If any of the three criteria do not match, return empty instead.
* Parse a DER header with the given tag value, constructed bit and tag class, and return the
* start and end offsets of the value octets. If any of the three criteria do not match, return
* empty instead.
*
* @param der DER source to read from.
* @param offset The offset in <code>der</code> from which to start reading.
Expand All @@ -359,11 +360,12 @@ private static ParseDerAnyResult parseDerAny(@NonNull byte[] der, int offset) {
* bit) of the tag octet.
* @param expectTagClass The expected tag class. This is the 2 most significant bits of the tag
* octet.
* @return A copy of the value octets, if the parsed tag matches <code>expectTag</code>, <code>
* @return The start and end offsets of the value octets, if the parsed tag matches <code>
* expectTag</code>, <code>
* constructed</code> and <code>expectTagClass</code>, otherwise empty. {@link
* ParseDerResult#nextOffset} is always returned.
*/
public static ParseDerResult<Optional<byte[]>> parseDerTaggedOrSkip(
public static ParseDerResult<Optional<Integer>> parseDerTaggedOrSkip(
@NonNull byte[] der,
int offset,
byte expectTag,
Expand All @@ -373,16 +375,16 @@ public static ParseDerResult<Optional<byte[]>> parseDerTaggedOrSkip(
if (result.tagValue == expectTag
&& result.constructed == constructed
&& result.tagClass == expectTagClass) {
return new ParseDerResult<>(Optional.of(result.content), result.nextOffset);
return new ParseDerResult<>(Optional.of(result.valueStart), result.valueEnd);
} else {
return new ParseDerResult<>(Optional.empty(), result.nextOffset);
return new ParseDerResult<>(Optional.empty(), result.valueEnd);
}
}

/**
* Parse a DER header with the given tag value, constructed bit and tag class, and return a copy
* of the value octets. If any of the three criteria do not match, throw an {@link
* IllegalArgumentException}.
* Parse a DER header with the given tag value, constructed bit and tag class, and return the
* start and end offsets of the value octets. If any of the three criteria do not match, throw an
* {@link IllegalArgumentException}.
*
* @param der DER source to read from.
* @param offset The offset in <code>der</code> from which to start reading.
Expand All @@ -392,11 +394,12 @@ public static ParseDerResult<Optional<byte[]>> parseDerTaggedOrSkip(
* bit) of the tag octet.
* @param expectTagClass The expected tag class. This is the 2 most significant bits of the tag
* octet.
* @return A copy of the value octets, if the parsed tag matches <code>expectTag</code>, <code>
* @return The start and end offsets of the value octets, if the parsed tag matches <code>
* expectTag</code>, <code>
* constructed</code> and <code>expectTagClass</code>, otherwise empty. {@link
* ParseDerResult#nextOffset} is always returned.
*/
private static ParseDerResult<byte[]> parseDerTagged(
private static ParseDerResult<Integer> parseDerTagged(
@NonNull byte[] der,
int offset,
byte expectTag,
Expand All @@ -406,7 +409,7 @@ private static ParseDerResult<byte[]> parseDerTagged(
if (result.tagValue == expectTag) {
if (result.constructed == constructed) {
if (result.tagClass == expectTagClass) {
return new ParseDerResult<>(result.content, result.nextOffset);
return new ParseDerResult<>(result.valueStart, result.valueEnd);
} else {
throw new IllegalArgumentException(
String.format(
Expand Down Expand Up @@ -476,16 +479,19 @@ public static <T> ParseDerResult<List<T>> parseDerSequenceContents(
*/
public static <T> ParseDerResult<List<T>> parseDerSequence(
@NonNull byte[] der, int offset, @NonNull ParseDerSequenceElementFunction<T> parseElement) {
final ParseDerResult<byte[]> seq =
final ParseDerResult<Integer> seq =
parseDerTagged(der, offset, (byte) 0x10, true, DerTagClass.UNIVERSAL);
final ParseDerResult<List<T>> res =
parseDerSequenceContents(seq.result, 0, seq.result.length, parseElement);
parseDerSequenceContents(der, seq.result, seq.nextOffset, parseElement);
return new ParseDerResult<>(res.result, seq.nextOffset);
}

/** Parse an Octet String. */
public static ParseDerResult<byte[]> parseDerOctetString(@NonNull byte[] der, int offset) {
return parseDerTagged(der, offset, (byte) 0x04, false, DerTagClass.UNIVERSAL);
ParseDerResult<Integer> res =
parseDerTagged(der, offset, (byte) 0x04, false, DerTagClass.UNIVERSAL);
return new ParseDerResult<>(
Arrays.copyOfRange(der, res.result, res.nextOffset), res.nextOffset);
}

public static byte[] encodeDerObjectId(@NonNull byte[] oid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,30 +208,31 @@ public static ParseCrlDistributionPointsExtensionResult parseCrlDistributionPoin
(innerSequenceDer, distributionPointChoiceOffset) -> {
// DistributionPoint ::= SEQUENCE {
// distributionPoint [0] DistributionPointName OPTIONAL,
final BinaryUtil.ParseDerResult<Optional<byte[]>> dpElement =
final BinaryUtil.ParseDerResult<Optional<Integer>> dpElementOffsets =
BinaryUtil.parseDerTaggedOrSkip(
innerSequenceDer,
distributionPointChoiceOffset,
(byte) 0,
true,
BinaryUtil.DerTagClass.CONTEXT_SPECIFIC);
if (dpElement.result.isPresent()) {
if (dpElementOffsets.result.isPresent()) {

// DistributionPointName ::= CHOICE {
// fullName [0] GeneralNames,
final BinaryUtil.ParseDerResult<Optional<byte[]>> dpNameElement =
BinaryUtil.parseDerTaggedOrSkip(
dpElement.result.get(),
0,
(byte) 0,
true,
BinaryUtil.DerTagClass.CONTEXT_SPECIFIC);
final BinaryUtil.ParseDerResult<Optional<Integer>>
dpNameElementOffsets =
BinaryUtil.parseDerTaggedOrSkip(
innerSequenceDer,
dpElementOffsets.result.get(),
(byte) 0,
true,
BinaryUtil.DerTagClass.CONTEXT_SPECIFIC);

if (dpNameElement.result.isPresent()) {
if (dpNameElementOffsets.result.isPresent()) {
return BinaryUtil.parseDerSequenceContents(
dpNameElement.result.get(),
0,
dpNameElement.result.get().length,
innerSequenceDer,
dpNameElementOffsets.result.get(),
dpNameElementOffsets.nextOffset,
(generalNamesDer, generalNamesElementOffset) -> {
// fullName [0] GeneralNames,
// GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
Expand All @@ -243,21 +244,26 @@ public static ParseCrlDistributionPointsExtensionResult parseCrlDistributionPoin
// https://datatracker.ietf.org/doc/html/rfc5280#appendix-A.2
// so the SEQUENCE tag in GeneralNames is implicit.
// The IA5String tag is also implicit from the CHOICE tag.
final BinaryUtil.ParseDerResult<Optional<byte[]>> generalName =
BinaryUtil.parseDerTaggedOrSkip(
generalNamesDer,
generalNamesElementOffset,
(byte) 6,
false,
BinaryUtil.DerTagClass.CONTEXT_SPECIFIC);
if (generalName.result.isPresent()) {
final BinaryUtil.ParseDerResult<Optional<Integer>>
generalNameOffsets =
BinaryUtil.parseDerTaggedOrSkip(
generalNamesDer,
generalNamesElementOffset,
(byte) 6,
false,
BinaryUtil.DerTagClass.CONTEXT_SPECIFIC);
if (generalNameOffsets.result.isPresent()) {
String uriString =
new String(
generalName.result.get(), StandardCharsets.US_ASCII);
Arrays.copyOfRange(
generalNamesDer,
generalNameOffsets.result.get(),
generalNameOffsets.nextOffset),
StandardCharsets.US_ASCII);
try {
return new BinaryUtil.ParseDerResult<>(
Optional.of(new URL(uriString)),
generalName.nextOffset);
generalNameOffsets.nextOffset);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(
String.format(
Expand All @@ -267,15 +273,15 @@ public static ParseCrlDistributionPointsExtensionResult parseCrlDistributionPoin
}
} else {
return new BinaryUtil.ParseDerResult<>(
Optional.empty(), generalName.nextOffset);
Optional.empty(), generalNameOffsets.nextOffset);
}
});
}
}

// Ignore all other forms of distribution points
return new BinaryUtil.ParseDerResult<>(
Collections.emptyList(), dpElement.nextOffset);
Collections.emptyList(), dpElementOffsets.nextOffset);
}));

return distributionPoints.result.stream()
Expand Down

0 comments on commit 0206707

Please sign in to comment.