From 59c2830d27fd44f9a3a663242a4aa61544ce622e Mon Sep 17 00:00:00 2001 From: Stas Dmytryshyn Date: Thu, 15 Feb 2024 19:04:14 +0100 Subject: [PATCH] feat: add rsa support for fingreprint (#18) * feat: add rsa support for fingreprint * fix: cleanup * fix: lint --- doc/util/fingerprint/fingerprint.go | 17 +++++++- doc/util/fingerprint/fingerprint_test.go | 51 ++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/doc/util/fingerprint/fingerprint.go b/doc/util/fingerprint/fingerprint.go index fe705b9..629dab1 100644 --- a/doc/util/fingerprint/fingerprint.go +++ b/doc/util/fingerprint/fingerprint.go @@ -10,6 +10,8 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" + "crypto/rsa" + "crypto/x509" "encoding/binary" "errors" "fmt" @@ -36,6 +38,9 @@ const ( // P521PubKeyMultiCodec for NIST P-521 public key in multicodec table. P521PubKeyMultiCodec = 0x1202 + // RSAPubKeyMultiCodec for RSA public key in multicodec table. + RSAPubKeyMultiCodec = 0x1205 + // Default BLS 12-381 public key length in G2 field. bls12381G2PublicKeyLen = 96 @@ -61,7 +66,7 @@ func CreateDIDKeyByCode(code uint64, pubKey []byte) (string, string) { // CreateDIDKeyByJwk creates a did:key ID using the multicodec key fingerprint as per the did:key format spec found at: // https://w3c-ccg.github.io/did-method-key/#format. -func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) { +func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) { //nolint:gocyclo if jsonWebKey == nil { return "", "", fmt.Errorf("jsonWebKey is required") } @@ -104,6 +109,16 @@ func CreateDIDKeyByJwk(jsonWebKey *jwk.JWK) (string, string, error) { default: return "", "", fmt.Errorf("unexpected OKP key type %T", key) } + case "RSA": + key, ok := jsonWebKey.Key.(*rsa.PublicKey) + if !ok { + return "", "", fmt.Errorf("unexpected RSA key type %T", jsonWebKey.Key) + } + + didKey, keyID := CreateDIDKeyByCode(RSAPubKeyMultiCodec, x509.MarshalPKCS1PublicKey(key)) + + return didKey, keyID, nil + default: return "", "", fmt.Errorf("unsupported kty %s", jsonWebKey.Kty) } diff --git a/doc/util/fingerprint/fingerprint_test.go b/doc/util/fingerprint/fingerprint_test.go index 2614e88..fdb16f5 100644 --- a/doc/util/fingerprint/fingerprint_test.go +++ b/doc/util/fingerprint/fingerprint_test.go @@ -10,13 +10,17 @@ import ( "crypto/ecdsa" "crypto/ed25519" "crypto/elliptic" + "crypto/rand" + "crypto/rsa" "encoding/base64" + "fmt" "math/big" "strings" "testing" "github.com/btcsuite/btcutil/base58" "github.com/go-jose/go-jose/v3" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/trustbloc/kms-go/doc/jose/jwk" @@ -303,6 +307,53 @@ func TestCreateDIDKeyByJwk(t *testing.T) { }) } +func TestRSAFingerprint(t *testing.T) { + testKeys := []struct { + bitCount int + expectedPrefix string + }{ + { + bitCount: 2048, + expectedPrefix: "did:key:z4MX", + }, + { + bitCount: 4096, + expectedPrefix: "did:key:zgg", + }, + } + + for _, testKey := range testKeys { + t.Run(fmt.Sprint("RSA-", testKey.bitCount), func(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, testKey.bitCount) + require.NoError(t, err) + + jwkKey, err := jwksupport.JWKFromKey(&key.PublicKey) + require.NoError(t, err) + + didKey, keyID, err := CreateDIDKeyByJwk(jwkKey) + require.NoError(t, err) + + assert.NotEmpty(t, didKey) + assert.NotEmpty(t, keyID) + assert.True(t, strings.HasPrefix(didKey, testKey.expectedPrefix)) + }) + } +} + +func TestRSAInvalidKey(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 2048) + require.NoError(t, err) + + jwkKey, err := jwksupport.JWKFromKey(&key.PublicKey) + require.NoError(t, err) + + jwkKey.Key = "invalid key type" + didKey, keyID, err := CreateDIDKeyByJwk(jwkKey) + require.Empty(t, didKey) + require.Empty(t, keyID) + require.ErrorContains(t, err, "unexpected RSA key type string") +} + func TestDIDKeyEd25519(t *testing.T) { const ( k1 = "did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH"