◐ Shell
clean mode source ↗

crypto: add ChaCha20-Poly1305 Web Cryptography algorithm · nodejs/node@3f47a2f

1+

'use strict';

2+3+

const {

4+

ArrayBufferIsView,

5+

ArrayBufferPrototypeSlice,

6+

ArrayFrom,

7+

PromiseReject,

8+

SafeSet,

9+

TypedArrayPrototypeSlice,

10+

} = primordials;

11+12+

const {

13+

ChaCha20Poly1305CipherJob,

14+

KeyObjectHandle,

15+

kCryptoJobAsync,

16+

kWebCryptoCipherDecrypt,

17+

kWebCryptoCipherEncrypt,

18+

} = internalBinding('crypto');

19+20+

const {

21+

hasAnyNotIn,

22+

jobPromise,

23+

validateKeyOps,

24+

kHandle,

25+

kKeyObject,

26+

} = require('internal/crypto/util');

27+28+

const {

29+

lazyDOMException,

30+

promisify,

31+

} = require('internal/util');

32+33+

const {

34+

InternalCryptoKey,

35+

SecretKeyObject,

36+

createSecretKey,

37+

} = require('internal/crypto/keys');

38+39+

const {

40+

randomBytes: _randomBytes,

41+

} = require('internal/crypto/random');

42+43+

const randomBytes = promisify(_randomBytes);

44+45+

function validateKeyLength(length) {

46+

if (length !== 256)

47+

throw lazyDOMException('Invalid key length', 'DataError');

48+

}

49+50+

function c20pCipher(mode, key, data, algorithm) {

51+

let tag;

52+

switch (mode) {

53+

case kWebCryptoCipherDecrypt: {

54+

const slice = ArrayBufferIsView(data) ?

55+

TypedArrayPrototypeSlice : ArrayBufferPrototypeSlice;

56+57+

if (data.byteLength < 16) {

58+

return PromiseReject(lazyDOMException(

59+

'The provided data is too small.',

60+

'OperationError'));

61+

}

62+63+

tag = slice(data, -16);

64+

data = slice(data, 0, -16);

65+

break;

66+

}

67+

case kWebCryptoCipherEncrypt:

68+

tag = 16;

69+

break;

70+

}

71+72+

return jobPromise(() => new ChaCha20Poly1305CipherJob(

73+

kCryptoJobAsync,

74+

mode,

75+

key[kKeyObject][kHandle],

76+

data,

77+

algorithm.iv,

78+

tag,

79+

algorithm.additionalData));

80+

}

81+82+

async function c20pGenerateKey(algorithm, extractable, keyUsages) {

83+

const { name } = algorithm;

84+85+

const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];

86+87+

const usagesSet = new SafeSet(keyUsages);

88+

if (hasAnyNotIn(usagesSet, checkUsages)) {

89+

throw lazyDOMException(

90+

`Unsupported key usage for a ${algorithm.name} key`,

91+

'SyntaxError');

92+

}

93+94+

const keyData = await randomBytes(32).catch((err) => {

95+

throw lazyDOMException(

96+

'The operation failed for an operation-specific reason' +

97+

`[${err.message}]`,

98+

{ name: 'OperationError', cause: err });

99+

});

100+101+

return new InternalCryptoKey(

102+

createSecretKey(keyData),

103+

{ name },

104+

ArrayFrom(usagesSet),

105+

extractable);

106+

}

107+108+

function c20pImportKey(

109+

algorithm,

110+

format,

111+

keyData,

112+

extractable,

113+

keyUsages) {

114+

const { name } = algorithm;

115+

const checkUsages = ['encrypt', 'decrypt', 'wrapKey', 'unwrapKey'];

116+117+

const usagesSet = new SafeSet(keyUsages);

118+

if (hasAnyNotIn(usagesSet, checkUsages)) {

119+

throw lazyDOMException(

120+

`Unsupported key usage for a ${algorithm.name} key`,

121+

'SyntaxError');

122+

}

123+124+

let keyObject;

125+

switch (format) {

126+

case 'KeyObject': {

127+

keyObject = keyData;

128+

break;

129+

}

130+

case 'raw-secret': {

131+

keyObject = createSecretKey(keyData);

132+

break;

133+

}

134+

case 'jwk': {

135+

if (!keyData.kty)

136+

throw lazyDOMException('Invalid keyData', 'DataError');

137+138+

if (keyData.kty !== 'oct')

139+

throw lazyDOMException('Invalid JWK "kty" Parameter', 'DataError');

140+141+

if (usagesSet.size > 0 &&

142+

keyData.use !== undefined &&

143+

keyData.use !== 'enc') {

144+

throw lazyDOMException('Invalid JWK "use" Parameter', 'DataError');

145+

}

146+147+

validateKeyOps(keyData.key_ops, usagesSet);

148+149+

if (keyData.ext !== undefined &&

150+

keyData.ext === false &&

151+

extractable === true) {

152+

throw lazyDOMException(

153+

'JWK "ext" Parameter and extractable mismatch',

154+

'DataError');

155+

}

156+157+

const handle = new KeyObjectHandle();

158+

try {

159+

handle.initJwk(keyData);

160+

} catch (err) {

161+

throw lazyDOMException(

162+

'Invalid keyData', { name: 'DataError', cause: err });

163+

}

164+165+

if (keyData.alg !== undefined && keyData.alg !== 'C20P') {

166+

throw lazyDOMException(

167+

'JWK "alg" does not match the requested algorithm',

168+

'DataError');

169+

}

170+171+

keyObject = new SecretKeyObject(handle);

172+

break;

173+

}

174+

default:

175+

return undefined;

176+

}

177+178+

validateKeyLength(keyObject.symmetricKeySize * 8);

179+180+

return new InternalCryptoKey(

181+

keyObject,

182+

{ name },

183+

keyUsages,

184+

extractable);

185+

}

186+187+

module.exports = {

188+

c20pCipher,

189+

c20pGenerateKey,

190+

c20pImportKey,

191+

};