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

Derive Key #83

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
d5d68ac
Add algorithms that didn't already exist
catdevman Jul 10, 2024
e573a20
Add ECDHKeyDeriveParams
catdevman Jul 10, 2024
977dc76
skeleton appears to be there, just need internal implementation
catdevman Jul 10, 2024
007e5bc
add derivekey operation
catdevman Jul 10, 2024
381ba93
skeleton
catdevman Jul 10, 2024
0298e06
params skeleton
catdevman Jul 10, 2024
638f53c
getting keydata
catdevman Jul 11, 2024
4eff252
deriveKey example without param
catdevman Jul 16, 2024
a24f7d8
Remove derivekey for derivebits which seems to be the operation the s…
catdevman Jul 16, 2024
2cb65bc
add DeriveKey to ecdh, hkdf, pbkdf2
catdevman Jul 16, 2024
60b701a
add examples and running them for test
catdevman Jul 16, 2024
425121f
good progress
catdevman Jul 20, 2024
1d64240
making some changes
catdevman Jul 22, 2024
4db18da
temp place to put these
catdevman Sep 26, 2024
c692850
I think this a good test... I took the example from MDN :shrug:
catdevman Oct 2, 2024
bbd2469
adding some algos that I think need to be implemented to fully do der…
catdevman Oct 2, 2024
bc62565
detect new key type and handle creating a KeyImportParam for PBKDF2
catdevman Oct 2, 2024
11d2c3c
continuing to write algo for DeriveKey
catdevman Oct 2, 2024
8a2cc56
more testing
catdevman Oct 11, 2024
e53faec
remove example for derive_key
catdevman Oct 21, 2024
58bcbd3
remove this file that had nothing in it
catdevman Oct 21, 2024
31ceaca
add pbkdf2 files; figure out why this is passing even though it shoul…
catdevman Oct 21, 2024
6023599
fix up tests so they run, failing tests are a good sign of being on t…
catdevman Oct 22, 2024
6ad2f28
add path for PBKDF2
catdevman Oct 22, 2024
1548174
privateKey and publicKey for deriveBitsPBKDF2 but I think this needs …
catdevman Oct 22, 2024
f2e26e5
clean this up later
catdevman Oct 22, 2024
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
2 changes: 1 addition & 1 deletion webcrypto/aes.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ func (aip *AESImportParams) ImportKey(
) (*CryptoKey, error) {
for _, usage := range keyUsages {
switch usage {
case EncryptCryptoKeyUsage, DecryptCryptoKeyUsage, WrapKeyCryptoKeyUsage, UnwrapKeyCryptoKeyUsage:
case EncryptCryptoKeyUsage, DecryptCryptoKeyUsage, WrapKeyCryptoKeyUsage, UnwrapKeyCryptoKeyUsage, DeriveKeyCryptoKeyUsage, DeriveBitsCryptoKeyUsage:
continue
default:
return nil, NewError(SyntaxError, "invalid key usage: "+usage)
Expand Down
27 changes: 25 additions & 2 deletions webcrypto/algorithm.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ const (

// ECDH represents the ECDH algorithm.
ECDH = "ECDH"

// HKDF represents the HKDF algoithm.
HKDF = "HKDF"

// PBKDF2 represents the PBKDF2 algoithm.
PBKDF2 = "PBKDF2"

// X25519 represents the X25519 algoithm.
X25519 = "X25519"
)

// HashAlgorithmIdentifier represents the name of a hash algorithm.
Expand Down Expand Up @@ -115,6 +124,9 @@ const (

// OperationIdentifierDigest represents the digest operation.
OperationIdentifierDigest OperationIdentifier = "digest"

// OperationGetKeyLength represents the get key length operation.
OperationGetKeyLength OperationIdentifier = "get key length"
)

// normalizeAlgorithm normalizes the given algorithm following the
Expand Down Expand Up @@ -151,7 +163,7 @@ func normalizeAlgorithm(rt *sobek.Runtime, v sobek.Value, op AlgorithmIdentifier
algorithm.Name = strings.ToUpper(algorithm.Name)

if !isRegisteredAlgorithm(algorithm.Name, op) {
return Algorithm{}, NewError(NotSupportedError, "unsupported algorithm: "+algorithm.Name)
return Algorithm{}, NewError(NotSupportedError, "unsupported algorithm: "+algorithm.Name+" for operation: "+op)
}

return algorithm, nil
Expand All @@ -173,11 +185,17 @@ func isRegisteredAlgorithm(algorithmName string, forOperation string) bool {
algorithmName == HMAC ||
isEllipticCurve(algorithmName)
case OperationIdentifierExportKey, OperationIdentifierImportKey:
return isAesAlgorithm(algorithmName) || algorithmName == HMAC || isEllipticCurve(algorithmName)
return isRSAAlgorithm(algorithmName) || isAesAlgorithm(algorithmName) || algorithmName == HMAC || isEllipticCurve(algorithmName) || algorithmName == PBKDF2 || algorithmName == HKDF
case OperationIdentifierEncrypt, OperationIdentifierDecrypt:
return isAesAlgorithm(algorithmName)
case OperationIdentifierSign, OperationIdentifierVerify:
return algorithmName == HMAC || algorithmName == ECDSA
case OperationIdentifierDeriveKey:
return algorithmName == ECDH || algorithmName == HKDF || algorithmName == PBKDF2
case OperationGetKeyLength:
return isAesAlgorithm(algorithmName)
case OperationIdentifierDeriveBits:
return algorithmName == ECDH || algorithmName == HKDF || algorithmName == PBKDF2 || algorithmName == X25519
default:
return false
}
Expand All @@ -191,6 +209,11 @@ func isHashAlgorithm(algorithmName string) bool {
return algorithmName == SHA1 || algorithmName == SHA256 || algorithmName == SHA384 || algorithmName == SHA512
}

func isRSAAlgorithm(algorithmName string) bool {
//TODO: make constants for these
return algorithmName == "RSASSA-PKCS1-v1_5" || algorithmName == "RSA-PSS" || algorithmName == "RSA-OAEP"
}

// hasAlg an internal interface that helps us to identify
// if a given object has an algorithm method.
type hasAlg interface {
Expand Down
5 changes: 4 additions & 1 deletion webcrypto/bits.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package webcrypto
type bitsDeriver func(CryptoKey, CryptoKey) ([]byte, error)

func newBitsDeriver(algName string) (bitsDeriver, error) {
if algName == ECDH {
switch algName {
case ECDH:
return deriveBitsECDH, nil
case PBKDF2:
return deriveBitsPBKDF2, nil
}

return nil, NewError(NotSupportedError, "unsupported algorithm for derive bits: "+algName)
Expand Down
12 changes: 12 additions & 0 deletions webcrypto/ecdh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package webcrypto

import "github.com/grafana/sobek"

func newECDHDeriveParams(rt *sobek.Runtime, normalized Algorithm, params sobek.Value) (*ECDHKeyDeriveParams, error) {
//TODO: add implmentation
return nil, nil
}

func (e *ECDHKeyDeriveParams) DeriveKey() (CryptoKeyGenerationResult, error){
return nil, nil
}
8 changes: 8 additions & 0 deletions webcrypto/elliptic_curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,14 @@ func deriveBitsECDH(privateKey CryptoKey, publicKey CryptoKey) ([]byte, error) {
return pk.ECDH(pc)
}

func deriveBitsHKDF() ([]byte, error) {
return nil, nil
}

func deriveBitsPBKDF2(privateKey, publicKey CryptoKey) ([]byte, error) {
return nil, nil
}

// The ECDSAParams represents the object that should be passed as the algorithm
// parameter into `SubtleCrypto.Sign` or `SubtleCrypto.Verify“ when using the
// ECDSA algorithm.
Expand Down
12 changes: 12 additions & 0 deletions webcrypto/hkdf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package webcrypto

import "github.com/grafana/sobek"

func newHKDFKeyDeriveParams(rt *sobek.Runtime, normalized Algorithm, params sobek.Value) (*HKDFParams, error) {
//TODO: add implmentation
return nil, nil
}

func (h HKDFParams) DeriveKey() (CryptoKeyGenerationResult, error){
return nil, nil
}
2 changes: 2 additions & 0 deletions webcrypto/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ func newKeyImporter(rt *sobek.Runtime, normalized Algorithm, params sobek.Value)
ki, err = newHMACImportParams(rt, normalized, params)
case ECDH, ECDSA:
ki, err = newEcKeyImportParams(rt, normalized, params)
case PBKDF2:
ki, err = newPBKDF2KeyImportParams(rt, normalized, params)
default:
return nil, errors.New("key import not implemented for algorithm " + normalized.Name)
}
Expand Down
11 changes: 11 additions & 0 deletions webcrypto/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,23 @@ type HMACSignatureParams struct {
Name AlgorithmIdentifier
}

// ECDHKeyDeriveParams represents the object that should be passed as the algorithm
// parameter into `SubtleCrypto.DeriveKey`, when using the ECDHKeyDerive algorithm.
type ECDHKeyDeriveParams struct {
// Name should be set to AlgorithmKindECDH
Name AlgorithmIdentifier

// Public shoudl set the PublicKey part of a CryptoKeyPair
Public *CryptoKey
}

// PBKDF2Params represents the object that should be passed as the algorithm
// parameter into `SubtleCrypto.DeriveKey`, when using the PBKDF2 algorithm.
type PBKDF2Params struct {
// Name should be set to AlgorithmKindPbkdf2.
Name AlgorithmIdentifier

//TODO: This really needs fixed before I can call deriveKey DONE
// FIXME: should also include SHA-1, unfortunately
// Hash identifies the name of the digest algorithm to use.
// You can use any of the following:
Expand Down
74 changes: 74 additions & 0 deletions webcrypto/pbkdf2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package webcrypto

import "github.com/grafana/sobek"

func newPBKDF2KeyDeriveParams(rt *sobek.Runtime, normalized Algorithm, params sobek.Value) (*PBKDF2Params, error) {
//TODO: add implmentation
hashValue, err := traverseObject(rt, params, "hash")
if err != nil {
return nil, NewError(SyntaxError, "could not get hash from algorithm parameter")
}

normalizedHash, err := normalizeAlgorithm(rt, hashValue, OperationIdentifierDeriveKey)
if err != nil {

return nil, err
}
return &PBKDF2Params{
Name: normalized.Name,
Hash: normalizedHash.Name,
Salt: []byte{},
}, nil
}

func (p PBKDF2Params) DeriveKey() (CryptoKeyGenerationResult, error) {
return nil, nil
}

// EcKeyImportParams represents the object that should be passed as the algorithm parameter
// into `SubtleCrypto.ImportKey` or `SubtleCrypto.UnwrapKey`, when generating any elliptic-curve-based
// key pair: that is, when the algorithm is identified as either of ECDSA or ECDH.
type PBKDF2KeyImportParams struct {
Algorithm
}

func newPBKDF2KeyImportParams(rt *sobek.Runtime, normalized Algorithm, params sobek.Value) (*PBKDF2KeyImportParams, error) {

return &PBKDF2KeyImportParams{
Algorithm: normalized,
}, nil
}

func (keyParams PBKDF2KeyImportParams) ImportKey(format KeyFormat, keyData []byte, keyUsages []CryptoKeyUsage) (*CryptoKey, error) {
for _, usage := range keyUsages {
switch usage {
case EncryptCryptoKeyUsage, DecryptCryptoKeyUsage, WrapKeyCryptoKeyUsage, UnwrapKeyCryptoKeyUsage, DeriveBitsCryptoKeyUsage, DeriveKeyCryptoKeyUsage:
continue
default:
return nil, NewError(SyntaxError, "invalid key usage: "+usage)
}
}
return &CryptoKey{
Algorithm: PBKDF2KeyAlgorithm{
Algorithm: keyParams.Algorithm,
},
Type: SecretCryptoKeyType,
handle: keyData,
}, nil
}

// Ensure that EcKeyImportParams implements the KeyImporter interface.
var _ KeyImporter = &PBKDF2KeyImportParams{}

// PBKDF2KeyAlgorithm is the algorithm for PBKDF2 keys as defined in the [specification].
//
// [specification]: https://www.w3.org/TR/WebCryptoAPI/#dfn-PBKDF2KeyAlgorithm //TODO: update this to something real
type PBKDF2KeyAlgorithm struct {
Algorithm
}

var _ hasAlg = (*PBKDF2KeyAlgorithm)(nil)

func (aka PBKDF2KeyAlgorithm) alg() string {
return aka.Name
}
89 changes: 83 additions & 6 deletions webcrypto/subtle_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,24 @@ func (sc *SubtleCrypto) GenerateKey(
return promise
}

// [x] Let algorithm, baseKey, derivedKeyType, extractable and usages be the algorithm, baseKey, derivedKeyType, extractable and keyUsages parameters passed to the deriveKey method, respectively.
// [x] Let normalizedAlgorithm be the result of normalizing an algorithm, with alg set to algorithm and op set to "deriveBits".
// [x] If an error occurred, return a Promise rejected with normalizedAlgorithm.
// [x] Let normalizedDerivedKeyAlgorithmImport be the result of normalizing an algorithm, with alg set to derivedKeyType and op set to "importKey".
// [x] If an error occurred, return a Promise rejected with normalizedDerivedKeyAlgorithmImport.
// [x] Let normalizedDerivedKeyAlgorithmLength be the result of normalizing an algorithm, with alg set to derivedKeyType and op set to "get key length".
// [x] If an error occurred, return a Promise rejected with normalizedDerivedKeyAlgorithmLength.
// [x] Let promise be a new Promise.
// [x] Return promise and asynchronously perform the remaining steps.
// [x] If the following steps or referenced procedures say to throw an error, reject promise with the returned error and then terminate the algorithm.
// [ ] If the name member of normalizedAlgorithm is not equal to the name attribute of the [[algorithm]] internal slot of baseKey then throw an InvalidAccessError.
// [ ] If the [[usages]] internal slot of baseKey does not contain an entry that is "deriveKey", then throw an InvalidAccessError.
// [ ] Let length be the result of performing the get key length algorithm specified by normalizedDerivedKeyAlgorithmLength using derivedKeyType.
// [ ] Let secret be the result of performing the derive bits operation specified by normalizedAlgorithm using key, algorithm and length.
// [ ] Let result be the result of performing the import key operation specified by normalizedDerivedKeyAlgorithmImport using "raw" as format, secret as keyData, derivedKeyType as algorithm and using extractable and usages.
// [ ] If the [[type]] internal slot of result is "secret" or "private" and usages is empty, then throw a SyntaxError.
// [ ] Resolve promise with result.

// DeriveKey can be used to derive a secret key from a master key.
//
// It takes as arguments some initial key material, the derivation
Expand All @@ -587,25 +605,84 @@ func (sc *SubtleCrypto) GenerateKey(
// function: for example, for PBKDF2 it might be a password, imported as a `SubtleCrypto.CryptoKey`
// using `SubtleCrypto.ImportKey`.
//
// The `derivedKeyAlgorithm` parameter should be one of:
// The `derivedKeyType` parameter should be one of:
// - an `SubtleCrypto.HMACKeyGenParams` object
// - For AES-CTR, AES-CBC, AES-GCM, AES-KW: pass an `SubtleCrypto.AESKeyGenParams`
//
// The `extractable` parameter indicates whether it will be possible to export the key
// using `SubtleCrypto.ExportKey` or `SubtleCrypto.WrapKey`.
//
// The `keyUsages` parameter is an array of strings indicating what the key can be used for.
//
//nolint:revive // remove the nolint directive when the method is implemented
func (sc *SubtleCrypto) DeriveKey(
algorithm sobek.Value,
baseKey sobek.Value,
derivedKeyAlgorithm sobek.Value,
derivedKeyType sobek.Value,
extractable bool,
keyUsages []CryptoKeyUsage,
) *sobek.Promise {
// TODO: implementation
return nil
rt := sc.vu.Runtime()
var normalizedAlgorithm, normalizedDerivedKeyAlgorithmImport, normalizedDerivedKeyAlgorithmLength Algorithm
var err error
var bk CryptoKey
err = func() error {
normalizedAlgorithm, err = normalizeAlgorithm(rt, algorithm, OperationIdentifierDeriveBits)
if err != nil {
return err
}
normalizedDerivedKeyAlgorithmImport, err = normalizeAlgorithm(rt, derivedKeyType, OperationIdentifierImportKey)
if err != nil {
return err
}
normalizedDerivedKeyAlgorithmLength, err = normalizeAlgorithm(rt, derivedKeyType, OperationGetKeyLength)
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() {
result, err := func() (CryptoKeyGenerationResult, error) {
//TODO: remove this when checks are all in, here to elminiate no use check
if normalizedAlgorithm.Name == "WHAT" && normalizedDerivedKeyAlgorithmImport.Name == "IS" && normalizedDerivedKeyAlgorithmLength.Name == "THIS" {
}
if err = rt.ExportTo(baseKey, &bk); err != nil {
return nil, NewError(TypeError, "baseKey is not good")
}
baseKeyAlgorithmNameValue, err := traverseObject(rt, baseKey.ToObject(rt), "algorithm", "name")
if err != nil {
return nil, err
}

if normalizedAlgorithm.Name != baseKeyAlgorithmNameValue.String() {
return nil, NewError(InvalidAccessError, "Key algorithm mismatch")
}

if !bk.ContainsUsage(DeriveKeyCryptoKeyUsage) {
return nil, NewError(InvalidAccessError, "baseKey does not have deriveKey usage")
}

return nil, 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
}

// DeriveBits derives an array of bits from a base key.
Expand Down
14 changes: 14 additions & 0 deletions webcrypto/subtle_crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ func TestSubtleCryptoDeriveBitsKeys(t *testing.T) {

assert.NoError(t, gotErr)
})
t.Run("pbkdf2", func(t *testing.T) {
t.Parallel()

ts := newConfiguredRuntime(t)

gotErr := ts.EventLoop.Start(func() error {
err := executeTestScripts(ts.VU.Runtime(), "./tests/derive_bits_keys", "pbkdf2_vectors.js", "pbkdf2.js")

return err
})

assert.NoError(t, gotErr)
})

}

func executeTestScripts(rt *sobek.Runtime, base string, scripts ...string) error {
Expand Down
Loading