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

ECDH support #67

Merged
merged 7 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,19 @@ The current state of the project is that it is an experimental module of the Web

| API | AES-CBC | AES-GCM | AES-CTR | AES-KW | HMAC | ECDSA | ECDH | RSASSA-PKCS1-v1_5 | RSA-PSS | RSA-OAEP |
| :---------------------------- | :------ | :------ | :------ | :----- | :--- | :---- | :--- | :---------------- | :------ | :------- |
| `crypto.subtle.generateKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |
| `crypto.subtle.importKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |
| `crypto.subtle.exportKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |
| `crypto.subtle.generateKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |
| `crypto.subtle.importKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |
| `crypto.subtle.exportKey()` | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | | ❌ | ❌ | ❌ |

> [!WARNING]
> Currently, only the `raw` and `jwk` (JSON Web Key) formats are supported for import/export operations.
> Currently, only the `raw` and `jwk` (JSON Web Key) formats are supported for import/export operations for the `AES-*` and `HMAC` algorithms. `ECDH` has support for `pkcs8` and `raw` formats.

##### Key derivation

| API | ECDH | HKDF | PBKDF2 |
| :--------------------------- | :--- | :--- | :----- |
| `crypto.subtle.deriveKey()` | ❌ | ❌ | ❌ |
| `crypto.subtle.deriveBits()` | | ❌ | ❌ |
| `crypto.subtle.deriveBits()` | | ❌ | ❌ |

##### Key wrapping

Expand Down
58 changes: 58 additions & 0 deletions examples/derive_bits/derive-bits-ecdh.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
try {
mstoykov marked this conversation as resolved.
Show resolved Hide resolved
// Generate a key pair for Alice
const aliceKeyPair = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
true,
["deriveKey", "deriveBits"]
);

// Generate a key pair for Bob
const bobKeyPair = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
true,
["deriveKey", "deriveBits"]
);

// Derive shared secret for Alice
const aliceSharedSecret = await deriveSharedSecret(
aliceKeyPair.privateKey,
bobKeyPair.publicKey
);

// Derive shared secret for Bob
const bobSharedSecret = await deriveSharedSecret(
bobKeyPair.privateKey,
aliceKeyPair.publicKey
);

console.log("alice shared secret: " + printArrayBuffer(aliceSharedSecret));
console.log("bob shared secret: " + printArrayBuffer(bobSharedSecret));
} catch (err) {
console.log("Error: " + JSON.stringify(err));
}
}

async function deriveSharedSecret(privateKey, publicKey) {
return crypto.subtle.deriveBits(
{
name: "ECDH",
public: publicKey, // An ECDH public key from the other party
},
privateKey, // Your ECDH private key
256 // the number of bits to derive
);
}

const printArrayBuffer = (buffer) => {
let view = new Uint8Array(buffer);
return Array.from(view);
};
17 changes: 17 additions & 0 deletions examples/generateKey/generateKey-ecdh.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const key = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256"
},
true,
[
"deriveKey",
"deriveBits"
]
);

console.log(JSON.stringify(key))
}
29 changes: 29 additions & 0 deletions examples/import_export/export-ecdh-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const generatedKeyPair = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256",
},
true,
["deriveKey", "deriveBits"]
);

const exportedPrivateKey = await crypto.subtle.exportKey(
"pkcs8",
generatedKeyPair.privateKey
);
console.log("exported private key: " + printArrayBuffer(exportedPrivateKey));

const exportedPublicKey = await crypto.subtle.exportKey(
"raw",
generatedKeyPair.publicKey
);
console.log("exported public key: " + printArrayBuffer(exportedPublicKey));
}

const printArrayBuffer = (buffer) => {
let view = new Uint8Array(buffer);
return Array.from(view);
};
97 changes: 97 additions & 0 deletions examples/import_export/import-ecdh-key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const aliceKeyPair = await importKeys(
new Uint8Array([
4, 8, 249, 89, 225, 84, 28, 108, 246, 144, 7, 182, 109, 32, 155, 16, 102,
22, 66, 253, 148, 220, 48, 6, 106, 21, 123, 98, 229, 191, 20, 200, 35, 5,
208, 131, 136, 154, 125, 18, 20, 202, 231, 168, 184, 127, 53, 186, 6, 136,
114, 101, 127, 109, 179, 44, 96, 108, 193, 126, 217, 131, 163, 131, 135,
]),
new Uint8Array([
48, 129, 135, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42,
134, 72, 206, 61, 3, 1, 7, 4, 109, 48, 107, 2, 1, 1, 4, 32, 194, 150, 86,
186, 233, 47, 132, 192, 213, 56, 60, 179, 112, 7, 89, 65, 116, 88, 8, 158,
228, 172, 190, 234, 143, 152, 33, 175, 47, 0, 39, 79, 161, 68, 3, 66, 0,
4, 8, 249, 89, 225, 84, 28, 108, 246, 144, 7, 182, 109, 32, 155, 16, 102,
22, 66, 253, 148, 220, 48, 6, 106, 21, 123, 98, 229, 191, 20, 200, 35, 5,
208, 131, 136, 154, 125, 18, 20, 202, 231, 168, 184, 127, 53, 186, 6, 136,
114, 101, 127, 109, 179, 44, 96, 108, 193, 126, 217, 131, 163, 131, 135,
])
);

const bobKeyPair = await importKeys(
new Uint8Array([
4, 218, 134, 37, 137, 90, 68, 101, 112, 234, 68, 87, 110, 182, 85, 178,
161, 106, 223, 50, 150, 9, 155, 68, 191, 51, 138, 185, 186, 226, 211, 25,
203, 96, 193, 213, 68, 7, 181, 238, 52, 154, 113, 56, 76, 86, 44, 245,
128, 194, 103, 14, 81, 229, 124, 189, 13, 252, 138, 98, 196, 218, 39, 34,
42,
]),
new Uint8Array([
48, 129, 135, 2, 1, 0, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42,
134, 72, 206, 61, 3, 1, 7, 4, 109, 48, 107, 2, 1, 1, 4, 32, 59, 168, 213,
160, 115, 123, 19, 203, 62, 86, 50, 152, 17, 210, 42, 35, 174, 230, 191,
11, 65, 239, 223, 130, 73, 53, 161, 46, 9, 210, 50, 4, 161, 68, 3, 66, 0,
4, 218, 134, 37, 137, 90, 68, 101, 112, 234, 68, 87, 110, 182, 85, 178,
161, 106, 223, 50, 150, 9, 155, 68, 191, 51, 138, 185, 186, 226, 211, 25,
203, 96, 193, 213, 68, 7, 181, 238, 52, 154, 113, 56, 76, 86, 44, 245,
128, 194, 103, 14, 81, 229, 124, 189, 13, 252, 138, 98, 196, 218, 39, 34,
42,
])
);

console.log("alice: ", JSON.stringify(aliceKeyPair));
console.log("bob: ", JSON.stringify(bobKeyPair));

// Derive shared secret for Alice
const aliceSharedSecret = await deriveSharedSecret(
aliceKeyPair.privateKey,
bobKeyPair.publicKey
);

// Derive shared secret for Bob
const bobSharedSecret = await deriveSharedSecret(
bobKeyPair.privateKey,
aliceKeyPair.publicKey
);

console.log("alice shared secret: " + printArrayBuffer(aliceSharedSecret));
console.log("bob shared secret: " + printArrayBuffer(bobSharedSecret));
}

const importKeys = async (publicKeyData, privateKeyData) => {
const publicKey = await crypto.subtle.importKey(
"raw",
publicKeyData,
{ name: "ECDH", namedCurve: "P-256" },
true,
[]
);

const privateKey = await crypto.subtle.importKey(
"pkcs8",
privateKeyData,
{ name: "ECDH", namedCurve: "P-256" },
true,
["deriveKey", "deriveBits"]
);

return { publicKey: publicKey, privateKey: privateKey };
};

async function deriveSharedSecret(privateKey, publicKey) {
return crypto.subtle.deriveBits(
{
name: "ECDH",
public: publicKey, // An ECDH public key from the other party
},
privateKey, // Your ECDH private key
256 // the number of bits to derive
);
}

const printArrayBuffer = (buffer) => {
let view = new Uint8Array(buffer);
return Array.from(view);
};
2 changes: 1 addition & 1 deletion webcrypto/aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func newAESKeyGenParams(rt *goja.Runtime, normalized Algorithm, params goja.Valu
func (akgp *AESKeyGenParams) GenerateKey(
extractable bool,
keyUsages []CryptoKeyUsage,
) (*CryptoKey, error) {
) (CryptoKeyGenerationResult, error) {
for _, usage := range keyUsages {
switch usage {
case WrapKeyCryptoKeyUsage, UnwrapKeyCryptoKeyUsage:
Expand Down
12 changes: 10 additions & 2 deletions webcrypto/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,12 @@ func isRegisteredAlgorithm(algorithmName string, forOperation string) bool {
return isHashAlgorithm(algorithmName)
case OperationIdentifierGenerateKey:
// FIXME: the presence of the hash algorithm here is for HMAC support and should be handled separately
return isAesAlgorithm(algorithmName) || isHashAlgorithm(algorithmName) || algorithmName == HMAC
return isAesAlgorithm(algorithmName) ||
isHashAlgorithm(algorithmName) ||
algorithmName == HMAC ||
isEllipticCurve(algorithmName)
case OperationIdentifierExportKey, OperationIdentifierImportKey:
return isAesAlgorithm(algorithmName) || algorithmName == HMAC
return isAesAlgorithm(algorithmName) || algorithmName == HMAC || isEllipticCurve(algorithmName)
case OperationIdentifierEncrypt, OperationIdentifierDecrypt:
return isAesAlgorithm(algorithmName)
case OperationIdentifierSign, OperationIdentifierVerify:
Expand All @@ -193,3 +196,8 @@ func isHashAlgorithm(algorithmName string) bool {
type hasAlg interface {
alg() string
}

func isEllipticCurve(algorithmName string) bool {
// TODO: algorithmName == ECDSA
return algorithmName == ECDH
}
11 changes: 11 additions & 0 deletions webcrypto/bits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package webcrypto

type bitsDeriver func(CryptoKey, CryptoKey) ([]byte, error)

func newBitsDeriver(algName string) (bitsDeriver, error) {
if algName == "ECDH" {
return deriveBitsECDH, nil
}

return nil, NewError(NotSupportedError, "unsupported algorithm: "+algName)
}
Loading
Loading