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

ECDSA support #69

Merged
merged 8 commits into from
Apr 17, 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
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,19 @@ The current state of the project is that it is an experimental module of the Web

| API | HMAC | ECDSA | RSASSA-PKCS1-v1_5 | RSA-PSS |
| :----------------------- | :--- | :---- | :---------------- | :------ |
| `crypto.subtle.sign()` | ✅ | | ❌ | ❌ |
| `crypto.subtle.verify()` | ✅ | | ❌ | ❌ |
| `crypto.subtle.sign()` | ✅ | | ❌ | ❌ |
| `crypto.subtle.verify()` | ✅ | | ❌ | ❌ |

##### Key generation, import and export

| 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 for the `AES-*` and `HMAC` algorithms. `ECDH` has support for `pkcs8` and `raw` formats.
> Currently, only the `raw` and `jwk` (JSON Web Key) formats are supported for import/export operations for the `AES-*` and `HMAC` algorithms. `ECDH` and `ECDSA` have support for `pkcs8`, `spki` and `raw` formats.

##### Key derivation

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

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

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

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

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);
};

46 changes: 46 additions & 0 deletions examples/import_export/import-ecdsa-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const aliceKeyPair = await importKeys(
new Uint8Array([
4, 106, 149, 34, 76, 184, 103, 101, 35, 234, 57, 76, 231, 21, 188, 244,
15, 179, 101, 113, 24, 6, 17, 21, 195, 60, 181, 73, 154, 170, 206, 21,
244, 102, 50, 21, 235, 66, 107, 55, 97, 177, 160, 21, 167, 210, 15, 233,
76, 31, 135, 131, 215, 123, 149, 171, 153, 231, 152, 197, 87, 176, 32, 39,
137,
]),
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, 41, 167, 202,
58, 174, 179, 236, 224, 240, 214, 91, 12, 207, 12, 10, 4, 200, 252, 81,
163, 175, 76, 120, 60, 102, 201, 132, 40, 177, 74, 244, 226, 161, 68, 3,
66, 0, 4, 106, 149, 34, 76, 184, 103, 101, 35, 234, 57, 76, 231, 21, 188,
244, 15, 179, 101, 113, 24, 6, 17, 21, 195, 60, 181, 73, 154, 170, 206,
21, 244, 102, 50, 21, 235, 66, 107, 55, 97, 177, 160, 21, 167, 210, 15,
233, 76, 31, 135, 131, 215, 123, 149, 171, 153, 231, 152, 197, 87, 176,
32, 39, 137,
])
);

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

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

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

return { publicKey: publicKey, privateKey: privateKey };
};
49 changes: 49 additions & 0 deletions examples/sign_verify/sign-verify-ecdsa.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
try {
const keyPair = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256",
},
true,
["sign", "verify"]
);

const data = string2ArrayBuffer("Hello World");

const alg = { name: "ECDSA", hash: {name: "SHA-256" } };

// makes a signature of the encoded data with the provided key
const signature = await crypto.subtle.sign(alg, keyPair.privateKey, data);

console.log("signature: ", printArrayBuffer(signature));

//Verifies the signature of the encoded data with the provided key
const verified = await crypto.subtle.verify(
alg,
keyPair.publicKey,
signature,
data
);

console.log("verified: ", verified);
} catch (err) {
console.log("err: " + JSON.stringify(err));
}
}

const string2ArrayBuffer = (str) => {
let buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
let bufView = new Uint16Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
};

const printArrayBuffer = (buffer) => {
let view = new Uint8Array(buffer);
return Array.from(view);
};
Comment on lines +37 to +49
Copy link
Contributor

Choose a reason for hiding this comment

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

Having seen this in a bunch of places at this point - maybe move them to a helper functio( in another PR)?

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 asking myself the same question but ended up this way since I wanted that example to be "completed" (without the need to have multiple files), just copy/run. Do you think it makes sense?

