Skip to content

Commit aa2605b

Browse files
committed
Parse X.509 CRLDistributionPoints extension
Co-authored by: Dennis Fokin <[email protected]>
1 parent 7c7de8f commit aa2605b

File tree

3 files changed

+336
-72
lines changed

3 files changed

+336
-72
lines changed

yubico-util/src/main/java/com/yubico/internal/util/BinaryUtil.java

Lines changed: 185 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@
2828
import java.io.InputStream;
2929
import java.nio.ByteBuffer;
3030
import java.nio.ByteOrder;
31+
import java.util.ArrayList;
3132
import java.util.Arrays;
32-
import lombok.AllArgsConstructor;
33+
import java.util.List;
34+
import java.util.Optional;
3335
import lombok.NonNull;
36+
import lombok.ToString;
37+
import lombok.Value;
3438

3539
public class BinaryUtil {
3640

@@ -229,10 +233,48 @@ public static byte[] encodeDerLength(final int length) {
229233
}
230234
}
231235

232-
@AllArgsConstructor
236+
@ToString
237+
public enum DerTagClass {
238+
UNIVERSAL,
239+
APPLICATION,
240+
CONTEXT_SPECIFIC,
241+
PRIVATE;
242+
243+
public static DerTagClass parse(byte tag) {
244+
switch ((tag >> 6) & 0x03) {
245+
case 0x0:
246+
return DerTagClass.UNIVERSAL;
247+
case 0x1:
248+
return DerTagClass.APPLICATION;
249+
case 0x2:
250+
return DerTagClass.CONTEXT_SPECIFIC;
251+
case 0x3:
252+
return DerTagClass.PRIVATE;
253+
default:
254+
throw new RuntimeException("This should be impossible");
255+
}
256+
}
257+
}
258+
259+
@Value
260+
private static class ParseDerAnyResult {
261+
DerTagClass tagClass;
262+
boolean constructed;
263+
byte tagValue;
264+
byte[] content;
265+
int nextOffset;
266+
}
267+
268+
@Value
233269
public static class ParseDerResult<T> {
234-
public final T result;
235-
public final int nextOffset;
270+
/** The parsed value, excluding the tag-and-length header. */
271+
public T result;
272+
273+
/**
274+
* The offset of the first octet past the end of the parsed value. In other words, the offset to
275+
* continue reading from.
276+
*/
277+
public int nextOffset;
236278
}
237279

238280
public static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int offset) {
@@ -287,46 +329,163 @@ public static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int of
287329
}
288330
}
289331

