Skip to content

Commit 1b547a4

Browse files
committed
Use byte[] instead of ByteArray in DER encoding and parsing functions
1 parent 7514663 commit 1b547a4

File tree

2 files changed

+94
-90
lines changed

2 files changed

+94
-90
lines changed

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

Lines changed: 61 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import com.google.common.primitives.Bytes;
2828
import com.upokecenter.cbor.CBORObject;
29+
import com.yubico.internal.util.BinaryUtil;
2930
import com.yubico.webauthn.data.ByteArray;
3031
import com.yubico.webauthn.data.COSEAlgorithmIdentifier;
3132
import java.io.IOException;
@@ -40,7 +41,6 @@
4041
import java.util.Arrays;
4142
import java.util.HashMap;
4243
import java.util.Map;
43-
import java.util.stream.Stream;
4444
import lombok.AllArgsConstructor;
4545
import lombok.NonNull;
4646

@@ -175,69 +175,70 @@ private static PublicKey importCoseRsaPublicKey(CBORObject cose)
175175
private static PublicKey importCoseEcdsaPublicKey(CBORObject cose)
176176
throws NoSuchAlgorithmException, InvalidKeySpecException {
177177
final int crv = cose.get(CBORObject.FromObject(-1)).AsInt32Value();
178-
final ByteArray x = new ByteArray(cose.get(CBORObject.FromObject(-2)).GetByteString());
179-
final ByteArray y = new ByteArray(cose.get(CBORObject.FromObject(-3)).GetByteString());
178+
final byte[] x = cose.get(CBORObject.FromObject(-2)).GetByteString();
179+
final byte[] y = cose.get(CBORObject.FromObject(-3)).GetByteString();
180180

181-
final ByteArray curveOid;
181+
final byte[] curveOid;
182182
switch (crv) {
183183
case 1:
184-
curveOid = P256_CURVE_OID;
184+
curveOid = P256_CURVE_OID.getBytes();
185185
break;
186186

187187
case 2:
188-
curveOid = P384_CURVE_OID;
188+
curveOid = P384_CURVE_OID.getBytes();
189189
break;
190190

191191
case 3:
192-
curveOid = P512_CURVE_OID;
192+
curveOid = P512_CURVE_OID.getBytes();
193193
break;
194194

195195
default:
196196
throw new IllegalArgumentException("Unknown COSE EC2 curve: " + crv);
197197
}
198198

199-
final ByteArray algId =
200-
encodeDerSequence(encodeDerObjectId(EC_PUBLIC_KEY_OID), encodeDerObjectId(curveOid));
199+
final byte[] algId =
200+
encodeDerSequence(
201+
encodeDerObjectId(EC_PUBLIC_KEY_OID.getBytes()), encodeDerObjectId(curveOid));
201202

202-
final ByteArray rawKey =
203+
final byte[] rawKey =
203204
encodeDerBitStringWithZeroUnused(
204-
new ByteArray(new byte[] {0x04}) // Raw EC public key with x and y
205-
.concat(x)
206-
.concat(y));
205+
BinaryUtil.concat(
206+
new byte[] {0x04}, // Raw EC public key with x and y
207+
x,
208+
y));
207209

208-
final ByteArray x509Key = encodeDerSequence(algId, rawKey);
210+
final byte[] x509Key = encodeDerSequence(algId, rawKey);
209211

210212
KeyFactory kFact = KeyFactory.getInstance("EC");
211-
return kFact.generatePublic(new X509EncodedKeySpec(x509Key.getBytes()));
213+
return kFact.generatePublic(new X509EncodedKeySpec(x509Key));
212214
}
213215