59 changes: 59 additions & 0 deletions examples/sign_verify/verify-spki-ecdsa-invalid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const publicKey = await crypto.subtle.importKey(
"spki",
new Uint8Array([
48, 118, 48, 16, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 5, 43, 129, 4, 0,
34, 3, 98, 0, 4, 29, 49, 157, 105, 45, 202, 95, 87, 84, 186, 123, 50, 193,
22, 66, 198, 216, 210, 180, 251, 130, 73, 195, 242, 20, 215, 30, 144, 181,
37, 41, 102, 217, 127, 123, 235, 31, 170, 177, 228, 243, 226, 96, 85, 73,
194, 238, 219, 82, 3, 41, 179, 190, 166, 181, 229, 86, 36, 161, 81, 80,
161, 105, 102, 99, 95, 25, 22, 239, 4, 221, 117, 142, 105, 64, 157, 6, 51,
203, 75, 37, 153, 65, 121, 178, 42, 118, 156, 116, 52, 54, 145, 14, 121,
153, 81,
]),
{ name: "ECDSA", namedCurve: "P-384" },
true,
["verify"]
);

let plaintText = new Uint8Array([
95, 77, 186, 79, 50, 12, 12, 232, 118, 114, 90, 252, 229, 251, 210, 91, 248,
62, 90, 113, 37, 160, 140, 175, 231, 60, 62, 186, 196, 33, 119, 157, 249,
213, 93, 24, 12, 58, 233, 148, 38, 69, 225, 216, 47, 238, 140, 157, 41, 75,
60, 177, 160, 138, 153, 49, 32, 27, 60, 14, 129, 252, 71, 202, 207, 131, 21,
162, 175, 102, 50, 65, 19, 195, 182, 98, 48, 195, 70, 8, 196, 244, 89, 54,
52, 206, 2, 178, 103, 54, 34, 119, 240, 168, 64, 202, 116, 188, 61, 26, 98,
54, 149, 44, 94, 215, 170, 248, 168, 254, 203, 221, 250, 117, 132, 230, 151,
140, 234, 93, 42, 91, 159, 183, 241, 180, 140, 139, 11, 229, 138, 48, 82, 2,
117, 77, 131, 118, 16, 115, 116, 121, 60, 240, 38, 170, 238, 83, 0, 114,
125, 131, 108, 215, 30, 113, 179, 69, 221, 178, 228, 68, 70, 255, 197, 185,
1, 99, 84, 19, 137, 13, 145, 14, 163, 128, 152, 74, 144, 25, 16, 49, 50, 63,
22, 219, 204, 157, 107, 225, 104, 184, 72, 133, 56, 76, 160, 62, 18, 96, 10,
193, 194, 72, 2, 138, 243, 114, 108, 201, 52, 99, 136, 46, 168, 192, 42,
171,
]);

let signature1 = new Uint8Array([
13, 217, 194, 199, 240, 182, 244, 217, 50, 130, 84, 169, 2, 232, 115, 116,
179, 192, 146, 25, 94, 107, 226, 26, 161, 166, 220, 216, 235, 166, 15, 123,
11, 56, 196, 0, 109, 250, 33, 70, 212, 233, 253, 35, 220, 51, 97, 121, 151,
64, 23, 73, 58, 31, 79, 116, 238, 207, 228, 85, 190, 61, 169, 237, 153, 100,
29, 129, 97, 13, 254, 180, 104, 182, 7, 218, 148, 29, 87, 20, 231, 181, 26,
238, 44, 69, 170, 14, 156, 77, 160, 33, 178, 55, 0,
]);

//Verifies the signature of the encoded data with the provided key
const verified = await crypto.subtle.verify(
{
name: "ECDSA",
hash: "SHA-384",
},
publicKey,
signature1,
plaintText
);

console.log("verified: ", verified);
}
55 changes: 55 additions & 0 deletions examples/sign_verify/verify-spki-ecdsa-valid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { crypto } from "k6/x/webcrypto";

