Skip to content

Commit

Permalink
Get Key from a DID's Verification Information (#342)
Browse files Browse the repository at this point in the history
* get verification information key

* remove logging

* pr comments

* rename
  • Loading branch information
decentralgabe committed Apr 7, 2023
1 parent 08475b4 commit 7a28bcc
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 12 deletions.
4 changes: 2 additions & 2 deletions did/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func codecToLDKeyType(codec multicodec.Code) (cryptosuite.LDKeyType, error) {
return cryptosuite.Ed25519VerificationKey2018, nil
case X25519MultiCodec:
return cryptosuite.X25519KeyAgreementKey2019, nil
case Secp256k1MultiCodec:
case SECP256k1MultiCodec:
return cryptosuite.ECDSASECP256k1VerificationKey2019, nil
case P256MultiCodec, P384MultiCodec, P521MultiCodec, RSAMultiCodec:
return cryptosuite.JSONWebKey2020Type, nil
Expand Down Expand Up @@ -202,7 +202,7 @@ func codecToKeyType(codec multicodec.Code) (crypto.KeyType, error) {
kt = crypto.Ed25519
case X25519MultiCodec:
kt = crypto.X25519
case Secp256k1MultiCodec:
case SECP256k1MultiCodec:
kt = crypto.SECP256k1
case P256MultiCodec:
kt = crypto.P256
Expand Down
2 changes: 1 addition & 1 deletion did/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const (

Ed25519MultiCodec = multicodec.Ed25519Pub
X25519MultiCodec = multicodec.X25519Pub
Secp256k1MultiCodec = multicodec.Secp256k1Pub
SECP256k1MultiCodec = multicodec.Secp256k1Pub
P256MultiCodec = multicodec.P256Pub
P384MultiCodec = multicodec.P384Pub
P521MultiCodec = multicodec.P521Pub
Expand Down
113 changes: 104 additions & 9 deletions did/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,110 @@ import (
"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
"github.com/TBD54566975/ssi-sdk/util"
"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/v2/jwk"
"github.com/mr-tron/base58"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-multicodec"
"github.com/multiformats/go-varint"
"github.com/pkg/errors"
)

// GetKeyFromVerificationMethod resolves a DID and provides a kid and public key needed for data verification
// it is possible that a DID has multiple verification methods, in which case a kid must be provided, otherwise
// resolution will fail.
// A KID can be fully qualified (e.g. did:example:123#key-1) or just the fragment (e.g. key-1, #key-1)
// Some DIDs, like did:key, use the entire DID as the KID, so we need to handle all three cases.
func GetKeyFromVerificationMethod(did Document, kid string) (gocrypto.PublicKey, error) {
if did.IsEmpty() {
return nil, errors.New("did doc cannot be empty")
}
if kid == "" {
return nil, errors.Errorf("kid is required for did: %s", did.ID)
}

verificationMethods := did.VerificationMethod
if len(verificationMethods) == 0 {
return nil, errors.Errorf("did<%s> has no verification methods", did.ID)
}

for _, method := range verificationMethods {
// make sure the kid matches the verification method
if matchesKIDConstruction(did.ID, kid, method.ID) {
return extractKeyFromVerificationMethod(method)
}
}

return nil, errors.Errorf("did<%s> has no verification methods with kid: %s", did.ID, kid)
}

// matchesKIDConstruction checks if the targetID matches possible combinations of the did and kid
func matchesKIDConstruction(did, kid, targetID string) bool {
maybeKID1 := kid // the kid == the kid
maybeKID2 := fmt.Sprintf("#%s", kid) // the kid == the fragment with a #
maybeKID3 := fmt.Sprintf("%s#%s", did, kid) // the kid == the DID ID + the fragment with a #
maybeKID4 := fmt.Sprintf("%s%s", did, kid) // the kid == the DID ID + the fragment without a #
return targetID == maybeKID1 || targetID == maybeKID2 || targetID == maybeKID3 || targetID == maybeKID4
}

func extractKeyFromVerificationMethod(method VerificationMethod) (gocrypto.PublicKey, error) {
switch {
case method.PublicKeyMultibase != "":
pubKeyBytes, multiBaseErr := multibaseToPubKeyBytes(method.PublicKeyMultibase)
if multiBaseErr != nil {
return nil, errors.Wrap(multiBaseErr, "converting multibase key")
}
return cryptosuite.PubKeyBytesToTypedKey(pubKeyBytes, method.Type)
case method.PublicKeyBase58 != "":
pubKeyDecoded, b58Err := base58.Decode(method.PublicKeyBase58)
if b58Err != nil {
return nil, errors.Wrap(b58Err, "decoding base58 key")
}
return cryptosuite.PubKeyBytesToTypedKey(pubKeyDecoded, method.Type)
case method.PublicKeyJWK != nil:
jwkBytes, jwkErr := json.Marshal(method.PublicKeyJWK)
if jwkErr != nil {
return nil, errors.Wrap(jwkErr, "marshalling jwk")
}
parsed, parseErr := jwk.ParseKey(jwkBytes)
if parseErr != nil {
return nil, errors.Wrap(parseErr, "parsing jwk")
}
var pubKey gocrypto.PublicKey
if err := parsed.Raw(&pubKey); err != nil {
return nil, errors.Wrap(err, "getting raw jwk")
}
return pubKey, nil
}
return nil, errors.New("no public key found in verification method")
}

// multibaseToPubKey converts a multibase encoded public key to public key bytes for known multibase encodings
func multibaseToPubKeyBytes(mb string) ([]byte, error) {
if mb == "" {
return nil, errors.New("multibase key cannot be empty")
}

encoding, decoded, err := multibase.Decode(mb)
if err != nil {
return nil, errors.Wrap(err, "decoding multibase key")
}
if encoding != Base58BTCMultiBase {
return nil, fmt.Errorf("expected %d encoding but found %d", Base58BTCMultiBase, encoding)
}

// n = # bytes for the int, which we expect to be two from our multicodec
_, n, err := varint.FromUvarint(decoded)
if err != nil {
return nil, errors.Wrap(err, "error parsing multibase varint")
}
if n != 2 {
return nil, errors.New("error parsing multibase varint")
}
pubKeyBytes := decoded[n:]
return pubKeyBytes, nil
}

// Encodes the public key provided
// Using a multi-codec encoding.
func encodePublicKeyWithKeyMultiCodecType(kt crypto.KeyType, pubKey gocrypto.PublicKey) (string, error) {
Expand Down Expand Up @@ -47,8 +145,7 @@ func decodeEncodedKey(d string) ([]byte, cryptosuite.LDKeyType, crypto.KeyType,
}

if encoding != Base58BTCMultiBase {
err := fmt.Errorf("expected %d encoding but found %d", Base58BTCMultiBase, encoding)
return nil, "", "", err
return nil, "", "", fmt.Errorf("expected %d encoding but found %d", Base58BTCMultiBase, encoding)
}

// n = # bytes for the int, which we expect to be two from our multicodec
Expand All @@ -57,8 +154,7 @@ func decodeEncodedKey(d string) ([]byte, cryptosuite.LDKeyType, crypto.KeyType,
return nil, "", "", err
}
if n != 2 {
errMsg := "Error parsing did:key varint"
return nil, "", "", errors.New(errMsg)
return nil, "", "", errors.New("error parsing did:key varint")
}

pubKeyBytes := decoded[n:]
Expand Down Expand Up @@ -91,7 +187,7 @@ func decodePublicKeyWithType(data []byte) ([]byte, cryptosuite.LDKeyType, error)
// n = # bytes for the int, which we expect to be two from our multicodec
multiCodec, n, err := varint.FromUvarint(decoded)
if err != nil {
return nil, "", err
return nil, "", errors.Wrap(err, "failed to decode public key from varint")
}

pubKeyBytes := decoded[n:]
Expand All @@ -103,13 +199,12 @@ func decodePublicKeyWithType(data []byte) ([]byte, cryptosuite.LDKeyType, error)
return pubKeyBytes, cryptosuite.X25519KeyAgreementKey2020, nil
case SHA256MultiCodec:
return pubKeyBytes, cryptosuite.Ed25519VerificationKey2020, nil
case Secp256k1MultiCodec:
case SECP256k1MultiCodec:
return pubKeyBytes, cryptosuite.ECDSASECP256k1VerificationKey2019, nil
case P256MultiCodec, P384MultiCodec, P521MultiCodec, RSAMultiCodec:
return pubKeyBytes, cryptosuite.JSONWebKey2020Type, nil
default:
err := fmt.Errorf("unknown multicodec for did:peer: %d", multiCodecValue)
return nil, "", err
return nil, "", fmt.Errorf("unknown multicodec for did:peer: %d", multiCodecValue)
}
}

Expand All @@ -120,7 +215,7 @@ func keyTypeToMultiCodec(kt crypto.KeyType) (multicodec.Code, error) {
case crypto.X25519:
return X25519MultiCodec, nil
case crypto.SECP256k1:
return Secp256k1MultiCodec, nil
return SECP256k1MultiCodec, nil
case crypto.P256:
return P256MultiCodec, nil
case crypto.P384:
Expand Down
Loading

0 comments on commit 7a28bcc

Please sign in to comment.