Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document JWK keys export/import with examples #1523

Merged
merged 3 commits into from Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -20,7 +20,7 @@ exportKey(format, key)

| Name | Type | Description |
| :------- | :------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------- |
| `format` | `string` | Defines the data format the key should be exported in. Currently supported formats: `raw`. |
| `format` | `string` | Defines the data format the key should be exported in. Currently supported formats: `raw`, `jwk`. |
| `key` | [CryptoKey](https://grafana.com/docs/k6/<K6_VERSION>/javascript-api/k6-experimental/webcrypto/cryptokey) | The [key](https://grafana.com/docs/k6/<K6_VERSION>/javascript-api/k6-experimental/webcrypto/cryptokey) to export. |

## Return Value
Expand Down Expand Up @@ -61,7 +61,7 @@ export default async function () {
const exportedKey = await crypto.subtle.exportKey('raw', generatedKey);

/**
* Reimport the key in raw format to verfiy its integrity.
* Reimport the key in raw format to verify its integrity.
*/
const importedKey = await crypto.subtle.importKey('raw', exportedKey, 'AES-CBC', true, [
'encrypt',
Expand Down
Expand Up @@ -18,7 +18,7 @@ importKey(format, keyData, algorithm, extractable, keyUsages)

| Name | Type | Description |
| :------------ | :-------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `format` | `string` | Defines the data format of the key to import. Currently supported formats: `raw`. |
| `format` | `string` | Defines the data format of the key to import. Currently supported formats: `raw`, `jwk`. |
| `keyData` | `ArrayBuffer`, `TypedArray` or `DataView` | the data to import the key from. |
| `algorithm` | a `string` or object with a single `name` string property | The algorithm to use to import the key. Currently supported algorithms: `AES-CBC`, `AES-GCM`, `AES-CTR`, and `HMAC`. |
| `extractable` | `boolean` | Indicates whether it will be possible to export the key using [exportKey](https://grafana.com/docs/k6/<K6_VERSION>/javascript-api/k6-experimental/webcrypto/subtlecrypto/exportkey). |
Expand All @@ -35,7 +35,9 @@ A `Promise` that resolves with the imported key as a [CryptoKey](https://grafana
| `SyntaxError` | Raised when the `keyUsages` parameter is empty but the key is of type `secret` or `private`. |
| `TypeError` | Raised when trying to use an invalid format, or if the `keyData` is not suited for that format. |

## Example
## Examples

### Round-trip key export/import

{{< code >}}

Expand All @@ -61,7 +63,7 @@ export default async function () {
const exportedKey = await crypto.subtle.exportKey('raw', generatedKey);

/**
* Reimport the key in raw format to verfiy its integrity.
* Reimport the key in raw format to verify its integrity.
*/
const importedKey = await crypto.subtle.importKey('raw', exportedKey, 'AES-CBC', true, [
'encrypt',
Expand All @@ -73,3 +75,150 @@ export default async function () {
```

{{< /code >}}

### Import a static raw key and decrypt transmitted data

This example demonstrates how to import a static `raw` key and decrypt some transmitted data (which contains initialization vector and encoded data) in `base64`.

{{< code >}}

```javascript
import { crypto } from 'k6/experimental/webcrypto';
import { b64decode } from 'k6/encoding';

export default async function () {
// transmitted data is the base64 of the initialization vector + encrypted data
// that unusually transmitted over the network
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the meaning here, could you expand a little bit on what you are trying to say here? 🤓 Is it that this data would not usually be transmitted?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to point out that in this example, transmittedData is the data that you got from somewhere, let's say in a response body, etc

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ooh I see. In that case, maybe we could reword it to: "this is data usually transmitted over the network".

Or include response body there as well as an example: "this is data usually transmitted over the network, such as a response body"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, I'm wondering if we should drop that part 😅

Actually, doesn't matter how data is received, essential details (in this example) that transmitted data is a concatenation of an initialization vector and encrypted data; some people could use this approach, that's why I used it to bring examples more real-world close

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a fair point! Actually, what if we added this context on line 81? Something along the lines of:

"This example demonstrates how to import a static raw key and decrypt some transmitted data in base64. The transmitted data in this example represents an initialization vector and encoded data, and in a real-world scenario, it can be a response body or other data received from a request."

It would keep the code sample concise but still give context to users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adjusted ✔️

const transmittedData = base64Decode(
'whzEN310mrlWIH/icf0dMquRZ2ENyfOzkvPuu92WR/9F8dbeFM8EGUVNIhaS'
);

// keyData is the key used to decrypt the data, that is usually stored in a secure location
// for the purpose of this example, we are using a static key
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
const keyData = new Uint8Array([
109, 151, 76, 33, 232, 253, 176, 90, 94, 40, 146, 227, 139, 208, 245, 139, 69, 215, 55, 197, 43,
122, 160, 178, 228, 104, 4, 115, 138, 159, 119, 49,
]);

try {
const result = await decrypt(keyData, transmittedData);

// should output decrypted message
// INFO[0000] result: 'my secret message' source=console
console.log("result: '" + result + "'");
} catch (e) {
console.log('Error: ' + JSON.stringify(e));
}
}

const decrypt = async (keyData, transmittedData) => {
const initializeVectorLength = 12;

// the first 12 bytes are the initialization vector
const iv = new Uint8Array(transmittedData.subarray(0, initializeVectorLength));

// the rest of the transmitted data is the encrypted data
const encryptedData = new Uint8Array(transmittedData.subarray(initializeVectorLength));

const importedKey = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'AES-GCM', length: '256' },
true,
['decrypt']
);

const plain = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
importedKey,
encryptedData
);

return arrayBufferToString(plain);
};

const arrayBufferToString = (buffer) => {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
};

const base64Decode = (base64String) => {
return new Uint8Array(b64decode(base64String));
};
```

{{< /code >}}

### Import a static JWK key and decrypt transmitted data

This example is almost the same as the previous one. Still, it demonstrates how to import a static `jwk` key and decrypt some transmitted data (which contains initialization vector and encoded data) in `base64`.
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved

{{< code >}}

```javascript
import { crypto } from 'k6/experimental/webcrypto';
import { b64decode } from 'k6/encoding';

export default async function () {
// transmitted data is the base64 of the initialization vector + encrypted data
// that unusually transmitted over the network
const transmittedData = base64Decode(
'drCfxl4O+5FcrHe8Bs0CvKlw3gZpv+S5if3zn7c4BJzHJ35QDFV4sJB0pbDT'
);

// keyData is the key used to decrypt the data, that is usually stored in a secure location
// for the purpose of this example, we are using a static key
olegbespalov marked this conversation as resolved.
Show resolved Hide resolved
const jwkKeyData = {
kty: 'oct',
ext: true,
key_ops: ['decrypt', 'encrypt'],
alg: 'A256GCM',
k: '9Id_8iG6FkGOWmc1S203vGVnTExtpDGxdQN7v7OV9Uc',
};

try {
const result = await decrypt(jwkKeyData, transmittedData);

// should output decrypted message
// INFO[0000] result: 'my secret message' source=console
console.log("result: '" + result + "'");
} catch (e) {
console.log('Error: ' + JSON.stringify(e));
}
}

const decrypt = async (keyData, transmittedData) => {
const initializeVectorLength = 12;

// the first 12 bytes are the initialization vector
const iv = new Uint8Array(transmittedData.subarray(0, initializeVectorLength));

// the rest of the transmitted data is the encrypted data
const encryptedData = new Uint8Array(transmittedData.subarray(initializeVectorLength));

const importedKey = await crypto.subtle.importKey(
'jwk',
keyData,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);

const plain = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: iv },
importedKey,
encryptedData
);

return arrayBufferToString(plain);
};

const arrayBufferToString = (buffer) => {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
};

const base64Decode = (base64String) => {
return new Uint8Array(b64decode(base64String));
};
```

{{< /code >}}