◐ Shell
clean mode source ↗

crypto: support ML-DSA in Web Cryptography · nodejs/node@a553822

1+

'use strict';

2+3+

const {

4+

SafeSet,

5+

} = primordials;

6+7+

const { Buffer } = require('buffer');

8+9+

const {

10+

KeyObjectHandle,

11+

SignJob,

12+

kCryptoJobAsync,

13+

kKeyTypePrivate,

14+

kKeyTypePublic,

15+

kSignJobModeSign,

16+

kSignJobModeVerify,

17+

} = internalBinding('crypto');

18+19+

const {

20+

codes: {

21+

ERR_CRYPTO_INVALID_JWK,

22+

},

23+

} = require('internal/errors');

24+25+

const {

26+

getUsagesUnion,

27+

hasAnyNotIn,

28+

jobPromise,

29+

validateKeyOps,

30+

kHandle,

31+

kKeyObject,

32+

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

33+34+

const {

35+

lazyDOMException,

36+

promisify,

37+

} = require('internal/util');

38+39+

const {

40+

generateKeyPair: _generateKeyPair,

41+

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

42+43+

const {

44+

InternalCryptoKey,

45+

PrivateKeyObject,

46+

PublicKeyObject,

47+

createPublicKey,

48+

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

49+50+

const generateKeyPair = promisify(_generateKeyPair);

51+52+

function verifyAcceptableMlDsaKeyUse(name, isPublic, usages) {

53+

const checkSet = isPublic ? ['verify'] : ['sign'];

54+

if (hasAnyNotIn(usages, checkSet)) {

55+

throw lazyDOMException(

56+

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

57+

'SyntaxError');

58+

}

59+

}

60+61+

function createMlDsaRawKey(name, keyData, isPublic) {

62+

const handle = new KeyObjectHandle();

63+

const keyType = isPublic ? kKeyTypePublic : kKeyTypePrivate;

64+

if (!handle.initMlDsaRaw(name, keyData, keyType)) {

65+

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

66+

}

67+68+

return isPublic ? new PublicKeyObject(handle) : new PrivateKeyObject(handle);

69+

}

70+71+

async function mlDsaGenerateKey(algorithm, extractable, keyUsages) {

72+

const { name } = algorithm;

73+74+

const usageSet = new SafeSet(keyUsages);

75+

if (hasAnyNotIn(usageSet, ['sign', 'verify'])) {

76+

throw lazyDOMException(

77+

`Unsupported key usage for an ${name} key`,

78+

'SyntaxError');

79+

}

80+81+

const keyPair = await generateKeyPair(name.toLowerCase()).catch((err) => {

82+

throw lazyDOMException(

83+

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

84+

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

85+

});

86+87+

const publicUsages = getUsagesUnion(usageSet, 'verify');

88+

const privateUsages = getUsagesUnion(usageSet, 'sign');

89+90+

const keyAlgorithm = { name };

91+92+

const publicKey =

93+

new InternalCryptoKey(

94+

keyPair.publicKey,

95+

keyAlgorithm,

96+

publicUsages,

97+

true);

98+99+

const privateKey =

100+

new InternalCryptoKey(

101+

keyPair.privateKey,

102+

keyAlgorithm,

103+

privateUsages,

104+

extractable);

105+106+

return { __proto__: null, privateKey, publicKey };

107+

}

108+109+

function mlDsaExportKey(key) {

110+

try {

111+

if (key.type === 'private') {

112+

const { priv } = key[kKeyObject][kHandle].exportJwk({}, false);

113+

return Buffer.alloc(32, priv, 'base64url').buffer;

114+

}

115+116+

const { pub } = key[kKeyObject][kHandle].exportJwk({}, false);

117+

return Buffer.alloc(Buffer.byteLength(pub, 'base64url'), pub, 'base64url').buffer;

118+

} catch (err) {

119+

throw lazyDOMException(

120+

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

121+

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

122+

}

123+

}

124+125+

function mlDsaImportKey(

126+

format,

127+

keyData,

128+

algorithm,

129+

extractable,

130+

keyUsages) {

131+132+

const { name } = algorithm;

133+

let keyObject;

134+

const usagesSet = new SafeSet(keyUsages);

135+

switch (format) {

136+

case 'KeyObject': {

137+

verifyAcceptableMlDsaKeyUse(name, keyData.type === 'public', usagesSet);

138+

keyObject = keyData;

139+

break;

140+

}

141+

case 'jwk': {

142+

if (!keyData.kty)

143+

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

144+

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

145+

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

146+

if (keyData.alg !== name)

147+

throw lazyDOMException(

148+

'JWK "alg" Parameter and algorithm name mismatch', 'DataError');

149+

const isPublic = keyData.priv === undefined;

150+151+

if (usagesSet.size > 0 && keyData.use !== undefined) {

152+

if (keyData.use !== 'sig')

153+

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

154+

}

155+156+

validateKeyOps(keyData.key_ops, usagesSet);

157+158+

if (keyData.ext !== undefined &&

159+

keyData.ext === false &&

160+

extractable === true) {

161+

throw lazyDOMException(

162+

'JWK "ext" Parameter and extractable mismatch',

163+

'DataError');

164+

}

165+166+

if (!isPublic && typeof keyData.pub !== 'string') {

167+

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

168+

}

169+170+

verifyAcceptableMlDsaKeyUse(

171+

name,

172+

isPublic,

173+

usagesSet);

174+175+

try {

176+

const publicKeyObject = createMlDsaRawKey(

177+

name,

178+

Buffer.from(keyData.pub, 'base64url'),

179+

true);

180+181+

if (isPublic) {

182+

keyObject = publicKeyObject;

183+

} else {

184+

keyObject = createMlDsaRawKey(

185+

name,

186+

Buffer.from(keyData.priv, 'base64url'),

187+

false);

188+189+

if (!createPublicKey(keyObject).equals(publicKeyObject)) {

190+

throw new ERR_CRYPTO_INVALID_JWK();

191+

}

192+

}

193+

} catch (err) {

194+

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

195+

}

196+

break;

197+

}

198+

case 'raw-public':

199+

case 'raw-seed': {

200+

const isPublic = format === 'raw-public';

201+

verifyAcceptableMlDsaKeyUse(name, isPublic, usagesSet);

202+203+

try {

204+

keyObject = createMlDsaRawKey(name, keyData, isPublic);

205+

} catch (err) {

206+

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

207+

}

208+

break;

209+

}

210+

default:

211+

return undefined;

212+

}

213+214+

if (keyObject.asymmetricKeyType !== name.toLowerCase()) {

215+

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

216+

}

217+218+

return new InternalCryptoKey(

219+

keyObject,

220+

{ name },

221+

keyUsages,

222+

extractable);

223+

}

224+225+

function mlDsaSignVerify(key, data, algorithm, signature) {

226+

const mode = signature === undefined ? kSignJobModeSign : kSignJobModeVerify;

227+

const type = mode === kSignJobModeSign ? 'private' : 'public';

228+229+

if (key.type !== type)

230+

throw lazyDOMException(`Key must be a ${type} key`, 'InvalidAccessError');

231+232+

return jobPromise(() => new SignJob(

233+

kCryptoJobAsync,

234+

mode,

235+

key[kKeyObject][kHandle],

236+

undefined,

237+

undefined,

238+

undefined,

239+

data,

240+

undefined,

241+

undefined,

242+

undefined,

243+

undefined,

244+

signature));

245+

}

246+247+

module.exports = {

248+

mlDsaExportKey,

249+

mlDsaImportKey,

250+

mlDsaGenerateKey,

251+

mlDsaSignVerify,

252+

};