@sigstore/core
Advanced tools
+12
-1
@@ -40,3 +40,9 @@ "use strict"; | ||
| for (let i = 0; i < byteCount; i++) { | ||
| len = len * 256 + stream.getUint8(); | ||
| const byte = stream.getUint8(); | ||
| // The first byte of a multi-byte length must not be zero; a leading zero | ||
| // means the length could have been encoded in fewer bytes (non-minimal). | ||
| if (i === 0 && byte === 0x00) { | ||
| throw new error_1.ASN1ParseError('non-minimal length encoding'); | ||
| } | ||
| len = len * 256 + byte; | ||
| } | ||
@@ -47,2 +53,7 @@ // This is a valid ASN.1 length encoding, but we don't support it. | ||
| } | ||
| // Lengths less than 128 must use the short form; rejecting them here ensures | ||
| // the encoding is minimal (strict DER). | ||
| if (len < 128) { | ||
| throw new error_1.ASN1ParseError('non-minimal length encoding'); | ||
| } | ||
| return len; | ||
@@ -49,0 +60,0 @@ } |
+25
-7
@@ -35,3 +35,10 @@ "use strict"; | ||
| static parseBuffer(buf) { | ||
| return parseStream(new stream_1.ByteStream(buf)); | ||
| const stream = new stream_1.ByteStream(buf); | ||
| const obj = parseStream(stream); | ||
| // Ensure the entire buffer was consumed; trailing data after the top-level | ||
| // object indicates a malformed (or maliciously padded) encoding. | ||
| if (stream.position !== stream.length) { | ||
| throw new error_1.ASN1ParseError('invalid trailing data'); | ||
| } | ||
| return obj; | ||
| } | ||
@@ -107,3 +114,10 @@ toDER() { | ||
| // Internal stream parsing functions | ||
| function parseStream(stream) { | ||
| // Maximum nesting depth for parsed ASN.1 objects. Bounds the mutual recursion | ||
| // between parseStream and collectSubs so that deeply nested DER cannot exhaust | ||
| // the call stack (denial of service). | ||
| const MAX_DEPTH = 100; | ||
| function parseStream(stream, depth = 0) { | ||
| if (depth > MAX_DEPTH) { | ||
| throw new error_1.ASN1ParseError('maximum nesting depth exceeded'); | ||
| } | ||
| // Parse tag, length, and value from stream | ||
@@ -119,9 +133,13 @@ const tag = new tag_1.ASN1Tag(stream.getUint8()); | ||
| if (tag.constructed) { | ||
| subs = collectSubs(stream, len); | ||
| subs = collectSubs(stream, len, depth); | ||
| } | ||
| else if (tag.isOctetString()) { | ||
| // Attempt to parse children of OCTETSTRING objects. If anything fails, | ||
| // assume the object is not constructed and treat as primitive. | ||
| // assume the object is not constructed and treat as primitive. This is | ||
| // intentional: it transparently unwraps DER content embedded in an OCTET | ||
| // STRING (e.g. X.509 extnValue, CMS eContent). The error is swallowed | ||
| // because a parse failure simply means the bytes are an opaque primitive | ||
| // value rather than a nested structure. | ||
| try { | ||
| subs = collectSubs(stream, len); | ||
| subs = collectSubs(stream, len, depth); | ||
| } | ||
@@ -138,3 +156,3 @@ catch (e) { | ||
| } | ||
| function collectSubs(stream, len) { | ||
| function collectSubs(stream, len, depth) { | ||
| // Calculate end of object content | ||
@@ -152,3 +170,3 @@ const end = stream.position + len; | ||
| while (stream.position < end) { | ||
| subs.push(parseStream(stream)); | ||
| subs.push(parseStream(stream, depth + 1)); | ||
| } | ||
@@ -155,0 +173,0 @@ // When we're done parsing children, we should be at the end of the object |
+24
-5
@@ -24,2 +24,3 @@ "use strict"; | ||
| */ | ||
| const error_1 = require("./error"); | ||
| const RE_TIME_SHORT_YEAR = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/; | ||
@@ -87,7 +88,9 @@ const RE_TIME_LONG_YEAR = /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\.\d{3})?Z$/; | ||
| let oid = `${first}.${second}`; | ||
| // Consume remaining bytes | ||
| let val = 0; | ||
| // Consume remaining bytes. Use a BigInt accumulator so that arcs which | ||
| // exceed 32 bits are not silently truncated (a truncated arc could be made | ||
| // to collide with a trusted OID). | ||
| let val = 0n; | ||
| for (; pos < end; ++pos) { | ||
| n = buf[pos]; | ||
| val = (val << 7) + (n & 0x7f); | ||
| val = (val << 7n) + BigInt(n & 0x7f); | ||
| // If the left-most bit is NOT set, then this is the last byte in the | ||
@@ -97,3 +100,3 @@ // sequence and we can add the value to the OID and reset the accumulator | ||
| oid += `.${val}`; | ||
| val = 0; | ||
| val = 0n; | ||
| } | ||
@@ -106,3 +109,15 @@ } | ||
| function parseBoolean(buf) { | ||
| return buf[0] !== 0; | ||
| // DER requires a BOOLEAN to be a single byte that is either 0x00 (false) or | ||
| // 0xff (true). Reject any other (non-canonical) encoding. | ||
| if (buf.length !== 1) { | ||
| throw new error_1.ASN1ParseError('invalid boolean'); | ||
| } | ||
| switch (buf[0]) { | ||
| case 0x00: | ||
| return false; | ||
| case 0xff: | ||
| return true; | ||
| default: | ||
| throw new error_1.ASN1ParseError('invalid boolean'); | ||
| } | ||
| } | ||
@@ -114,2 +129,6 @@ // Parse a bit string from the DER-encoded buffer | ||
| const unused = buf[0]; | ||
| // The number of unused bits must be in the range 0-7. | ||
| if (unused > 7) { | ||
| throw new error_1.ASN1ParseError('invalid bit string'); | ||
| } | ||
| const start = 1; | ||
@@ -116,0 +135,0 @@ const end = buf.length; |
+1
-1
| { | ||
| "name": "@sigstore/core", | ||
| "version": "4.0.0", | ||
| "version": "4.0.1", | ||
| "description": "Base library for Sigstore", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
92557
2.59%2044
2.4%