From 26bf263c197f5b8dce1bdb5225c082a0f2012199 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Wed, 17 Apr 2024 16:18:55 +0200 Subject: [PATCH 1/6] refactor: GenerateKey to make it event loop safe --- webcrypto/subtle_crypto.go | 62 +++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 21 deletions(-) diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 6cf6560..661a4e9 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -496,46 +496,66 @@ func (sc *SubtleCrypto) Digest(algorithm goja.Value, data goja.Value) *goja.Prom // // The `keyUsages` parameter is an array of strings indicating what the key can be used for. func (sc *SubtleCrypto) GenerateKey(algorithm goja.Value, extractable bool, keyUsages []CryptoKeyUsage) *goja.Promise { - promise, resolve, reject := promises.New(sc.vu) + rt := sc.vu.Runtime() - normalized, err := normalizeAlgorithm(sc.vu.Runtime(), algorithm, OperationIdentifierGenerateKey) - if err != nil { - reject(err) - return promise - } + var keyGenerator KeyGenerator + + err := func() error { + normalized, err := normalizeAlgorithm(rt, algorithm, OperationIdentifierGenerateKey) + if err != nil { + return err + } - keyGenerator, err := newKeyGenerator(sc.vu.Runtime(), normalized, algorithm) + keyGenerator, err = newKeyGenerator(rt, normalized, algorithm) + if err != nil { + return err + } + + return nil + }() + + promise, resolve, reject := rt.NewPromise() if err != nil { reject(err) return promise } + callback := sc.vu.RegisterCallback() go func() { - // 7. - result, err := keyGenerator.GenerateKey(extractable, keyUsages) - if err != nil { - reject(err) - return - } + result, err := func() (CryptoKeyGenerationResult, error) { + result, err := keyGenerator.GenerateKey(extractable, keyUsages) + if err != nil { + return nil, err + } + + if result.IsKeyPair() { + return result, nil + } - if !result.IsKeyPair() { cryptoKey, err := result.ResolveCryptoKey() if err != nil { - reject(NewError(OperationError, "usages cannot not be empty for a secret or private CryptoKey")) - return + return nil, NewError(OperationError, "usages cannot not be empty for a secret or private CryptoKey") } - // 8. isSecretKey := cryptoKey.Type == SecretCryptoKeyType isPrivateKey := cryptoKey.Type == PrivateCryptoKeyType isUsagesEmpty := len(cryptoKey.Usages) == 0 if (isSecretKey || isPrivateKey) && isUsagesEmpty { - reject(NewError(SyntaxError, "usages cannot not be empty for a secret or private CryptoKey")) - return + return nil, NewError(SyntaxError, "usages cannot not be empty for a secret or private CryptoKey") } - } - resolve(result) + return result, nil + }() + + callback(func() error { + if err != nil { + reject(err) + return nil //nolint:nilerr // we return nil to indicate that the error was handled + } + + resolve(result) + return nil + }) }() return promise From e4fbb1868c8cac15bb2de01afc6d0422205530e5 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Wed, 17 Apr 2024 16:53:04 +0200 Subject: [PATCH 2/6] refactor: DeriveBits moving variables to isolate state and check for the nil --- webcrypto/subtle_crypto.go | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 661a4e9..31983e0 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -7,6 +7,7 @@ import ( "hash" "github.com/dop251/goja" + "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" "go.k6.io/k6/js/promises" ) @@ -634,8 +635,10 @@ func (sc *SubtleCrypto) DeriveKey( func (sc *SubtleCrypto) DeriveBits(algorithm goja.Value, baseKey goja.Value, length int) *goja.Promise { rt := sc.vu.Runtime() - var publicKey, privateKey CryptoKey - var algName string + var ( + publicKey, privateKey CryptoKey + deriver bitsDeriver + ) err := func() error { if err := rt.ExportTo(baseKey, &privateKey); err != nil { @@ -647,18 +650,25 @@ func (sc *SubtleCrypto) DeriveBits(algorithm goja.Value, baseKey goja.Value, len } alg := algorithm.ToObject(rt) + if common.IsNullish(alg) { + return NewError(InvalidAccessError, "algorithm is not an object") + } pcValue := alg.Get("public") if err := rt.ExportTo(pcValue, &publicKey); err != nil { return NewError(InvalidAccessError, "algorithm's public is not a valid CryptoKey") } - algName = alg.Get("name").String() - if publicKey.Type != PublicCryptoKeyType { return NewError(InvalidAccessError, "algorithm's public key is not a public key") } + var err error + deriver, err = newBitsDeriver(alg.Get("name").String()) + if err != nil { + return err + } + return nil }() @@ -671,11 +681,6 @@ func (sc *SubtleCrypto) DeriveBits(algorithm goja.Value, baseKey goja.Value, len callback := sc.vu.RegisterCallback() go func() { result, err := func() ([]byte, error) { - deriver, err := newBitsDeriver(algName) - if err != nil { - return nil, err - } - b, err := deriver(privateKey, publicKey) if err != nil { return nil, NewError(OperationError, err.Error()) From cc21794611005f566a3f9ff2397734b6064e8508 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Wed, 17 Apr 2024 17:05:30 +0200 Subject: [PATCH 3/6] refactor: ImportKey to make it event loop safe --- webcrypto/subtle_crypto.go | 104 ++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 31983e0..49e90a4 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -725,7 +725,7 @@ func (sc *SubtleCrypto) DeriveBits(algorithm goja.Value, baseKey goja.Value, len // `ALGORITHM` is the name of the algorithm. // - for PBKDF2: pass the string "PBKDF2" // - for HKDF: pass the string "HKDF" -func (sc *SubtleCrypto) ImportKey( +func (sc *SubtleCrypto) ImportKey( //nolint:funlen // we have a lot of error handling format KeyFormat, keyData goja.Value, algorithm goja.Value, @@ -733,72 +733,80 @@ func (sc *SubtleCrypto) ImportKey( keyUsages []CryptoKeyUsage, ) *goja.Promise { rt := sc.vu.Runtime() - promise, resolve, reject := promises.New(sc.vu) - var keyBytes []byte + var ( + keyBytes []byte + ki KeyImporter + ) + + err := func() error { + switch format { + case Pkcs8KeyFormat, RawKeyFormat, SpkiKeyFormat: + ab, err := exportArrayBuffer(rt, keyData) + if err != nil { + return err + } - // 2. - switch format { - case Pkcs8KeyFormat, RawKeyFormat, SpkiKeyFormat: - ab, err := exportArrayBuffer(rt, keyData) + keyBytes = make([]byte, len(ab)) + copy(keyBytes, ab) + case JwkKeyFormat: + var err error + keyBytes, err = json.Marshal(keyData.Export()) + if err != nil { + return NewError(ImplementationError, "invalid keyData format for JWK format: "+err.Error()) + } + default: + return NewError(ImplementationError, "unsupported format "+format) + } + normalized, err := normalizeAlgorithm(rt, algorithm, OperationIdentifierImportKey) if err != nil { - reject(err) - return promise + return err } - keyBytes = make([]byte, len(ab)) - copy(keyBytes, ab) - case JwkKeyFormat: - var err error - keyBytes, err = json.Marshal(keyData.Export()) + ki, err = newKeyImporter(rt, normalized, algorithm) if err != nil { - reject(NewError(ImplementationError, "wrong keyData format for JWK format: "+err.Error())) - return promise + return err } - default: - reject(NewError(ImplementationError, "unsupported format "+format)) - return promise - } - // 3. - normalized, err := normalizeAlgorithm(rt, algorithm, OperationIdentifierImportKey) - if err != nil { - reject(err) - return promise - } + return nil + }() - ki, err := newKeyImporter(rt, normalized, algorithm) + promise, resolve, reject := rt.NewPromise() if err != nil { reject(err) return promise } - // 5. + callback := sc.vu.RegisterCallback() go func() { - // 8. - result, err := ki.ImportKey(format, keyBytes, keyUsages) - if err != nil { - reject(err) - return - } + result, err := func() (*CryptoKey, error) { + result, err := ki.ImportKey(format, keyBytes, keyUsages) + if err != nil { + return nil, err + } - // 9. - isSecretKey := result.Type == SecretCryptoKeyType - isPrivateKey := result.Type == PrivateCryptoKeyType - isUsagesEmpty := len(keyUsages) == 0 - if (isSecretKey || isPrivateKey) && isUsagesEmpty { - reject(NewError(SyntaxError, "usages cannot not be empty for a secret or private CryptoKey")) - return - } + isSecretKey := result.Type == SecretCryptoKeyType + isPrivateKey := result.Type == PrivateCryptoKeyType + isUsagesEmpty := len(keyUsages) == 0 + if (isSecretKey || isPrivateKey) && isUsagesEmpty { + return nil, NewError(SyntaxError, "usages cannot not be empty for a secret or private CryptoKey") + } + + result.Extractable = extractable + result.Usages = keyUsages - // 10. - result.Extractable = extractable + return result, nil + }() - // 11. - result.Usages = keyUsages + callback(func() error { + if err != nil { + reject(err) + return nil //nolint:nilerr // we return nil to indicate that the error was handled + } - // 12. - resolve(result) + resolve(result) + return nil + }) }() return promise From 58424ca6aa38f1a94b5917372580110edf849a66 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Thu, 18 Apr 2024 08:49:14 +0200 Subject: [PATCH 4/6] refactor: simplify internals of EC keys exporting --- webcrypto/elliptic_curve.go | 9 +++++++-- webcrypto/subtle_crypto.go | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/webcrypto/elliptic_curve.go b/webcrypto/elliptic_curve.go index b4e1e19..c9ccc80 100644 --- a/webcrypto/elliptic_curve.go +++ b/webcrypto/elliptic_curve.go @@ -409,18 +409,23 @@ func pickEllipticCurve(k string) (elliptic.Curve, error) { } } -func exportECKey(alg string, ck *CryptoKey, format KeyFormat) (interface{}, error) { +func exportECKey(ck *CryptoKey, format KeyFormat) (interface{}, error) { if ck.handle == nil { return nil, NewError(OperationError, "key data is not accessible") } + alg, ok := ck.Algorithm.(EcKeyAlgorithm) + if !ok { + return nil, NewError(InvalidAccessError, "key algorithm is not a valid EC algorithm") + } + switch format { case RawKeyFormat: if ck.Type != PublicCryptoKeyType { return nil, NewError(InvalidAccessError, "key is not a valid elliptic curve public key") } - bytes, err := extractPublicKeyBytes(alg, ck.handle) + bytes, err := extractPublicKeyBytes(alg.Name, ck.handle) if err != nil { return nil, NewError(OperationError, "unable to extract public key data: "+err.Error()) } diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 49e90a4..20980ee 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -881,7 +881,7 @@ func (sc *SubtleCrypto) ExportKey(format KeyFormat, key goja.Value) *goja.Promis return } case ECDH, ECDSA: - result, err = exportECKey(keyAlgorithmName, ck, format) + result, err = exportECKey(ck, format) if err != nil { reject(err) return From b241d214188b2fc4fbfc7e0dd38954ffbaeb7904 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Thu, 18 Apr 2024 08:58:54 +0200 Subject: [PATCH 5/6] refactor: ExportKey to make it event loop safe --- webcrypto/subtle_crypto.go | 117 +++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 20980ee..0470e7b 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -9,7 +9,6 @@ import ( "github.com/dop251/goja" "go.k6.io/k6/js/common" "go.k6.io/k6/js/modules" - "go.k6.io/k6/js/promises" ) // FIXME: SubtleCrypto is described as an "interface", should it be a nested module @@ -826,83 +825,89 @@ func (sc *SubtleCrypto) ImportKey( //nolint:funlen // we have a lot of error han // // The `format` parameter identifies the format of the key data. // The `key` parameter is the key to export, as a CryptoKey object. -func (sc *SubtleCrypto) ExportKey(format KeyFormat, key goja.Value) *goja.Promise { +func (sc *SubtleCrypto) ExportKey( //nolint:funlen // we have a lot of error handling + format KeyFormat, + key goja.Value, +) *goja.Promise { rt := sc.vu.Runtime() - promise, resolve, reject := promises.New(sc.vu) - var algorithm Algorithm - algValue := key.ToObject(rt).Get("algorithm") - if err := rt.ExportTo(algValue, &algorithm); err != nil { - reject(NewError(SyntaxError, "key is not a valid CryptoKey")) - return promise - } + var ( + ck *CryptoKey + keyExporter func(*CryptoKey, KeyFormat) (interface{}, error) + ) - ck, ok := key.Export().(*CryptoKey) - if !ok { - reject(NewError(ImplementationError, "unable to extract CryptoKey")) - return promise - } + err := func() error { + var algorithm Algorithm + algValue := key.ToObject(rt).Get("algorithm") + if err := rt.ExportTo(algValue, &algorithm); err != nil { + return NewError(SyntaxError, "key is not a valid CryptoKey") + } + + var ok bool + ck, ok = key.Export().(*CryptoKey) + if !ok { + return NewError(ImplementationError, "unable to extract CryptoKey") + } - inputAlgorithm := key.ToObject(rt).Get("algorithm").ToObject(rt) + inputAlgorithm := key.ToObject(rt).Get("algorithm").ToObject(rt) - keyAlgorithmName := inputAlgorithm.Get("name").String() - if algorithm.Name != keyAlgorithmName { - reject(NewError(InvalidAccessError, "algorithm name does not match key algorithm name")) - return promise - } + keyAlgorithmName := inputAlgorithm.Get("name").String() + if algorithm.Name != keyAlgorithmName { + return NewError(InvalidAccessError, "algorithm name does not match key algorithm name") + } - go func() { - // 5. if !isRegisteredAlgorithm(algorithm.Name, OperationIdentifierExportKey) { - reject(NewError(NotSupportedError, "unsupported algorithm "+algorithm.Name)) - return + return NewError(NotSupportedError, "unsupported algorithm "+algorithm.Name) } - // 6. if !ck.Extractable { - reject(NewError(InvalidAccessError, "the key is not extractable")) - return + return NewError(InvalidAccessError, "the key is not extractable") } - var result interface{} - var err error - switch keyAlgorithmName { case AESCbc, AESCtr, AESGcm: - result, err = exportAESKey(ck, format) - if err != nil { - reject(err) - return - } + keyExporter = exportAESKey case HMAC: - result, err = exportHMACKey(ck, format) - if err != nil { - reject(err) - return - } + keyExporter = exportHMACKey case ECDH, ECDSA: - result, err = exportECKey(ck, format) + keyExporter = exportECKey + default: + return NewError(NotSupportedError, "unsupported algorithm "+keyAlgorithmName) + } + + return nil + }() + + promise, resolve, reject := rt.NewPromise() + if err != nil { + reject(err) + return promise + } + + callback := sc.vu.RegisterCallback() + go func() { + result, err := keyExporter(ck, format) + + callback(func() error { if err != nil { reject(err) - return + return nil //nolint:nilerr // we return nil to indicate that the error was handled } - default: - reject(NewError(NotSupportedError, "unsupported algorithm "+keyAlgorithmName)) - return - } - if !isBinaryExportedFormat(format) { - resolve(result) - return - } + if !isBinaryExportedFormat(format) { + resolve(result) + return nil + } - b, ok := result.([]byte) - if !ok { - reject(NewError(ImplementationError, "for "+format+" []byte expected as result")) - return - } + b, ok := result.([]byte) + if !ok { + reject(NewError(ImplementationError, "for "+format+" []byte expected as result")) + return nil + } - resolve(rt.NewArrayBuffer(b)) + resolve(rt.NewArrayBuffer(b)) + return nil + }) }() return promise From adaa7ede2043128d9126c16aaa8ed810cc851538 Mon Sep 17 00:00:00 2001 From: Oleg Bespalov Date: Thu, 18 Apr 2024 10:08:30 +0200 Subject: [PATCH 6/6] refactor: Export key, more checks for nil, remove redundant check --- webcrypto/subtle_crypto.go | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/webcrypto/subtle_crypto.go b/webcrypto/subtle_crypto.go index 0470e7b..2bd49ab 100644 --- a/webcrypto/subtle_crypto.go +++ b/webcrypto/subtle_crypto.go @@ -837,23 +837,21 @@ func (sc *SubtleCrypto) ExportKey( //nolint:funlen // we have a lot of error han ) err := func() error { - var algorithm Algorithm - algValue := key.ToObject(rt).Get("algorithm") - if err := rt.ExportTo(algValue, &algorithm); err != nil { - return NewError(SyntaxError, "key is not a valid CryptoKey") + keyObj := key.ToObject(rt) + if common.IsNullish(keyObj) { + return NewError(InvalidAccessError, "key is not an object") } var ok bool ck, ok = key.Export().(*CryptoKey) if !ok { - return NewError(ImplementationError, "unable to extract CryptoKey") + return NewError(ImplementationError, "unable to extract CryptoKey from key object") } - inputAlgorithm := key.ToObject(rt).Get("algorithm").ToObject(rt) - - keyAlgorithmName := inputAlgorithm.Get("name").String() - if algorithm.Name != keyAlgorithmName { - return NewError(InvalidAccessError, "algorithm name does not match key algorithm name") + var algorithm Algorithm + algObj := keyObj.Get("algorithm") + if err := rt.ExportTo(algObj, &algorithm); err != nil { + return NewError(SyntaxError, "key is not a valid Algorithm") } if !isRegisteredAlgorithm(algorithm.Name, OperationIdentifierExportKey) { @@ -864,7 +862,7 @@ func (sc *SubtleCrypto) ExportKey( //nolint:funlen // we have a lot of error han return NewError(InvalidAccessError, "the key is not extractable") } - switch keyAlgorithmName { + switch algorithm.Name { case AESCbc, AESCtr, AESGcm: keyExporter = exportAESKey case HMAC: @@ -872,7 +870,7 @@ func (sc *SubtleCrypto) ExportKey( //nolint:funlen // we have a lot of error han case ECDH, ECDSA: keyExporter = exportECKey default: - return NewError(NotSupportedError, "unsupported algorithm "+keyAlgorithmName) + return NewError(NotSupportedError, "unsupported algorithm "+algorithm.Name) } return nil