diff --git a/Makefile b/Makefile index e9f117a25f0..2c301f9fac9 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ all: clean format tests build build: go build +build-debug: + go build -gcflags="all=-N -l" + ## format: Applies Go formatting to code. format: find . -name '*.go' -exec gofmt -s -w {} + diff --git a/examples/webcrypto/sign_verify/sign-verify-ed25519.js b/examples/webcrypto/sign_verify/sign-verify-ed25519.js new file mode 100644 index 00000000000..fd3dd41f550 --- /dev/null +++ b/examples/webcrypto/sign_verify/sign-verify-ed25519.js @@ -0,0 +1,44 @@ +import { crypto } from "k6/experimental/webcrypto"; + +export default async function () { + const keyPair = await crypto.subtle.generateKey( + { + name: "Ed25519", + }, + true, + ["sign", "verify"] + ); + + const data = string2ArrayBuffer("Hello World"); + + const alg = { name: "Ed25519" }; + + // 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, + otherKeyPair.publicKey, + signature, + data + ); + + console.log("verified: ", verified); +} + +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); +}; diff --git a/internal/js/modules/k6/webcrypto/algorithm.go b/internal/js/modules/k6/webcrypto/algorithm.go index 9b9df5b4767..845e49d9a23 100644 --- a/internal/js/modules/k6/webcrypto/algorithm.go +++ b/internal/js/modules/k6/webcrypto/algorithm.go @@ -52,6 +52,8 @@ const ( // ECDH represents the ECDH algorithm. ECDH = "ECDH" + + Ed25519 = "ED25519" // TODO: This should be "Ed25519" ) // HashAlgorithmIdentifier represents the name of a hash algorithm. @@ -187,16 +189,18 @@ func isRegisteredAlgorithm(algorithmName string, forOperation string) bool { isHashAlgorithm(algorithmName) || algorithmName == HMAC || isEllipticCurve(algorithmName) || - isRSAAlgorithm(algorithmName) + isRSAAlgorithm(algorithmName) || + algorithmName == Ed25519 case OperationIdentifierExportKey, OperationIdentifierImportKey: return isAesAlgorithm(algorithmName) || algorithmName == HMAC || isEllipticCurve(algorithmName) || - isRSAAlgorithm(algorithmName) + isRSAAlgorithm(algorithmName) || + algorithmName == Ed25519 case OperationIdentifierEncrypt, OperationIdentifierDecrypt: return isAesAlgorithm(algorithmName) || algorithmName == RSAOaep case OperationIdentifierSign, OperationIdentifierVerify: - return algorithmName == HMAC || algorithmName == ECDSA || algorithmName == RSAPss || algorithmName == RSASsaPkcs1v15 + return algorithmName == HMAC || algorithmName == ECDSA || algorithmName == RSAPss || algorithmName == RSASsaPkcs1v15 || algorithmName == Ed25519 default: return false } diff --git a/internal/js/modules/k6/webcrypto/ed25519.go b/internal/js/modules/k6/webcrypto/ed25519.go new file mode 100644 index 00000000000..9c78e7b3cd2 --- /dev/null +++ b/internal/js/modules/k6/webcrypto/ed25519.go @@ -0,0 +1,102 @@ +package webcrypto + +import ( + "crypto/ed25519" + "crypto/rand" + "fmt" +) + +// Ed25519KeyGenParams represents the object that should be passed as the algorithm +// paramter into `SubtleCrypto.GenerateKey`, when generating an Ed25519 key pair. +// The Ed25519 key generation expects only the algorithm type as a parameter. +type Ed25519KeyGenParams struct { + Algorithm +} + +var _ KeyGenerator = &Ed25519KeyGenParams{} + +func newEd25519KeyGenParams(normalized Algorithm) (KeyGenerator, error) { + return &Ed25519KeyGenParams{ + Algorithm: normalized, + }, nil +} + +func (kgp *Ed25519KeyGenParams) GenerateKey(extractable bool, keyUsages []CryptoKeyUsage) (CryptoKeyGenerationResult, error) { + rawPublicKey, rawPrivateKey, err := generateEd25519KeyPair(keyUsages) + if err != nil { + return nil, err + } + + alg := KeyAlgorithm{ + Algorithm: kgp.Algorithm, + } + privateKey := &CryptoKey{ + Type: PrivateCryptoKeyType, + Extractable: extractable, + Algorithm: alg, + Usages: UsageIntersection( + keyUsages, + []CryptoKeyUsage{SignCryptoKeyUsage}, + ), + handle: rawPrivateKey, + } + + publicKey := &CryptoKey{ + Type: PublicCryptoKeyType, + Extractable: true, + Algorithm: alg, + Usages: UsageIntersection( + keyUsages, + []CryptoKeyUsage{VerifyCryptoKeyUsage}, + ), + handle: rawPublicKey, + } + + return &CryptoKeyPair{ + PrivateKey: privateKey, + PublicKey: publicKey, + }, nil +} + +func generateEd25519KeyPair(keyUsages []CryptoKeyUsage) (ed25519.PublicKey, ed25519.PrivateKey, error) { + for _, usage := range keyUsages { + switch usage { + case SignCryptoKeyUsage, VerifyCryptoKeyUsage: + continue + default: + return nil, nil, NewError(SyntaxError, fmt.Sprintf("Invalid key usage: %s", usage)) + } + } + + return ed25519.GenerateKey(rand.Reader) +} + +type ed25519SignerVerifier struct{} + +func (ed25519SignerVerifier) Sign(key CryptoKey, data []byte) ([]byte, error) { + if key.Type != PrivateCryptoKeyType { + return nil, NewError(InvalidAccessError, "Must use private key to sign data") + } + + keyHandle, ok := key.handle.(ed25519.PrivateKey) + if !ok { + return nil, NewError(InvalidAccessError, "Key handle is not an Ed25519 Private Key") + } + + return ed25519.Sign(keyHandle, data), nil +} + +func (ed25519SignerVerifier) Verify(key CryptoKey, signature, data []byte) (bool, error) { + if key.Type != PublicCryptoKeyType { + return false, NewError(InvalidAccessError, "Must use public key to verify data") + } + + keyHandle, ok := key.handle.(ed25519.PublicKey) + if !ok { + return false, NewError(InvalidAccessError, "Key handle is not an Ed25519 public key") + } + + // TODO: verify that the ed25519 library conducts small-order checks, if not add them here + + return ed25519.Verify(keyHandle, data, signature), nil +} diff --git a/internal/js/modules/k6/webcrypto/key.go b/internal/js/modules/k6/webcrypto/key.go index 0f56a4b3d13..a31de5fc266 100644 --- a/internal/js/modules/k6/webcrypto/key.go +++ b/internal/js/modules/k6/webcrypto/key.go @@ -191,8 +191,10 @@ func newKeyGenerator(rt *sobek.Runtime, normalized Algorithm, params sobek.Value kg, err = newECKeyGenParams(rt, normalized, params) case RSASsaPkcs1v15, RSAPss, RSAOaep: kg, err = newRsaHashedKeyGenParams(rt, normalized, params) + case Ed25519: + kg, err = newEd25519KeyGenParams(normalized) default: - validAlgorithms := []string{AESCbc, AESCtr, AESGcm, AESKw, HMAC, ECDH, ECDSA, RSASsaPkcs1v15, RSAPss, RSAOaep} + validAlgorithms := []string{AESCbc, AESCtr, AESGcm, AESKw, HMAC, ECDH, ECDSA, RSASsaPkcs1v15, RSAPss, RSAOaep, Ed25519} return nil, NewError( NotImplemented, "unsupported key generation algorithm '"+normalized.Name+"', "+ diff --git a/internal/js/modules/k6/webcrypto/signer.go b/internal/js/modules/k6/webcrypto/signer.go index 98a8257f7b9..878b684688d 100644 --- a/internal/js/modules/k6/webcrypto/signer.go +++ b/internal/js/modules/k6/webcrypto/signer.go @@ -18,6 +18,8 @@ func newSignerVerifier(rt *sobek.Runtime, normalized Algorithm, params sobek.Val return &rsaSsaPkcs1v15SignerVerifier{}, nil case RSAPss: return newRSAPssParams(rt, normalized, params) + case Ed25519: + return &ed25519SignerVerifier{}, nil default: return nil, NewError(NotSupportedError, "unsupported algorithm for signing/verifying: "+normalized.Name) } diff --git a/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__failures.js.patch b/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__failures.js.patch index 201d5f94cf2..901b69fe0a8 100644 --- a/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__failures.js.patch +++ b/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__failures.js.patch @@ -1,18 +1,15 @@ diff --git a/WebCryptoAPI/generateKey/failures.js b/WebCryptoAPI/generateKey/failures.js -index e0f0279a6..61495ca75 100644 +index e0f0279a6..5b3b69764 100644 --- a/WebCryptoAPI/generateKey/failures.js +++ b/WebCryptoAPI/generateKey/failures.js -@@ -32,10 +32,10 @@ function run_test(algorithmNames) { - {name: "RSA-OAEP", resultType: "CryptoKeyPair", usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"], mandatoryUsages: ["decrypt", "unwrapKey"]}, +@@ -33,9 +33,9 @@ function run_test(algorithmNames) { {name: "ECDSA", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, -- {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, - {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, -- {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, -- {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, -+ // {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, + // {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, -+ // {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, +- {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + // {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, ]; diff --git a/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__successes.js.patch b/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__successes.js.patch index 50d839c0aab..447b5b87b56 100644 --- a/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__successes.js.patch +++ b/internal/js/modules/k6/webcrypto/tests/wpt-patches/WebCryptoAPI__generateKey__successes.js.patch @@ -1,5 +1,5 @@ diff --git a/WebCryptoAPI/generateKey/successes.js b/WebCryptoAPI/generateKey/successes.js -index a9a168e1a..88861ab87 100644 +index a9a168e1a..e3976c378 100644 --- a/WebCryptoAPI/generateKey/successes.js +++ b/WebCryptoAPI/generateKey/successes.js @@ -21,17 +21,17 @@ function run_test(algorithmNames, slowTest) { @@ -16,11 +16,10 @@ index a9a168e1a..88861ab87 100644 {name: "ECDH", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, - {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, - {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, -- {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, -- {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, -+ // {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, ++ {name: "Ed25519", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign", "wrapKey"]}, + // {name: "Ed448", resultType: "CryptoKeyPair", usages: ["sign", "verify"], mandatoryUsages: ["sign"]}, -+ // {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + {name: "X25519", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, +- {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, + // {name: "X448", resultType: "CryptoKeyPair", usages: ["deriveKey", "deriveBits"], mandatoryUsages: ["deriveKey", "deriveBits"]}, ];