|
| 1 | +(() => { |
| 2 | + |
| 3 | + /* |
| 4 | + Salt that is to be used in derivation of the key-wrapping key, |
| 5 | + alongside the password the user supplies. |
| 6 | + This must match the salt value that was originally used to derive |
| 7 | + the key. |
| 8 | + */ |
| 9 | + const saltBytes = [180,253,62,216,47,35,90,55,218,233,103,10,172,143,161,177]; |
| 10 | + |
| 11 | + /* |
| 12 | + IV that is to be used in decrypting the key to unwrap. |
| 13 | + This must the the same IV that was originally used to encrypt |
| 14 | + the key. |
| 15 | + */ |
| 16 | + const ivBytes = [212,187,26,247,172,51,37,151,27,177,249,142]; |
| 17 | + |
| 18 | + /* |
| 19 | + The wrapped key itself. |
| 20 | + */ |
| 21 | + const wrappedKeyBytes = [6,155,182,208,7,141,44,18,3,151,58,126,68,100,252, |
| 22 | + 225,241,11,25,201,153,171,102,174,150,29,62,195,110,138,106,109,14,6,108, |
| 23 | + 148,104,221,22,93,102,221,146,25,65,112,4,140,79,194,164,163,156,250,108, |
| 24 | + 11,14,220,78,194,161,17,14,57,121,70,13,28,220,210,78,32,46,217,36,165,220, |
| 25 | + 170,244,152,214,150,83,2,138,128,11,251,227,213,72,100,158,10,162,40,195, |
| 26 | + 60,248,77,37,156,34,10,213,171,67,147,73,231,31,63,80,176,103,206,187,164, |
| 27 | + 214,250,49,223,185,5,48,241,17,1,253,59,185,181,209,255,42,223,175,90,159, |
| 28 | + 174,169,205,156,120,195,1,135,165,226,46,119,27,97,183,23,197,227,85,138, |
| 29 | + 235,79,158,167,59,62,194,34,210,214,240,215,101,233,63,138,53,87,253,189, |
| 30 | + 27,66,150,76,242,76,102,174,179,163,184,205,11,161,224,19,110,34,175,192, |
| 31 | + 101,117,169,86,66,56,241,128,13,156,165,125,139,110,138,50,108,129,251,137, |
| 32 | + 26,186,110,117,113,207,179,59,213,18,175,14,203,192,2,97,131,125,167,227, |
| 33 | + 182,87,72,123,54,156,60,195,88,224,96,46,126,245,251,247,147,110,147,173, |
| 34 | + 82,106,93,210,55,71,127,133,41,37,181,17,106,16,158,220,136,43,75,133,96, |
| 35 | + 240,151,116,40,44,254,2,32,74,226,193,172,48,211,71,109,163,143,30,92,28, |
| 36 | + 30,183,25,16,176,207,77,93,139,242,114,91,218,126,123,234,18,9,245,53,46, |
| 37 | + 172,215,62,92,249,191,17,27,0,58,151,33,23,169,93,177,253,152,147,198,196, |
| 38 | + 226,42,202,166,99,250,127,40,221,196,121,195,198,235,30,159,159,95,182,107, |
| 39 | + 175,137,177,49,72,63,131,162,198,186,22,255,230,237,195,56,147,177,101,52, |
| 40 | + 227,125,32,180,242,47,92,212,6,148,218,107,125,137,123,15,51,107,159,228, |
| 41 | + 238,212,60,54,184,48,110,248,252,208,46,23,149,78,169,201,68,242,193,251, |
| 42 | + 156,227,42,90,109,102,172,61,207,124,96,98,79,37,218,16,212,139,162,0,183, |
| 43 | + 235,171,75,18,84,160,120,173,156,187,99,24,58,88,213,148,24,193,111,75,169, |
| 44 | + 10,158,207,148,84,249,156,248,19,221,2,175,1,8,74,221,212,244,123,34,223, |
| 45 | + 175,54,166,101,51,175,141,80,87,9,146,72,223,46,251,199,192,2,22,125,16,15, |
| 46 | + 99,26,159,165,133,172,169,26,236,44,86,182,162,81,143,249,15,207,12,232,15, |
| 47 | + 205,199,78,133,199,19,232,183,33,183,72,117,72,27,43,254,13,17,252,1,143, |
| 48 | + 137,154,10,4,77,85,24,85,143,200,81,76,171,43,124,42,191,150,70,10,90,178, |
| 49 | + 198,40,233,233,225,146,231,209,254,2,90,216,5,97,105,204,82,88,81,99,92, |
| 50 | + 159,116,192,223,148,252,12,24,197,211,187,212,98,252,201,154,184,65,54,47, |
| 51 | + 13,106,151,168,208,112,212,74,204,36,233,98,104,58,103,1,194,13,26,109,101, |
| 52 | + 60,42,3,215,20,25,99,176,63,28,112,102,121,190,96,198,228,196,78,38,82,37, |
| 53 | + 248,42,150,115,6,10,22,101,42,237,175,69,232,212,231,40,193,70,211,245,106, |
| 54 | + 231,175,150,88,105,170,139,238,196,64,218,250,47,165,22,36,196,161,30,79, |
| 55 | + 175,14,133,88,129,182,56,140,147,168,134,91,68,172,110,195,134,156,68,78, |
| 56 | + 249,215,68,250,11,23,70,59,156,99,75,249,159,84,16,206,93,16,130,34,66,210, |
| 57 | + 82,252,53,251,84,59,226,212,154,15,20,163,58,228,109,53,214,151,237,10,169, |
| 58 | + 107,180,123,174,159,182,8,240,115,115,220,131,128,79,80,61,133,58,24,98, |
| 59 | + 193,225,56,36,159,254,199,49,44,160,28,81,140,163,24,143,114,31,237,235, |
| 60 | + 250,83,72,215,44,232,182,45,39,182,193,248,65,174,186,52,219,30,198,48,1, |
| 61 | + 134,151,81,114,38,124,7,213,205,138,28,22,216,76,46,224,241,88,156,7,62, |
| 62 | + 23,104,34,54,25,156,93,212,133,182,61,93,255,195,68,244,234,53,132,151,140, |
| 63 | + 72,146,127,113,227,34,243,218,222,47,218,113,18,173,203,158,133,90,156,214, |
| 64 | + 77,20,113,1,231,164,52,55,69,132,24,68,131,212,7,153,34,179,113,156,81, |
| 65 | + 127,83,57,29,195,90,64,211,115,202,188,5,42,188,142,203,109,231,53,206,72, |
| 66 | + 220,90,23,12,1,178,122,60,221,68,6,14,154,108,203,171,142,159,249,13,55,52, |
| 67 | + 110,214,33,147,164,181,50,79,164,200,83,251,40,105,223,50,0,115,240,146,23, |
| 68 | + 122,80,204,169,38,198,154,31,29,23,236,39,35,131,147,242,163,138,158,236, |
| 69 | + 117,7,108,33,132,98,50,111,46,146,251,82,34,85,5,130,237,67,40,170,235,124, |
| 70 | + 92,66,71,239,12,97,136,251,1,206,13,51,232,92,46,35,95,5,123,24,183,99,243, |
| 71 | + 124,75,155,89,66,54,72,17,255,99,137,199,232,204,9,248,78,35,218,136,117, |
| 72 | + 239,102,240,187,40,89,244,140,109,229,120,116,54,207,171,11,248,190,199,81, |
| 73 | + 53,109,8,188,51,93,165,34,255,165,191,198,130,220,41,192,166,194,69,104, |
| 74 | + 124,158,122,236,176,24,60,87,240,42,158,143,37,143,208,155,249,230,21,4, |
| 75 | + 230,56,194,62,235,132,14,50,180,216,134,28,25,159,64,199,161,236,60,233, |
| 76 | + 160,172,68,169,2,5,252,190,20,54,115,248,63,93,107,156,8,96,85,32,189,118, |
| 77 | + 66,114,126,64,203,97,235,13,18,102,192,51,59,5,122,171,96,129,40,32,154,4, |
| 78 | + 191,234,75,184,112,201,244,110,50,216,44,88,139,175,58,112,7,52,25,64,112, |
| 79 | + 40,148,187,39,234,96,151,16,158,114,113,109,164,47,108,94,148,35,232,221, |
| 80 | + 33,110,126,170,25,234,45,165,180,210,193,120,247,155,127]; |
| 81 | + |
| 82 | + /* |
| 83 | + Convert an array of byte values to an ArrayBuffer. |
| 84 | + */ |
| 85 | + function bytesToArrayBuffer(bytes) { |
| 86 | + const bytesAsArrayBuffer = new ArrayBuffer(bytes.length); |
| 87 | + const bytesUint8 = new Uint8Array(bytesAsArrayBuffer); |
| 88 | + bytesUint8.set(bytes); |
| 89 | + return bytesAsArrayBuffer; |
| 90 | + } |
| 91 | + |
| 92 | + /* |
| 93 | + Get some key material to use as input to the deriveKey method. |
| 94 | + The key material is a password supplied by the user. |
| 95 | + */ |
| 96 | + function getKeyMaterial() { |
| 97 | + let password = window.prompt("Enter your password"); |
| 98 | + let enc = new TextEncoder(); |
| 99 | + return window.crypto.subtle.importKey( |
| 100 | + "raw", |
| 101 | + enc.encode(password), |
| 102 | + {name: "PBKDF2"}, |
| 103 | + false, |
| 104 | + ["deriveBits", "deriveKey"] |
| 105 | + ); |
| 106 | + } |
| 107 | + |
| 108 | + /* |
| 109 | + Derive an AES-GCM key using PBKDF2. |
| 110 | + */ |
| 111 | + async function getUnwrappingKey() { |
| 112 | + // 1. get the key material (user-supplied password) |
| 113 | + const keyMaterial = await getKeyMaterial(); |
| 114 | + // 2 initialize the salt parameter. |
| 115 | + // The salt must match the salt originally used to derive the key. |
| 116 | + // In this example it's supplied as a constant "saltBytes". |
| 117 | + const saltBuffer = bytesToArrayBuffer(saltBytes); |
| 118 | + // 3 derive the key from key material and salt |
| 119 | + return window.crypto.subtle.deriveKey( |
| 120 | + { |
| 121 | + "name": "PBKDF2", |
| 122 | + salt: saltBuffer, |
| 123 | + "iterations": 100000, |
| 124 | + "hash": "SHA-256" |
| 125 | + }, |
| 126 | + keyMaterial, |
| 127 | + { "name": "AES-GCM", "length": 256}, |
| 128 | + true, |
| 129 | + [ "wrapKey", "unwrapKey" ] |
| 130 | + ); |
| 131 | + } |
| 132 | + |
| 133 | + /* |
| 134 | + Unwrap an RSA-PSS private signing key from an ArrayBuffer containing |
| 135 | + the raw bytes. |
| 136 | + Takes an array containing the bytes, and returns a Promise |
| 137 | + that will resolve to a CryptoKey representing the private key. |
| 138 | + */ |
| 139 | + async function unwrapPrivateKey(wrappedKey) { |
| 140 | + // 1. get the unwrapping key |
| 141 | + const unwrappingKey = await getUnwrappingKey(); |
| 142 | + // 2. initialize the wrapped key |
| 143 | + const wrappedKeyBuffer = bytesToArrayBuffer(wrappedKey); |
| 144 | + // 3. initialize the iv |
| 145 | + const ivBuffer = bytesToArrayBuffer(ivBytes); |
| 146 | + // 4. unwrap the key |
| 147 | + return window.crypto.subtle.unwrapKey( |
| 148 | + "pkcs8", // import format |
| 149 | + wrappedKeyBuffer, // ArrayBuffer representing key to unwrap |
| 150 | + unwrappingKey, // CryptoKey representing key encryption key |
| 151 | + { // algorithm params for key encryption key |
| 152 | + name: "AES-GCM", |
| 153 | + iv: ivBuffer |
| 154 | + }, |
| 155 | + { // algorithm params for key to unwrap |
| 156 | + name: "RSA-PSS", |
| 157 | + hash: "SHA-256" |
| 158 | + }, |
| 159 | + true, // extractability of key to unwrap |
| 160 | + ["sign"] // key usages for key to unwrap |
| 161 | + ); |
| 162 | + } |
| 163 | + |
| 164 | + /* |
| 165 | + Fetch the contents of the "message" textbox, and encode it |
| 166 | + in a form we can use for the sign operation. |
| 167 | + */ |
| 168 | + function getMessageEncoding() { |
| 169 | + const messageBox = document.querySelector("#raw-message"); |
| 170 | + const message = messageBox.value; |
| 171 | + const enc = new TextEncoder(); |
| 172 | + return enc.encode(message); |
| 173 | + } |
| 174 | + |
| 175 | + /* |
| 176 | + Get the encoded message-to-sign, sign it and display a representation |
| 177 | + of the first part of it in the "signature" element. |
| 178 | + */ |
| 179 | + async function signMessage(privateKey) { |
| 180 | + const encoded = getMessageEncoding(); |
| 181 | + const signature = await window.crypto.subtle.sign( |
| 182 | + { |
| 183 | + name: "RSA-PSS", |
| 184 | + saltLength: 32, |
| 185 | + }, |
| 186 | + privateKey, |
| 187 | + encoded |
| 188 | + ); |
| 189 | + |
| 190 | + const signatureValue = document.querySelector(".pkcs8 .signature-value"); |
| 191 | + signatureValue.classList.add('fade-in'); |
| 192 | + signatureValue.addEventListener('animationend', () => { |
| 193 | + signatureValue.classList.remove('fade-in'); |
| 194 | + }); |
| 195 | + const buffer = new Uint8Array(signature, 0, 5); |
| 196 | + signatureValue.textContent = `${buffer}...[${signature.byteLength} bytes total]`; |
| 197 | + } |
| 198 | + |
| 199 | + /* |
| 200 | + When the user clicks "Unwrap Key" |
| 201 | + - unwrap the key |
| 202 | + - enable the "Sign" button |
| 203 | + - add a listener to "Sign" that uses the key. |
| 204 | + */ |
| 205 | + const unwrapKeyButton = document.querySelector(".pkcs8 .unwrap-key-button"); |
| 206 | + unwrapKeyButton.addEventListener("click", async () => { |
| 207 | + const privateKey = await unwrapPrivateKey(wrappedKeyBytes); |
| 208 | + const signButton = document.querySelector(".pkcs8 .sign-button"); |
| 209 | + signButton.removeAttribute("disabled"); |
| 210 | + signButton.addEventListener("click", () => { |
| 211 | + signMessage(privateKey); |
| 212 | + }); |
| 213 | + }); |
| 214 | + |
| 215 | +})(); |
0 commit comments