File
Blob: src/workerd/api/node/tests/crypto_sign-test.js
| 1 | // Copyright (c) 2025 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | import { |
| 5 | createSign, |
| 6 | createVerify, |
| 7 | createPrivateKey, |
| 8 | createPublicKey, |
| 9 | generateKeyPairSync, |
| 10 | sign, |
| 11 | verify, |
| 12 | } from 'node:crypto'; |
| 13 | |
| 14 | import { ok, strictEqual, throws } from 'node:assert'; |
| 15 | |
| 16 | const rsaSig = |
| 17 | '26bb4d9641ecec048b791322c6427f62f3f4e21f7198e9e7544c8a56af40' + |
| 18 | '27bcfe1b306291188f97ced0e3ceaa5ded1ae1406ec30e46a18434e55dc6' + |
| 19 | 'c9f237e26b124bf7ec77e54483d7782b805aa9a74bbe0f4aa8658d7620e4' + |
| 20 | 'd3a305777b5dc8262c675bf23c8dc5acfe8c4fa1ca8acdd956cdcbdf1fb2' + |
| 21 | 'f879b37dc0ed8ec51815ff02b98eefde44ac79f886902ea69e5ed2561e9e' + |
| 22 | 'eb2a74ec1de677b285f8108f25e9f34cc826fbbd1ad091d231dc73eaf28a' + |
| 23 | 'e02f09ad84ec9a38d8a7e28bea26fb7d4db20eecd075b5b261ca7320af94' + |
| 24 | 'cd58ba4b26d895df4fd6f68bd4d82acdcb35557012e2f69739ce8cf4a66e' + |
| 25 | 'bf4550ee50f6c9cec642d66fef71495a'; |
| 26 | |
| 27 | export const rsaSignVerifyObjects = { |
| 28 | test(_, env) { |
| 29 | const key = createPrivateKey(env['rsa_private.pem']); |
| 30 | |
| 31 | throws(() => createSign(), { |
| 32 | message: |
| 33 | 'The "algorithm" argument must be of type string. Received undefined', |
| 34 | }); |
| 35 | |
| 36 | const signer = createSign('sha256'); |
| 37 | signer.update('hello world'); |
| 38 | |
| 39 | throws(() => signer.update(1), { |
| 40 | message: /argument must be of type string/, |
| 41 | }); |
| 42 | |
| 43 | throws(() => signer.sign(), { |
| 44 | message: 'No key provided to sign', |
| 45 | }); |
| 46 | |
| 47 | throws(() => signer.sign(env['rsa_public.pem']), { |
| 48 | message: 'Failed to parse private key', |
| 49 | }); |
| 50 | |
| 51 | const pub = createPublicKey(env['rsa_public.pem']); |
| 52 | throws(() => signer.sign(pub), { |
| 53 | code: 'ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE', |
| 54 | }); |
| 55 | |
| 56 | const signature = signer.sign(key, 'hex'); |
| 57 | |
| 58 | throws(() => signer.sign(key, 'hex'), { |
| 59 | message: 'Signing context has already been finalized', |
| 60 | }); |
| 61 | |
| 62 | strictEqual(signature, rsaSig); |
| 63 | |
| 64 | const verify = createVerify('sha256'); |
| 65 | |
| 66 | throws(() => createVerify(), { |
| 67 | message: |
| 68 | 'The "algorithm" argument must be of type string. Received undefined', |
| 69 | }); |
| 70 | |
| 71 | verify.update('hello world'); |
| 72 | |
| 73 | throws(() => verify.update(1), { |
| 74 | message: /argument must be of type string/, |
| 75 | }); |
| 76 | |
| 77 | throws(() => verify.verify(), { |
| 78 | message: 'No key provided to sign', |
| 79 | }); |
| 80 | |
| 81 | strictEqual(verify.verify(env['rsa_public.pem'], signature, 'hex'), true); |
| 82 | |
| 83 | throws(() => verify.verify(env['rsa_public.pem'], signature, 'hex'), { |
| 84 | message: 'Verification context has already been finalized', |
| 85 | }); |
| 86 | }, |
| 87 | }; |
| 88 | |
| 89 | export const rsaSignVerifyOneshot = { |
| 90 | test(_, env) { |
| 91 | const key = createPrivateKey(env['rsa_private.pem']); |
| 92 | const sig = sign('sha256', Buffer.from('hello world'), key); |
| 93 | strictEqual(sig.toString('hex'), rsaSig); |
| 94 | strictEqual( |
| 95 | verify('sha256', Buffer.from('hello world'), env['rsa_public.pem'], sig), |
| 96 | true |
| 97 | ); |
| 98 | }, |
| 99 | }; |
| 100 | |
| 101 | export const ed25519SignVerifyObjects = { |
| 102 | test(_, env) { |
| 103 | // Sign object is not allowed with ed25519 |
| 104 | throws( |
| 105 | () => { |
| 106 | const key = createPrivateKey(env['ed25519_private.pem']); |
| 107 | const signer = createSign('sha256'); |
| 108 | signer.update('hello world'); |
| 109 | const _signature = signer.sign(key, 'hex'); |
| 110 | }, |
| 111 | { |
| 112 | message: 'Failed to set signature digest', |
| 113 | } |
| 114 | ); |
| 115 | }, |
| 116 | }; |
| 117 | |
| 118 | export const ed25519SignVerifyOneshot = { |
| 119 | test(_, env) { |
| 120 | const key = createPrivateKey(env['ed25519_private.pem']); |
| 121 | const sig = sign(null, Buffer.from('hello world'), key); |
| 122 | strictEqual( |
| 123 | verify(null, Buffer.from('hello world'), env['ed25519_public.pem'], sig), |
| 124 | true |
| 125 | ); |
| 126 | }, |
| 127 | }; |
| 128 | |
| 129 | export const dsaSignVerifyObjects = { |
| 130 | test(_, env) { |
| 131 | const pvt = createPrivateKey(env['dsa_private.pem']); |
| 132 | const pub = createPublicKey(env['dsa_public.pem']); |
| 133 | const signer = createSign('sha256'); |
| 134 | const verifier = createVerify('sha256'); |
| 135 | signer.update(''); |
| 136 | verifier.update(''); |
| 137 | throws(() => signer.sign(pvt), { |
| 138 | message: 'Signing with DSA keys is not currently supported', |
| 139 | }); |
| 140 | throws(() => verifier.verify(pub, Buffer.alloc(0)), { |
| 141 | message: 'Verifying with DSA keys is not currently supported', |
| 142 | }); |
| 143 | throws(() => sign('sha256', Buffer.alloc(0), pvt), { |
| 144 | message: 'Signing with DSA keys is not currently supported', |
| 145 | }); |
| 146 | throws(() => verify('sha256', Buffer.alloc(0), pub, Buffer.alloc(0)), { |
| 147 | message: 'Verifying with DSA keys is not currently supported', |
| 148 | }); |
| 149 | }, |
| 150 | }; |
| 151 | |
| 152 | export const testSignLength = { |
| 153 | test() { |
| 154 | // Tests that generated signatures are not overly long. |
| 155 | const message = `Test message 123: ${Math.random().toString(36).substring(2, 15)}`; |
| 156 | |
| 157 | const keyPair = generateKeyPairSync('ec', { |
| 158 | namedCurve: 'prime256v1', |
| 159 | publicKeyEncoding: { |
| 160 | type: 'spki', |
| 161 | format: 'pem', |
| 162 | }, |
| 163 | privateKeyEncoding: { |
| 164 | type: 'pkcs8', |
| 165 | format: 'pem', |
| 166 | }, |
| 167 | }); |
| 168 | |
| 169 | for (let n = 0; n < 1000; n++) { |
| 170 | const sign = createSign('SHA256'); |
| 171 | sign.write(Buffer.from(message)); |
| 172 | sign.end(); |
| 173 | |
| 174 | const sig = sign.sign(keyPair.privateKey); |
| 175 | |
| 176 | const verify = createVerify('SHA256'); |
| 177 | verify.write(Buffer.from(message)); |
| 178 | verify.end(); |
| 179 | |
| 180 | // It will only verify correctly if the signature is the correct length. |
| 181 | ok(verify.verify(keyPair.publicKey, sig)); |
| 182 | } |
| 183 | }, |
| 184 | }; |
| 185 | |
| 186 | // Test that Web Crypto keys (from crypto.subtle) can be used with |
| 187 | // Node.js crypto sign/verify one-shot functions. |
| 188 | export const webCryptoKeySignVerify = { |
| 189 | async test() { |
| 190 | const keyPair = await crypto.subtle.generateKey( |
| 191 | { name: 'ECDSA', namedCurve: 'P-256' }, |
| 192 | true, |
| 193 | ['sign', 'verify'] |
| 194 | ); |
| 195 | const data = Buffer.from('hello world'); |
| 196 | const sig = sign('SHA256', data, keyPair.privateKey); |
| 197 | ok(sig instanceof Buffer); |
| 198 | ok(sig.length > 0); |
| 199 | ok(verify('SHA256', data, keyPair.publicKey, sig)); |
| 200 | }, |
| 201 | }; |
| 202 | |
| 203 | export const webCryptoKeySignVerifyP384 = { |
| 204 | async test() { |
| 205 | const keyPair = await crypto.subtle.generateKey( |
| 206 | { name: 'ECDSA', namedCurve: 'P-384' }, |
| 207 | true, |
| 208 | ['sign', 'verify'] |
| 209 | ); |
| 210 | const data = Buffer.from('test data'); |
| 211 | const sig = sign('SHA384', data, keyPair.privateKey); |
| 212 | ok(sig instanceof Buffer); |
| 213 | ok(sig.length > 0); |
| 214 | ok(verify('SHA384', data, keyPair.publicKey, sig)); |
| 215 | }, |
| 216 | }; |