Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

demo of full authorization flow #147

Merged
merged 22 commits into from
Aug 31, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions did/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package did
import (
gocrypto "crypto"
"fmt"
"strings"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
Expand All @@ -14,6 +15,10 @@ import (
"github.com/sirupsen/logrus"
)

var (
UnsupportedDIDErorr = errors.New("unsupported Method for DID")
andorsk marked this conversation as resolved.
Show resolved Hide resolved
decentralgabe marked this conversation as resolved.
Show resolved Hide resolved
)

// Encodes the public key provided
// Using a multi-codec encoding.
func encodePublicKeyWithKeyMultiCodecType(kt crypto.KeyType, pubKey gocrypto.PublicKey) (string, error) {
Expand Down Expand Up @@ -144,3 +149,29 @@ func keyTypeToMultiCodec(kt crypto.KeyType) (multicodec.Code, error) {
logrus.WithError(err).Error()
return 0, err
}

// Resolves a DID
// Right the current implementation ssk-sdk does
// not have a universal resolver.
// https://github.com/decentralized-identity/universal-resolver
// is a case where a universal resolver is implemented,
// but the resolution would need to be hooked with the sdk.
// in the actual SDK
func ResolveDID(didStr string) (*DIDDocument, error) {
split := strings.Split(string(didStr), ":")
if len(split) < 2 {
return nil, errors.New("invalid DID. Does not split correctly")
}
method := split[1]
switch method {
case DIDKeyPrefix:
return DIDKey(didStr).Expand()
case DIDWebPrefix:
return DIDWeb(didStr).Resolve()
case PeerMethodPrefix:
did, _, _, err := DIDPeer(didStr).Resolve()
return did, err
default:
return nil, fmt.Errorf("%v. Got %v method", UnsupportedDIDErorr, method)
decentralgabe marked this conversation as resolved.
Show resolved Hide resolved
}
}
184 changes: 184 additions & 0 deletions example/use_cases/employer_university_flow/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// A dead simple example of a full. Simulates that a student has graduated from a
// university. They are given a VC from the university and it is registered. An
// employer wants to ascertain if the student graduated from the university.
// They will request for the information, the student will respond.
//
// We use two different did methods here. did:key and a custom did method specified
// in this file: did:example. The university uses did:example and the
// user uses did:key.

// InitalizationStep: Initialize the Wallet/Holder and the University
// Step 0: Univesity issues a VC to the Holder and sends it over
// Step 1: Verifier requests data from the holder
// Step 2: Holder sends credential
// Step 3: Verifier grants access based on the result

// |--------------------------|
// | |
// | Issuer (University) |
// | |
// |__________________________|
// / \
// / \ Trusts University
// ----------------- / Issues VC -------------------------
// | | / | |
// | Holder | / <---------------------> | Verifier (Employer) |
// | \Wallet | PresentationRequest | |
// |----------------| --------------------------
//
// A couple nuances that are necessary to understand at a high level before
// digging into this code.
//
// 1. A DID can be used against different method types. Each method has
// different funtions. For example, bitcoin works differently than peer.
// did:btcn vs. did:peer is how these methods specified.
//
// 2. A Verified Credential (VC) contains a cyrptographic proof, either explicit
// or embedded into the VC. For the purposes of this demo, the proof is
// embedded in a JSON Web Token (JTW)
//
// 3. When the Verifier wants to validate a user, they send a Presentation Request.
// The response will contain the VC. The Verifier will be able to determine if the VC
// has been tampered with due to the proof.
//
// The objects being created are in the following order:
//
// 1. DID's and wallets are created for the holder, issuer, and verifier
// 3. VC is issued to the student holder
// 4. PresentationRequest submitted by the verifier
// 5. PresentationSubmission returned by the holder
// 6. Authorization from the Verifier.

package main

import (
"encoding/json"
"fmt"
"os"

"github.com/TBD54566975/ssi-sdk/example"
util "github.com/TBD54566975/ssi-sdk/example"
emp "github.com/TBD54566975/ssi-sdk/example/use_cases/employer_university_flow/pkg"

"github.com/TBD54566975/ssi-sdk/credential/signing"
"github.com/TBD54566975/ssi-sdk/cryptosuite"
"github.com/sirupsen/logrus"
)

// Set to debug mode here
var debug = os.Getenv("DEBUG")
andorsk marked this conversation as resolved.
Show resolved Hide resolved

type Mode string

const (
DebugMode = "1"
)

// set mode for debugging
// in bash:
// export DEBUG=1
func init() {
if debug == DebugMode {
println("Debug mode")
logrus.SetLevel(logrus.DebugLevel)
}
}

// In this example, we will
// buile a simple example of a standard flow
// between a student, a university, and an employer
// 1. A student graduates from a university.
// The university issues a VC to the student, saying they graduated
// 2. The student will store it in a "wallet"
// 3. An employer sends a request to verify that the student graduated
// the university.
func main() {

step := 0

example.WriteStep("Starting University Flow", step)
step += 1

// Wallet initialization
example.WriteStep("Initializing Student", step)
step += 1

student, err := emp.NewEntity("Student", "key")
util.HandleExampleError(err, "failed to create student")

example.WriteStep("Initializing Employer", step)
step += 1

employer, err := emp.NewEntity("Employer", "peer")
util.HandleExampleError(err, "failed to make employer identity")
verifier_did, err := employer.GetWallet().GetDID("main")
util.HandleExampleError(err, "failed to create employer")

example.WriteStep("Initializing University", step)
step += 1

university, err := emp.NewEntity("University", "peer")
util.HandleExampleError(err, "failed to create university")
vcDID, err := university.GetWallet().GetDID("main")

util.HandleExampleError(err, "falied to initialize verifier")
example.WriteNote(fmt.Sprintf("Initialized Verifier DID: %s and registered it", vcDID))
emp.TrustedEntities.Issuers[vcDID] = true

example.WriteStep("Example University Creates VC for Holder", step)
step += 1

example.WriteNote("DID is shared from holder")
holderDID, err := student.GetWallet().GetDID("main")
util.HandleExampleError(err, "failed to store did from university")

vc, err := emp.BuildExampleUniversityVC(vcDID, holderDID)
util.HandleExampleError(err, "failed to build vc")

example.WriteStep("Example University Sends VC to Holder", step)
step += 1

student.GetWallet().AddCredentials(*vc)
msg := fmt.Sprintf("VC puts into wallet. Wallet size is now: %d", student.GetWallet().Size())
example.WriteNote(msg)

example.WriteNote(fmt.Sprintf("initialized verifier DID: %v", verifier_did))
example.WriteStep("Employer wants to verify student graduated from Example University. Sends a presentation request", step)
step += 1

presentationData, err := emp.MakePresentationData("test-id", "id-1")
util.HandleExampleError(err, "failed to create pd")

dat, err := json.Marshal(presentationData)
util.HandleExampleError(err, "failed to marshal presentation data")
logrus.Debugf("Presentation Data:\n%v", string(dat))

jwk, err := cryptosuite.GenerateJSONWebKey2020(cryptosuite.OKP, cryptosuite.Ed25519)
util.HandleExampleError(err, "failed to generate json web key")

presentationRequest, _, err := emp.MakePresentationRequest(*jwk, presentationData, holderDID)
util.HandleExampleError(err, "failed to make presentation request")

verifier, err := cryptosuite.NewJSONWebKeyVerifier(jwk.ID, jwk.PublicKeyJWK)
util.HandleExampleError(err, "failed to build json web key verifier")

signer, err := cryptosuite.NewJSONWebKeySigner(jwk.ID, jwk.PrivateKeyJWK, cryptosuite.AssertionMethod)
util.HandleExampleError(err, "failed to build json web key signer")

example.WriteNote("Student returns claims via a Presentation Submission")
submission, err := emp.BuildPresentationSubmission(presentationRequest, signer, *verifier, *vc)
util.HandleExampleError(err, "failed to buidl presentation submission")
vp, err := signing.VerifyVerifiablePresentationJWT(*verifier, string(submission))
util.HandleExampleError(err, "failed to verify jwt")

dat, err = json.Marshal(vp)
util.HandleExampleError(err, "failed to marshal submission")
logrus.Debugf("Submission:\n%v", string(dat))

example.WriteStep(fmt.Sprintf("Employer Attempting to Grant Access"), step)
if err = emp.ValidateAccess(*verifier, submission); err == nil {
example.WriteOK("Access Granted!")
} else {
example.WriteError(fmt.Sprintf("Access was not granted! Reason: %s", err))
}
}
9 changes: 9 additions & 0 deletions example/use_cases/employer_university_flow/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package main

import "testing"

func TestUniversityEmployerFlow(t *testing.T) {

// If there is an error in main this test will fail
main()
}
73 changes: 73 additions & 0 deletions example/use_cases/employer_university_flow/pkg/issuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package pkg

import (
"encoding/json"
"fmt"
"time"

"github.com/TBD54566975/ssi-sdk/credential"
"github.com/TBD54566975/ssi-sdk/example"
"github.com/sirupsen/logrus"
)

// Make a Verifiable Credential
// using the VC data type directly.
// Alternatively, use the builder
// A VC is set of tamper-evident claims and metadata
// that cryptographically prove who issued it
// Building a VC means using the CredentialBuilder
// as part of the credentials package in the ssk-sdk.
// VerifiableCredential is the verifiable credential model outlined in the
// vc-data-model spec https://www.w3.org/TR/2021/REC-vc-data-model-20211109/#basic-concept
func BuildExampleUniversityVC(universityID, recipient string) (*credential.VerifiableCredential, error) {

knownContext := []string{"https://www.w3.org/2018/credentials/v1",
"https://www.w3.org/2018/credentials/examples/v1"} // JSON-LD context statement
knownID := "http://example.edu/credentials/1872"
knownType := []string{"VerifiableCredential", "AlumniCredential"}
knownIssuer := "https://example.edu/issuers/565049"
knownIssuanceDate := time.Now().Format(time.RFC3339)
knownSubject := map[string]interface{}{
"id": universityID, //did:<method-name>:<method-specific-id>
"alumniOf": map[string]interface{}{ // claims are here
"id": recipient,
"name": []interface{}{
map[string]interface{}{"value": "Example University",
"lang": "en",
}, map[string]interface{}{
"value": "Exemple d'Université",
"lang": "fr",
},
},
},
}
// This is an embedded proof.
// For more information
// https://github.com/TBD54566975/ssi-sdk/blob/main/cryptosuite/jwssignaturesuite_test.go#L357
// https://www.w3.org/TR/vc-data-model/#proofs-signatures

// For more information on VC object, go to:
// https://github.com/TBD54566975/ssi-sdk/blob/main/credential/model.go
knownCred := credential.VerifiableCredential{
Context: knownContext,
ID: knownID,
Type: knownType,
Issuer: knownIssuer,
IssuanceDate: knownIssuanceDate,
CredentialSubject: knownSubject,
}

if err := knownCred.IsValid(); err != nil {
return nil, err
}

if dat, err := json.Marshal(knownCred); err == nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if this is an error? does the "VC issued" line still show?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking we just ignore errors that are logging/marshaling json errors, but we can handle them. I don't think they should happen often if at all.

logrus.Debug(string(dat))
} else {
return nil, err
}

example.WriteNote(fmt.Sprintf("VC issued from %s to %s", universityID, recipient))

return &knownCred, nil
}
16 changes: 16 additions & 0 deletions example/use_cases/employer_university_flow/pkg/trust.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pkg

type trustedEntitiesStore struct {
Issuers map[string]bool
}

func (t *trustedEntitiesStore) isTrusted(did string) bool {
if v, ok := t.Issuers[did]; ok {
return v
}
return false
}

var TrustedEntities = trustedEntitiesStore{
Issuers: make(map[string]bool),
}
Loading