Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Commit

Permalink
Schema Signing & Verification (#123)
Browse files Browse the repository at this point in the history
* light request validation

* Scheme delete api

* optional schema signing

* schema signing testing

* verification not working yet

* verification api and test

* tests

* generate new swagger

* create one DB per test
  • Loading branch information
Gabe authored Oct 11, 2022
1 parent 556edf7 commit cbc49af
Show file tree
Hide file tree
Showing 18 changed files with 931 additions and 208 deletions.
295 changes: 251 additions & 44 deletions doc/swagger.yaml

Large diffs are not rendered by default.

124 changes: 14 additions & 110 deletions internal/credential/verification.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,11 @@
package credential

import (
"crypto"
"fmt"

credsdk "github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/TBD54566975/ssi-sdk/credential/verification"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
didsdk "github.com/TBD54566975/ssi-sdk/did"
"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/jwk"
"github.com/mr-tron/base58"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-varint"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"

"github.com/tbd54566975/ssi-service/internal/keyaccess"
"github.com/tbd54566975/ssi-service/internal/util"
Expand Down Expand Up @@ -53,7 +43,12 @@ func (v CredentialVerifier) VerifyJWTCredential(token string) error {
if err != nil {
return util.LoggingErrorMsg(err, "could not parse credential from JWT")
}
kid, pubKey, err := v.getVerificationInformation(cred.Issuer.(string), "")
issuerDID := cred.Issuer.(string)
resolved, err := v.resolver.Resolve(issuerDID)
if err != nil {
return errors.Wrapf(err, "failed to resolve did: %s", issuerDID)
}
kid, pubKey, err := keyaccess.GetVerificationInformation(resolved.DIDDocument, "")
if err != nil {
return util.LoggingErrorMsg(err, "could not get verification from credential")
}
Expand All @@ -74,115 +69,24 @@ func (v CredentialVerifier) VerifyJWTCredential(token string) error {
// a set of static verification checks on the credential as per the credential service's configuration.
func (v CredentialVerifier) VerifyDataIntegrityCredential(credential credsdk.VerifiableCredential) error {
// TODO(gabe): perhaps this should be a verification method referenced on the proof object, not the issuer
kid, pubKey, err := v.getVerificationInformation(credential.Issuer.(string), "")
issuerDID := credential.Issuer.(string)
resolved, err := v.resolver.Resolve(issuerDID)
if err != nil {
return util.LoggingErrorMsg(err, "could not get verification from credential")
return errors.Wrapf(err, "failed to resolve did: %s", issuerDID)
}
kid, pubKey, err := keyaccess.GetVerificationInformation(resolved.DIDDocument, "")
if err != nil {
return util.LoggingErrorMsg(err, "could not get verification information from credential")
}
verifier, err := keyaccess.NewDataIntegrityKeyAccess(kid, pubKey)
if err != nil {
return util.LoggingErrorMsg(err, "could not create verifier")
}
if err := verifier.Verify(&credential); err != nil {
return util.LoggingErrorMsg(err, "could not verify credential's signature")
return util.LoggingErrorMsg(err, "could not verify the credential's signature")
}
if err = v.verifier.VerifyCredential(credential); err != nil {
return util.LoggingErrorMsg(err, "static credential verification failed")
}
return err
}

// getVerificationInformation resolves a DID and provides a kid and public key needed for credential verification
// it is possible that a DID has multiple verification methods, in which case a kid must be provided, otherwise
// resolution will fail.
func (v CredentialVerifier) getVerificationInformation(did, maybeKID string) (kid string, pubKey crypto.PublicKey, err error) {
resolved, err := v.resolver.Resolve(did)
if err != nil {
return "", nil, errors.Wrapf(err, "failed to resolve did: %s", did)
}
if resolved.DIDDocument.IsEmpty() {
return "", nil, errors.Errorf("did doc: %s is empty", did)
}
verificationMethods := resolved.DIDDocument.VerificationMethod
if len(verificationMethods) == 0 {
return "", nil, errors.Errorf("did doc: %s has no verification methods", did)
}

// handle the case where a kid is provided && there are multiple verification methods
if len(verificationMethods) > 1 {
if kid == "" {
return "", nil, errors.Errorf("kid is required for did: %s, which has multiple verification methods", did)
}
for _, method := range verificationMethods {
if method.ID == kid {
kid = did
pubKey, err = extractKeyFromVerificationMethod(verificationMethods[0])
return
}
}
}
// TODO(gabe): some DIDs, like did:key have KIDs that aren't used, so we need to know when to use a kid vs the DID
kid = did
pubKey, err = extractKeyFromVerificationMethod(verificationMethods[0])
return
}

func extractKeyFromVerificationMethod(method didsdk.VerificationMethod) (pubKey crypto.PublicKey, err error) {
if method.PublicKeyMultibase != "" {
pubKeyBytes, multiBaseErr := multibaseToPubKeyBytes(method.PublicKeyMultibase)
if multiBaseErr != nil {
err = multiBaseErr
return
}
pubKey, err = cryptosuite.PubKeyBytesToTypedKey(pubKeyBytes, method.Type)
return
} else if method.PublicKeyBase58 != "" {
pubKeyDecoded, b58Err := base58.Decode(method.PublicKeyBase58)
if b58Err != nil {
err = b58Err
return
}
pubKey, err = cryptosuite.PubKeyBytesToTypedKey(pubKeyDecoded, method.Type)
return
} else if method.PublicKeyJWK != nil {
jwkBytes, jwkErr := json.Marshal(method.PublicKeyJWK)
if err != nil {
err = jwkErr
return
}
pubKey, err = jwk.ParseKey(jwkBytes)
return
}
err = errors.New("no public key found in verification method")
return
}

// multibaseToPubKey converts a multibase encoded public key to public key bytes for known multibase encodings
func multibaseToPubKeyBytes(mb string) ([]byte, error) {
if mb == "" {
err := fmt.Errorf("could not decode value: %s", mb)
logrus.WithError(err).Error()
return nil, err
}

encoding, decoded, err := multibase.Decode(mb)
if err != nil {
logrus.WithError(err).Error("could not decode did:key")
return nil, err
}
if encoding != didsdk.Base58BTCMultiBase {
err := fmt.Errorf("expected %d encoding but found %d", didsdk.Base58BTCMultiBase, encoding)
logrus.WithError(err).Error()
return nil, err
}

// n = # bytes for the int, which we expect to be two from our multicodec
_, n, err := varint.FromUvarint(decoded)
if err != nil {
return nil, err
}
if n != 2 {
return nil, errors.New("error parsing did:key varint")
}
pubKeyBytes := decoded[n:]
return pubKeyBytes, nil
}
108 changes: 108 additions & 0 deletions internal/keyaccess/verification.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package keyaccess

import (
"crypto"
"fmt"

"github.com/TBD54566975/ssi-sdk/cryptosuite"
didsdk "github.com/TBD54566975/ssi-sdk/did"
"github.com/goccy/go-json"
"github.com/lestrrat-go/jwx/jwk"
"github.com/mr-tron/base58"
"github.com/multiformats/go-multibase"
"github.com/multiformats/go-varint"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// GetVerificationInformation 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.
func GetVerificationInformation(did didsdk.DIDDocument, maybeKID string) (kid string, pubKey crypto.PublicKey, err error) {
if did.IsEmpty() {
return "", nil, errors.Errorf("did doc: %+v is empty", did)
}
verificationMethods := did.VerificationMethod
if len(verificationMethods) == 0 {
return "", nil, errors.Errorf("did doc: %s has no verification methods", did.ID)
}

// handle the case where a kid is provided && there are multiple verification methods
if len(verificationMethods) > 1 {
if kid == "" {
return "", nil, errors.Errorf("kid is required for did: %s, which has multiple verification methods", did.ID)
}
for _, method := range verificationMethods {
if method.ID == kid {
kid = did.ID
pubKey, err = extractKeyFromVerificationMethod(verificationMethods[0])
return
}
}
}
// TODO(gabe): some DIDs, like did:key have KIDs that aren't used, so we need to know when to use a kid vs the DID
kid = did.ID
pubKey, err = extractKeyFromVerificationMethod(verificationMethods[0])
return
}

func extractKeyFromVerificationMethod(method didsdk.VerificationMethod) (pubKey crypto.PublicKey, err error) {
if method.PublicKeyMultibase != "" {
pubKeyBytes, multiBaseErr := multibaseToPubKeyBytes(method.PublicKeyMultibase)
if multiBaseErr != nil {
err = multiBaseErr
return
}
pubKey, err = cryptosuite.PubKeyBytesToTypedKey(pubKeyBytes, method.Type)
return
} else if method.PublicKeyBase58 != "" {
pubKeyDecoded, b58Err := base58.Decode(method.PublicKeyBase58)
if b58Err != nil {
err = b58Err
return
}
pubKey, err = cryptosuite.PubKeyBytesToTypedKey(pubKeyDecoded, method.Type)
return
} else if method.PublicKeyJWK != nil {
jwkBytes, jwkErr := json.Marshal(method.PublicKeyJWK)
if err != nil {
err = jwkErr
return
}
pubKey, err = jwk.ParseKey(jwkBytes)
return
}
err = errors.New("no public key found in verification method")
return
}

// multibaseToPubKey converts a multibase encoded public key to public key bytes for known multibase encodings
func multibaseToPubKeyBytes(mb string) ([]byte, error) {
if mb == "" {
err := fmt.Errorf("could not decode value: %s", mb)
logrus.WithError(err).Error()
return nil, err
}

encoding, decoded, err := multibase.Decode(mb)
if err != nil {
logrus.WithError(err).Error("could not decode did:key")
return nil, err
}
if encoding != didsdk.Base58BTCMultiBase {
err := fmt.Errorf("expected %d encoding but found %d", didsdk.Base58BTCMultiBase, encoding)
logrus.WithError(err).Error()
return nil, err
}

// n = # bytes for the int, which we expect to be two from our multicodec
_, n, err := varint.FromUvarint(decoded)
if err != nil {
return nil, err
}
if n != 2 {
return nil, errors.New("error parsing did:key varint")
}
pubKeyBytes := decoded[n:]
return pubKeyBytes, nil
}
5 changes: 5 additions & 0 deletions pkg/server/framework/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"reflect"
"strings"

"github.com/TBD54566975/ssi-sdk/util"
"github.com/goccy/go-json"

"github.com/dimfeld/httptreemux/v5"
Expand Down Expand Up @@ -95,3 +96,7 @@ func Decode(r *http.Request, val interface{}) error {

return nil
}

func ValidateRequest(request interface{}) error {
return util.IsValidStruct(request)
}
9 changes: 8 additions & 1 deletion pkg/server/router/credential.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,15 @@ type CreateCredentialResponse struct {
// @Router /v1/credentials [put]
func (cr CredentialRouter) CreateCredential(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
var request CreateCredentialRequest
invalidCreateCredentialRequest := "invalid create credential request"
if err := framework.Decode(r, &request); err != nil {
errMsg := "invalid create credential request"
errMsg := invalidCreateCredentialRequest
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}

if err := framework.ValidateRequest(request); err != nil {
errMsg := invalidCreateCredentialRequest
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}
Expand Down
9 changes: 8 additions & 1 deletion pkg/server/router/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,15 @@ func (dr DIDRouter) CreateDIDByMethod(ctx context.Context, w http.ResponseWriter
}

var request CreateDIDByMethodRequest
invalidCreateDIDRequest := "invalid create DID request"
if err := framework.Decode(r, &request); err != nil {
errMsg := "invalid create DID request"
errMsg := invalidCreateDIDRequest
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}

if err := framework.ValidateRequest(request); err != nil {
errMsg := invalidCreateDIDRequest
logrus.WithError(err).Error(errMsg)
return framework.NewRequestError(errors.Wrap(err, errMsg), http.StatusBadRequest)
}
Expand Down
2 changes: 0 additions & 2 deletions pkg/server/router/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,11 @@ func TestManifestRouter(t *testing.T) {
createApplicationRequest := getValidApplicationRequest(applicantDID.DID.ID, createdManifest.Manifest.ID, createManifestRequest.Manifest.PresentationDefinition.InputDescriptors[0].ID)

createdApplicationResponse, err := manifestService.ProcessApplicationSubmission(createApplicationRequest)

assert.NoError(tt, err)
assert.NotEmpty(tt, createdManifest)
assert.NotEmpty(tt, createdApplicationResponse.Response.ID)
assert.Equal(tt, len(createManifestRequest.Manifest.OutputDescriptors), len(createdApplicationResponse.Credential))
})

}

func getValidManifestRequest(issuerDID string) manifest.CreateManifestRequest {
Expand Down
Loading

0 comments on commit cbc49af

Please sign in to comment.