From b5b935b788bce7c45f1f26d3ffd4659d64930547 Mon Sep 17 00:00:00 2001 From: Sandra Vrtikapa Date: Wed, 2 Dec 2020 23:53:57 -0500 Subject: [PATCH] feat: Add max operation hash length protocol parameter Add MAX_OPERATION_HASH_LENGTH parameter to protocol and validate the following values against it: - did suffix and reveal value in core index file (reference operation is generated by batch writer/values to be checked in observer) - deltaHash, recoveryCommitment (suffix data) - input - updateCommitment(delta) - input - deltaHash, recoveryCommitment - JWS (recovery) - input Closes #517 Signed-off-by: Sandra Vrtikapa --- pkg/api/protocol/protocol.go | 2 + pkg/mocks/protocol.go | 1 + pkg/processor/processor_test.go | 1 + .../operationapplier/operationapplier_test.go | 1 + pkg/versions/0_1/operationparser/create.go | 26 ++++--- .../0_1/operationparser/create_test.go | 67 ++++++++++++++----- .../0_1/operationparser/deactivate.go | 2 +- .../0_1/operationparser/deactivate_test.go | 2 +- .../0_1/operationparser/operation_test.go | 16 +++-- pkg/versions/0_1/operationparser/recover.go | 9 ++- .../0_1/operationparser/recover_test.go | 37 +++++++--- pkg/versions/0_1/operationparser/update.go | 12 +--- .../0_1/operationparser/update_test.go | 28 ++++---- pkg/versions/0_1/txnprovider/provider.go | 30 ++++++--- pkg/versions/0_1/txnprovider/provider_test.go | 57 +++++++++++++--- 15 files changed, 200 insertions(+), 91 deletions(-) diff --git a/pkg/api/protocol/protocol.go b/pkg/api/protocol/protocol.go index 11ff1bb7..fd4e0fcd 100644 --- a/pkg/api/protocol/protocol.go +++ b/pkg/api/protocol/protocol.go @@ -37,6 +37,8 @@ type Protocol struct { // MaxOperationSize is maximum operation size in bytes (used to reject operations before parsing them) // It has to be greater than max delta size (big) + max proof size (medium) + other small values (operation type, suffix-data) MaxOperationSize uint `json:"maxOperationSize"` + // MaxOperationHashLength is maximum operation hash length + MaxOperationHashLength uint `json:"maxOperationHashLength"` // MaxDeltaSize is maximum size of operation's delta property. MaxDeltaSize uint `json:"maxDeltaSize"` // MaxProofSize is maximum size of operation's proof property. diff --git a/pkg/mocks/protocol.go b/pkg/mocks/protocol.go index 11ac6036..80c43b31 100644 --- a/pkg/mocks/protocol.go +++ b/pkg/mocks/protocol.go @@ -143,6 +143,7 @@ func GetDefaultProtocolParameters() protocol.Protocol { MultihashAlgorithm: sha2_256, MaxOperationCount: 2, MaxOperationSize: MaxOperationByteSize, + MaxOperationHashLength: 100, MaxDeltaSize: MaxDeltaByteSize, MaxProofSize: MaxProofByteSize, MaxCasURILength: 100, diff --git a/pkg/processor/processor_test.go b/pkg/processor/processor_test.go index 61f4da58..da8d6ea0 100644 --- a/pkg/processor/processor_test.go +++ b/pkg/processor/processor_test.go @@ -1265,6 +1265,7 @@ func newMockProtocolClient() *mocks.MockProtocolClient { MultihashAlgorithm: sha2_512, MaxOperationCount: 2, MaxOperationSize: mocks.MaxOperationByteSize, + MaxOperationHashLength: 100, MaxDeltaSize: mocks.MaxDeltaByteSize, MaxProofSize: 700, // has to be increased from 500 since we now use sha2_512 MaxCasURILength: 100, diff --git a/pkg/versions/0_1/operationapplier/operationapplier_test.go b/pkg/versions/0_1/operationapplier/operationapplier_test.go index eebf8505..93194119 100644 --- a/pkg/versions/0_1/operationapplier/operationapplier_test.go +++ b/pkg/versions/0_1/operationapplier/operationapplier_test.go @@ -47,6 +47,7 @@ var ( MultihashAlgorithm: sha2_256, MaxOperationCount: 2, MaxOperationSize: 2000, + MaxOperationHashLength: 100, MaxProofSize: 500, MaxDeltaSize: 1000, MaxCasURILength: 100, diff --git a/pkg/versions/0_1/operationparser/create.go b/pkg/versions/0_1/operationparser/create.go index cce3e0e2..41bdf4e2 100644 --- a/pkg/versions/0_1/operationparser/create.go +++ b/pkg/versions/0_1/operationparser/create.go @@ -99,13 +99,25 @@ func (p *Parser) ValidateDelta(delta *model.DeltaModel) error { } } - if !hashing.IsComputedUsingMultihashAlgorithm(delta.UpdateCommitment, uint64(p.MultihashAlgorithm)) { - return fmt.Errorf("next update commitment hash is not computed with the required supported hash algorithm: %d", p.MultihashAlgorithm) + if err := p.validateMultihash(delta.UpdateCommitment, "update commitment"); err != nil { + return err } return p.validateDeltaSize(delta) } +func (p *Parser) validateMultihash(mh, alias string) error { + if len(mh) > int(p.MaxOperationHashLength) { + return fmt.Errorf("%s length[%d] exceeds maximum hash length[%d]", alias, len(mh), p.MaxOperationHashLength) + } + + if !hashing.IsComputedUsingMultihashAlgorithm(mh, uint64(p.MultihashAlgorithm)) { + return fmt.Errorf("%s is not computed with the required hash algorithm: %d", alias, p.MultihashAlgorithm) + } + + return nil +} + func (p *Parser) validateDeltaSize(delta *model.DeltaModel) error { canonicalDelta, err := canonicalizer.MarshalCanonical(delta) if err != nil { @@ -135,15 +147,11 @@ func (p *Parser) ValidateSuffixData(suffixData *model.SuffixDataModel) error { return errors.New("missing suffix data") } - if !hashing.IsComputedUsingMultihashAlgorithm(suffixData.RecoveryCommitment, uint64(p.MultihashAlgorithm)) { - return fmt.Errorf("next recovery commitment hash is not computed with the required supported hash algorithm: %d", p.MultihashAlgorithm) + if err := p.validateMultihash(suffixData.RecoveryCommitment, "recovery commitment"); err != nil { + return err } - if !hashing.IsComputedUsingMultihashAlgorithm(suffixData.DeltaHash, uint64(p.MultihashAlgorithm)) { - return fmt.Errorf("patch data hash is not computed with the required supported hash algorithm: %d", p.MultihashAlgorithm) - } - - return nil + return p.validateMultihash(suffixData.DeltaHash, "delta hash") } func (p *Parser) validateCreateRequest(create *model.CreateRequest) error { diff --git a/pkg/versions/0_1/operationparser/create_test.go b/pkg/versions/0_1/operationparser/create_test.go index c8487f69..4e719379 100644 --- a/pkg/versions/0_1/operationparser/create_test.go +++ b/pkg/versions/0_1/operationparser/create_test.go @@ -22,13 +22,16 @@ import ( "github.com/trustbloc/sidetree-core-go/pkg/versions/0_1/model" ) -const invalid = "invalid" +const ( + invalid = "invalid" +) func TestParseCreateOperation(t *testing.T) { p := protocol.Protocol{ - MaxDeltaSize: maxDeltaSize, - MultihashAlgorithm: sha2_256, - Patches: []string{"replace", "add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, + MaxOperationHashLength: 100, + MaxDeltaSize: maxDeltaSize, + MultihashAlgorithm: sha2_256, + Patches: []string{"replace", "add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, } parser := New(p) @@ -78,7 +81,7 @@ func TestParseCreateOperation(t *testing.T) { op, err := parser.ParseCreateOperation(request, true) require.Error(t, err) - require.Contains(t, err.Error(), "next recovery commitment hash is not computed with the required supported hash algorithm: 18") + require.Contains(t, err.Error(), "recovery commitment is not computed with the required hash algorithm: 18") require.Nil(t, op) }) t.Run("missing delta", func(t *testing.T) { @@ -158,7 +161,8 @@ func TestParseCreateOperation(t *testing.T) { func TestValidateSuffixData(t *testing.T) { p := protocol.Protocol{ - MultihashAlgorithm: sha2_256, + MaxOperationHashLength: maxHashLength, + MultihashAlgorithm: sha2_256, } parser := New(p) @@ -170,7 +174,7 @@ func TestValidateSuffixData(t *testing.T) { suffixData.DeltaHash = "" err = parser.ValidateSuffixData(suffixData) require.Error(t, err) - require.Contains(t, err.Error(), "patch data hash is not computed with the required supported hash algorithm") + require.Contains(t, err.Error(), "delta hash is not computed with the required hash algorithm: 18") }) t.Run("invalid next recovery commitment hash", func(t *testing.T) { suffixData, err := getSuffixData() @@ -179,7 +183,20 @@ func TestValidateSuffixData(t *testing.T) { suffixData.RecoveryCommitment = "" err = parser.ValidateSuffixData(suffixData) require.Error(t, err) - require.Contains(t, err.Error(), "next recovery commitment hash is not computed with the required supported hash algorithm") + require.Contains(t, err.Error(), "recovery commitment is not computed with the required hash algorithm: 18") + }) + t.Run("recovery commitment exceeds maximum hash length", func(t *testing.T) { + lowHashLength := protocol.Protocol{ + MaxOperationHashLength: 10, + MultihashAlgorithm: sha2_256, + } + + suffixData, err := getSuffixData() + require.NoError(t, err) + + err = New(lowHashLength).ValidateSuffixData(suffixData) + require.Error(t, err) + require.Contains(t, err.Error(), "recovery commitment length[46] exceeds maximum hash length[10]") }) } @@ -187,9 +204,10 @@ func TestValidateDelta(t *testing.T) { patches := []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"} p := protocol.Protocol{ - MaxDeltaSize: maxDeltaSize, - MultihashAlgorithm: sha2_256, - Patches: patches, + MaxOperationHashLength: maxHashLength, + MaxDeltaSize: maxDeltaSize, + MultihashAlgorithm: sha2_256, + Patches: patches, } parser := New(p) @@ -204,9 +222,10 @@ func TestValidateDelta(t *testing.T) { t.Run("error - delta exceeds max delta size ", func(t *testing.T) { parserWithLowMaxDeltaSize := New(protocol.Protocol{ - MaxDeltaSize: 50, - MultihashAlgorithm: sha2_256, - Patches: patches, + MaxOperationHashLength: maxHashLength, + MaxDeltaSize: 50, + MultihashAlgorithm: sha2_256, + Patches: patches, }) delta, err := getDelta() @@ -225,8 +244,26 @@ func TestValidateDelta(t *testing.T) { err = parser.ValidateDelta(delta) require.Error(t, err) require.Contains(t, err.Error(), - "next update commitment hash is not computed with the required supported hash algorithm") + "update commitment is not computed with the required hash algorithm: 18") }) + + t.Run("update commitment exceeds maximum hash length", func(t *testing.T) { + lowMaxHashLength := protocol.Protocol{ + MaxOperationHashLength: 10, + MaxDeltaSize: 50, + MultihashAlgorithm: sha2_256, + Patches: patches, + } + + delta, err := getDelta() + require.NoError(t, err) + + err = New(lowMaxHashLength).ValidateDelta(delta) + require.Error(t, err) + require.Contains(t, err.Error(), + "update commitment length[46] exceeds maximum hash length[10]") + }) + t.Run("missing patches", func(t *testing.T) { delta, err := getDelta() require.NoError(t, err) diff --git a/pkg/versions/0_1/operationparser/deactivate.go b/pkg/versions/0_1/operationparser/deactivate.go index b9c0dd92..d8784c24 100644 --- a/pkg/versions/0_1/operationparser/deactivate.go +++ b/pkg/versions/0_1/operationparser/deactivate.go @@ -85,7 +85,7 @@ func (p *Parser) ParseSignedDataForDeactivate(compactJWS string) (*model.Deactiv } if err := p.validateSigningKey(signedData.RecoveryKey, p.KeyAlgorithms); err != nil { - return nil, fmt.Errorf("signed data for deactivate: %s", err.Error()) + return nil, fmt.Errorf("validate signed data for deactivate: %s", err.Error()) } return signedData, nil diff --git a/pkg/versions/0_1/operationparser/deactivate_test.go b/pkg/versions/0_1/operationparser/deactivate_test.go index 8bf2db2a..e97bc9f3 100644 --- a/pkg/versions/0_1/operationparser/deactivate_test.go +++ b/pkg/versions/0_1/operationparser/deactivate_test.go @@ -124,7 +124,7 @@ func TestParseDeactivateOperation(t *testing.T) { op, err := parser.ParseDeactivateOperation(request, false) require.Error(t, err) - require.Contains(t, err.Error(), "signed data for deactivate: key algorithm 'crv' is not in the allowed list [other]") + require.Contains(t, err.Error(), "validate signed data for deactivate: key algorithm 'crv' is not in the allowed list [other]") require.Nil(t, op) }) } diff --git a/pkg/versions/0_1/operationparser/operation_test.go b/pkg/versions/0_1/operationparser/operation_test.go index 77e07376..d4ce0f6e 100644 --- a/pkg/versions/0_1/operationparser/operation_test.go +++ b/pkg/versions/0_1/operationparser/operation_test.go @@ -19,19 +19,21 @@ const ( namespace = "did:sidetree" maxOperationSize = 2000 + maxHashLength = 100 maxDeltaSize = 1000 maxProofSize = 500 ) func TestGetOperation(t *testing.T) { p := protocol.Protocol{ - MaxOperationSize: maxOperationSize, - MaxDeltaSize: maxDeltaSize, - MaxProofSize: maxProofSize, - MultihashAlgorithm: sha2_256, - SignatureAlgorithms: []string{"alg"}, - KeyAlgorithms: []string{"crv"}, - Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, + MaxOperationSize: maxOperationSize, + MaxOperationHashLength: maxHashLength, + MaxDeltaSize: maxDeltaSize, + MaxProofSize: maxProofSize, + MultihashAlgorithm: sha2_256, + SignatureAlgorithms: []string{"alg"}, + KeyAlgorithms: []string{"crv"}, + Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, } parser := New(p) diff --git a/pkg/versions/0_1/operationparser/recover.go b/pkg/versions/0_1/operationparser/recover.go index cd0628f9..e142189a 100644 --- a/pkg/versions/0_1/operationparser/recover.go +++ b/pkg/versions/0_1/operationparser/recover.go @@ -97,13 +97,12 @@ func (p *Parser) validateSignedDataForRecovery(signedData *model.RecoverSignedDa return err } - code := uint64(p.MultihashAlgorithm) - if !hashing.IsComputedUsingMultihashAlgorithm(signedData.RecoveryCommitment, code) { - return fmt.Errorf("next recovery commitment hash is not computed with the required hash algorithm: %d", code) + if err := p.validateMultihash(signedData.RecoveryCommitment, "recovery commitment"); err != nil { + return err } - if !hashing.IsComputedUsingMultihashAlgorithm(signedData.DeltaHash, code) { - return fmt.Errorf("patch data hash is not computed with the required hash algorithm: %d", code) + if err := p.validateMultihash(signedData.DeltaHash, "delta hash"); err != nil { + return err } return validateCommitment(signedData.RecoveryKey, p.MultihashAlgorithm, signedData.RecoveryCommitment) diff --git a/pkg/versions/0_1/operationparser/recover_test.go b/pkg/versions/0_1/operationparser/recover_test.go index b418a7b2..8012ef1b 100644 --- a/pkg/versions/0_1/operationparser/recover_test.go +++ b/pkg/versions/0_1/operationparser/recover_test.go @@ -29,12 +29,13 @@ const ( func TestParseRecoverOperation(t *testing.T) { p := protocol.Protocol{ - MaxDeltaSize: maxDeltaSize, - MaxProofSize: maxProofSize, - MultihashAlgorithm: sha2_256, - SignatureAlgorithms: []string{"alg"}, - KeyAlgorithms: []string{"crv"}, - Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, + MaxOperationHashLength: maxHashLength, + MaxDeltaSize: maxDeltaSize, + MaxProofSize: maxProofSize, + MultihashAlgorithm: sha2_256, + SignatureAlgorithms: []string{"alg"}, + KeyAlgorithms: []string{"crv"}, + Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, } parser := New(p) @@ -145,7 +146,7 @@ func TestParseRecoverOperation(t *testing.T) { op, err := parser.ParseRecoverOperation(request, false) require.Error(t, err) - require.Contains(t, err.Error(), "signed data for recovery: missing signing key") + require.Contains(t, err.Error(), "validate signed data for recovery: missing signing key") require.Nil(t, op) }) @@ -194,8 +195,9 @@ func TestParseRecoverOperation(t *testing.T) { func TestValidateSignedDataForRecovery(t *testing.T) { p := protocol.Protocol{ - MultihashAlgorithm: sha2_256, - KeyAlgorithms: []string{"crv"}, + MaxOperationHashLength: maxHashLength, + MultihashAlgorithm: sha2_256, + KeyAlgorithms: []string{"crv"}, } parser := New(p) @@ -213,14 +215,27 @@ func TestValidateSignedDataForRecovery(t *testing.T) { signed.DeltaHash = "" err := parser.validateSignedDataForRecovery(signed) require.Error(t, err) - require.Contains(t, err.Error(), "patch data hash is not computed with the required hash algorithm") + require.Contains(t, err.Error(), "delta hash is not computed with the required hash algorithm: 18") }) t.Run("invalid next recovery commitment hash", func(t *testing.T) { signed := getSignedDataForRecovery() signed.RecoveryCommitment = "" err := parser.validateSignedDataForRecovery(signed) require.Error(t, err) - require.Contains(t, err.Error(), "next recovery commitment hash is not computed with the required hash algorithm") + require.Contains(t, err.Error(), "recovery commitment is not computed with the required hash algorithm: 18") + }) + t.Run("recovery commitment exceeds maximum hash length", func(t *testing.T) { + lowMaxHashLength := protocol.Protocol{ + MaxOperationHashLength: 10, + MultihashAlgorithm: sha2_256, + KeyAlgorithms: []string{"crv"}, + } + + signed := getSignedDataForRecovery() + + err := New(lowMaxHashLength).validateSignedDataForRecovery(signed) + require.Error(t, err) + require.Contains(t, err.Error(), "recovery commitment length[46] exceeds maximum hash length[10]") }) } diff --git a/pkg/versions/0_1/operationparser/update.go b/pkg/versions/0_1/operationparser/update.go index af836c95..b05357f2 100644 --- a/pkg/versions/0_1/operationparser/update.go +++ b/pkg/versions/0_1/operationparser/update.go @@ -12,7 +12,6 @@ import ( "fmt" "github.com/trustbloc/sidetree-core-go/pkg/api/operation" - "github.com/trustbloc/sidetree-core-go/pkg/hashing" "github.com/trustbloc/sidetree-core-go/pkg/versions/0_1/model" ) @@ -83,7 +82,7 @@ func (p *Parser) ParseSignedDataForUpdate(compactJWS string) (*model.UpdateSigne } if err := p.validateSignedDataForUpdate(schema); err != nil { - return nil, err + return nil, fmt.Errorf("validate signed data for update: %s", err.Error()) } return schema, nil @@ -103,13 +102,8 @@ func (p *Parser) validateUpdateRequest(update *model.UpdateRequest) error { func (p *Parser) validateSignedDataForUpdate(signedData *model.UpdateSignedDataModel) error { if err := p.validateSigningKey(signedData.UpdateKey, p.KeyAlgorithms); err != nil { - return fmt.Errorf("signed data for update: %s", err.Error()) - } - - code := uint64(p.MultihashAlgorithm) - if !hashing.IsComputedUsingMultihashAlgorithm(signedData.DeltaHash, code) { - return fmt.Errorf("delta hash is not computed with the required multihash algorithm: %d", code) + return err } - return nil + return p.validateMultihash(signedData.DeltaHash, "delta hash") } diff --git a/pkg/versions/0_1/operationparser/update_test.go b/pkg/versions/0_1/operationparser/update_test.go index b2bbeb20..610b7aa3 100644 --- a/pkg/versions/0_1/operationparser/update_test.go +++ b/pkg/versions/0_1/operationparser/update_test.go @@ -24,12 +24,13 @@ import ( func TestParseUpdateOperation(t *testing.T) { p := protocol.Protocol{ - MaxDeltaSize: maxDeltaSize, - MaxProofSize: maxProofSize, - MultihashAlgorithm: sha2_256, - SignatureAlgorithms: []string{"alg"}, - KeyAlgorithms: []string{"crv"}, - Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, + MaxOperationHashLength: maxHashLength, + MaxDeltaSize: maxDeltaSize, + MaxProofSize: maxProofSize, + MultihashAlgorithm: sha2_256, + SignatureAlgorithms: []string{"alg"}, + KeyAlgorithms: []string{"crv"}, + Patches: []string{"add-public-keys", "remove-public-keys", "add-services", "remove-services", "ietf-json-patch"}, } parser := New(p) @@ -81,7 +82,7 @@ func TestParseUpdateOperation(t *testing.T) { require.Error(t, err) require.Nil(t, schema) require.Contains(t, err.Error(), - "next update commitment hash is not computed with the required supported hash algorithm") + "update commitment is not computed with the required hash algorithm: 18") }) t.Run("invalid signed data", func(t *testing.T) { delta, err := getUpdateDelta() @@ -140,10 +141,11 @@ func TestParseUpdateOperation(t *testing.T) { func TestParseSignedDataForUpdate(t *testing.T) { p := protocol.Protocol{ - MaxProofSize: maxProofSize, - MultihashAlgorithm: sha2_256, - SignatureAlgorithms: []string{"alg"}, - KeyAlgorithms: []string{"crv"}, + MaxOperationHashLength: maxHashLength, + MaxProofSize: maxProofSize, + MultihashAlgorithm: sha2_256, + SignatureAlgorithms: []string{"alg"}, + KeyAlgorithms: []string{"crv"}, } parser := New(p) @@ -176,7 +178,7 @@ func TestParseSignedDataForUpdate(t *testing.T) { schema, err := parser.ParseSignedDataForUpdate(compactJWS) require.Error(t, err) require.Nil(t, schema) - require.Contains(t, err.Error(), "delta hash is not computed with the required multihash algorithm: 18") + require.Contains(t, err.Error(), "delta hash is not computed with the required hash algorithm: 18") }) t.Run("payload not JSON object", func(t *testing.T) { compactJWS, err := signutil.SignPayload([]byte("test"), NewMockSigner()) @@ -205,7 +207,7 @@ func TestValidateUpdateDelta(t *testing.T) { err = parser.ValidateDelta(delta) require.Error(t, err) require.Contains(t, err.Error(), - "next update commitment hash is not computed with the required supported hash algorithm") + "update commitment is not computed with the required hash algorithm") }) } diff --git a/pkg/versions/0_1/txnprovider/provider.go b/pkg/versions/0_1/txnprovider/provider.go index e2500eb8..e35d4bd5 100644 --- a/pkg/versions/0_1/txnprovider/provider.go +++ b/pkg/versions/0_1/txnprovider/provider.go @@ -391,29 +391,37 @@ func (h *OperationProvider) validateCoreIndexOperations(ops *models.CoreOperatio } for i, op := range ops.Recover { - err := validateOperationReference(op) + err := h.validateOperationReference(op) if err != nil { - return fmt.Errorf("failed to validate signed operation for recover[%d]: %s", i, err.Error()) + return fmt.Errorf("failed to validate operation reference for recover[%d]: %s", i, err.Error()) } } for i, op := range ops.Deactivate { - err := validateOperationReference(op) + err := h.validateOperationReference(op) if err != nil { - return fmt.Errorf("failed to validate signed operation for deactivate[%d]: %s", i, err.Error()) + return fmt.Errorf("failed to validate operation reference for deactivate[%d]: %s", i, err.Error()) } } return nil } -func validateOperationReference(op models.OperationReference) error { - if op.DidSuffix == "" { - return errors.New("missing did suffix") +func (h *OperationProvider) validateOperationReference(op models.OperationReference) error { + if err := h.validateRequiredMultihash(op.DidSuffix, "did suffix"); err != nil { + return err + } + + return h.validateRequiredMultihash(op.RevealValue, "reveal value") +} + +func (h *OperationProvider) validateRequiredMultihash(mh, alias string) error { + if mh == "" { + return fmt.Errorf("missing %s", alias) } - if op.RevealValue == "" { - return errors.New("missing reveal value") + if len(mh) > int(h.MaxOperationHashLength) { + return fmt.Errorf("%s length[%d] exceeds maximum hash length[%d]", alias, len(mh), h.MaxOperationHashLength) } return nil @@ -557,9 +565,9 @@ func (h *OperationProvider) validateProvisionalIndexOperations(ops *models.Provi } for i, op := range ops.Update { - err := validateOperationReference(op) + err := h.validateOperationReference(op) if err != nil { - return fmt.Errorf("failed to validate signed operation for update[%d]: %s", i, err.Error()) + return fmt.Errorf("failed to validate operation reference for update[%d]: %s", i, err.Error()) } } diff --git a/pkg/versions/0_1/txnprovider/provider_test.go b/pkg/versions/0_1/txnprovider/provider_test.go index 1f90f096..30a0df13 100644 --- a/pkg/versions/0_1/txnprovider/provider_test.go +++ b/pkg/versions/0_1/txnprovider/provider_test.go @@ -31,7 +31,7 @@ const ( maxFileSize = 2000 // in bytes sampleCasURI = "bafkreih6ot2yfqcerzp5l2qupc77it2vdmepfhszitmswnpdtk34m4ura4" - longCasURI = "bafkreih6ot2yfqcerzp5l2qupc77it2vdmepfhszitmswnpdtk34m4ura4bafkreih6ot2yfqcerzp5l2qupc77it2vdmepfhszitmswnpdtk34m4ura4" + longValue = "bafkreih6ot2yfqcerzp5l2qupc77it2vdmepfhszitmswnpdtk34m4ura4bafkreih6ot2yfqcerzp5l2qupc77it2vdmepfhszitmswnpdtk34m4ura4" ) func TestNewOperationProvider(t *testing.T) { @@ -505,7 +505,7 @@ func TestHandler_ValidateCoreIndexFile(t *testing.T) { provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateCoreIndexFile(batchFiles.CoreIndex) require.Error(t, err) - require.Contains(t, err.Error(), "failed to validate signed operation for recover[0]: missing did suffix") + require.Contains(t, err.Error(), "failed to validate operation reference for recover[0]: missing did suffix") }) t.Run("error - invalid reveal value for deactivate", func(t *testing.T) { @@ -518,7 +518,46 @@ func TestHandler_ValidateCoreIndexFile(t *testing.T) { provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateCoreIndexFile(batchFiles.CoreIndex) require.Error(t, err) - require.Contains(t, err.Error(), "failed to validate signed operation for deactivate[0]: missing reveal value") + require.Contains(t, err.Error(), "failed to validate operation reference for deactivate[0]: missing reveal value") + }) + + t.Run("error - reveal value exceeds maximum hash length", func(t *testing.T) { + batchFiles, err := generateDefaultBatchFiles() + require.NoError(t, err) + + // invalidate reveal value for first deactivate + batchFiles.CoreIndex.Operations.Deactivate[0].RevealValue = longValue + + provider := NewOperationProvider(p, operationparser.New(p), nil, nil) + err = provider.validateCoreIndexFile(batchFiles.CoreIndex) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to validate operation reference for deactivate[0]: reveal value length[118] exceeds maximum hash length[100]") + }) + + t.Run("error - did suffix exceeds maximum hash length", func(t *testing.T) { + batchFiles, err := generateDefaultBatchFiles() + require.NoError(t, err) + + // invalidate reveal value for first deactivate + batchFiles.CoreIndex.Operations.Recover[0].DidSuffix = longValue + + provider := NewOperationProvider(p, operationparser.New(p), nil, nil) + err = provider.validateCoreIndexFile(batchFiles.CoreIndex) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to validate operation reference for recover[0]: did suffix length[118] exceeds maximum hash length[100]") + }) + + t.Run("error - recovery commitment length exceeds max hash length", func(t *testing.T) { + lowMaxHashLength := mocks.GetDefaultProtocolParameters() + lowMaxHashLength.MaxOperationHashLength = 10 + + batchFiles, err := generateDefaultBatchFiles() + require.NoError(t, err) + + provider := NewOperationProvider(lowMaxHashLength, operationparser.New(lowMaxHashLength), nil, nil) + err = provider.validateCoreIndexFile(batchFiles.CoreIndex) + require.Error(t, err) + require.Contains(t, err.Error(), "failed to validate suffix data for create[0]: recovery commitment length[46] exceeds maximum hash length[10]") }) } @@ -614,7 +653,7 @@ func TestHandler_ValidateProvisionalIndexFile(t *testing.T) { provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateProvisionalIndexFile(batchFiles.ProvisionalIndex) require.Error(t, err) - require.Contains(t, err.Error(), "failed to validate signed operation for update[0]: missing did suffix") + require.Contains(t, err.Error(), "failed to validate operation reference for update[0]: missing did suffix") }) t.Run("error - missing reveal value", func(t *testing.T) { @@ -627,7 +666,7 @@ func TestHandler_ValidateProvisionalIndexFile(t *testing.T) { provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateProvisionalIndexFile(batchFiles.ProvisionalIndex) require.Error(t, err) - require.Contains(t, err.Error(), "failed to validate signed operation for update[0]: missing reveal value") + require.Contains(t, err.Error(), "failed to validate operation reference for update[0]: missing reveal value") }) t.Run("success - validate IPFS CID", func(t *testing.T) { @@ -646,7 +685,7 @@ func TestHandler_ValidateProvisionalIndexFile(t *testing.T) { batchFiles, err := generateDefaultBatchFiles() require.NoError(t, err) - batchFiles.ProvisionalIndex.ProvisionalProofFileURI = longCasURI + batchFiles.ProvisionalIndex.ProvisionalProofFileURI = longValue provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateProvisionalIndexFile(batchFiles.ProvisionalIndex) @@ -658,7 +697,7 @@ func TestHandler_ValidateProvisionalIndexFile(t *testing.T) { batchFiles, err := generateDefaultBatchFiles() require.NoError(t, err) - batchFiles.ProvisionalIndex.Chunks[0].ChunkFileURI = longCasURI + batchFiles.ProvisionalIndex.Chunks[0].ChunkFileURI = longValue provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateProvisionalIndexFile(batchFiles.ProvisionalIndex) @@ -919,7 +958,7 @@ func TestHandler_ValidateCorePoofFile(t *testing.T) { batchFiles, err := generateDefaultBatchFiles() require.NoError(t, err) - batchFiles.CoreIndex.CoreProofFileURI = longCasURI + batchFiles.CoreIndex.CoreProofFileURI = longValue provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateCoreIndexFile(batchFiles.CoreIndex) @@ -931,7 +970,7 @@ func TestHandler_ValidateCorePoofFile(t *testing.T) { batchFiles, err := generateDefaultBatchFiles() require.NoError(t, err) - batchFiles.CoreIndex.ProvisionalIndexFileURI = longCasURI + batchFiles.CoreIndex.ProvisionalIndexFileURI = longValue provider := NewOperationProvider(p, operationparser.New(p), nil, nil) err = provider.validateCoreIndexFile(batchFiles.CoreIndex)