export default async function () {
const publicKey = await crypto.subtle.importKey(
"spki",
new Uint8Array([
48, 89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206,
61, 3, 1, 7, 3, 66, 0, 4, 10, 5, 30, 56, 111, 103, 196, 166, 225, 229,
203, 238, 125, 55, 116, 91, 88, 142, 190, 114, 15, 117, 89, 22, 40, 111,
150, 41, 105, 122, 57, 23, 17, 216, 106, 234, 201, 103, 8, 210, 58, 38,
35, 216, 198, 237, 187, 84, 217, 164, 63, 100, 6, 105, 49, 128, 15, 53,
29, 158, 117, 235, 238, 30,
]),
{ name: "ECDSA", namedCurve: "P-256" },
true,
["verify"]
);

let plaintText = new Uint8Array([
95, 77, 186, 79, 50, 12, 12, 232, 118, 114, 90, 252, 229, 251, 210, 91, 248,
62, 90, 113, 37, 160, 140, 175, 231, 60, 62, 186, 196, 33, 119, 157, 249,
213, 93, 24, 12, 58, 233, 148, 38, 69, 225, 216, 47, 238, 140, 157, 41, 75,
60, 177, 160, 138, 153, 49, 32, 27, 60, 14, 129, 252, 71, 202, 207, 131, 21,
162, 175, 102, 50, 65, 19, 195, 182, 98, 48, 195, 70, 8, 196, 244, 89, 54,
52, 206, 2, 178, 103, 54, 34, 119, 240, 168, 64, 202, 116, 188, 61, 26, 98,
54, 149, 44, 94, 215, 170, 248, 168, 254, 203, 221, 250, 117, 132, 230, 151,
140, 234, 93, 42, 91, 159, 183, 241, 180, 140, 139, 11, 229, 138, 48, 82, 2,
117, 77, 131, 118, 16, 115, 116, 121, 60, 240, 38, 170, 238, 83, 0, 114,
125, 131, 108, 215, 30, 113, 179, 69, 221, 178, 228, 68, 70, 255, 197, 185,
1, 99, 84, 19, 137, 13, 145, 14, 163, 128, 152, 74, 144, 25, 16, 49, 50, 63,
22, 219, 204, 157, 107, 225, 104, 184, 72, 133, 56, 76, 160, 62, 18, 96, 10,
193, 194, 72, 2, 138, 243, 114, 108, 201, 52, 99, 136, 46, 168, 192, 42,
171,
]);

let signature1 = new Uint8Array([
83, 223, 63, 226, 42, 29, 106, 105, 225, 145, 197, 180, 118, 154, 109, 110,
66, 67, 47, 251, 53, 190, 203, 65, 207, 36, 19, 57, 49, 122, 124, 118, 59,
74, 222, 134, 42, 235, 180, 229, 134, 24, 205, 81, 171, 156, 100, 218, 127,
242, 126, 53, 27, 77, 249, 101, 157, 132, 244, 30, 67, 30, 64, 12,
]);

//Verifies the signature of the encoded data with the provided key
const verified = await crypto.subtle.verify(
{
name: "ECDSA",
hash: "SHA-256",
},
publicKey,
signature1,
plaintText
);

console.log("verified: ", verified);
}
5 changes: 2 additions & 3 deletions webcrypto/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func isRegisteredAlgorithm(algorithmName string, forOperation string) bool {
case OperationIdentifierEncrypt, OperationIdentifierDecrypt:
return isAesAlgorithm(algorithmName)
case OperationIdentifierSign, OperationIdentifierVerify:
return algorithmName == HMAC
return algorithmName == HMAC || algorithmName == ECDSA
default:
return false
}
Expand All @@ -198,6 +198,5 @@ type hasAlg interface {
}

func isEllipticCurve(algorithmName string) bool {
// TODO: algorithmName == ECDSA
return algorithmName == ECDH
return algorithmName == ECDH || algorithmName == ECDSA
}
4 changes: 2 additions & 2 deletions webcrypto/bits.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ package webcrypto
type bitsDeriver func(CryptoKey, CryptoKey) ([]byte, error)

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

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