Skip to content

Commit 765f8b1

Browse files
author
wbamberg
authored
Merge pull request mdn#38 from wbamberg/add-webcrypto-wrap-unwrap
Add examples for wrapKey and unwrapKey
2 parents 60b72ef + b7a5c48 commit 765f8b1

File tree

10 files changed

+1075
-0
lines changed

10 files changed

+1075
-0
lines changed

web-crypto/unwrap-key/index.html

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Web Crypto API example</title>
6+
<link rel="stylesheet" href="style.css">
7+
</head>
8+
9+
<body>
10+
<main>
11+
<h1>Web Crypto: unwrapKey</h1>
12+
13+
<section class="description">
14+
<p>This page shows the use of the <code>unwrapKey()</code> function of the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API">Web Crypto API</a>. It contains two separate examples:</p>
15+
<ul>
16+
<li>Raw/AES-GCM/AES-KW: unwrap an AES-GCM encryption key, that was encrypted using AES-KW and exported in "raw" format.</li>
17+
<li>PKCS #8/RSA-PSS/AES-GCM: unwrap an RSA-PSS signing key, that was encrypted using AES-GCM and exported in "pkcs8" format.</li>
18+
</ul>
19+
<p>Both examples have the same structure: you get a message and a button labeled "Unwrap Key".</p>
20+
<p>If you click "Unwrap Key" then the example unwraps its particular key. First it will ask for a password: the password you must enter is <strong>correct horse battery staple</strong>. It uses the password to derive the key that was used to encrypt the wrapped key. It then unwraps the wrapped key. If you get the password wrong, unwrapping will fail.</p>
21+
<p>If unwrapping was successful, a new button is displayed, that enables you to use the unwrapped key. For "raw/AES-GCM" the new button is labeled "Encrypt", and for "pkcs8/RSA-PSS" it's labeled "Sign". You can click the new button to use the unwrapped key to perform the appropriate operation and display the result.</p>
22+
</section>
23+
24+
<section class="examples">
25+
26+
<section class="unwrap-key raw">
27+
<h2 class="unwrap-key-heading">Raw/AES-GCM/AES-KW</h2>
28+
<section class="unwrap-key-controls">
29+
<div class="message-control">
30+
<label for="raw-message">Enter a message to encrypt:</label>
31+
<input type="text" id="raw-message" name="message" size="25"
32+
value="The owl hoots at midnight">
33+
</div>
34+
<div class="ciphertext">Ciphertext:<span class="ciphertext-value"></span></div>
35+
<input class="unwrap-key-button" type="button" value="Unwrap Key">
36+
<input class="encrypt-button hidden" type="button" value="Encrypt" disabled>
37+
</section>
38+
</section>
39+
40+
<section class="unwrap-key pkcs8">
41+
<h2 class="unwrap-key-heading">PKCS #8/RSA-PSS/AES-GCM</h2>
42+
<section class="unwrap-key-controls">
43+
<div class="message-control">
44+
<label for="pkcs8-message">Enter a message to sign:</label>
45+
<input type="text" id="pkcs8-message" name="message" size="25"
46+
value="The tiger prowls at dawn">
47+
</div>
48+
<div class="signature">Signature:<span class="signature-value"></span></div>
49+
<input class="unwrap-key-button" type="button" value="Unwrap Key">
50+
<input class="sign-button hidden" type="button" value="Sign" disabled>
51+
</section>
52+
</section>
53+
54+
</section>
55+
</main>
56+
57+
</body>
58+
<script src="raw.js"></script>
59+
<script src="pkcs8.js"></script>
60+
</html>

web-crypto/unwrap-key/pkcs8.js

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
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+
The unwrapped signing key.
84+
*/
85+
let signingKey;
86+
87+
const signButton = document.querySelector(".pkcs8 .sign-button");
88+
89+
/*
90+
Convert an array of byte values to an ArrayBuffer.
91+
*/
92+
function bytesToArrayBuffer(bytes) {
93+
const bytesAsArrayBuffer = new ArrayBuffer(bytes.length);
94+
const bytesUint8 = new Uint8Array(bytesAsArrayBuffer);
95+
bytesUint8.set(bytes);
96+
return bytesAsArrayBuffer;
97+
}
98+
99+
/*
100+
Get some key material to use as input to the deriveKey method.
101+
The key material is a password supplied by the user.
102+
*/
103+
function getKeyMaterial() {
104+
const password = window.prompt("Enter your password. The password is: 'correct horse battery staple'.");
105+
const enc = new TextEncoder();
106+
return window.crypto.subtle.importKey(
107+
"raw",
108+
enc.encode(password),
109+
{name: "PBKDF2"},
110+
false,
111+
["deriveBits", "deriveKey"]
112+
);
113+
}
114+
115+
/*
116+
Derive an AES-GCM key using PBKDF2.
117+
*/
118+
async function getUnwrappingKey() {
119+
// 1. get the key material (user-supplied password)
120+
const keyMaterial = await getKeyMaterial();
121+
// 2 initialize the salt parameter.
122+
// The salt must match the salt originally used to derive the key.
123+
// In this example it's supplied as a constant "saltBytes".
124+
const saltBuffer = bytesToArrayBuffer(saltBytes);
125+
// 3 derive the key from key material and salt
126+
return window.crypto.subtle.deriveKey(
127+
{
128+
"name": "PBKDF2",
129+
salt: saltBuffer,
130+
"iterations": 100000,
131+
"hash": "SHA-256"
132+
},
133+
keyMaterial,
134+
{ "name": "AES-GCM", "length": 256},
135+
true,
136+
[ "wrapKey", "unwrapKey" ]
137+
);
138+
}
139+
140+
/*
141+
Unwrap an RSA-PSS private signing key from an ArrayBuffer containing
142+
the raw bytes.
143+
Takes an array containing the bytes, and returns a Promise
144+
that will resolve to a CryptoKey representing the private key.
145+
*/
146+
async function unwrapPrivateKey(wrappedKey) {
147+
// 1. get the unwrapping key
148+
const unwrappingKey = await getUnwrappingKey();
149+
// 2. initialize the wrapped key
150+
const wrappedKeyBuffer = bytesToArrayBuffer(wrappedKey);
151+
// 3. initialize the iv
152+
const ivBuffer = bytesToArrayBuffer(ivBytes);
153+
// 4. unwrap the key
154+
return window.crypto.subtle.unwrapKey(
155+
"pkcs8", // import format
156+
wrappedKeyBuffer, // ArrayBuffer representing key to unwrap
157+
unwrappingKey, // CryptoKey representing key encryption key
158+
{ // algorithm params for key encryption key
159+
name: "AES-GCM",
160+
iv: ivBuffer
161+
},
162+
{ // algorithm params for key to unwrap
163+
name: "RSA-PSS",
164+
hash: "SHA-256"
165+
},
166+
true, // extractability of key to unwrap
167+
["sign"] // key usages for key to unwrap
168+
);
169+
}
170+
171+
/*
172+
Fetch the contents of the "message" textbox, and encode it
173+
in a form we can use for the sign operation.
174+
*/
175+
function getMessageEncoding() {
176+
const messageBox = document.querySelector("#raw-message");
177+
const message = messageBox.value;
178+
const enc = new TextEncoder();
179+
return enc.encode(message);
180+
}
181+
182+
/*
183+
Get the encoded message-to-sign, sign it and display a representation
184+
of the first part of it in the "signature" element.
185+
*/
186+
async function signMessage() {
187+
const encoded = getMessageEncoding();
188+
const signature = await window.crypto.subtle.sign(
189+
{
190+
name: "RSA-PSS",
191+
saltLength: 32,
192+
},
193+
signingKey,
194+
encoded
195+
);
196+
197+
const signatureValue = document.querySelector(".pkcs8 .signature-value");
198+
signatureValue.classList.add('fade-in');
199+
signatureValue.addEventListener('animationend', () => {
200+
signatureValue.classList.remove('fade-in');
201+
});
202+
const buffer = new Uint8Array(signature, 0, 5);
203+
signatureValue.textContent = `${buffer}...[${signature.byteLength} bytes total]`;
204+
}
205+
206+
/*
207+
Hide and disable the sign button if key unwrapping failed.
208+
*/
209+
function resetSignButton() {
210+
signButton.setAttribute("disabled", true);
211+
signButton.classList.add("hidden");
212+
}
213+
214+
/*
215+
Show and enable the sign button if key unwrapping succeeded.
216+
*/
217+
function enableSignButton() {
218+
signButton.classList.add('fade-in');
219+
signButton.addEventListener('animationend', () => {
220+
signButton.classList.remove('fade-in');
221+
});
222+
signButton.removeAttribute("disabled");
223+
signButton.classList.remove("hidden");
224+
}
225+
226+
/*
227+
When the user clicks "Unwrap Key"
228+
- unwrap the key
229+
- if unwrapping succeeded, enable the "Sign" button
230+
*/
231+
const unwrapKeyButton = document.querySelector(".pkcs8 .unwrap-key-button");
232+
unwrapKeyButton.addEventListener("click", async () => {
233+
try {
234+
signingKey = await unwrapPrivateKey(wrappedKeyBytes);
235+
enableSignButton();
236+
}
237+
catch(e) {
238+
resetSignButton();
239+
alert("Incorrect password");
240+
}
241+
});
242+
243+
signButton.addEventListener("click", signMessage);
244+
245+
})();

0 commit comments

Comments
 (0)