Skip to content

Commit 9492d73

Browse files
committed
crypto: protect against string collisions
1 parent 47db204 commit 9492d73

File tree

8 files changed

+31
-2
lines changed

8 files changed

+31
-2
lines changed

doc/api/errors.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,6 +2929,13 @@ instance.setEncoding('utf8');
29292929
An attempt was made to call [`stream.write()`][] after `stream.end()` has been
29302930
called.
29312931

2932+
<a id="ERR_STRING_NOT_WELL_FORMED"></a>
2933+
2934+
### `ERR_STRING_NOT_WELL_FORMED`
2935+
2936+
Input string was not well-formed Unicode and could not be converted to bytes
2937+
without collisions.
2938+
29322939
<a id="ERR_STRING_TOO_LONG"></a>
29332940

29342941
### `ERR_STRING_TOO_LONG`

lib/internal/crypto/hash.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const {
5353
const {
5454
validateEncoding,
5555
validateString,
56+
validateStringWellFormed,
5657
validateObject,
5758
validateUint32,
5859
} = require('internal/validators');
@@ -136,6 +137,7 @@ Hash.prototype.update = function update(data, encoding) {
136137

137138
if (typeof data === 'string') {
138139
validateEncoding(data, encoding);
140+
validateStringWellFormed(data);
139141
} else if (!isArrayBufferView(data)) {
140142
throw new ERR_INVALID_ARG_TYPE(
141143
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);
@@ -231,7 +233,9 @@ async function asyncDigest(algorithm, data) {
231233

232234
function hash(algorithm, input, options) {
233235
validateString(algorithm, 'algorithm');
234-
if (typeof input !== 'string' && !isArrayBufferView(input)) {
236+
if (typeof input === 'string') {
237+
validateStringWellFormed(input);
238+
} else if (!isArrayBufferView(input)) {
235239
throw new ERR_INVALID_ARG_TYPE('input', ['Buffer', 'TypedArray', 'DataView', 'string'], input);
236240
}
237241
let outputEncoding;

lib/internal/crypto/sig.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const {
1818
validateFunction,
1919
validateEncoding,
2020
validateString,
21+
validateStringWellFormed,
2122
} = require('internal/validators');
2223

2324
const {
@@ -71,6 +72,7 @@ Sign.prototype._write = function _write(chunk, encoding, callback) {
7172
Sign.prototype.update = function update(data, encoding) {
7273
if (typeof data === 'string') {
7374
validateEncoding(data, encoding);
75+
validateStringWellFormed(data);
7476
} else if (!isArrayBufferView(data)) {
7577
throw new ERR_INVALID_ARG_TYPE(
7678
'data', ['string', 'Buffer', 'TypedArray', 'DataView'], data);

lib/internal/crypto/util.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ const {
6767
validateArray,
6868
validateNumber,
6969
validateString,
70+
validateStringWellFormed,
7071
} = require('internal/validators');
7172

7273
const { Buffer } = require('buffer');
@@ -99,6 +100,7 @@ const kKeyObject = Symbol('kKeyObject');
99100
// to break them unnecessarily.
100101
function toBuf(val, encoding) {
101102
if (typeof val === 'string') {
103+
validateStringWellFormed(val);
102104
if (encoding === 'buffer')
103105
encoding = 'utf8';
104106
return Buffer.from(val, encoding);
@@ -147,6 +149,7 @@ const getArrayBufferOrView = hideStackFrames((buffer, name, encoding) => {
147149
if (isAnyArrayBuffer(buffer))
148150
return buffer;
149151
if (typeof buffer === 'string') {
152+
validateStringWellFormed(buffer);
150153
if (encoding === 'buffer')
151154
encoding = 'utf8';
152155
return Buffer.from(buffer, encoding);

lib/internal/errors.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,6 +1776,7 @@ E('ERR_STREAM_UNSHIFT_AFTER_END_EVENT',
17761776
'stream.unshift() after end event', Error);
17771777
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode', Error);
17781778
E('ERR_STREAM_WRITE_AFTER_END', 'write after end', Error);
1779+
E('ERR_STRING_NOT_WELL_FORMED', 'Invalid non-well formed string input', Error);
17791780
E('ERR_SYNTHETIC', 'JavaScript Callstack', Error);
17801781
E('ERR_SYSTEM_ERROR', 'A system error occurred', SystemError, HideStackFramesError);
17811782
E('ERR_TEST_FAILURE', function(error, failureType) {

lib/internal/validators.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const {
1616
ObjectPrototypeHasOwnProperty,
1717
RegExpPrototypeExec,
1818
String,
19+
StringPrototypeIsWellFormed,
1920
StringPrototypeToUpperCase,
2021
StringPrototypeTrim,
2122
} = primordials;
@@ -27,6 +28,7 @@ const {
2728
ERR_INVALID_THIS: { HideStackFramesError: ERR_INVALID_THIS },
2829
ERR_OUT_OF_RANGE: { HideStackFramesError: ERR_OUT_OF_RANGE },
2930
ERR_SOCKET_BAD_PORT: { HideStackFramesError: ERR_SOCKET_BAD_PORT },
31+
ERR_STRING_NOT_WELL_FORMED: { HideStackFramesError: ERR_STRING_NOT_WELL_FORMED },
3032
ERR_UNKNOWN_SIGNAL: { HideStackFramesError: ERR_UNKNOWN_SIGNAL },
3133
},
3234
hideStackFrames,
@@ -164,6 +166,14 @@ const validateString = hideStackFrames((value, name) => {
164166
throw new ERR_INVALID_ARG_TYPE(name, 'string', value);
165167
});
166168

169+
/**
170+
* @param {string} data
171+
*/
172+
const validateStringWellFormed = hideStackFrames((value) => {
173+
if (typeof value === 'string' && StringPrototypeIsWellFormed(value)) return;
174+
throw new ERR_STRING_NOT_WELL_FORMED();
175+
});
176+
167177
/**
168178
* @callback validateNumber
169179
* @param {*} value
@@ -643,6 +653,7 @@ module.exports = {
643653
validatePort,
644654
validateSignalName,
645655
validateString,
656+
validateStringWellFormed,
646657
validateUint32,
647658
validateUndefined,
648659
validateUnion,

test/parallel/test-crypto-strings.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ const signing = [
7676
{ type: 'ed25519', hashes: [null] },
7777
];
7878

79-
const expectedError = Error; // TODO
79+
const expectedError = /ERR_STRING_NOT_WELL_FORMED/;
8080

8181
async function testOneArg(f) {
8282
assert(f.length === 1 || f.length === 2);

typings/primordials.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ declare namespace primordials {
409409
export const StringPrototypeFontcolor: UncurryThis<typeof String.prototype.fontcolor>
410410
export const StringPrototypeFontsize: UncurryThis<typeof String.prototype.fontsize>
411411
export const StringPrototypeFixed: UncurryThis<typeof String.prototype.fixed>
412+
export const StringPrototypeIsWellFormed: UncurryThis<typeof String.prototype.isWellFormed>
412413
export const StringPrototypeIncludes: UncurryThis<typeof String.prototype.includes>
413414
export const StringPrototypeIndexOf: UncurryThis<typeof String.prototype.indexOf>
414415
export const StringPrototypeItalics: UncurryThis<typeof String.prototype.italics>

0 commit comments

Comments
 (0)