It looks like neither Hyperledger Aries Go framework, nor the Google Tink crypto library, which it uses under the hood, provide helper functions to convert public key bytes to JWK format (or I haven’t found the way). When you export public key bytes from the KSM, they are unmarshalled from their internal representation and serialized using x509.MarshalPKIXPublicKey(), elliptic.Marshal(), and some are marshalled as JSON using json.Marshal() to custom Aries PublicKey struct. This means that we can’t use standard Go JOSE libraries to convert these public keys to JWK directly.

This post shows how to make two kinds of keys to JWK, but I’ll try to explain the process I followed to discover them, so if you need to export other public key types, you’ll probably know where to look.

One of the key types - NISTP256ECDHKWType - is mandatory in the current implementation of Hyperledger Aries Go if you want to use authenticated encryption (authcrypt) of DIDComm messages and follow the DIDComm V2 spec.

Why would you want to convert Aries public keys to JWK?

To use them as verification methods in DID documents. It is a common and widely supported encoding of public keys in DID documents (publicKeyJwk)

Below are example functions to convert ECDSA and ECDSA-ECDH-KW keys:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
type JWK struct {
	Kty string `json:"kty"`
	Crv string `json:"crv"`
	X   string `json:"x"`
	Y   string `json:"y"`
	Kid string `json:"kid"`
}

func main() {
	// Aries KMS is created and initialized and you have a reference to it.
	// ...

	keyID := "..."
	pubkey, keyType, err := ariesKMS.ExportPublicKeyBytes(keyID)
	if err != nil {
		log.Fatalln(err)
	}

	jwk, err := PublicKeyToJWK(keyID, keyType, pubKey)
	if err != nil {
		log.Fatalln(err)
	}

	jwkBytes, err := json.Marshal(jwk)
	if err != nil {
		log.Fatalln(err)
	}

	log.Printf("jwkBytes = %#+v\n", jwkBytes)
}


// PublicKeyToJWK converts public key bytes from the binary format of the Tink
// key to a JWK.
func PublicKeyToJWK(keyID string, kt kms.KeyType, pubKey []byte) (interface{}, error) {
	switch kt {
	case kms.ECDSAP256TypeIEEEP1363, kms.ECDSAP384TypeIEEEP1363, kms.ECDSAP521TypeIEEEP1363:
		return ecKeyToJwk(keyID, kt, pubKey)
	case kms.NISTP256ECDHKWType, kms.NISTP384ECDHKWType, kms.NISTP521ECDHKWType:
		return ecdhKWToJwk(keyID, pubKey)
	default:
		return nil, fmt.Errorf("unsupported key type: %v", kt)
	}
}

func ecKeyToJwk(keyID string, kt kms.KeyType, pubKey []byte) (interface{}, error) {
	var x, y *big.Int
	var curve elliptic.Curve

	switch kt {
	case kms.ECDSAP256TypeIEEEP1363:
		curve = elliptic.P256()
		x, y = elliptic.Unmarshal(curve, pubKey)
	case kms.ECDSAP384TypeIEEEP1363:
		curve = elliptic.P384()
		x, y = elliptic.Unmarshal(curve, pubKey)
	case kms.ECDSAP521TypeIEEEP1363:
		curve = elliptic.P521()
		x, y = elliptic.Unmarshal(curve, pubKey)
	default:
		return nil, fmt.Errorf("unsupported key type: %v", kt)
	}

	xEncoded := base64.RawURLEncoding.EncodeToString(x.Bytes())
	yEncoded := base64.RawURLEncoding.EncodeToString(y.Bytes())

	jwk := JWK{
		Kty: "EC",
		Crv: curve.Params().Name,
		X:   xEncoded,
		Y:   yEncoded,
		Kid: keyID,
	}

	return jwk, nil
}

func ecdhKWToJwk(keyID string, pubKey []byte) (interface{}, error) {
	var ariesKey cryptoapi.PublicKey
	if err := json.Unmarshal(pubKey, &ariesKey); err != nil {
		return nil, err
	}

	xEncoded := base64.RawURLEncoding.EncodeToString(ariesKey.X)
	yEncoded := base64.RawURLEncoding.EncodeToString(ariesKey.Y)

	jwk := JWK{
		Kty: ariesKey.Type,
		Crv: altCurveName(ariesKey.Curve),
		X:   xEncoded,
		Y:   yEncoded,
		Kid: keyID,
	}

	return jwk, nil
}

func altCurveName(name string) string {
	switch name {
	case "NIST_P256":
		return "P-256"
	case "NIST_P384":
		return "P-384"
	case "NIST_P521":
		return "P-521"
	default:
		return name
	}
}

How to find serializations for other supported key types?

By looking at the public key serialization process for your needed key type.

Keys are created by different KeyManager implementations.

Public keys are serialized in the pubkey_writer.go file. Best to checkout the Hyperledger Aries Go github repo and dive into this file and follow the different serialization functions for the key types you need.