Skip to content

Commit c5a23e4

Browse files
committed
Add function WebAuthnCodecs.parseDerSequence
1 parent 5517149 commit c5a23e4

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

webauthn-server-core/src/main/java/com/yubico/webauthn/WebAuthnCodecs.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,23 @@ static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int offset) {
299299
}
300300
}
301301

302+
/** Parse a SEQUENCE and return a copy of the content octets. */
303+
static ParseDerResult<ByteArray> parseDerSequence(@NonNull byte[] der, int offset) {
304+
final int len = der.length - offset;
305+
if (len == 0) {
306+
throw new IllegalArgumentException(
307+
String.format("Empty input at offset %d: %s", offset, new ByteArray(der)));
308+
} else if ((der[offset] & 0xff) == 0x30) {
309+
final ParseDerResult<Integer> contentLen = parseDerLength(der, offset + 1);
310+
final int contentEnd = contentLen.nextOffset + contentLen.result;
311+
return new ParseDerResult<>(
312+
new ByteArray(Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd)), contentEnd);
313+
} else {
314+
throw new IllegalArgumentException(
315+
String.format("Not a SEQUENCE tag (0x30) at offset %d: %s", offset, new ByteArray(der)));
316+
}
317+
}
318+
302319
private static ByteArray encodeDerObjectId(final ByteArray oid) {
303320
return new ByteArray(new byte[] {0x06, (byte) oid.size()}).concat(oid);
304321
}
@@ -310,7 +327,7 @@ private static ByteArray encodeDerBitStringWithZeroUnused(final ByteArray conten
310327
.concat(content);
311328
}
312329

313-
private static ByteArray encodeDerSequence(final ByteArray... items) {
330+
static ByteArray encodeDerSequence(final ByteArray... items) {
314331
final ByteArray content =
315332
Stream.of(items).reduce(ByteArray::concat).orElseGet(() -> new ByteArray(new byte[0]));
316333
return new ByteArray(new byte[] {0x30}).concat(encodeDerLength(content.size())).concat(content);

webauthn-server-core/src/test/scala/com/yubico/webauthn/WebAuthnCodecsSpec.scala

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,50 @@ class WebAuthnCodecsSpec
179179
.parseDerLength(Array(0x84, 0, 1, 33, 7).map(_.toByte), 0)
180180
.result should equal(73991)
181181
}
182+
183+
it("encodeDerSequence and parseDerSequenceEnd are (almost) each other's inverse.") {
184+
forAll { (data: Array[ByteArray], prefix: ByteArray) =>
185+
val encoded = WebAuthnCodecs.encodeDerSequence(data: _*)
186+
val decoded = WebAuthnCodecs.parseDerSequence(encoded.getBytes, 0)
187+
val encodedWithPrefix = prefix.concat(encoded)
188+
val decodedWithPrefix = WebAuthnCodecs.parseDerSequence(
189+
encodedWithPrefix.getBytes,
190+
prefix.size,
191+
)
192+
193+
val expectedContent: ByteArray =
194+
data.fold(new ByteArray(Array.empty))((a, b) => a.concat(b))
195+
decoded.result should equal(expectedContent)
196+
decodedWithPrefix.result should equal(expectedContent)
197+
decoded.nextOffset should equal(encoded.size)
198+
decodedWithPrefix.nextOffset should equal(prefix.size + encoded.size)
199+
}
200+
}
201+
202+
it("parseDerSequence fails if the first byte is not 0x30.") {
203+
forAll { (tag: Byte, data: Array[ByteArray]) =>
204+
whenever(tag != 0x30) {
205+
val encoded = WebAuthnCodecs.encodeDerSequence(data: _*)
206+
an[IllegalArgumentException] shouldBe thrownBy {
207+
WebAuthnCodecs.parseDerSequence(
208+
encoded.getBytes.updated(0, tag),
209+
0,
210+
)
211+
}
212+
}
213+
}
214+
}
215+
216+
it("parseDerSequence fails on empty input.") {
217+
an[IllegalArgumentException] shouldBe thrownBy {
218+
WebAuthnCodecs.parseDerSequence(Array.empty, 0)
219+
}
220+
forAll { data: Array[Byte] =>
221+
an[IllegalArgumentException] shouldBe thrownBy {
222+
WebAuthnCodecs.parseDerSequence(data, data.length)
223+
}
224+
}
225+
}
182226
}
183227
}
184228
}

0 commit comments

Comments
 (0)