Skip to content

Commit

Permalink
Merge branch 'main' into issue_122
Browse files Browse the repository at this point in the history
  • Loading branch information
andresuribe87 authored Jul 7, 2023
2 parents d3c4ae9 + e7d184d commit 03dec5e
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 53 deletions.
26 changes: 25 additions & 1 deletion doc/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,25 @@ definitions:
description: Whether this credential is currently suspended.
type: boolean
type: object
github_com_tbd54566975_ssi-service_pkg_service_did.CreateIONDIDOptions:
properties:
jwsPublicKeys:
description: |-
List of JSON Web Signatures serialized using compact serialization. The payload must be a JSON object that
represents a publicKey object. Such object must follow the schema described in step 3 of
https://identity.foundation/sidetree/spec/#add-public-keys. The payload must be signed
with the private key associated with the `publicKeyJwk` that will be added in the DID document.
The input will be parsed and verified, and the payload will be used to add public keys to the DID document in the
same way in which the `add-public-keys` patch action adds keys (see https://identity.foundation/sidetree/spec/#add-public-keys).
items:
type: string
type: array
serviceEndpoints:
description: Services to add to the DID document that will be created.
items:
$ref: '#/definitions/github_com_TBD54566975_ssi-sdk_did.Service'
type: array
type: object
github_com_tbd54566975_ssi-service_pkg_service_framework.Status:
properties:
message:
Expand Down Expand Up @@ -2464,7 +2483,12 @@ paths:
name: request
required: true
schema:
$ref: '#/definitions/pkg_server_router.CreateDIDByMethodRequest'
allOf:
- $ref: '#/definitions/pkg_server_router.CreateDIDByMethodRequest'
- properties:
options:
$ref: '#/definitions/github_com_tbd54566975_ssi-service_pkg_service_did.CreateIONDIDOptions'
type: object
produces:
- application/json
responses:
Expand Down
42 changes: 22 additions & 20 deletions integration/didion_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,28 @@ func TestCreateIssuerDIDIONIntegration(t *testing.T) {
assert.NotEmpty(t, verificationMethodID)
SetValue(didIONContext, "verificationMethodID", verificationMethodID)

verificationMethod1ID, err := getJSONElement(didIONOutput, "$.did.verificationMethod[1].id")
assert.NoError(t, err)
assert.Equal(t, "#externalVerificationMethodId", verificationMethod1ID)

verificationMethod1KID, err := getJSONElement(didIONOutput, "$.did.verificationMethod[1].publicKeyJwk.kid")
assert.NoError(t, err)
assert.Equal(t, "myExternalPublicKID", verificationMethod1KID)

keyAgreementKID, err := getJSONElement(didIONOutput, "$.did.keyAgreement[0]")
assert.NoError(t, err)
assert.Equal(t, "#externalVerificationMethodId", keyAgreementKID)

capabilityInvocationKID, err := getJSONElement(didIONOutput, "$.did.capabilityInvocation[0]")
assert.NoError(t, err)
assert.Equal(t, "#externalVerificationMethodId", capabilityInvocationKID)

capabilityDelegationKID, err := getJSONElement(didIONOutput, "$.did.capabilityDelegation[0]")
assert.NoError(t, err)
assert.Equal(t, "#externalVerificationMethodId", capabilityDelegationKID)

// The jwsPublicKeys entry represents the following:
//{
// "id": "test-id",
// "type": "JsonWebKey2020",
// "publicKeyJwk": {
// "kty": "OKP",
// "crv": "Ed25519",
// "x": "ghzv2q5WwYO3Y6ZH-MQJvBkd45zbTSyJ6gU1q3yk01E",
// "alg": "EdDSA",
// "kid": "test-kid"
// },
// "purposes": [
// "authentication"
// ]
//}
verificationMethod2ID, err := getJSONElement(didIONOutput, "$.did.verificationMethod[1].id")
assert.NoError(t, err)
assert.Equal(t, "#test-id", verificationMethod2ID)

verificationMethod2KID, err := getJSONElement(didIONOutput, "$.did.verificationMethod[1].publicKeyJwk.kid")
assert.NoError(t, err)
assert.Equal(t, "test-kid", verificationMethod2KID)
}

func TestCreateAliceDIDKeyForDIDIONIntegration(t *testing.T) {
Expand Down
20 changes: 2 additions & 18 deletions integration/testdata/did-ion-input.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,8 @@
"keyType":"Ed25519",
"options": {
"serviceEndpoints": [],
"publicKeys": [
{
"id": "externalVerificationMethodId",
"type": "JsonWebKey2020",
"publicKeyJwk": {
"kty": "OKP",
"crv": "Ed25519",
"x": "CV-aGlld3nVdgnhoZK0D36Wk-9aIMlZjZOK2XhPMnkQ",
"kid": "myExternalPublicKID"
},
"purposes": [
"authentication",
"assertionMethod",
"capabilityInvocation",
"capabilityDelegation",
"keyAgreement"
]
}
"jwsPublicKeys": [
"eyJhbGciOiJFZERTQSJ9.eyJpZCI6InRlc3QtaWQiLCJ0eXBlIjoiSnNvbldlYktleTIwMjAiLCJwdWJsaWNLZXlKd2siOnsia3R5IjoiT0tQIiwiY3J2IjoiRWQyNTUxOSIsIngiOiJnaHp2MnE1V3dZTzNZNlpILU1RSnZCa2Q0NXpiVFN5SjZnVTFxM3lrMDFFIiwiYWxnIjoiRWREU0EiLCJraWQiOiJ0ZXN0LWtpZCJ9LCJwdXJwb3NlcyI6WyJhdXRoZW50aWNhdGlvbiJdfQ.Q27mR09J2Uq8zdgEQ-oM5clsSJOEso_XMxbTeD_o3s33rWAy3yVwgm1ZfziRPnLUsEO4hsGsZZtr3FEFwl91Bw"
]
}
}
4 changes: 2 additions & 2 deletions pkg/server/router/did.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ type CreateDIDByMethodResponse struct {
// @Tags DecentralizedIdentityAPI
// @Accept json
// @Produce json
// @Param method path string true "Method"
// @Param request body CreateDIDByMethodRequest true "request body"
// @Param method path string true "Method"
// @Param request body CreateDIDByMethodRequest{options=did.CreateIONDIDOptions} true "request body"
// @Success 201 {object} CreateDIDByMethodResponse
// @Failure 400 {string} string "Bad request"
// @Failure 500 {string} string "Internal server error"
Expand Down
49 changes: 46 additions & 3 deletions pkg/service/did/ion.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import (
"github.com/TBD54566975/ssi-sdk/did/ion"
"github.com/TBD54566975/ssi-sdk/did/resolution"
"github.com/TBD54566975/ssi-sdk/util"
"github.com/goccy/go-json"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/v2/jws"
"github.com/mr-tron/base58"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -52,8 +54,16 @@ type ionHandler struct {
var _ MethodHandler = (*ionHandler)(nil)

type CreateIONDIDOptions struct {
ServiceEndpoints []did.Service `json:"serviceEndpoints"`
PublicKeys []ion.PublicKey `json:"publicKeys"`
// Services to add to the DID document that will be created.
ServiceEndpoints []did.Service `json:"serviceEndpoints"`

// List of JSON Web Signatures serialized using compact serialization. The payload must be a JSON object that
// represents a publicKey object. Such object must follow the schema described in step 3 of
// https://identity.foundation/sidetree/spec/#add-public-keys. The payload must be signed
// with the private key associated with the `publicKeyJwk` that will be added in the DID document.
// The input will be parsed and verified, and the payload will be used to add public keys to the DID document in the
// same way in which the `add-public-keys` patch action adds keys (see https://identity.foundation/sidetree/spec/#add-public-keys).
JWSPublicKeys []string `json:"jwsPublicKeys"`
}

func (c CreateIONDIDOptions) Method() did.Method {
Expand Down Expand Up @@ -88,6 +98,7 @@ func (h *ionHandler) CreateDID(ctx context.Context, request CreateDIDRequest) (*
// process options
var opts CreateIONDIDOptions
var ok bool
var publicKeysFromJWS []ion.PublicKey
if request.Options != nil {
opts, ok = request.Options.(CreateIONDIDOptions)
if !ok || request.Options.Method() != did.IONMethod {
Expand All @@ -96,6 +107,38 @@ func (h *ionHandler) CreateDID(ctx context.Context, request CreateDIDRequest) (*
if err := util.IsValidStruct(opts); err != nil {
return nil, errors.Wrap(err, "processing options")
}

publicKeysFromJWS = make([]ion.PublicKey, 0, len(opts.JWSPublicKeys))
for _, jwsString := range opts.JWSPublicKeys {
m, err := jws.ParseString(jwsString)
if err != nil {
return nil, errors.Wrapf(err, "parsing JWS string <%s>", jwsString)
}

headers, err := jwx.GetJWSHeaders([]byte(jwsString))
if err != nil {
return nil, errors.Wrapf(err, "getting JWS headers from <%s>", jwsString)
}

var publicKey ion.PublicKey
if err := json.Unmarshal(m.Payload(), &publicKey); err != nil {
return nil, errors.Wrap(err, "unmarshalling payload")
}
if err := util.IsValidStruct(publicKey); err != nil {
return nil, errors.Wrap(err, "invalid publicKey in payload")
}

goPublicKey, err := publicKey.PublicKeyJWK.ToPublicKey()
if err != nil {
return nil, errors.Wrap(err, "converting JWK to go crypto public key")
}

if _, err := jws.Verify([]byte(jwsString), jws.WithKey(headers.Algorithm(), goPublicKey)); err != nil {
return nil, errors.Wrapf(err, "verifying JWS for <%s>", jwsString)
}

publicKeysFromJWS = append(publicKeysFromJWS, publicKey)
}
}

// create a key for the docs
Expand All @@ -117,7 +160,7 @@ func (h *ionHandler) CreateDID(ctx context.Context, request CreateDIDRequest) (*
Purposes: []ion.PublicKeyPurpose{ion.Authentication, ion.AssertionMethod},
},
}
pubKeys = append(pubKeys, opts.PublicKeys...)
pubKeys = append(pubKeys, publicKeysFromJWS...)

// generate the did document's initial state
doc := ion.Document{PublicKeys: pubKeys, Services: opts.ServiceEndpoints}
Expand Down
61 changes: 52 additions & 9 deletions pkg/service/did/ion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"testing"

"github.com/TBD54566975/ssi-sdk/crypto"
"github.com/TBD54566975/ssi-sdk/crypto/jwx"
"github.com/TBD54566975/ssi-sdk/did"
"github.com/TBD54566975/ssi-sdk/did/ion"
"github.com/goccy/go-json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -24,7 +26,7 @@ var BasicDIDResolution []byte
func TestIONHandler(t *testing.T) {
for _, test := range testutil.TestDatabases {
t.Run(test.Name, func(t *testing.T) {
t.Run("Test Create ION Handler", func(tt *testing.T) {
t.Run("Create ION Handler", func(tt *testing.T) {
handler, err := NewIONHandler("", nil, nil)
assert.Error(tt, err)
assert.Empty(tt, handler)
Expand Down Expand Up @@ -56,7 +58,7 @@ func TestIONHandler(t *testing.T) {
assert.Equal(tt, handler.GetMethod(), did.IONMethod)
})

t.Run("Test Create DID", func(tt *testing.T) {
t.Run("Create DID", func(tt *testing.T) {
// create a handler
s := test.ServiceStorage(t)
keystoreService := testKeyStoreService(tt, s)
Expand All @@ -72,16 +74,57 @@ func TestIONHandler(t *testing.T) {
BodyString(string(BasicDIDResolution))
defer gock.Off()

publicKey, privKey, err := crypto.GenerateEd25519Key()
require.NoError(tt, err)
signer, err := jwx.NewJWXSigner("test-id", "test-kid", privKey)
require.NoError(tt, err)
jwxJWK, err := jwx.PublicKeyToPublicKeyJWK("test-kid", publicKey)
require.NoError(tt, err)
ionPublicKey := ion.PublicKey{
ID: "test-id",
Type: "JsonWebKey2020",
PublicKeyJWK: *jwxJWK,
Purposes: []ion.PublicKeyPurpose{ion.Authentication},
}
ionPublicKeyData, err := json.Marshal(ionPublicKey)
require.NoError(tt, err)
jwsPublicKey, err := signer.SignJWS(ionPublicKeyData)
require.NoError(tt, err)

// create a did
created, err := handler.CreateDID(context.Background(), CreateDIDRequest{
createDIDRequest := CreateDIDRequest{
Method: did.IONMethod,
KeyType: crypto.Ed25519,
Options: CreateIONDIDOptions{
ServiceEndpoints: []did.Service{},
JWSPublicKeys: []string{string(jwsPublicKey)},
},
}

tt.Run("good input returns no error", func(t *testing.T) {
created, err := handler.CreateDID(context.Background(), createDIDRequest)
assert.NoError(tt, err)
assert.NotEmpty(tt, created)
})

tt.Run("signing with another key returns error", func(ttt *testing.T) {
a := createDIDRequest
_, privKey, err := crypto.GenerateEd25519Key()
require.NoError(ttt, err)
signer2, err := jwx.NewJWXSigner("test-id", "test-kid", privKey)
require.NoError(ttt, err)
signedWithOtherKey, err := signer2.SignJWS(ionPublicKeyData)
require.NoError(ttt, err)
a.Options.(CreateIONDIDOptions).JWSPublicKeys[0] = string(signedWithOtherKey)

_, err = handler.CreateDID(context.Background(), createDIDRequest)

assert.Error(ttt, err)
assert.ErrorContains(ttt, err, "verifying JWS for")
})
assert.NoError(tt, err)
assert.NotEmpty(tt, created)
})

t.Run("Test Create DID", func(tt *testing.T) {
t.Run("Get a Created DID", func(tt *testing.T) {
gock.New("https://ion.tbddev.org").
Post("/operations").
Reply(200).
Expand Down Expand Up @@ -114,7 +157,7 @@ func TestIONHandler(t *testing.T) {
assert.NotEmpty(tt, gotDID)
})

t.Run("Test Get DID from storage", func(tt *testing.T) {
t.Run("Get DID from storage", func(tt *testing.T) {
// create a handler
s := test.ServiceStorage(t)
keystoreService := testKeyStoreService(tt, s)
Expand Down Expand Up @@ -153,7 +196,7 @@ func TestIONHandler(t *testing.T) {
assert.Equal(tt, created.DID.ID, gotDID.DID.ID)
})

t.Run("Test Get DIDs from storage", func(tt *testing.T) {
t.Run("Get DIDs from storage", func(tt *testing.T) {
// create a handler
s := test.ServiceStorage(t)
keystoreService := testKeyStoreService(tt, s)
Expand Down Expand Up @@ -211,7 +254,7 @@ func TestIONHandler(t *testing.T) {
assert.Len(tt, gotDeletedDIDs.DIDs, 1)
})

t.Run("Test Get DID from resolver", func(tt *testing.T) {
t.Run("Get DID from resolver", func(tt *testing.T) {
// create a handler
s := test.ServiceStorage(t)
keystoreService := testKeyStoreService(tt, s)
Expand Down

0 comments on commit 03dec5e

Please sign in to comment.