Skip to content

Commit 5517149

Browse files
committed
Add function WebAuthnCodecs.parseDerLength and expand encodeDerLength domain
1 parent 45a4ca6 commit 5517149

File tree

2 files changed

+139
-9
lines changed

2 files changed

+139
-9
lines changed

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

Lines changed: 84 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import java.util.HashMap;
4242
import java.util.Map;
4343
import java.util.stream.Stream;
44+
import lombok.AllArgsConstructor;
45+
import lombok.NonNull;
4446

4547
final class WebAuthnCodecs {
4648

@@ -209,17 +211,91 @@ private static PublicKey importCoseEcdsaPublicKey(CBORObject cose)
209211
return kFact.generatePublic(new X509EncodedKeySpec(x509Key.getBytes()));
210212
}
211213

212-
private static ByteArray encodeDerLength(final int length) {
213-
if (length <= 127) {
214-
return new ByteArray(new byte[] {(byte) length});
214+
static ByteArray encodeDerLength(final int length) {
215+
if (length < 0) {
216+
throw new IllegalArgumentException("Length is negative: " + length);
217+
} else if (length <= 0x7f) {
218+
return new ByteArray(new byte[] {(byte) (length & 0xff)});
219+
} else if (length <= 0xff) {
220+
return new ByteArray(new byte[] {(byte) (0x80 | 0x01), (byte) (length & 0xff)});
215221
} else if (length <= 0xffff) {
216-
if (length <= 255) {
217-
return new ByteArray(new byte[] {-127, (byte) length});
222+
return new ByteArray(
223+
new byte[] {(byte) (0x80 | 0x02), (byte) ((length >> 8) & 0xff), (byte) (length & 0xff)});
224+
} else if (length <= 0xffffff) {
225+
return new ByteArray(
226+
new byte[] {
227+
(byte) (0x80 | 0x03),
228+
(byte) ((length >> 16) & 0xff),
229+
(byte) ((length >> 8) & 0xff),
230+
(byte) (length & 0xff)
231+
});
232+
} else {
233+
return new ByteArray(
234+
new byte[] {
235+
(byte) (0x80 | 0x04),
236+
(byte) ((length >> 24) & 0xff),
237+
(byte) ((length >> 16) & 0xff),
238+
(byte) ((length >> 8) & 0xff),
239+
(byte) (length & 0xff)
240+
});
241+
}
242+
}
243+
244+
@AllArgsConstructor
245+
static class ParseDerResult<T> {
246+
final T result;
247+
final int nextOffset;
248+
}
249+
250+
static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int offset) {
251+
final int len = der.length - offset;
252+
if (len == 0) {
253+
throw new IllegalArgumentException("Empty input");
254+
} else if ((der[offset] & 0x80) == 0) {
255+
return new ParseDerResult<>(der[offset] & 0xff, offset + 1);
256+
} else {
257+
final int longLen = der[offset] & 0x7f;
258+
if (len >= longLen) {
259+
switch (longLen) {
260+
case 0:
261+
throw new IllegalArgumentException(
262+
String.format(
263+
"Empty length encoding at offset %d: %s", offset, new ByteArray(der)));
264+
case 1:
265+
return new ParseDerResult<>(der[offset + 1] & 0xff, offset + 2);
266+
case 2:
267+
return new ParseDerResult<>(
268+
((der[offset + 1] & 0xff) << 8) | (der[offset + 2] & 0xff), offset + 3);
269+
case 3:
270+
return new ParseDerResult<>(
271+
((der[offset + 1] & 0xff) << 16)
272+
| ((der[offset + 2] & 0xff) << 8)
273+
| (der[offset + 3] & 0xff),
274+
offset + 4);
275+
case 4:
276+
if ((der[offset + 1] & 0x80) == 0) {
277+
return new ParseDerResult<>(
278+
((der[offset + 1] & 0xff) << 24)
279+
| ((der[offset + 2] & 0xff) << 16)
280+
| ((der[offset + 3] & 0xff) << 8)
281+
| (der[offset + 4] & 0xff),
282+
offset + 5);
283+
} else {
284+
throw new UnsupportedOperationException(
285+
String.format(
286+
"Length out of range of int: 0x%02x%02x%02x%02x",
287+
der[offset + 1], der[offset + 2], der[offset + 3], der[offset + 4]));
288+
}
289+
default:
290+
throw new UnsupportedOperationException(
291+
String.format("Length is too long for int: %d octets", longLen));
292+
}
218293
} else {
219-
return new ByteArray(new byte[] {-126, (byte) (length >> 8), (byte) (length & 0x00ff)});
294+
throw new IllegalArgumentException(
295+
String.format(
296+
"Length encoding needs %d octets but only %s remain at index %d: %s",
297+
longLen, len - (offset + 1), offset + 1, new ByteArray(der)));
220298
}
221-
} else {
222-
throw new UnsupportedOperationException("Too long: " + length);
223299
}
224300
}
225301

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

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
package com.yubico.webauthn
2626

2727
import com.yubico.webauthn.data.ByteArray
28+
import com.yubico.webauthn.data.Generators.arbitraryByteArray
2829
import com.yubico.webauthn.test.Util
2930
import org.junit.runner.RunWith
3031
import org.scalacheck.Arbitrary
@@ -125,6 +126,59 @@ class WebAuthnCodecsSpec
125126
}
126127

127128
}
128-
}
129129

