test_crypto_encrypt.js (6512B)
1 // Test PushCrypto.encrypt() 2 "use strict"; 3 4 const { PushCrypto } = ChromeUtils.importESModule( 5 "resource://gre/modules/PushCrypto.sys.mjs" 6 ); 7 8 let from64 = v => { 9 // allow whitespace in the strings. 10 let stripped = v.replace(/ |\t|\r|\n/g, ""); 11 return new Uint8Array( 12 ChromeUtils.base64URLDecode(stripped, { padding: "reject" }) 13 ); 14 }; 15 16 let to64 = v => ChromeUtils.base64URLEncode(v, { pad: false }); 17 18 // A helper function to take a public key (as a buffer containing a 65-byte 19 // buffer of uncompressed EC points) and a private key (32byte buffer) and 20 // return 2 crypto keys. 21 async function importKeyPair(publicKeyBuffer, privateKeyBuffer) { 22 let jwk = { 23 kty: "EC", 24 crv: "P-256", 25 x: to64(publicKeyBuffer.slice(1, 33)), 26 y: to64(publicKeyBuffer.slice(33, 65)), 27 ext: true, 28 }; 29 let publicKey = await crypto.subtle.importKey( 30 "jwk", 31 jwk, 32 { name: "ECDH", namedCurve: "P-256" }, 33 true, 34 [] 35 ); 36 jwk.d = to64(privateKeyBuffer); 37 let privateKey = await crypto.subtle.importKey( 38 "jwk", 39 jwk, 40 { name: "ECDH", namedCurve: "P-256" }, 41 true, 42 ["deriveBits"] 43 ); 44 return { publicKey, privateKey }; 45 } 46 47 // The example from draft-ietf-webpush-encryption-09. 48 add_task(async function static_aes128gcm() { 49 let fixture = { 50 ciphertext: 51 from64(`DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27ml 52 mlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPT 53 pK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN`), 54 plaintext: new TextEncoder().encode( 55 "When I grow up, I want to be a watermelon" 56 ), 57 authSecret: from64("BTBZMqHH6r4Tts7J_aSIgg"), 58 receiver: { 59 private: from64("q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94"), 60 public: from64(`BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcx 61 aOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4`), 62 }, 63 sender: { 64 private: from64("yfWPiYE-n46HLnH0KqZOF1fJJU3MYrct3AELtAQ-oRw"), 65 public: from64(`BP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIg 66 Dll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A8`), 67 }, 68 salt: from64("DGv6ra1nlYgDCS1FRnbzlw"), 69 }; 70 71 let options = { 72 senderKeyPair: await importKeyPair( 73 fixture.sender.public, 74 fixture.sender.private 75 ), 76 salt: fixture.salt, 77 }; 78 79 let { ciphertext, encoding } = await PushCrypto.encrypt( 80 fixture.plaintext, 81 fixture.receiver.public, 82 fixture.authSecret, 83 options 84 ); 85 86 Assert.deepEqual(ciphertext, fixture.ciphertext); 87 Assert.equal(encoding, "aes128gcm"); 88 89 // and for fun, decrypt it and check the plaintext. 90 let recvKeyPair = await importKeyPair( 91 fixture.receiver.public, 92 fixture.receiver.private 93 ); 94 let jwk = await crypto.subtle.exportKey("jwk", recvKeyPair.privateKey); 95 let plaintext = await PushCrypto.decrypt( 96 jwk, 97 fixture.receiver.public, 98 fixture.authSecret, 99 { encoding: "aes128gcm" }, 100 ciphertext 101 ); 102 Assert.deepEqual(plaintext, fixture.plaintext); 103 }); 104 105 // This is how we expect real code to interact with .encrypt. 106 add_task(async function aes128gcm_simple() { 107 let [recvPublicKey, recvPrivateKey] = await PushCrypto.generateKeys(); 108 109 let message = new TextEncoder().encode("Fast for good."); 110 let authSecret = crypto.getRandomValues(new Uint8Array(16)); 111 let { ciphertext, encoding } = await PushCrypto.encrypt( 112 message, 113 recvPublicKey, 114 authSecret 115 ); 116 Assert.equal(encoding, "aes128gcm"); 117 // and decrypt it. 118 let plaintext = await PushCrypto.decrypt( 119 recvPrivateKey, 120 recvPublicKey, 121 authSecret, 122 { encoding }, 123 ciphertext 124 ); 125 deepEqual(message, plaintext); 126 }); 127 128 // Variable record size tests 129 add_task(async function aes128gcm_rs() { 130 let [recvPublicKey, recvPrivateKey] = await PushCrypto.generateKeys(); 131 132 for (let rs of [-1, 0, 1, 17]) { 133 let payload = "x".repeat(1024); 134 info(`testing expected encoder failure with rs=${rs}`); 135 let message = new TextEncoder().encode(payload); 136 let authSecret = crypto.getRandomValues(new Uint8Array(16)); 137 await Assert.rejects( 138 PushCrypto.encrypt(message, recvPublicKey, authSecret, { rs }), 139 /recordsize is too small/ 140 ); 141 } 142 for (let rs of [-1, 0, 1, 17]) { 143 let payload = "x".repeat(1024); 144 info(`testing expected decoder failure with rs=${rs}`); 145 let message = new TextEncoder().encode(payload); 146 let authSecret = crypto.getRandomValues(new Uint8Array(16)); 147 const { ciphertext, encoding } = await PushCrypto.encrypt( 148 message, 149 recvPublicKey, 150 authSecret 151 ); 152 // Given it doesn't make sense to encrypt with invalid param, 153 // we just overwrite the header with invalid value 154 new DataView(ciphertext.buffer).setUint32(16, rs); 155 await Assert.rejects( 156 PushCrypto.decrypt( 157 recvPrivateKey, 158 recvPublicKey, 159 authSecret, 160 { encoding }, 161 ciphertext 162 ), 163 /Record sizes smaller than 18 are invalid/ 164 ); 165 } 166 for (let rs of [18, 50, 1024, 4096, 16384]) { 167 info(`testing expected success with rs=${rs}`); 168 let payload = "x".repeat(rs * 3); 169 let message = new TextEncoder().encode(payload); 170 let authSecret = crypto.getRandomValues(new Uint8Array(16)); 171 let { ciphertext, encoding } = await PushCrypto.encrypt( 172 message, 173 recvPublicKey, 174 authSecret, 175 { rs } 176 ); 177 Assert.equal(encoding, "aes128gcm"); 178 // and decrypt it. 179 let plaintext = await PushCrypto.decrypt( 180 recvPrivateKey, 181 recvPublicKey, 182 authSecret, 183 { encoding }, 184 ciphertext 185 ); 186 deepEqual(message, plaintext); 187 } 188 }); 189 190 // And try and hit some edge-cases. 191 add_task(async function aes128gcm_edgecases() { 192 let [recvPublicKey, recvPrivateKey] = await PushCrypto.generateKeys(); 193 194 for (let size of [ 195 0, 196 4096 - 16, 197 4096 - 16 - 1, 198 4096 - 16 + 1, 199 4095, 200 4096, 201 4097, 202 10240, 203 ]) { 204 info(`testing encryption of ${size} byte payload`); 205 let message = new TextEncoder().encode("x".repeat(size)); 206 let authSecret = crypto.getRandomValues(new Uint8Array(16)); 207 let { ciphertext, encoding } = await PushCrypto.encrypt( 208 message, 209 recvPublicKey, 210 authSecret 211 ); 212 Assert.equal(encoding, "aes128gcm"); 213 // and decrypt it. 214 let plaintext = await PushCrypto.decrypt( 215 recvPrivateKey, 216 recvPublicKey, 217 authSecret, 218 { encoding }, 219 ciphertext 220 ); 221 deepEqual(message, plaintext); 222 } 223 });