package document import ( "encoding/json" "errors" "fmt" "io" "net/url" "github.com/ucan-wg/go-did-it" verifications "github.com/ucan-wg/go-did-it/verifiers/_methods" ) var _ did.Document = &Document{} // Document is a did.Document decoded from an arbitrary Json Document. // It does not know anything about the DID method used to produce that document. type Document struct { context []string id string alsoKnownAs []*url.URL controllers []string verificationMethods map[string]did.VerificationMethod authentication []did.VerificationMethodSignature assertion []did.VerificationMethodSignature keyAgreement []did.VerificationMethodKeyAgreement capabilityInvocation []did.VerificationMethodSignature capabilityDelegation []did.VerificationMethodSignature services did.Services } type aux struct { Context []string `json:"@context"` Id string `json:"id"` AlsoKnownAs []string `json:"alsoKnownAs,omitempty"` Controllers json.RawMessage `json:"controllers,omitempty"` VerificationMethods []json.RawMessage `json:"verificationMethod,omitempty"` Authentication []json.RawMessage `json:"authentication,omitempty"` Assertion []json.RawMessage `json:"assertionMethod,omitempty"` KeyAgreement []json.RawMessage `json:"keyAgreement,omitempty"` CapabilityInvocation []json.RawMessage `json:"capabilityInvocation,omitempty"` CapabilityDelegation []json.RawMessage `json:"capabilityDelegation,omitempty"` Services did.Services `json:"service,omitempty"` } // FromJsonReader decodes an arbitrary Json DID Document into a usable did.Document. func FromJsonReader(reader io.Reader) (*Document, error) { // 1 MiB read limit to shut down abuse reader = io.LimitReader(reader, 1<<20) var aux aux err := json.NewDecoder(reader).Decode(&aux) if err != nil { return nil, err } return fromAux(&aux) } // FromJsonBytes decodes an arbitrary Json DID Document into a usable did.Document. func FromJsonBytes(data []byte) (*Document, error) { var aux aux err := json.Unmarshal(data, &aux) if err != nil { return nil, err } return fromAux(&aux) } func fromAux(aux *aux) (*Document, error) { var err error res := Document{ context: aux.Context, id: aux.Id, services: aux.Services, } // id if !did.HasValidDIDSyntax(aux.Id) { // also enforce being required return nil, errors.New("id has invalid DID syntax") } // alsoKnownAs res.alsoKnownAs = make([]*url.URL, len(aux.AlsoKnownAs)) for i, u := range aux.AlsoKnownAs { res.alsoKnownAs[i], err = url.Parse(u) if err != nil { return nil, fmt.Errorf("invalid alsoKnownAs: %w", err) } } // controller var s string var ss []string switch { case len(aux.Controllers) == 0: // nothing to do case json.Unmarshal(aux.Controllers, &s) == nil: // we have a single string if !did.HasValidDIDSyntax(s) { return nil, errors.New("controllers has invalid DID syntax") } res.controllers = []string{s} case json.Unmarshal(aux.Controllers, &ss) == nil: // we have an array of strings res.controllers = make([]string, len(ss)) for i, s := range ss { if !did.HasValidDIDSyntax(s) { return nil, errors.New("one controllers has an invalid DID syntax") } res.controllers[i] = s } default: return nil, fmt.Errorf("invalid controllers") } // verificationMethods res.verificationMethods = map[string]did.VerificationMethod{} for _, m := range aux.VerificationMethods { vm, err := verifications.UnmarshalJSON(m) if err != nil { return nil, fmt.Errorf("invalid verificationMethods: %w", err) } res.verificationMethods[vm.ID()] = vm } // authentication res.authentication, err = resolveVerificationMethods[did.VerificationMethodSignature](&res, aux.Authentication) if err != nil { return nil, fmt.Errorf("invalid authentication: %w", err) } // assertion res.assertion, err = resolveVerificationMethods[did.VerificationMethodSignature](&res, aux.Assertion) if err != nil { return nil, fmt.Errorf("invalid assertion: %w", err) } // keyAgreement res.keyAgreement, err = resolveVerificationMethods[did.VerificationMethodKeyAgreement](&res, aux.KeyAgreement) if err != nil { return nil, fmt.Errorf("invalid keyAgreement: %w", err) } // capabilityInvocation res.capabilityInvocation, err = resolveVerificationMethods[did.VerificationMethodSignature](&res, aux.CapabilityInvocation) if err != nil { return nil, fmt.Errorf("invalid capabilityInvocation: %w", err) } // capabilityDelegation res.capabilityDelegation, err = resolveVerificationMethods[did.VerificationMethodSignature](&res, aux.CapabilityDelegation) if err != nil { return nil, fmt.Errorf("invalid capabilityDelegation: %w", err) } return &res, nil } func resolveVerificationMethods[T did.VerificationMethod](doc *Document, msgs []json.RawMessage) ([]T, error) { res := make([]T, len(msgs)) for i, auth := range msgs { var s string if json.Unmarshal(auth, &s) == nil { // We have a string, we need to resolve it. // This can normally be an internal reference (with a fragment), but can also be a complete DID URL that // requires an external lookup. For simplicity, we don't support that (yet?). vm, ok := doc.verificationMethods[s] if !ok { return nil, fmt.Errorf("invalid verification method reference: %s", s) } cast, ok := vm.(T) if !ok { return nil, fmt.Errorf("resolved verification method doesn't match the expected capabilities: %T instead of %T", vm, new(T)) } res[i] = cast continue } vm, err := verifications.UnmarshalJSON(auth) if err == nil { // we have a complete verification method vms, ok := vm.(T) if !ok { return nil, fmt.Errorf("verification method doesn't match the expected capabilities: %T instead of %T", vm, new(T)) } res[i] = vms continue } return nil, fmt.Errorf("invalid verification method value: %w", err) } return res, nil } func (d Document) MarshalJSON() ([]byte, error) { var err error data := aux{Context: d.context, Id: d.id, Services: d.services} // alsoKnownAs data.AlsoKnownAs = make([]string, len(d.alsoKnownAs)) for i, u := range d.alsoKnownAs { data.AlsoKnownAs[i] = u.String() } // controllers switch { case len(d.controllers) == 1: data.Controllers, err = json.Marshal(d.controllers[0]) case len(d.controllers) > 1: data.Controllers, err = json.Marshal(d.controllers) } if err != nil { return nil, err } // verificationMethods data.VerificationMethods = make([]json.RawMessage, len(d.verificationMethods)) i := 0 for _, method := range d.verificationMethods { data.VerificationMethods[i], err = json.Marshal(method) if err != nil { return nil, err } i++ } // authentication data.Authentication, err = marshalMethods[did.VerificationMethodSignature](&d, d.authentication) if err != nil { return nil, err } // assertion data.Assertion, err = marshalMethods[did.VerificationMethodSignature](&d, d.assertion) if err != nil { return nil, err } // keyAgreement data.KeyAgreement, err = marshalMethods[did.VerificationMethodKeyAgreement](&d, d.keyAgreement) if err != nil { return nil, err } // capabilityInvocation data.CapabilityInvocation, err = marshalMethods[did.VerificationMethodSignature](&d, d.capabilityInvocation) if err != nil { return nil, err } // capabilityDelegation data.CapabilityDelegation, err = marshalMethods[did.VerificationMethodSignature](&d, d.capabilityDelegation) if err != nil { return nil, err } return json.Marshal(data) } func marshalMethods[T did.VerificationMethod](d *Document, methods []T) ([]json.RawMessage, error) { var err error res := make([]json.RawMessage, len(methods)) for i, method := range methods { if _, ok := d.verificationMethods[method.ID()]; ok { res[i], err = json.Marshal(method.ID()) } else { res[i], err = json.Marshal(method) } if err != nil { return nil, err } } return res, nil } func (d Document) Context() []string { return d.context } func (d Document) ID() string { return d.id } func (d Document) Controllers() []string { return d.controllers } func (d Document) AlsoKnownAs() []*url.URL { return d.alsoKnownAs } func (d Document) VerificationMethods() map[string]did.VerificationMethod { return d.verificationMethods } func (d Document) Authentication() []did.VerificationMethodSignature { return d.authentication } func (d Document) Assertion() []did.VerificationMethodSignature { return d.assertion } func (d Document) KeyAgreement() []did.VerificationMethodKeyAgreement { return d.keyAgreement } func (d Document) CapabilityInvocation() []did.VerificationMethodSignature { return d.capabilityInvocation } func (d Document) CapabilityDelegation() []did.VerificationMethodSignature { return d.capabilityDelegation } func (d Document) Services() did.Services { return d.services }