214-
static ByteArray encodeDerLength(final int length) {
216+
static byte[] encodeDerLength(final int length) {
215217
if (length < 0) {
216218
throw new IllegalArgumentException("Length is negative: " + length);
217219
} else if (length <= 0x7f) {
218-
return new ByteArray(new byte[] {(byte) (length & 0xff)});
220+
return new byte[] {(byte) (length & 0xff)};
219221
} else if (length <= 0xff) {
220-
return new ByteArray(new byte[] {(byte) (0x80 | 0x01), (byte) (length & 0xff)});
222+
return new byte[] {(byte) (0x80 | 0x01), (byte) (length & 0xff)};
221223
} else if (length <= 0xffff) {
222-
return new ByteArray(
223-
new byte[] {(byte) (0x80 | 0x02), (byte) ((length >> 8) & 0xff), (byte) (length & 0xff)});
224+
return new byte[] {
225+
(byte) (0x80 | 0x02), (byte) ((length >> 8) & 0xff), (byte) (length & 0xff)
226+
};
224227
} 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-
});
228+
return new byte[] {
229+
(byte) (0x80 | 0x03),
230+
(byte) ((length >> 16) & 0xff),
231+
(byte) ((length >> 8) & 0xff),
232+
(byte) (length & 0xff)
233+
};
232234
} 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-
});
235+
return new byte[] {
236+
(byte) (0x80 | 0x04),
237+
(byte) ((length >> 24) & 0xff),
238+
(byte) ((length >> 16) & 0xff),
239+
(byte) ((length >> 8) & 0xff),
240+
(byte) (length & 0xff)
241+
};
241242
}
242243
}
243244

@@ -260,7 +261,7 @@ static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int offset) {
260261
case 0:
261262
throw new IllegalArgumentException(
262263
String.format(
263-
"Empty length encoding at offset %d: %s", offset, new ByteArray(der)));
264+
"Empty length encoding at offset %d: 0x%s", offset, BinaryUtil.toHex(der)));
264265
case 1:
265266
return new ParseDerResult<>(der[offset + 1] & 0xff, offset + 2);
266267
case 2:
@@ -293,36 +294,36 @@ static ParseDerResult<Integer> parseDerLength(@NonNull byte[] der, int offset) {
293294
} else {
294295
throw new IllegalArgumentException(
295296
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)));
297+
"Length encoding needs %d octets but only %s remain at index %d: 0x%s",
298+
longLen, len - (offset + 1), offset + 1, BinaryUtil.toHex(der)));
298299
}
299300
}
300301
}
301302

302-
private static ParseDerResult<ByteArray> parseDerTagged(
303+
private static ParseDerResult<byte[]> parseDerTagged(
303304
@NonNull byte[] der, int offset, byte expectTag) {
304305
final int len = der.length - offset;
305306
if (len == 0) {
306307
throw new IllegalArgumentException(
307-
String.format("Empty input at offset %d: %s", offset, new ByteArray(der)));
308+
String.format("Empty input at offset %d: 0x%s", offset, BinaryUtil.toHex(der)));
308309
} else {
309310
final byte tag = der[offset];
310311
if (tag == expectTag) {
311312
final ParseDerResult<Integer> contentLen = parseDerLength(der, offset + 1);
312313
final int contentEnd = contentLen.nextOffset + contentLen.result;
313314
return new ParseDerResult<>(
314-
new ByteArray(Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd)), contentEnd);
315+
Arrays.copyOfRange(der, contentLen.nextOffset, contentEnd), contentEnd);
315316
} else {
316317
throw new IllegalArgumentException(
317318
String.format(
318-
"Incorrect tag: 0x%02x (expected 0x%02x) at offset %d: %s",
319-
tag, expectTag, offset, new ByteArray(der)));
319+
"Incorrect tag: 0x%02x (expected 0x%02x) at offset %d: 0x%s",
320+
tag, expectTag, offset, BinaryUtil.toHex(der)));
320321
}
321322
}
322323
}
323324

324325
/** Parse a SEQUENCE and return a copy of the content octets. */
325-
static ParseDerResult<ByteArray> parseDerSequence(@NonNull byte[] der, int offset) {
326+
static ParseDerResult<byte[]> parseDerSequence(@NonNull byte[] der, int offset) {
326327
return parseDerTagged(der, offset, (byte) 0x30);
327328
}
328329

@@ -331,7 +332,7 @@ static ParseDerResult<ByteArray> parseDerSequence(@NonNull byte[] der, int offse
331332
* "constructed" encoding (bit 6 is 1), with a prescribed tag value, and return a copy of the
332333
* content octets.
333334
*/
334-
static ParseDerResult<ByteArray> parseDerExplicitlyTaggedContextSpecificConstructed(
335+
static ParseDerResult<byte[]> parseDerExplicitlyTaggedContextSpecificConstructed(
335336
@NonNull byte[] der, int offset, byte tagNumber) {
336337
if (tagNumber <= 30 && tagNumber >= 0) {
337338
return parseDerTagged(der, offset, (byte) ((tagNumber & 0x1f) | 0xa0));
@@ -341,21 +342,21 @@ static ParseDerResult<ByteArray> parseDerExplicitlyTaggedContextSpecificConstruc
341342
}
342343
}
343344

344-
private static ByteArray encodeDerObjectId(final ByteArray oid) {
345-
return new ByteArray(new byte[] {0x06, (byte) oid.size()}).concat(oid);
345+
private static byte[] encodeDerObjectId(@NonNull byte[] oid) {
346+
byte[] result = new byte[2 + oid.length];
347+
result[0] = 0x06;
348+
result[1] = (byte) oid.length;
349+
return BinaryUtil.copyInto(oid, result, 2);
346350
}
347351

348-
private static ByteArray encodeDerBitStringWithZeroUnused(final ByteArray content) {
349-
return new ByteArray(new byte[] {0x03})
350-
.concat(encodeDerLength(1 + content.size()))
351-
.concat(new ByteArray(new byte[] {0}))
352-
.concat(content);
352+
private static byte[] encodeDerBitStringWithZeroUnused(@NonNull byte[] content) {
353+
return BinaryUtil.concat(
354+
new byte[] {0x03}, encodeDerLength(1 + content.length), new byte[] {0}, content);
353355
}
354356

355-
static ByteArray encodeDerSequence(final ByteArray... items) {
356-
final ByteArray content =
357-
Stream.of(items).reduce(ByteArray::concat).orElseGet(() -> new ByteArray(new byte[0]));
358-
return new ByteArray(new byte[] {0x30}).concat(encodeDerLength(content.size())).concat(content);
357+
static byte[] encodeDerSequence(final byte[]... items) {
358+
byte[] content = BinaryUtil.concat(items);
359+
return BinaryUtil.concat(new byte[] {0x30}, encodeDerLength(content.length), content);
359360
}
360361

361362
private static PublicKey importCoseEdDsaPublicKey(CBORObject cose)
@@ -371,12 +372,12 @@ private static PublicKey importCoseEdDsaPublicKey(CBORObject cose)
371372

372373
private static PublicKey importCoseEd25519PublicKey(CBORObject cose)
373374
throws InvalidKeySpecException, NoSuchAlgorithmException {
374-
final ByteArray rawKey = new ByteArray(cose.get(CBORObject.FromObject(-2)).GetByteString());
375-
final ByteArray x509Key =
376-
encodeDerSequence(ED25519_ALG_ID, encodeDerBitStringWithZeroUnused(rawKey));
375+
final byte[] rawKey = cose.get(CBORObject.FromObject(-2)).GetByteString();
376+
final byte[] x509Key =
377+
encodeDerSequence(ED25519_ALG_ID.getBytes(), encodeDerBitStringWithZeroUnused(rawKey));
377378

378379
KeyFactory kFact = KeyFactory.getInstance("EdDSA");
379-
return kFact.generatePublic(new X509EncodedKeySpec(x509Key.getBytes()));
380+
return kFact.generatePublic(new X509EncodedKeySpec(x509Key));
380381
}
381382

382383
static String getJavaAlgorithmName(COSEAlgorithmIdentifier alg) {

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

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424

2525
package com.yubico.webauthn
2626

27+
import com.yubico.internal.util.BinaryUtil
2728
import com.yubico.webauthn.data.ByteArray
28-
import com.yubico.webauthn.data.Generators.arbitraryByteArray
2929
import com.yubico.webauthn.test.Util
3030
import org.junit.runner.RunWith
3131
import org.scalacheck.Arbitrary
@@ -129,24 +129,26 @@ class WebAuthnCodecsSpec
129129

130130
describe("DER parsing and encoding:") {
131131
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)
132+
forAll(
133+
Gen.chooseNum(0, Int.MaxValue),
134+
Arbitrary.arbitrary[Array[Byte]],
135+
) { (len: Int, prefix: Array[Byte]) =>
136+
val encoded = WebAuthnCodecs.encodeDerLength(len)
137+
val decoded = WebAuthnCodecs.parseDerLength(encoded, 0)
138+
val decodedWithPrefix = WebAuthnCodecs.parseDerLength(
139+
BinaryUtil.concat(prefix, encoded),
140+
prefix.length,
141+
)
142+
143+
decoded.result should equal(len)
144+
decoded.nextOffset should equal(encoded.length)
145+
decodedWithPrefix.result should equal(len)
146+
decodedWithPrefix.nextOffset should equal(
147+
prefix.length + encoded.length
148+
)
149+
150+
val recoded = WebAuthnCodecs.encodeDerLength(decoded.result)
151+
recoded should equal(encoded)
150152
}
151153
}
152154

@@ -181,31 +183,32 @@ class WebAuthnCodecsSpec
181183
}
182184

183185
it("encodeDerSequence and parseDerSequenceEnd are (almost) each other's inverse.") {
184-
forAll { (data: Array[ByteArray], prefix: ByteArray) =>
186+
forAll { (data: Array[Array[Byte]], prefix: Array[Byte]) =>
185187
val encoded = WebAuthnCodecs.encodeDerSequence(data: _*)
186-
val decoded = WebAuthnCodecs.parseDerSequence(encoded.getBytes, 0)
187-
val encodedWithPrefix = prefix.concat(encoded)
188+
val decoded = WebAuthnCodecs.parseDerSequence(encoded, 0)
189+
val encodedWithPrefix = BinaryUtil.concat(prefix, encoded)
188190
val decodedWithPrefix = WebAuthnCodecs.parseDerSequence(
189-
encodedWithPrefix.getBytes,
190-
prefix.size,
191+
encodedWithPrefix,
192+
prefix.length,
191193
)
192194

193-
val expectedContent: ByteArray =
194-
data.fold(new ByteArray(Array.empty))((a, b) => a.concat(b))
195+
val expectedContent: Array[Byte] = BinaryUtil.concat(data: _*)
195196
decoded.result should equal(expectedContent)
196197
decodedWithPrefix.result should equal(expectedContent)
197-
decoded.nextOffset should equal(encoded.size)
198-
decodedWithPrefix.nextOffset should equal(prefix.size + encoded.size)
198+
decoded.nextOffset should equal(encoded.length)
199+
decodedWithPrefix.nextOffset should equal(
200+
prefix.length + encoded.length
201+
)
199202
}
200203
}
201204

202205
it("parseDerSequence fails if the first byte is not 0x30.") {
203-
forAll { (tag: Byte, data: Array[ByteArray]) =>
206+
forAll { (tag: Byte, data: Array[Array[Byte]]) =>
204207
whenever(tag != 0x30) {
205208
val encoded = WebAuthnCodecs.encodeDerSequence(data: _*)
206209
an[IllegalArgumentException] shouldBe thrownBy {
207210
WebAuthnCodecs.parseDerSequence(
208-
encoded.getBytes.updated(0, tag),
211+
encoded.updated(0, tag),
209212
0,
210213
)
211214
}

0 commit comments

Comments
 (0)