130+
describe("DER parsing and encoding:") {
131+
it("encodeDerLength and parseDerLength are each other's inverse.") {
132+
forAll(Gen.chooseNum(0, Int.MaxValue), arbitraryByteArray.arbitrary) {
133+
(len: Int, prefix: ByteArray) =>
134+
val encoded = WebAuthnCodecs.encodeDerLength(len)
135+
val decoded = WebAuthnCodecs.parseDerLength(encoded.getBytes, 0)
136+
val decodedWithPrefix = WebAuthnCodecs.parseDerLength(
137+
prefix.concat(encoded).getBytes,
138+
prefix.size,
139+
)
140+
141+
decoded.result should equal(len)
142+
decoded.nextOffset should equal(encoded.size)
143+
decodedWithPrefix.result should equal(len)
144+
decodedWithPrefix.nextOffset should equal(
145+
prefix.size + encoded.size
146+
)
147+
148+
val recoded = WebAuthnCodecs.encodeDerLength(decoded.result)
149+
recoded should equal(encoded)
150+
}
151+
}
152+
153+
it("parseDerLength tolerates unnecessarily long encodings.") {
154+
WebAuthnCodecs
155+
.parseDerLength(Array(0x81, 0).map(_.toByte), 0)
156+
.result should equal(0)
157+
WebAuthnCodecs
158+
.parseDerLength(Array(0x82, 0, 0).map(_.toByte), 0)
159+
.result should equal(0)
160+
WebAuthnCodecs
161+
.parseDerLength(Array(0x83, 0, 0, 0).map(_.toByte), 0)
162+
.result should equal(0)
163+
WebAuthnCodecs
164+
.parseDerLength(Array(0x84, 0, 0, 0, 0).map(_.toByte), 0)
165+
.result should equal(0)
166+
WebAuthnCodecs
167+
.parseDerLength(Array(0x81, 7).map(_.toByte), 0)
168+
.result should equal(7)
169+
WebAuthnCodecs
170+
.parseDerLength(Array(0x82, 0, 7).map(_.toByte), 0)
171+
.result should equal(7)
172+
WebAuthnCodecs
173+
.parseDerLength(Array(0x83, 0, 0, 7).map(_.toByte), 0)
174+
.result should equal(7)
175+
WebAuthnCodecs
176+
.parseDerLength(Array(0x84, 0, 0, 4, 2).map(_.toByte), 0)
177+
.result should equal(1026)
178+
WebAuthnCodecs
179+
.parseDerLength(Array(0x84, 0, 1, 33, 7).map(_.toByte), 0)
180+
.result should equal(73991)
181+
}
182+
}
183+
}
130184
}

0 commit comments

Comments
 (0)