290-
private static ParseDerResult<byte[]> parseDerTagged(
291-
@NonNull byte[] der, int offset, byte expectTag) {
332+
private static ParseDerAnyResult parseDerAny(@NonNull byte[] der, int offset) {
292333
final int len = der.length - offset;
293334
if (len == 0) {
294335
throw new IllegalArgumentException(
295336
String.format("Empty input at offset %d: 0x%s", offset, BinaryUtil.toHex(der)));
296337
} else {
297338
final byte tag = der[offset];
298-
if (tag == expectTag) {
299-
final ParseDerResult<Integer> contentLen = parseDerLength(der, offset + 1);
300-
final int contentEnd = contentLen.nextOffset + contentLen.result;
301-
return new ParseDerResult<>(
302-
Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd), contentEnd);
339+
final ParseDerResult<Integer> contentLen = parseDerLength(der, offset + 1);
340+
final int contentEnd = contentLen.nextOffset + contentLen.result;
341+
return new ParseDerAnyResult(
342+
DerTagClass.parse(tag),
343+
(tag & 0x20) != 0,
344+
(byte) (tag & 0x1f),
345+
Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd),
346+
contentEnd);
347+
}
348+
}
349+
350+
/**
351+
* Parse a DER header with the given tag value, constructed bit and tag class, and return a copy
352+
* of the value octets. If any of the three criteria do not match, return empty instead.
353+
*
354+
* @param der DER source to read from.
355+
* @param offset The offset in <code>der</code> from which to start reading.
356+
* @param expectTag The expected tag value, excluding the constructed bit and tag class. This is
357+
* the 5 least significant bits of the tag octet.
358+
* @param constructed The expected "constructed" bit. This is bit 6 (the third-most significant
359+
* bit) of the tag octet.
360+
* @param expectTagClass The expected tag class. This is the 2 most significant bits of the tag
361+
* octet.
362+
* @return A copy of the value octets, if the parsed tag matches <code>expectTag</code>, <code>
363+
* constructed</code> and <code>expectTagClass</code>, otherwise empty. {@link
364+
* ParseDerResult#nextOffset} is always returned.
365+
*/
366+
public static ParseDerResult<Optional<byte[]>> parseDerTaggedOrSkip(
367+
@NonNull byte[] der,
368+
int offset,
369+
byte expectTag,
370+
boolean constructed,
371+
DerTagClass expectTagClass) {
372+
final ParseDerAnyResult result = parseDerAny(der, offset);
373+
if (result.tagValue == expectTag
374+
&& result.constructed == constructed
375+
&& result.tagClass == expectTagClass) {
376+
return new ParseDerResult<>(Optional.of(result.content), result.nextOffset);
377+
} else {
378+
return new ParseDerResult<>(Optional.empty(), result.nextOffset);
379+
}
380+
}
381+
382+
/**
383+
* Parse a DER header with the given tag value, constructed bit and tag class, and return a copy
384+
* of the value octets. If any of the three criteria do not match, throw an {@link
385+
* IllegalArgumentException}.
386+
*
387+
* @param der DER source to read from.
388+
* @param offset The offset in <code>der</code> from which to start reading.
389+
* @param expectTag The expected tag value, excluding the constructed bit and tag class. This is
390+
* the 5 least significant bits of the tag octet.
391+
* @param constructed The expected "constructed" bit. This is bit 6 (the third-most significant
392+
* bit) of the tag octet.
393+
* @param expectTagClass The expected tag class. This is the 2 most significant bits of the tag
394+
* octet.
395+
* @return A copy of the value octets, if the parsed tag matches <code>expectTag</code>, <code>
396+
* constructed</code> and <code>expectTagClass</code>, otherwise empty. {@link
397+
* ParseDerResult#nextOffset} is always returned.
398+
*/
399+
private static ParseDerResult<byte[]> parseDerTagged(
400+
@NonNull byte[] der,
401+
int offset,
402+
byte expectTag,
403+
boolean constructed,
404+
DerTagClass expectTagClass) {
405+
final ParseDerAnyResult result = parseDerAny(der, offset);
406+
if (result.tagValue == expectTag) {
407+
if (result.constructed == constructed) {
408+
if (result.tagClass == expectTagClass) {
409+
return new ParseDerResult<>(result.content, result.nextOffset);
410+
} else {
411+
throw new IllegalArgumentException(
412+
String.format(
413+
"Incorrect tag class: expected %s, found %s at offset %d: 0x%s",
414+
expectTagClass, result.tagClass, offset, BinaryUtil.toHex(der)));
415+
}
303416
} else {
304417
throw new IllegalArgumentException(
305418
String.format(
306-
"Incorrect tag: 0x%02x (expected 0x%02x) at offset %d: 0x%s",
307-
tag, expectTag, offset, BinaryUtil.toHex(der)));
419+
"Incorrect constructed bit: expected %s, found %s at offset %d: 0x%s",
420+
constructed, result.constructed, offset, BinaryUtil.toHex(der)));
308421
}
422+
} else {
423+
throw new IllegalArgumentException(
424+
String.format(
425+
"Incorrect tag: expected 0x%02x, found 0x%02x at offset %d: 0x%s",
426+
expectTag, result.tagValue, offset, BinaryUtil.toHex(der)));
309427
}
310428
}
311429

