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

feat: #2246 Support for DID Web Method #2288

Merged
merged 1 commit into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693 h1:wD1IWQwAhdWclCwaf6DdzgCAe9Bfz1M+4AHRd7N786Y=
github.com/square/go-jose/v3 v3.0.0-20200630053402-0a67ce9b0693/go.mod h1:6hSY48PjDm4UObWmGLyJE9DxYVKTgR9kbCspXXJEhcU=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
Expand Down
2 changes: 1 addition & 1 deletion pkg/doc/did/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (d *DID) String() string {
func Parse(did string) (*DID, error) {
// I could not find a good ABNF parser :(
const idchar = `a-zA-Z0-9-_\.`
regex := fmt.Sprintf(`^did:[a-z0-9]+:(:+|[:%s]+)*[%s]+$`, idchar, idchar)
regex := fmt.Sprintf(`^did:[a-z0-9]+:(:+|[:%s]+)*[%%:%s]+[^:]$`, idchar, idchar)
amodkala marked this conversation as resolved.
Show resolved Hide resolved

r, err := regexp.Compile(regex)
if err != nil {
Expand Down
9 changes: 9 additions & 0 deletions pkg/framework/aries/api/vdr/vdr.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"time"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
Expand Down Expand Up @@ -55,6 +56,7 @@ const (

// ResolveDIDOpts holds the options for did resolve.
type ResolveDIDOpts struct {
HTTPClient *http.Client
ResultType ResultType
VersionID interface{}
VersionTime string
Expand All @@ -64,6 +66,13 @@ type ResolveDIDOpts struct {
// ResolveOpts is a did resolve option.
type ResolveOpts func(opts *ResolveDIDOpts)

// WithHTTPClient the HTTP client input option can be used to resolve with a specific http client.
func WithHTTPClient(httpClient *http.Client) ResolveOpts {
return func(opts *ResolveDIDOpts) {
opts.HTTPClient = httpClient
}
}

// WithResultType the result type input option can be used to request a certain type of result.
func WithResultType(resultType ResultType) ResolveOpts {
return func(opts *ResolveDIDOpts) {
Expand Down
19 changes: 19 additions & 0 deletions pkg/vdr/web/creator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"fmt"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
"github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
)

// Build creates a did:web diddoc (unsupported at the moment).
func (v *VDR) Build(pubKey *vdr.PubKey, opts ...vdr.DocOpts) (*did.Doc, error) {
return nil, fmt.Errorf("error building did:web did doc --> build not supported in http binding vdr")
}
25 changes: 25 additions & 0 deletions pkg/vdr/web/creator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
)

func TestCreateDID(t *testing.T) {
t.Run("test create did failure", func(t *testing.T) {
v := New()
doc, err := v.Build(&vdr.PubKey{})
require.Nil(t, doc)
require.Error(t, err)
require.Contains(t, err.Error(), "build not supported in http binding vdr")
})
}
48 changes: 48 additions & 0 deletions pkg/vdr/web/did.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"fmt"
"net/url"
"strings"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
)

const (
defaultPath = "/.well-known/doc.json"
documentPath = "/doc.json"
)

// parseDIDWeb consumes a did:web identifier and returns the URL location of the did Doc.
func parseDIDWeb(id string) (string, string, error) {
var address, host string

parsedDID, err := did.Parse(id)
if err != nil {
return address, host, fmt.Errorf("invalid did, does not conform to generic did standard --> %w", err)
}

pathComponents := strings.Split(parsedDID.MethodSpecificID, ":")

pathComponents[0], err = url.QueryUnescape(pathComponents[0])
if err != nil {
return address, host, fmt.Errorf("error parsing did:web did")
}

host = strings.Split(pathComponents[0], ":")[0]

switch len(pathComponents) {
case 1:
address = "https://" + pathComponents[0] + defaultPath
default:
address = "https://" + strings.Join(pathComponents, "/") + documentPath
}

return address, host, nil
}
70 changes: 70 additions & 0 deletions pkg/vdr/web/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"fmt"
"io"
"io/ioutil"
"net/http"

"github.com/hyperledger/aries-framework-go/pkg/common/log"
"github.com/hyperledger/aries-framework-go/pkg/doc/did"
"github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
)

var logger = log.New("aries-framework/pkg/vdr/web")

// Read resolves a did:web did.
func (v *VDR) Read(didID string, opts ...vdr.ResolveOpts) (*did.Doc, error) {
// apply resolve opts
docOpts := &vdr.ResolveDIDOpts{
HTTPClient: &http.Client{},
}

for _, opt := range opts {
opt(docOpts)
}

address, host, err := parseDIDWeb(didID)
if err != nil {
return nil, fmt.Errorf("error resolving did:web did --> could not parse did:web did --> %w", err)
}

resp, err := docOpts.HTTPClient.Get(address)
if err != nil {
return nil, fmt.Errorf("error resolving did:web did --> http request unsuccessful --> %w", err)
}

for _, i := range resp.TLS.PeerCertificates {
err = (*i).VerifyHostname(host)
if err != nil {
return nil, fmt.Errorf("error resolving did:web did --> identifier does not match TLS host --> %w", err)
}
}

defer closeResponseBody(resp.Body)

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("error resolving did:web did --> error reading http response body: %s --> %w", body, err)
}

doc, err := did.ParseDocument(body)
if err != nil {
return nil, fmt.Errorf("error resolving did:web did --> error parsing did doc --> %w", err)
}

return doc, nil
}

func closeResponseBody(respBody io.Closer) {
e := respBody.Close()
if e != nil {
logger.Errorf("Failed to close response body: %v", e)
}
}
132 changes: 132 additions & 0 deletions pkg/vdr/web/resolver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"fmt"
"net/http"
"net/http/httptest"
urlapi "net/url"
"strings"
"testing"

"github.com/stretchr/testify/require"

didapi "github.com/hyperledger/aries-framework-go/pkg/doc/did"
"github.com/hyperledger/aries-framework-go/pkg/framework/aries/api/vdr"
)

const (
prefix = "did:web:"

validURL = "www.example.org"
validURLWithPath = "www.example.org/user/example"
validDID = prefix + validURL
validDIDWithPath = prefix + "www.example.org:user:example"
validDIDWithHost = prefix + "localhost%3A8080"
validDIDWithHostAndPath = prefix + "localhost%3A8080:user:example"

invalidDIDNoMethod = "did:" + validURL
invalidDIDNoPrefix = validURL

validDoc = `{
"@context": ["https://w3id.org/did/v1"],
"id": "did:web:www.example.org"
}`

invalidDoc = `{}`
)

func TestParseDID(t *testing.T) {
t.Run("test parse did success", func(t *testing.T) {
address, host, err := parseDIDWeb(validDID)
require.NoError(t, err)
require.Equal(t, "https://"+validURL+defaultPath, address)
require.Equal(t, validURL, host)
address, host, err = parseDIDWeb(validDIDWithPath)
require.NoError(t, err)
require.Equal(t, "https://"+validURLWithPath+documentPath, address)
require.Equal(t, validURL, host)
address, host, err = parseDIDWeb(validDIDWithHost)
require.NoError(t, err)
require.Equal(t, "https://localhost:8080/.well-known/doc.json", address)
require.Equal(t, "localhost", host)
address, host, err = parseDIDWeb(validDIDWithHostAndPath)
require.NoError(t, err)
require.Equal(t, "https://localhost:8080/user/example/doc.json", address)
require.Equal(t, "localhost", host)
})

t.Run("test parse did failure", func(t *testing.T) {
v := New()
doc, err := v.Read(invalidDIDNoMethod)
require.Error(t, err)
require.Nil(t, doc)
require.Contains(t, err.Error(), "does not conform to generic did standard")
doc, err = v.Read(invalidDIDNoPrefix)
require.Error(t, err)
require.Nil(t, doc)
require.Contains(t, err.Error(), "does not conform to generic did standard")
})
}

func TestResolveDID(t *testing.T) {
amodkala marked this conversation as resolved.
Show resolved Hide resolved
t.Run("test resolve did with request failure", func(t *testing.T) {
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(invalidDoc))
require.NoError(t, err)
}))
defer s.Close()
did := fmt.Sprintf("did:web:%s", urlapi.QueryEscape(strings.TrimPrefix(s.URL, "https://")))
v := New()
doc, err := v.Read(did)
require.Nil(t, doc)
require.Error(t, err)
require.Contains(t, err.Error(), "http request unsuccessful")
})
t.Run("test resolve did with invalid doc format failure", func(t *testing.T) {
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(invalidDoc))
require.NoError(t, err)
}))
defer s.Close()
did := fmt.Sprintf("did:web:%s", urlapi.QueryEscape(strings.TrimPrefix(s.URL, "https://")))
v := New()
doc, err := v.Read(did, vdr.WithHTTPClient(s.Client()))
require.Nil(t, doc)
require.Error(t, err)
require.Contains(t, err.Error(), "error parsing did doc")
})
t.Run("test resolve did success", func(t *testing.T) {
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(validDoc))
require.NoError(t, err)
}))
defer s.Close()
did := fmt.Sprintf("did:web:%s", urlapi.QueryEscape(strings.TrimPrefix(s.URL, "https://")))
v := New()
doc, err := v.Read(did, vdr.WithHTTPClient(s.Client()))
require.Nil(t, err)
expectedDoc, err := didapi.ParseDocument([]byte(validDoc))
require.Nil(t, err)
require.Equal(t, expectedDoc, doc)
})
t.Run("test resolve did with path success", func(t *testing.T) {
s := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte(validDoc))
require.NoError(t, err)
}))
defer s.Close()
did := fmt.Sprintf("did:web:%s:user:example", urlapi.QueryEscape(strings.TrimPrefix(s.URL, "https://")))
v := New()
doc, err := v.Read(did, vdr.WithHTTPClient(s.Client()))
require.Nil(t, err)
expectedDoc, err := didapi.ParseDocument([]byte(validDoc))
require.Nil(t, err)
amodkala marked this conversation as resolved.
Show resolved Hide resolved
require.Equal(t, expectedDoc, doc)
})
}
18 changes: 18 additions & 0 deletions pkg/vdr/web/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"fmt"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
)

// Store method (unsupported at the moment).
func (v *VDR) Store(doc *did.Doc) error {
return fmt.Errorf("error storing did:web did doc --> store not supported in http binding vdr")
}
24 changes: 24 additions & 0 deletions pkg/vdr/web/store_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package web

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/hyperledger/aries-framework-go/pkg/doc/did"
)

func TestStoreDID(t *testing.T) {
t.Run("test store did failure", func(t *testing.T) {
v := New()
err := v.Store(&did.Doc{})
require.NotNil(t, err)
require.Contains(t, err.Error(), "store not supported in http binding vdr")
})
}
Loading