From fb978ee5742a9e637a7be395d23bb27740f30aa6 Mon Sep 17 00:00:00 2001 From: Steve Moyer Date: Thu, 17 Oct 2024 15:18:31 -0400 Subject: [PATCH] feat(did): strengthen DID crypto --- did/crypto.go | 48 +++++++++++++++++++++++++++++++++++++++---- did/crypto_test.go | 8 +++++++- did/did.go | 51 +++++++++++++++++++++++++++++++++++++++++----- go.mod | 8 ++++++++ go.sum | 21 +++++++++++++++++++ 5 files changed, 126 insertions(+), 10 deletions(-) diff --git a/did/crypto.go b/did/crypto.go index d723f5e..9b11d75 100644 --- a/did/crypto.go +++ b/did/crypto.go @@ -4,6 +4,8 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" + "crypto/x509" "errors" "fmt" @@ -84,6 +86,8 @@ func GenerateECDSAWithCurve(code multicodec.Code) (crypto.PrivKey, DID, error) { } +// FromPrivKey is a convenience function that returns the DID associated +// with the public key associated with the provided private key. func FromPrivKey(privKey crypto.PrivKey) (DID, error) { return FromPubKey(privKey.GetPublic()) } @@ -117,17 +121,53 @@ func FromPubKey(pubKey crypto.PubKey) (DID, error) { } } - pubBytes, err := pubKey.Raw() - if err != nil { - return Undef, err + var bytes []byte + + switch pubKey.Type() { + case pb.KeyType_ECDSA: + pkix, err := pubKey.Raw() + if err != nil { + return Undef, err + } + + publicKey, err := x509.ParsePKIXPublicKey(pkix) + if err != nil { + return Undef, err + } + + ecdsaPublicKey := publicKey.(*ecdsa.PublicKey) + + bytes = elliptic.MarshalCompressed(ecdsaPublicKey.Curve, ecdsaPublicKey.X, ecdsaPublicKey.Y) + case pb.KeyType_Ed25519, pb.KeyType_Secp256k1: + var err error + + if bytes, err = pubKey.Raw(); err != nil { + return Undef, err + } + case pb.KeyType_RSA: + var err error + + pkix, err := pubKey.Raw() + if err != nil { + return Undef, err + } + + publicKey, err := x509.ParsePKIXPublicKey(pkix) + if err != nil { + return Undef, err + } + + bytes = x509.MarshalPKCS1PublicKey(publicKey.(*rsa.PublicKey)) } return DID{ code: code, - bytes: string(append(varint.ToUvarint(uint64(code)), pubBytes...)), + bytes: string(append(varint.ToUvarint(uint64(code)), bytes...)), }, nil } +// ToPubKey returns the crypto.PubKey encapsulated in the DID formed by +// parsing the provided string. func ToPubKey(s string) (crypto.PubKey, error) { id, err := Parse(s) if err != nil { diff --git a/did/crypto_test.go b/did/crypto_test.go index 6395811..43d0bea 100644 --- a/did/crypto_test.go +++ b/did/crypto_test.go @@ -27,6 +27,10 @@ func TestFromPubKey(t *testing.T) { require.NoError(t, err) _, ecdsaP521, err := crypto.GenerateECDSAKeyPairWithCurve(elliptic.P521(), rand.Reader) require.NoError(t, err) + _, ed25519, err := crypto.GenerateEd25519Key(rand.Reader) + require.NoError(t, err) + _, rsa, err := crypto.GenerateRSAKeyPair(2048, rand.Reader) + require.NoError(t, err) _, secp256k1PubKey1, err := crypto.GenerateSecp256k1Key(rand.Reader) require.NoError(t, err) @@ -47,7 +51,9 @@ func TestFromPubKey(t *testing.T) { t.Run("ECDSA with P256 curve", test(ecdsaP256, did.P256)) t.Run("ECDSA with P384 curve", test(ecdsaP384, did.P384)) t.Run("ECDSA with P521 curve", test(ecdsaP521, did.P521)) - t.Run("With secp256k1 (secp256k1)", test(secp256k1PubKey1, did.Secp256k1)) + t.Run("Ed25519", test(ed25519, did.Ed25519)) + t.Run("RSA", test(rsa, did.RSA)) + t.Run("secp256k1", test(secp256k1PubKey1, did.Secp256k1)) id, err := did.FromPubKey(examplePubKey(t)) require.NoError(t, err) diff --git a/did/did.go b/did/did.go index 111f210..bf92946 100644 --- a/did/did.go +++ b/did/did.go @@ -1,6 +1,9 @@ package did import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/x509" "fmt" "strings" @@ -34,6 +37,10 @@ type DID struct { bytes string // as string instead of []byte to allow the == operator } +// Parse returns the DID from the string representation or an error if +// the prefix and method are incorrect, if an unknown encryption algorithm +// is specified or if the method-specific-identifier's bytes don't +// represent a public key for the specified encryption algorithm. func Parse(str string) (DID, error) { const keyPrefix = "did:key:" @@ -60,6 +67,7 @@ func Parse(str string) (DID, error) { } } +// MustParse is like Parse but panics instead of returning an error. func MustParse(str string) DID { did, err := Parse(str) if err != nil { @@ -73,16 +81,16 @@ func (d DID) Defined() bool { return d.code == 0 || len(d.bytes) > 0 } -// PubKey returns the public key encapsulated in the did:key. +// PubKey returns the public key encapsulated by the did:key. func (d DID) PubKey() (crypto.PubKey, error) { unmarshaler, ok := map[multicodec.Code]crypto.PubKeyUnmarshaller{ X25519: crypto.UnmarshalEd25519PublicKey, Ed25519: crypto.UnmarshalEd25519PublicKey, - P256: crypto.UnmarshalECDSAPublicKey, - P384: crypto.UnmarshalECDSAPublicKey, - P521: crypto.UnmarshalECDSAPublicKey, + P256: ecdsaPubKeyUnmarshaler(elliptic.P256()), + P384: ecdsaPubKeyUnmarshaler(elliptic.P384()), + P521: ecdsaPubKeyUnmarshaler(elliptic.P521()), Secp256k1: crypto.UnmarshalSecp256k1PublicKey, - RSA: crypto.UnmarshalRsaPublicKey, + RSA: rsaPubKeyUnmarshaller, }[d.code] if !ok { return nil, fmt.Errorf("unsupported multicodec: %d", d.code) @@ -97,3 +105,36 @@ func (d DID) String() string { key, _ := mbase.Encode(mbase.Base58BTC, []byte(d.bytes)) return "did:key:" + key } + +func ecdsaPubKeyUnmarshaler(curve elliptic.Curve) crypto.PubKeyUnmarshaller { + return func(data []byte) (crypto.PubKey, error) { + x, y := elliptic.UnmarshalCompressed(curve, data) + + ecdsaPublicKey := &ecdsa.PublicKey{ + Curve: curve, + X: x, + Y: y, + } + + pkix, err := x509.MarshalPKIXPublicKey(ecdsaPublicKey) + if err != nil { + return nil, err + } + + return crypto.UnmarshalECDSAPublicKey(pkix) + } +} + +func rsaPubKeyUnmarshaller(data []byte) (crypto.PubKey, error) { + rsaPublicKey, err := x509.ParsePKCS1PublicKey(data) + if err != nil { + return nil, err + } + + pkix, err := x509.MarshalPKIXPublicKey(rsaPublicKey) + if err != nil { + return nil, err + } + + return crypto.UnmarshalRsaPublicKey(pkix) +} diff --git a/go.mod b/go.mod index d5ad0f9..2193ff4 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ipfs/go-cid v0.4.1 github.com/ipld/go-ipld-prime v0.21.0 + github.com/lestrrat-go/jwx/v2 v2.1.1 github.com/libp2p/go-libp2p v0.36.3 github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multibase v0.2.0 @@ -18,13 +19,20 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/minio/sha256-simd v1.0.1 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect github.com/multiformats/go-base36 v0.2.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 1d69a3a..82932d5 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= @@ -9,6 +10,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3 github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -25,6 +28,18 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.1 h1:Y2ltVl8J6izLYFs54BVcpXLv5msSW4o8eXwnzZLI32E= +github.com/lestrrat-go/jwx/v2 v2.1.1/go.mod h1:4LvZg7oxu6Q5VJwn7Mk/UwooNRnTHUpXBj2C4j3HNx0= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-libp2p v0.36.3 h1:NHz30+G7D8Y8YmznrVZZla0ofVANrvBl2c+oARfMeDQ= @@ -54,6 +69,8 @@ github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= @@ -61,6 +78,9 @@ github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hg github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= @@ -86,6 +106,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=