312-
/** Parse a SEQUENCE and return a copy of the content octets. */
313-
public static ParseDerResult<byte[]> parseDerSequence(@NonNull byte[] der, int offset) {
314-
return parseDerTagged(der, offset, (byte) 0x30);
430+
/** Function to parse an element of a DER SEQUENCE. */
431+
@FunctionalInterface
432+
public interface ParseDerSequenceElementFunction<T> {
433+
/**
434+
* Parse an element of a DER SEQUENCE.
435+
*
436+
* @param sequenceDer The content octets of the parent SEQUENCE. This includes ALL elements in
437+
* the sequence.
438+
* @param elementOffset The offset into <code>sequenceDer</code> from where to parse the
439+
* element.
440+
* @return A {@link ParseDerResult} whose {@link ParseDerResult#result} is the parsed element
441+
* and {@link ParseDerResult#nextOffset} is the offset of the first octet past the end of
442+
* the parsed element.
443+
*/
444+
ParseDerResult<T> parse(@NonNull byte[] sequenceDer, int elementOffset);
315445
}
316446

317447
/**
318-
* Parse an explicitly tagged value of class "context-specific" (bits 8-7 are 0b10), in
319-
* "constructed" encoding (bit 6 is 1), with a prescribed tag value, and return a copy of the
320-
* content octets.
448+
* Parse the elements of a SEQUENCE using the given element parsing function.
449+
*
450+
* @param der DER source array to read from
451+
* @param offset Offset from which to begin reading the first element
452+
* @param endOffset Offset of the first octet past the end of the sequence
453+
* @param parseElement Function to use to parse each element in the sequence.
321454
*/
322-
private static ParseDerResult<byte[]> parseDerExplicitlyTaggedContextSpecificConstructed(
323-
@NonNull byte[] der, int offset, byte tagNumber) {
324-
if (tagNumber <= 30 && tagNumber >= 0) {
325-
return parseDerTagged(der, offset, (byte) ((tagNumber & 0x1f) | 0xa0));
326-
} else {
327-
throw new UnsupportedOperationException(
328-
String.format("Tag number out of range: %d (expected 0 to 30, inclusive)", tagNumber));
455+
public static <T> ParseDerResult<List<T>> parseDerSequenceContents(
456+
@NonNull byte[] der,
457+
int offset,
458+
int endOffset,
459+
@NonNull ParseDerSequenceElementFunction<T> parseElement) {
460+
List<T> result = new ArrayList<>();
461+
int seqOffset = offset;
462+
while (seqOffset < endOffset) {
463+
ParseDerResult<T> elementResult = parseElement.parse(der, seqOffset);
464+
result.add(elementResult.result);
465+
seqOffset = elementResult.nextOffset;
329466
}
467+
return new ParseDerResult<>(result, endOffset);
468+
}
469+
470+
/**
471+
* Parse a SEQUENCE using the given element parsing function.
472+
*
473+
* @param der DER source array to read from
474+
* @param offset Offset from which to begin reading the SEQUENCE
475+
* @param parseElement Function to use to parse each element in the sequence.
476+
*/
477+
public static <T> ParseDerResult<List<T>> parseDerSequence(
478+
@NonNull byte[] der, int offset, @NonNull ParseDerSequenceElementFunction<T> parseElement) {
479+
final ParseDerResult<byte[]> seq =
480+
parseDerTagged(der, offset, (byte) 0x10, true, DerTagClass.UNIVERSAL);
481+
final ParseDerResult<List<T>> res =
482+
parseDerSequenceContents(seq.result, 0, seq.result.length, parseElement);
483+
return new ParseDerResult<>(res.result, seq.nextOffset);
484+
}
485+
486+
/** Parse an Octet String. */
487+
public static ParseDerResult<byte[]> parseDerOctetString(@NonNull byte[] der, int offset) {
488+
return parseDerTagged(der, offset, (byte) 0x04, false, DerTagClass.UNIVERSAL);
330489
}
331490

332491
public static byte[] encodeDerObjectId(@NonNull byte[] oid) {

0 commit comments

Comments
 (0)