Skip to content

Commit 8a10498

Browse files
author
Will Bamberg
committed
Add examples for unwrapKey
1 parent 952590a commit 8a10498

File tree

4 files changed

+522
-0
lines changed

4 files changed

+522
-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 two buttons, one labeled "Unwrap Key" and the other labeled either "Sign" or "Encrypt", depending on the type of key we are unwrapping. The "Sign"/"Encrypt" button is disabled initially.</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, the "Sign"/"Encrypt" button is then enabled, and you can click it 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" 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" 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: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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

Comments
 (0)