@marcuspuchalla/nachos 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -0
- package/LICENSE +674 -0
- package/README.md +345 -0
- package/dist/chunk-2FUTHZQQ.cjs +755 -0
- package/dist/chunk-2FUTHZQQ.cjs.map +1 -0
- package/dist/chunk-2HBCILJS.cjs +2034 -0
- package/dist/chunk-2HBCILJS.cjs.map +1 -0
- package/dist/chunk-7CFYWHS6.js +742 -0
- package/dist/chunk-7CFYWHS6.js.map +1 -0
- package/dist/chunk-PD72MVTX.cjs +160 -0
- package/dist/chunk-PD72MVTX.cjs.map +1 -0
- package/dist/chunk-ZDZ2B5PE.js +149 -0
- package/dist/chunk-ZDZ2B5PE.js.map +1 -0
- package/dist/chunk-ZRPJUEIZ.js +2020 -0
- package/dist/chunk-ZRPJUEIZ.js.map +1 -0
- package/dist/encoder/index.cjs +57 -0
- package/dist/encoder/index.cjs.map +1 -0
- package/dist/encoder/index.d.cts +72 -0
- package/dist/encoder/index.d.ts +72 -0
- package/dist/encoder/index.js +4 -0
- package/dist/encoder/index.js.map +1 -0
- package/dist/index.cjs +606 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +494 -0
- package/dist/index.d.ts +494 -0
- package/dist/index.js +523 -0
- package/dist/index.js.map +1 -0
- package/dist/metafile-cjs.json +1 -0
- package/dist/metafile-esm.json +1 -0
- package/dist/parser/index.cjs +85 -0
- package/dist/parser/index.cjs.map +1 -0
- package/dist/parser/index.d.cts +72 -0
- package/dist/parser/index.d.ts +72 -0
- package/dist/parser/index.js +4 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/types-DvNlfbKB.d.cts +301 -0
- package/dist/types-DvNlfbKB.d.ts +301 -0
- package/dist/useCborSimpleEncoder-ButVU988.d.cts +268 -0
- package/dist/useCborSimpleEncoder-TVxzNJ_9.d.ts +268 -0
- package/dist/useCborTag-B_iaShG6.d.ts +142 -0
- package/dist/useCborTag-BfTIV8HM.d.cts +142 -0
- package/package.json +102 -0
- package/src/__tests__/public-api.test.ts +326 -0
- package/src/encoder/__tests__/cbor-collection-encoder.test.ts +331 -0
- package/src/encoder/__tests__/cbor-integer-encoder.test.ts +283 -0
- package/src/encoder/__tests__/cbor-simple-encoder.test.ts +224 -0
- package/src/encoder/__tests__/cbor-string-encoder.test.ts +345 -0
- package/src/encoder/__tests__/cbor-tag-encoder.test.ts +565 -0
- package/src/encoder/composables/#useCborTagEncoder.ts# +158 -0
- package/src/encoder/composables/useCborCollectionEncoder.ts +424 -0
- package/src/encoder/composables/useCborEncoder.ts +203 -0
- package/src/encoder/composables/useCborIntegerEncoder.ts +188 -0
- package/src/encoder/composables/useCborSimpleEncoder.ts +266 -0
- package/src/encoder/composables/useCborStringEncoder.ts +266 -0
- package/src/encoder/composables/useCborTagEncoder.ts +158 -0
- package/src/encoder/index.ts +35 -0
- package/src/encoder/types.ts +88 -0
- package/src/encoder/utils.ts +80 -0
- package/src/index.ts +434 -0
- package/src/parser/__tests__/ast-tree-structure.test.ts +311 -0
- package/src/parser/__tests__/cbor-collection-errors.test.ts +296 -0
- package/src/parser/__tests__/cbor-collection.test.ts +369 -0
- package/src/parser/__tests__/cbor-deterministic-encoding.test.ts +432 -0
- package/src/parser/__tests__/cbor-diagnostic.test.ts +333 -0
- package/src/parser/__tests__/cbor-duplicate-keys.test.ts +235 -0
- package/src/parser/__tests__/cbor-float-errors.test.ts +222 -0
- package/src/parser/__tests__/cbor-float.test.ts +502 -0
- package/src/parser/__tests__/cbor-integer-errors.test.ts +139 -0
- package/src/parser/__tests__/cbor-integer.test.ts +200 -0
- package/src/parser/__tests__/cbor-map-duplicate-keys.test.ts +403 -0
- package/src/parser/__tests__/cbor-parser-errors.test.ts +126 -0
- package/src/parser/__tests__/cbor-security-dos-protection.test.ts +503 -0
- package/src/parser/__tests__/cbor-sequences.test.ts +150 -0
- package/src/parser/__tests__/cbor-source-map.test.ts +321 -0
- package/src/parser/__tests__/cbor-standard-tags.test.ts +340 -0
- package/src/parser/__tests__/cbor-string-errors.test.ts +227 -0
- package/src/parser/__tests__/cbor-string.test.ts +224 -0
- package/src/parser/__tests__/cbor-tag-advanced.test.ts +500 -0
- package/src/parser/__tests__/cbor-tag-errors.test.ts +447 -0
- package/src/parser/__tests__/cbor-tag-source-map.test.ts +360 -0
- package/src/parser/__tests__/cbor-tag.test.ts +684 -0
- package/src/parser/__tests__/extreme-edge-cases.test.ts +146 -0
- package/src/parser/__tests__/pathBuilder.test.ts +256 -0
- package/src/parser/__tests__/rfc-test-vectors.test.ts +607 -0
- package/src/parser/__tests__/security-limits.test.ts +248 -0
- package/src/parser/__tests__/utils-errors.test.ts +127 -0
- package/src/parser/composables/useCborCollection.ts +509 -0
- package/src/parser/composables/useCborDiagnostic.ts +381 -0
- package/src/parser/composables/useCborFloat.ts +256 -0
- package/src/parser/composables/useCborInteger.ts +114 -0
- package/src/parser/composables/useCborParser.ts +951 -0
- package/src/parser/composables/useCborString.ts +330 -0
- package/src/parser/composables/useCborStringTypes.ts +129 -0
- package/src/parser/composables/useCborTag.ts +739 -0
- package/src/parser/index.ts +56 -0
- package/src/parser/types.ts +371 -0
- package/src/parser/utils/pathBuilder.ts +259 -0
- package/src/parser/utils.ts +398 -0
- package/src/utils/__tests__/logger.test.ts +186 -0
- package/src/utils/logger.ts +96 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Parser Error Handling Tests
|
|
3
|
+
* Tests all error cases and edge cases for 100% code coverage
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { describe, it, expect } from 'vitest'
|
|
7
|
+
import { useCborParser } from '../composables/useCborParser'
|
|
8
|
+
|
|
9
|
+
describe('useCborParser - Error Handling', () => {
|
|
10
|
+
describe('Input Validation Errors', () => {
|
|
11
|
+
it('should throw error for empty hex string', () => {
|
|
12
|
+
const { parse } = useCborParser()
|
|
13
|
+
expect(() => parse('')).toThrow('Empty hex string')
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('should throw error for whitespace-only hex string', () => {
|
|
17
|
+
const { parse } = useCborParser()
|
|
18
|
+
expect(() => parse(' ')).toThrow('Empty hex string')
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('should throw error for odd-length hex string', () => {
|
|
22
|
+
const { parse } = useCborParser()
|
|
23
|
+
expect(() => parse('1')).toThrow('Hex string must have even length')
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('should throw error for hex string with odd length (3 chars)', () => {
|
|
27
|
+
const { parse } = useCborParser()
|
|
28
|
+
expect(() => parse('123')).toThrow('Hex string must have even length')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should throw error for invalid hex characters', () => {
|
|
32
|
+
const { parse } = useCborParser()
|
|
33
|
+
expect(() => parse('1g')).toThrow('Invalid hex character')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should throw error for hex with special characters', () => {
|
|
37
|
+
const { parse } = useCborParser()
|
|
38
|
+
expect(() => parse('12@4')).toThrow('Invalid hex character')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('should throw error for hex with spaces not removed', () => {
|
|
42
|
+
const { parse } = useCborParser()
|
|
43
|
+
// Spaces should be removed, but test with invalid chars after spaces
|
|
44
|
+
expect(() => parse('ZZ')).toThrow('Invalid hex character')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('Unknown Major Type Error', () => {
|
|
49
|
+
it('should never throw unknown major type error (all types 0-7 are valid)', () => {
|
|
50
|
+
// This tests the default case in the switch statement
|
|
51
|
+
// However, extractCborHeader can only return 0-7, so this is defensive programming
|
|
52
|
+
// We can't actually trigger this without mocking, but we have the code path
|
|
53
|
+
|
|
54
|
+
// All major types should be handled:
|
|
55
|
+
const { parse } = useCborParser()
|
|
56
|
+
|
|
57
|
+
// MT 0: Integer
|
|
58
|
+
expect(() => parse('00')).not.toThrow()
|
|
59
|
+
|
|
60
|
+
// MT 1: Negative integer
|
|
61
|
+
expect(() => parse('20')).not.toThrow()
|
|
62
|
+
|
|
63
|
+
// MT 2: Byte string
|
|
64
|
+
expect(() => parse('40')).not.toThrow()
|
|
65
|
+
|
|
66
|
+
// MT 3: Text string
|
|
67
|
+
expect(() => parse('60')).not.toThrow()
|
|
68
|
+
|
|
69
|
+
// MT 4: Array
|
|
70
|
+
expect(() => parse('80')).not.toThrow()
|
|
71
|
+
|
|
72
|
+
// MT 5: Map
|
|
73
|
+
expect(() => parse('a0')).not.toThrow()
|
|
74
|
+
|
|
75
|
+
// MT 6: Tag
|
|
76
|
+
expect(() => parse('c000')).not.toThrow()
|
|
77
|
+
|
|
78
|
+
// MT 7: Float/Simple
|
|
79
|
+
expect(() => parse('f4')).not.toThrow()
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
describe('parseWithSourceMap', () => {
|
|
84
|
+
it('should delegate to parse for now', () => {
|
|
85
|
+
const { parseWithSourceMap } = useCborParser()
|
|
86
|
+
|
|
87
|
+
// Test with various types
|
|
88
|
+
expect(parseWithSourceMap('00').value).toBe(0)
|
|
89
|
+
expect(parseWithSourceMap('20').value).toBe(-1)
|
|
90
|
+
expect(parseWithSourceMap('60').value).toBe('')
|
|
91
|
+
expect(parseWithSourceMap('f5').value).toBe(true)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('should return same result as parse', () => {
|
|
95
|
+
const { parse, parseWithSourceMap } = useCborParser()
|
|
96
|
+
|
|
97
|
+
const testCases = ['00', '1864', '6449455446', '83010203', 'a0', 'f4']
|
|
98
|
+
|
|
99
|
+
for (const hexString of testCases) {
|
|
100
|
+
const parseResult = parse(hexString)
|
|
101
|
+
const sourceMapResult = parseWithSourceMap(hexString)
|
|
102
|
+
expect(sourceMapResult.value).toEqual(parseResult.value)
|
|
103
|
+
expect(sourceMapResult.bytesRead).toBe(parseResult.bytesRead)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
describe('Hex String Cleaning', () => {
|
|
109
|
+
it('should handle hex with spaces', () => {
|
|
110
|
+
const { parse } = useCborParser()
|
|
111
|
+
|
|
112
|
+
// Should remove spaces before parsing
|
|
113
|
+
expect(parse('18 64').value).toBe(100)
|
|
114
|
+
expect(parse('83 01 02 03').value).toEqual([1, 2, 3])
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('should handle hex with tabs and newlines', () => {
|
|
118
|
+
const { parse } = useCborParser()
|
|
119
|
+
|
|
120
|
+
// Should remove all whitespace
|
|
121
|
+
expect(parse('18\t64').value).toBe(100)
|
|
122
|
+
expect(parse('18\n64').value).toBe(100)
|
|
123
|
+
expect(parse('18\r\n64').value).toBe(100)
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
})
|
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CBOR Denial of Service (DoS) Protection Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for critical security vulnerabilities:
|
|
5
|
+
* - CVE-2020-28491: Bignum memory exhaustion
|
|
6
|
+
* - RUSTSEC-2019-0025: Tag nesting stack overflow
|
|
7
|
+
* - Indefinite-length break code validation
|
|
8
|
+
* - Float canonical encoding
|
|
9
|
+
*
|
|
10
|
+
* These tests verify protections against known CBOR implementation vulnerabilities.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it, expect } from 'vitest'
|
|
14
|
+
import { useCborTag } from '../composables/useCborTag'
|
|
15
|
+
import { useCborParser } from '../composables/useCborParser'
|
|
16
|
+
import { useCborCollection } from '../composables/useCborCollection'
|
|
17
|
+
|
|
18
|
+
describe('CVE-2020-28491: Bignum Memory Exhaustion Protection', () => {
|
|
19
|
+
describe('Tag 2: Positive Bignum Size Limits', () => {
|
|
20
|
+
it('should accept tag 2 bignum within size limit', () => {
|
|
21
|
+
const { parseTag } = useCborTag()
|
|
22
|
+
|
|
23
|
+
// Tag 2 with 512-byte bignum (within 1KB default limit)
|
|
24
|
+
const bignumBytes = '00'.repeat(512)
|
|
25
|
+
const bignum = `c2590200${bignumBytes}` // c2 = tag 2, 590200 = byte string of 512 bytes
|
|
26
|
+
|
|
27
|
+
const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
|
|
28
|
+
|
|
29
|
+
expect(result.value.tag).toBe(2)
|
|
30
|
+
expect(typeof result.value.value).toBe('bigint')
|
|
31
|
+
// 512 bytes of 0x00 = BigInt 0
|
|
32
|
+
expect(result.value.value).toBe(0n)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('should reject tag 2 bignum exceeding size limit', () => {
|
|
36
|
+
const { parseTag } = useCborTag()
|
|
37
|
+
|
|
38
|
+
// Tag 2 with 2KB bignum (exceeds 1KB limit)
|
|
39
|
+
const bignumBytes = '00'.repeat(2048)
|
|
40
|
+
const bignum = `c2590800${bignumBytes}` // 2048 bytes
|
|
41
|
+
|
|
42
|
+
expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
|
|
43
|
+
.toThrow(/bignum.*exceeds.*1024 bytes/i)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should reject tag 2 with massive bignum (memory DoS)', () => {
|
|
47
|
+
const { parseTag } = useCborTag()
|
|
48
|
+
|
|
49
|
+
// Tag 2 with 2048-byte bignum (exceeds 1024 byte limit)
|
|
50
|
+
// c2 = tag 2, 590800 = byte string of 2048 bytes
|
|
51
|
+
const bignumBytes = '00'.repeat(2048)
|
|
52
|
+
const bignum = `c2590800${bignumBytes}`
|
|
53
|
+
|
|
54
|
+
expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
|
|
55
|
+
.toThrow(/bignum.*exceeds.*1024 bytes/i)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('should accept tag 2 with exactly max size', () => {
|
|
59
|
+
const { parseTag } = useCborTag()
|
|
60
|
+
|
|
61
|
+
// Exactly 1024 bytes (at limit)
|
|
62
|
+
const bignumBytes = 'ff'.repeat(1024)
|
|
63
|
+
const bignum = `c2590400${bignumBytes}`
|
|
64
|
+
|
|
65
|
+
const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
|
|
66
|
+
|
|
67
|
+
expect(result.value.tag).toBe(2)
|
|
68
|
+
expect(typeof result.value.value).toBe('bigint')
|
|
69
|
+
// 1024 bytes of 0xff should be a large positive bigint
|
|
70
|
+
// 2^(1024*8) - 1
|
|
71
|
+
expect(result.value.value > 0n).toBe(true)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('should reject tag 2 with 1 byte over limit', () => {
|
|
75
|
+
const { parseTag } = useCborTag()
|
|
76
|
+
|
|
77
|
+
// 1025 bytes (1 over limit)
|
|
78
|
+
const bignumBytes = 'ff'.repeat(1025)
|
|
79
|
+
const bignum = `c2590401${bignumBytes}`
|
|
80
|
+
|
|
81
|
+
expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
|
|
82
|
+
.toThrow(/bignum.*exceeds.*1024 bytes/i)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('should use default bignum limit when not specified', () => {
|
|
86
|
+
const { parseTag } = useCborTag()
|
|
87
|
+
|
|
88
|
+
// 2KB bignum with no limit specified (should use default 1KB)
|
|
89
|
+
const bignumBytes = 'aa'.repeat(2048)
|
|
90
|
+
const bignum = `c2590800${bignumBytes}`
|
|
91
|
+
|
|
92
|
+
expect(() => parseTag(bignum)) // No options = use defaults
|
|
93
|
+
.toThrow(/bignum.*exceeds/i)
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('should allow custom bignum limits', () => {
|
|
97
|
+
const { parseTag } = useCborTag()
|
|
98
|
+
|
|
99
|
+
// 2KB bignum with 4KB limit (should pass)
|
|
100
|
+
const bignumBytes = 'bb'.repeat(2048)
|
|
101
|
+
const bignum = `c2590800${bignumBytes}`
|
|
102
|
+
|
|
103
|
+
const result = parseTag(bignum, { limits: { maxBignumBytes: 4096 } })
|
|
104
|
+
|
|
105
|
+
expect(result.value.tag).toBe(2)
|
|
106
|
+
expect(typeof result.value.value).toBe('bigint')
|
|
107
|
+
// 2048 bytes of 0xbb should be a large positive bigint
|
|
108
|
+
expect(result.value.value > 0n).toBe(true)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('should accept empty bignum (edge case)', () => {
|
|
112
|
+
const { parseTag } = useCborTag()
|
|
113
|
+
|
|
114
|
+
// Tag 2 with empty byte string (0 bytes)
|
|
115
|
+
const bignum = 'c240' // c2 = tag 2, 40 = empty byte string
|
|
116
|
+
|
|
117
|
+
const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
|
|
118
|
+
|
|
119
|
+
expect(result.value.tag).toBe(2)
|
|
120
|
+
expect(typeof result.value.value).toBe('bigint')
|
|
121
|
+
// Empty byte string = BigInt 0
|
|
122
|
+
expect(result.value.value).toBe(0n)
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
describe('Tag 3: Negative Bignum Size Limits', () => {
|
|
127
|
+
it('should accept tag 3 bignum within size limit', () => {
|
|
128
|
+
const { parseTag } = useCborTag()
|
|
129
|
+
|
|
130
|
+
// Tag 3 with 256-byte bignum
|
|
131
|
+
const bignumBytes = 'ff'.repeat(256)
|
|
132
|
+
const bignum = `c3590100${bignumBytes}` // c3 = tag 3, 590100 = 256 bytes
|
|
133
|
+
|
|
134
|
+
const result = parseTag(bignum, { limits: { maxBignumBytes: 1024 } })
|
|
135
|
+
|
|
136
|
+
expect(result.value.tag).toBe(3)
|
|
137
|
+
expect(typeof result.value.value).toBe('bigint')
|
|
138
|
+
// Tag 3 = -1 - n, where n is the bignum value
|
|
139
|
+
// 256 bytes of 0xff should be a large negative bigint
|
|
140
|
+
expect(result.value.value < 0n).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('should reject tag 3 bignum exceeding size limit', () => {
|
|
144
|
+
const { parseTag } = useCborTag()
|
|
145
|
+
|
|
146
|
+
// Tag 3 with 4KB bignum (exceeds 1KB limit)
|
|
147
|
+
const bignumBytes = 'ff'.repeat(4096)
|
|
148
|
+
const bignum = `c3591000${bignumBytes}` // 4096 bytes
|
|
149
|
+
|
|
150
|
+
expect(() => parseTag(bignum, { limits: { maxBignumBytes: 1024 } }))
|
|
151
|
+
.toThrow(/bignum.*exceeds.*1024 bytes/i)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should handle both tag 2 and tag 3 with same limit', () => {
|
|
155
|
+
const { parseTag } = useCborTag()
|
|
156
|
+
|
|
157
|
+
const limit = { maxBignumBytes: 512 }
|
|
158
|
+
|
|
159
|
+
// Tag 2 with 512 bytes (OK)
|
|
160
|
+
const bignum2 = `c2590200${'aa'.repeat(512)}`
|
|
161
|
+
expect(() => parseTag(bignum2, { limits: limit })).not.toThrow()
|
|
162
|
+
|
|
163
|
+
// Tag 3 with 512 bytes (OK)
|
|
164
|
+
const bignum3 = `c3590200${'bb'.repeat(512)}`
|
|
165
|
+
expect(() => parseTag(bignum3, { limits: limit })).not.toThrow()
|
|
166
|
+
|
|
167
|
+
// Tag 2 with 513 bytes (FAIL)
|
|
168
|
+
const bignum2Fail = `c2590201${'cc'.repeat(513)}`
|
|
169
|
+
expect(() => parseTag(bignum2Fail, { limits: limit })).toThrow()
|
|
170
|
+
|
|
171
|
+
// Tag 3 with 513 bytes (FAIL)
|
|
172
|
+
const bignum3Fail = `c3590201${'dd'.repeat(513)}`
|
|
173
|
+
expect(() => parseTag(bignum3Fail, { limits: limit })).toThrow()
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
describe('Other Tags (Non-Bignum) Should Not Be Limited', () => {
|
|
178
|
+
it('should NOT apply bignum limit to tag 1 (epoch time)', () => {
|
|
179
|
+
const { parseTag } = useCborTag()
|
|
180
|
+
|
|
181
|
+
// Tag 1 with large integer (not a bignum)
|
|
182
|
+
const epochTag = 'c11a514b67b0' // Tag 1 with 1363896240
|
|
183
|
+
|
|
184
|
+
const result = parseTag(epochTag, { limits: { maxBignumBytes: 100 } })
|
|
185
|
+
|
|
186
|
+
expect(result.value.tag).toBe(1)
|
|
187
|
+
expect(result.value.value).toBe(1363896240)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('should NOT apply bignum limit to tag 24 (embedded CBOR)', () => {
|
|
191
|
+
const { parseTag } = useCborTag()
|
|
192
|
+
|
|
193
|
+
// Tag 24 with byte string containing CBOR (not a bignum)
|
|
194
|
+
const embeddedCBOR = 'd8184401020304' // Tag 24 with h'01020304'
|
|
195
|
+
|
|
196
|
+
const result = parseTag(embeddedCBOR, { limits: { maxBignumBytes: 2 } })
|
|
197
|
+
|
|
198
|
+
expect(result.value.tag).toBe(24)
|
|
199
|
+
expect(result.value.value).toBeInstanceOf(Uint8Array)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('should NOT apply bignum limit to tag 258 (set)', () => {
|
|
203
|
+
const { parseTag } = useCborTag()
|
|
204
|
+
|
|
205
|
+
// Tag 258 with array (not a bignum)
|
|
206
|
+
const setTag = 'd9010283010203' // Tag 258 with [1, 2, 3]
|
|
207
|
+
|
|
208
|
+
const result = parseTag(setTag, { limits: { maxBignumBytes: 2 } })
|
|
209
|
+
|
|
210
|
+
expect(result.value.tag).toBe(258)
|
|
211
|
+
expect(result.value.value).toBeInstanceOf(Array)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
describe('RUSTSEC-2019-0025: Tag Nesting Stack Overflow Protection', () => {
|
|
217
|
+
describe('Tag Nesting Depth Limits', () => {
|
|
218
|
+
it('should accept tag nesting within depth limit', () => {
|
|
219
|
+
const { parseTag } = useCborTag()
|
|
220
|
+
|
|
221
|
+
// 10 nested tags (within 64 default limit)
|
|
222
|
+
const nested = 'c0'.repeat(10) + '00' // Tag 0 nested 10 times
|
|
223
|
+
|
|
224
|
+
const result = parseTag(nested, { limits: { maxTagDepth: 64 } })
|
|
225
|
+
|
|
226
|
+
expect(result.value.tag).toBe(0)
|
|
227
|
+
// Nested structure: tag 0 -> tag 0 -> ... -> 0
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should reject tag nesting exceeding depth limit', () => {
|
|
231
|
+
const { parseTag } = useCborTag()
|
|
232
|
+
|
|
233
|
+
// 100 nested tags (exceeds 64 default limit)
|
|
234
|
+
const nested = 'c0'.repeat(100) + '00'
|
|
235
|
+
|
|
236
|
+
expect(() => parseTag(nested, { limits: { maxTagDepth: 64 } }))
|
|
237
|
+
.toThrow(/tag nesting depth.*exceeds.*64/i)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
it('should reject tag nesting at exactly limit + 1', () => {
|
|
241
|
+
const { parseTag } = useCborTag()
|
|
242
|
+
|
|
243
|
+
// 11 nested tags with limit of 10 (should fail at 11th)
|
|
244
|
+
const nested = 'c0'.repeat(11) + '00'
|
|
245
|
+
|
|
246
|
+
expect(() => parseTag(nested, { limits: { maxTagDepth: 10 } }))
|
|
247
|
+
.toThrow(/tag nesting depth.*exceeds.*10/i)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should accept tag nesting at exactly the limit', () => {
|
|
251
|
+
const { parseTag } = useCborTag()
|
|
252
|
+
|
|
253
|
+
// 10 nested tags with limit of 10 (should pass)
|
|
254
|
+
const nested = 'c0'.repeat(10) + '00'
|
|
255
|
+
|
|
256
|
+
const result = parseTag(nested, { limits: { maxTagDepth: 10 } })
|
|
257
|
+
|
|
258
|
+
expect(result.value.tag).toBe(0)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
it('should use default tag depth limit when not specified', () => {
|
|
262
|
+
const { parseTag } = useCborTag()
|
|
263
|
+
|
|
264
|
+
// 100 nested tags (exceeds default 64)
|
|
265
|
+
const nested = 'c0'.repeat(100) + '00'
|
|
266
|
+
|
|
267
|
+
expect(() => parseTag(nested)) // No options = use defaults
|
|
268
|
+
.toThrow(/tag nesting depth.*exceeds/i)
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
it('should allow custom tag depth limits', () => {
|
|
272
|
+
const { parseTag } = useCborTag()
|
|
273
|
+
|
|
274
|
+
// 100 nested tags with 150 limit (should pass)
|
|
275
|
+
const nested = 'c0'.repeat(100) + '00'
|
|
276
|
+
|
|
277
|
+
const result = parseTag(nested, { limits: { maxTagDepth: 150 } })
|
|
278
|
+
|
|
279
|
+
expect(result.value.tag).toBe(0)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('should handle nested tags with different tag numbers', () => {
|
|
283
|
+
const { parseTag } = useCborTag()
|
|
284
|
+
|
|
285
|
+
// Tag 1 -> Tag 2 -> Tag 3 -> 0
|
|
286
|
+
const nested = 'c1c2c300' // Tag 1( Tag 2( Tag 3( 0 )))
|
|
287
|
+
|
|
288
|
+
const result = parseTag(nested, { limits: { maxTagDepth: 5 } })
|
|
289
|
+
|
|
290
|
+
expect(result.value.tag).toBe(1)
|
|
291
|
+
expect(result.value.value.tag).toBe(2)
|
|
292
|
+
expect((result.value.value as any).value).toHaveProperty('tag', 3)
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
it('should reject deeply nested mixed tags', () => {
|
|
296
|
+
const { parseTag } = useCborTag()
|
|
297
|
+
|
|
298
|
+
// Mix of tag numbers, deeply nested
|
|
299
|
+
const nested = 'c0c1c2c3c0c1c2c3c0c1c2' + '00' // 11 tags
|
|
300
|
+
|
|
301
|
+
expect(() => parseTag(nested, { limits: { maxTagDepth: 10 } }))
|
|
302
|
+
.toThrow(/tag nesting depth.*exceeds/i)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should handle tag depth separate from collection depth', () => {
|
|
306
|
+
const { parseTag } = useCborTag()
|
|
307
|
+
|
|
308
|
+
// Tag 1 containing array (tag depth 1, collection depth 1)
|
|
309
|
+
const tagWithArray = 'c183010203' // Tag 1([1, 2, 3])
|
|
310
|
+
|
|
311
|
+
const result = parseTag(tagWithArray, {
|
|
312
|
+
limits: { maxTagDepth: 5, maxDepth: 5 }
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
expect(result.value.tag).toBe(1)
|
|
316
|
+
expect(result.value.value).toEqual([1, 2, 3])
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
it('should prevent stack overflow with minimal input (< 1KB)', () => {
|
|
320
|
+
const { parseTag } = useCborTag()
|
|
321
|
+
|
|
322
|
+
// RUSTSEC-2019-0025: Small input (< 1KB) that causes stack overflow
|
|
323
|
+
// 200 nested tags = 400 bytes of input
|
|
324
|
+
const nested = 'c0'.repeat(200) + '00'
|
|
325
|
+
|
|
326
|
+
expect(() => parseTag(nested, { limits: { maxTagDepth: 64 } }))
|
|
327
|
+
.toThrow(/tag nesting depth.*exceeds/i)
|
|
328
|
+
|
|
329
|
+
// Verify input is indeed small
|
|
330
|
+
expect(nested.length).toBeLessThan(1000)
|
|
331
|
+
})
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
describe('Indefinite-Length Break Code Validation', () => {
|
|
336
|
+
describe('Indefinite-Length Arrays - Break Code Required', () => {
|
|
337
|
+
it('should accept indefinite array with proper break code', () => {
|
|
338
|
+
const { parseArray } = useCborCollection()
|
|
339
|
+
|
|
340
|
+
// Indefinite array: [_ 1, 2, 3, BREAK]
|
|
341
|
+
const indefinite = '9f010203ff' // 9f = indefinite array, ff = break
|
|
342
|
+
|
|
343
|
+
const result = parseArray(indefinite, { allowIndefinite: true })
|
|
344
|
+
|
|
345
|
+
expect(Array.isArray(result.value)).toBe(true)
|
|
346
|
+
expect((result.value as any[])[0]).toBe(1)
|
|
347
|
+
expect((result.value as any[])[1]).toBe(2)
|
|
348
|
+
expect((result.value as any[])[2]).toBe(3)
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
it('should throw error for indefinite array without break code', () => {
|
|
352
|
+
const { parseArray } = useCborCollection()
|
|
353
|
+
|
|
354
|
+
// Indefinite array missing break: [_ 1, 2, 3 (NO FF)
|
|
355
|
+
const noBreak = '9f010203' // Missing 0xff
|
|
356
|
+
|
|
357
|
+
expect(() => parseArray(noBreak, { allowIndefinite: true }))
|
|
358
|
+
.toThrow(/missing.*break/i)
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('should accept empty indefinite array with break', () => {
|
|
362
|
+
const { parseArray } = useCborCollection()
|
|
363
|
+
|
|
364
|
+
// Indefinite array: [_ BREAK]
|
|
365
|
+
const emptyIndefinite = '9fff' // Just start + break
|
|
366
|
+
|
|
367
|
+
const result = parseArray(emptyIndefinite, { allowIndefinite: true })
|
|
368
|
+
|
|
369
|
+
expect(Array.isArray(result.value)).toBe(true)
|
|
370
|
+
expect(result.value).toHaveLength(0)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
it('should handle nested indefinite arrays with breaks', () => {
|
|
374
|
+
const { parseArray } = useCborCollection()
|
|
375
|
+
|
|
376
|
+
// [_ [_ 1 BREAK] BREAK]
|
|
377
|
+
const nestedIndefinite = '9f9f01ffff'
|
|
378
|
+
|
|
379
|
+
const result = parseArray(nestedIndefinite, { allowIndefinite: true })
|
|
380
|
+
|
|
381
|
+
expect(Array.isArray(result.value)).toBe(true)
|
|
382
|
+
expect(result.value).toHaveLength(1)
|
|
383
|
+
expect(Array.isArray((result.value as any[])[0])).toBe(true)
|
|
384
|
+
expect((result.value as any[])[0][0]).toBe(1)
|
|
385
|
+
})
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
describe('Indefinite-Length Maps - Break Code Required', () => {
|
|
389
|
+
it('should accept indefinite map with proper break code', () => {
|
|
390
|
+
const { parseMap } = useCborCollection()
|
|
391
|
+
|
|
392
|
+
// Indefinite map: {_ "a": 1, "b": 2, BREAK}
|
|
393
|
+
const indefinite = 'bf616101616202ff' // bf = indefinite map, ff = break
|
|
394
|
+
|
|
395
|
+
const result = parseMap(indefinite, { allowIndefinite: true })
|
|
396
|
+
|
|
397
|
+
expect(result.value).toEqual(new Map([['a', 1], ['b', 2]]))
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
it('should throw error for indefinite map without break code', () => {
|
|
401
|
+
const { parseMap } = useCborCollection()
|
|
402
|
+
|
|
403
|
+
// Indefinite map missing break: {_ "a": 1 (NO FF)
|
|
404
|
+
const noBreak = 'bf616101' // Missing 0xff
|
|
405
|
+
|
|
406
|
+
expect(() => parseMap(noBreak, { allowIndefinite: true }))
|
|
407
|
+
.toThrow(/missing.*break/i)
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
it('should accept empty indefinite map with break', () => {
|
|
411
|
+
const { parseMap } = useCborCollection()
|
|
412
|
+
|
|
413
|
+
// Indefinite map: {_ BREAK}
|
|
414
|
+
const emptyIndefinite = 'bfff'
|
|
415
|
+
|
|
416
|
+
const result = parseMap(emptyIndefinite, { allowIndefinite: true })
|
|
417
|
+
|
|
418
|
+
expect(result.value).toEqual(new Map())
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
|
|
422
|
+
describe('Indefinite-Length in Strict Mode', () => {
|
|
423
|
+
it('should reject indefinite arrays in canonical/strict mode', () => {
|
|
424
|
+
const { parseArray } = useCborCollection()
|
|
425
|
+
|
|
426
|
+
const indefinite = '9f010203ff'
|
|
427
|
+
|
|
428
|
+
expect(() => parseArray(indefinite, { validateCanonical: true }))
|
|
429
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
it('should reject indefinite maps in canonical/strict mode', () => {
|
|
433
|
+
const { parseMap } = useCborCollection()
|
|
434
|
+
|
|
435
|
+
const indefinite = 'bf616101ff'
|
|
436
|
+
|
|
437
|
+
expect(() => parseMap(indefinite, { validateCanonical: true }))
|
|
438
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
439
|
+
})
|
|
440
|
+
|
|
441
|
+
it('should enforce strict mode disallows indefinite encoding', () => {
|
|
442
|
+
const { parse } = useCborParser()
|
|
443
|
+
|
|
444
|
+
const indefiniteArray = '9f0102ff'
|
|
445
|
+
|
|
446
|
+
expect(() => parse(indefiniteArray, { strict: true }))
|
|
447
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
448
|
+
})
|
|
449
|
+
})
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
describe('Combined Security Protections', () => {
|
|
453
|
+
it('should handle all limits simultaneously', () => {
|
|
454
|
+
const { parse } = useCborParser()
|
|
455
|
+
|
|
456
|
+
// Complex nested structure within all limits
|
|
457
|
+
const complexCBOR = 'c1a2616183010203616282c240c300'
|
|
458
|
+
// Tag 1( {"a": [1,2,3], "b": [Tag 2(h''), Tag 3(0)]} )
|
|
459
|
+
|
|
460
|
+
const result = parse(complexCBOR, {
|
|
461
|
+
limits: {
|
|
462
|
+
maxDepth: 10,
|
|
463
|
+
maxTagDepth: 5,
|
|
464
|
+
maxBignumBytes: 1024,
|
|
465
|
+
maxArrayLength: 100,
|
|
466
|
+
maxMapSize: 100
|
|
467
|
+
}
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
expect(result.value.tag).toBe(1)
|
|
471
|
+
})
|
|
472
|
+
|
|
473
|
+
it('should reject when any single limit is exceeded', () => {
|
|
474
|
+
const { parseTag } = useCborTag()
|
|
475
|
+
|
|
476
|
+
// Exceeds tag depth but not bignum size
|
|
477
|
+
const deepTags = 'c0'.repeat(100) + '00'
|
|
478
|
+
|
|
479
|
+
expect(() => parseTag(deepTags, {
|
|
480
|
+
limits: {
|
|
481
|
+
maxTagDepth: 10,
|
|
482
|
+
maxBignumBytes: 10000 // Very permissive
|
|
483
|
+
}
|
|
484
|
+
})).toThrow(/tag nesting depth/i)
|
|
485
|
+
})
|
|
486
|
+
|
|
487
|
+
it('should use strict mode to enable all security validations', () => {
|
|
488
|
+
const { parse } = useCborParser()
|
|
489
|
+
|
|
490
|
+
// Non-canonical integer (wasteful encoding)
|
|
491
|
+
expect(() => parse('180a', { strict: true }))
|
|
492
|
+
.toThrow(/non-canonical/i)
|
|
493
|
+
|
|
494
|
+
// Indefinite length
|
|
495
|
+
expect(() => parse('9f01ff', { strict: true }))
|
|
496
|
+
.toThrow(/indefinite.*not allowed/i)
|
|
497
|
+
|
|
498
|
+
// Would reject duplicate keys if map had them
|
|
499
|
+
const duplicateMap = 'a2616101616102'
|
|
500
|
+
expect(() => parse(duplicateMap, { strict: true }))
|
|
501
|
+
.toThrow(/duplicate/i)
|
|
502
|
+
})
|
|
503
|
+
})
|