Skip to content

Commit

Permalink
ecdh: import/export private key
Browse files Browse the repository at this point in the history
  • Loading branch information
olegbespalov committed Apr 11, 2024
1 parent 14a5413 commit 68aa59c
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 106 deletions.
22 changes: 0 additions & 22 deletions examples/export_key/generateKey-ecdh.js

This file was deleted.

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);
};
34 changes: 0 additions & 34 deletions examples/import_export/import-export-ecdh-key.js

This file was deleted.

104 changes: 60 additions & 44 deletions webcrypto/elliptic_curve.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ package webcrypto

import (
"crypto/ecdh"
"crypto/ecdsa"
"crypto/rand"
"encoding/json"
"crypto/x509"
"errors"
"log"

"github.com/lestrrat-go/jwx/v2/jwk"

"github.com/dop251/goja"
)
Expand Down Expand Up @@ -52,32 +50,49 @@ var _ KeyImporter = &EcKeyImportParams{}
func (e *EcKeyImportParams) ImportKey(
format KeyFormat,
keyData []byte,
keyUsages []CryptoKeyUsage,
_ []CryptoKeyUsage,
) (*CryptoKey, error) {
if len(keyUsages) > 0 {
return nil, NewError(SyntaxError, "key usages should be empty")
}
var keyType CryptoKeyType
var handle any

// only raw format is supported
if format != RawKeyFormat {
return nil, NewError(NotSupportedError, unsupportedKeyFormatErrorMsg+" "+format)
}
if format == RawKeyFormat {
// raw key type is always public
keyType = PublicCryptoKeyType

// pick the elliptic curve
c, err := pickEllipticCurve(e.NamedCurve)
if err != nil {
log.Printf("invalid elliptic curve: %v\n", err)
return nil, NewError(NotSupportedError, "invalid elliptic curve "+string(e.NamedCurve))
}
// pick the elliptic curve
c, err := pickEllipticCurve(e.NamedCurve)
if err != nil {
return nil, NewError(NotSupportedError, "invalid elliptic curve "+string(e.NamedCurve))
}

// import the key data
publicKey, err := c.NewPublicKey(keyData)
if err != nil {
log.Printf("unable to import key data: %v\n", err)
return nil, NewError(DataError, "unable to import key data: "+err.Error())
handle, err = c.NewPublicKey(keyData)
if err != nil {
return nil, NewError(DataError, "unable to import key data: "+err.Error())
}
}

// log.Printf("publicKey: %v\n", publicKey)
if format == Pkcs8KeyFormat {
// pkcs8 key type is always private
keyType = PrivateCryptoKeyType

var err error
parsedKey, err := x509.ParsePKCS8PrivateKey(keyData)
if err != nil {
return nil, NewError(DataError, "unable to import key data: "+err.Error())
}

// check if the key is an ECDSA key
ecdsaKey, ok := parsedKey.(*ecdsa.PrivateKey)
if !ok {
return nil, NewError(DataError, "a private key is not an ECDSA key")
}

// try to restore the ECDH key
handle, err = ecdsaKey.ECDH()
if err != nil {
return nil, NewError(DataError, "unable to import key data: "+err.Error())
}
}

return &CryptoKey{
Algorithm: EcKeyAlgorithm{
Expand All @@ -86,8 +101,8 @@ func (e *EcKeyImportParams) ImportKey(
},
NamedCurve: e.NamedCurve,
},
Type: PublicCryptoKeyType, // TODO: check if this is correct
handle: publicKey,
Type: keyType,
handle: handle,
}, nil
}

Expand All @@ -103,10 +118,6 @@ const (

// EllipticCurveKindP521 represents the P-521 curve.
EllipticCurveKindP521 EllipticCurveKind = "P-521"

// TODO: check why this isn't a valid curve
// EllipticCurveKind25519 represents the Curve25519 curve.
// EllipticCurveKind25519 EllipticCurveKind = "Curve25519"
)

// IsEllipticCurve returns true if the given string is a valid EllipticCurveKind,
Expand Down Expand Up @@ -230,9 +241,6 @@ func pickEllipticCurve(k EllipticCurveKind) (ecdh.Curve, error) {
return ecdh.P384(), nil
case EllipticCurveKindP521:
return ecdh.P521(), nil
// TODO: check why this fails
// case EllipticCurveKind25519:
// return ecdh.X25519(), nil
default:
return nil, errors.New("invalid elliptic curve")
}
Expand All @@ -244,25 +252,33 @@ func exportECKey(ck *CryptoKey, format KeyFormat) ([]byte, error) {
}

switch format {
case JwkKeyFormat:
key, err := jwk.FromRaw(ck.handle)
if err != nil {
return nil, NewError(OperationError, "unable to export key to JWK format: "+err.Error())
}

b, err := json.Marshal(key)
if err != nil {
return nil, NewError(OperationError, "unable to marshal key to JWK format"+err.Error())
case RawKeyFormat:
if ck.Type != PublicCryptoKeyType {
return nil, NewError(InvalidAccessError, "key is not a valid elliptic curve public key")
}

return b, nil
case RawKeyFormat:
k, ok := ck.handle.(*ecdh.PublicKey)
if !ok {
return nil, NewError(OperationError, "key data isn't a valid elliptic curve public key")
}

return k.Bytes(), nil
case Pkcs8KeyFormat:
if ck.Type != PrivateCryptoKeyType {
return nil, NewError(InvalidAccessError, "key is not a valid elliptic curve private key")
}

k, ok := ck.handle.(*ecdh.PrivateKey)
if !ok {
return nil, NewError(OperationError, "key data isn't a valid elliptic curve private key")
}

bytes, err := x509.MarshalPKCS8PrivateKey(k)
if err != nil {
return nil, NewError(OperationError, "unable to marshal key to PKCS8 format: "+err.Error())
}

return bytes, nil
default:
return nil, NewError(NotSupportedError, "unsupported key format "+format)
}
Expand Down
10 changes: 7 additions & 3 deletions webcrypto/subtle_crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ func (sc *SubtleCrypto) DeriveBits(algorithm goja.Value, baseKey goja.Value, len
}

if privateKey.Type != PrivateCryptoKeyType {
return NewError(InvalidAccessError, "provided baseKey is not a private key")
return NewError(InvalidAccessError, fmt.Sprintf("provided baseKey is not a private key: %v", privateKey))
}

alg := algorithm.ToObject(rt)
Expand Down Expand Up @@ -702,7 +702,7 @@ func (sc *SubtleCrypto) ImportKey(

// 2.
switch format {
case RawKeyFormat:
case Pkcs8KeyFormat, RawKeyFormat:
ab, err := exportArrayBuffer(rt, keyData)
if err != nil {
reject(err)
Expand Down Expand Up @@ -846,7 +846,7 @@ func (sc *SubtleCrypto) ExportKey(format KeyFormat, key goja.Value) *goja.Promis
return
}

if format != RawKeyFormat {
if !isBinaryExportedFormat(format) {
resolve(result)
return
}
Expand All @@ -863,6 +863,10 @@ func (sc *SubtleCrypto) ExportKey(format KeyFormat, key goja.Value) *goja.Promis
return promise
}

func isBinaryExportedFormat(format KeyFormat) bool {
return format == RawKeyFormat || format == Pkcs8KeyFormat
}

// WrapKey "wraps" a key.
//
// This means that it exports the key in an external, portable format, then encrypts the exported key.
Expand Down
2 changes: 1 addition & 1 deletion webcrypto/tests/generateKey/failures.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ function run_test(algorithmNames) {
function testError(algorithm, extractable, usages, expectedError, testTag) {
return crypto.subtle.generateKey(algorithm, extractable, usages)
.then(function(result) {
assert_unreached("Operation succeeded, but should not have, alg:" + JSON.stringify(algorithm) + ", ext:" + extractable + ", usages:" + usages);
assert_unreached("Operation succeeded, but should not have");
}, function(err) {
if (typeof expectedError === "number") {
assert_equals(err.code, expectedError, testTag + " not supported");
Expand Down
Loading

0 comments on commit 68aa59c

Please sign in to comment.