From 685c211ccc588399b5673da5ecbb0f0a6f7cd6ec Mon Sep 17 00:00:00 2001 From: vbasiuk Date: Fri, 20 Dec 2024 13:13:51 +0200 Subject: [PATCH] ParseAcceptProfile --- packers/anoncrypt.go | 3 +- protocol/accept.go | 22 +++++ protocol/auth.go | 1 + utils/accept.go | 192 +++++++++++++++++++++++++++++++++++++++++++ utils/accept_test.go | 124 ++++++++++++++++++++++++++++ 5 files changed, 341 insertions(+), 1 deletion(-) create mode 100644 utils/accept.go create mode 100644 utils/accept_test.go diff --git a/packers/anoncrypt.go b/packers/anoncrypt.go index 341292b..c427fbe 100644 --- a/packers/anoncrypt.go +++ b/packers/anoncrypt.go @@ -98,9 +98,10 @@ func (p *AnoncryptPacker) MediaType() iden3comm.MediaType { func (p *AnoncryptPacker) GetSupportedProfiles() []string { return []string{ fmt.Sprintf( - "%s;env=%s", + "%s;env=%s&alg=%s", protocol.ProtocolVersionV1, p.MediaType(), + jose.ECDH_ES_A256KW, ), } } diff --git a/protocol/accept.go b/protocol/accept.go index 66df62f..84330df 100644 --- a/protocol/accept.go +++ b/protocol/accept.go @@ -1,5 +1,19 @@ package protocol +import ( + "github.com/iden3/iden3comm/v2" +) + +// AcceptProfile is a struct that represents the accept header +type AcceptProfile struct { + ProtocolVersion AcceptProtocolVersion + Env iden3comm.MediaType + Circuits []AcceptAuthCircuits + AcceptJwzAlgorithms []AcceptJwzAlgorithms + AcceptJwsAlgorithms []AcceptJwsAlgorithms + AcceptAnoncryptAlgorithms []AcceptAnoncryptAlgorithms +} + // AcceptProtocolVersion is a type of supported versions of the protocol used in the accept header type AcceptProtocolVersion string @@ -36,3 +50,11 @@ const ( // AcceptJwsAlgorithmsES256KR is a ES256K-R accepted JWS algorithm AcceptJwsAlgorithmsES256KR AcceptJwsAlgorithms = "ES256K-R" ) + +// AcceptAnoncryptAlgorithms is a type of accepted anoncrypt algorithms +type AcceptAnoncryptAlgorithms string + +const ( + // AcceptAnoncryptECDHESA256KW is a ECDH-ES+A256KW accepted Anoncrypt algorithm + AcceptAnoncryptECDHESA256KW AcceptAnoncryptAlgorithms = "ECDH-ES+A256KW" +) diff --git a/protocol/auth.go b/protocol/auth.go index 98c6704..f9be80c 100644 --- a/protocol/auth.go +++ b/protocol/auth.go @@ -60,6 +60,7 @@ type AuthorizationRequestMessageBody struct { Message string `json:"message,omitempty"` DIDDoc json.RawMessage `json:"did_doc,omitempty"` Scope []ZeroKnowledgeProofRequest `json:"scope"` + Accept []string `json:"accept,omitempty"` } // ZeroKnowledgeProofRequest represents structure of zkp request object diff --git a/utils/accept.go b/utils/accept.go new file mode 100644 index 0000000..36a4359 --- /dev/null +++ b/utils/accept.go @@ -0,0 +1,192 @@ +package utils + +import ( + "errors" + "strings" + + "github.com/iden3/iden3comm/v2" + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" +) + +// ParseAcceptProfile parses the accept profile string and returns the AcceptProfile struct +func ParseAcceptProfile(profile string) (protocol.AcceptProfile, error) { + params := strings.Split(profile, ";") + if len(params) < 2 { + return protocol.AcceptProfile{}, errors.New("invalid accept profile value") + } + + protocolVersion := strings.TrimSpace(params[0]) + if !isProtocolVersion(protocolVersion) { + return protocol.AcceptProfile{}, errors.New("protocol version '" + protocolVersion + "' not supported") + } + + envParam := strings.Split(params[1], "=") + if len(envParam) != 2 { + return protocol.AcceptProfile{}, errors.New("invalid accept profile 'env' parameter") + } + + env := strings.TrimSpace(envParam[1]) + if !isMediaType(env) { + return protocol.AcceptProfile{}, errors.New("envelop '" + env + "' not supported") + } + + circuitsIndex := -1 + for i, param := range params { + if strings.Contains(param, "circuitId=") { + circuitsIndex = i + break + } + } + + if env != string(packers.MediaTypeZKPMessage) && circuitsIndex > 0 { + return protocol.AcceptProfile{}, errors.New("circuits not supported for env '" + env + "'") + } + + var circuits []protocol.AcceptAuthCircuits + if circuitsIndex > 0 { + circuitsStr := strings.Split(strings.Split(params[circuitsIndex], "=")[1], ",") + for _, c := range circuitsStr { + c = strings.TrimSpace(c) + if !isAcceptAuthCircuits(c) { + return protocol.AcceptProfile{}, errors.New("circuit '" + c + "' not supported") + } + circuits = append(circuits, protocol.AcceptAuthCircuits(c)) + } + } + + algIndex := -1 + for i, param := range params { + if strings.Contains(param, "alg=") { + algIndex = i + break + } + } + + var jwzAlgs []protocol.AcceptJwzAlgorithms + var jwsAlgs []protocol.AcceptJwsAlgorithms + var anoncryptAlgs []protocol.AcceptAnoncryptAlgorithms + if algIndex > 0 { + algStr := strings.Split(strings.Split(params[algIndex], "=")[1], ",") + switch env { + case string(packers.MediaTypeZKPMessage): + for _, a := range algStr { + a = strings.TrimSpace(a) + if !isAcceptJwzAlgorithms(a) { + return protocol.AcceptProfile{}, errors.New("algorithm '" + a + "' not supported for '" + env + "'") + } + jwzAlgs = append(jwzAlgs, protocol.AcceptJwzAlgorithms(a)) + } + case string(packers.MediaTypeSignedMessage): + for _, a := range algStr { + a = strings.TrimSpace(a) + if !isAcceptJwsAlgorithms(a) { + return protocol.AcceptProfile{}, errors.New("algorithm '" + a + "' not supported for '" + env + "'") + } + jwsAlgs = append(jwsAlgs, protocol.AcceptJwsAlgorithms(a)) + } + case string(packers.MediaTypeEncryptedMessage): + for _, a := range algStr { + a = strings.TrimSpace(a) + if !isAcceptAnoncryptAlgorithms(a) { + return protocol.AcceptProfile{}, errors.New("algorithm '" + a + "' not supported for '" + env + "'") + } + anoncryptAlgs = append(anoncryptAlgs, protocol.AcceptAnoncryptAlgorithms(a)) + } + default: + return protocol.AcceptProfile{}, errors.New("algorithm not supported for '" + env + "'") + } + } + + return protocol.AcceptProfile{ + ProtocolVersion: protocol.AcceptProtocolVersion(protocolVersion), + Env: iden3comm.MediaType(env), + Circuits: circuits, + AcceptJwsAlgorithms: jwsAlgs, + AcceptJwzAlgorithms: jwzAlgs, + AcceptAnoncryptAlgorithms: anoncryptAlgs, + }, nil +} + +func isProtocolVersion(value string) bool { + // List all possible protocol versions + validVersions := []protocol.AcceptProtocolVersion{ + protocol.ProtocolVersionV1, + } + for _, v := range validVersions { + if protocol.AcceptProtocolVersion(value) == v { + return true + } + } + return false +} + +func isAcceptAuthCircuits(value string) bool { + // List all possible authentication circuits + validCircuits := []protocol.AcceptAuthCircuits{ + protocol.AcceptAuthCircuitsAuthV2, + protocol.AcceptAuthCircuitsAuthV3, + } + for _, v := range validCircuits { + if protocol.AcceptAuthCircuits(value) == v { + return true + } + } + return false +} + +func isAcceptJwzAlgorithms(value string) bool { + // List all possible JWZ algorithms + validAlgorithms := []protocol.AcceptJwzAlgorithms{ + protocol.AcceptJwzAlgorithmsGroth16, + } + for _, v := range validAlgorithms { + if protocol.AcceptJwzAlgorithms(value) == v { + return true + } + } + return false +} + +func isAcceptJwsAlgorithms(value string) bool { + // List all possible JWS algorithms + validAlgorithms := []protocol.AcceptJwsAlgorithms{ + protocol.AcceptJwsAlgorithmsES256K, + protocol.AcceptJwsAlgorithmsES256KR, + } + for _, v := range validAlgorithms { + if protocol.AcceptJwsAlgorithms(value) == v { + return true + } + } + return false +} + +func isAcceptAnoncryptAlgorithms(value string) bool { + // List all possible Anoncrypt algorithms + validAlgorithms := []protocol.AcceptAnoncryptAlgorithms{ + protocol.AcceptAnoncryptECDHESA256KW, + } + for _, v := range validAlgorithms { + if protocol.AcceptAnoncryptAlgorithms(value) == v { + return true + } + } + return false +} + +func isMediaType(value string) bool { + // List all possible JWS algorithms + validAlgorithms := []iden3comm.MediaType{ + packers.MediaTypeEncryptedMessage, + packers.MediaTypePlainMessage, + packers.MediaTypeZKPMessage, + packers.MediaTypeSignedMessage, + } + for _, v := range validAlgorithms { + if iden3comm.MediaType(value) == v { + return true + } + } + return false +} diff --git a/utils/accept_test.go b/utils/accept_test.go new file mode 100644 index 0000000..c2d8c73 --- /dev/null +++ b/utils/accept_test.go @@ -0,0 +1,124 @@ +package utils + +import ( + "errors" + "testing" + + "github.com/iden3/iden3comm/v2/packers" + "github.com/iden3/iden3comm/v2/protocol" + "github.com/stretchr/testify/assert" +) + +func TestAcceptProfileParser(t *testing.T) { + type expected struct { + profile protocol.AcceptProfile + err error + } + for _, tc := range []struct { + desc string + accept string + expected expected + }{ + { + desc: "Valid plain text accept profile", + accept: "iden3comm/v1;env=application/iden3comm-plain-json", + expected: expected{ + profile: protocol.AcceptProfile{ + ProtocolVersion: protocol.ProtocolVersionV1, + Env: packers.MediaTypePlainMessage, + }, + }, + }, + { + desc: "Valid anoncrypt accept profile", + accept: "iden3comm/v1;env=application/iden3comm-encrypted-json;alg=ECDH-ES+A256KW", + expected: expected{ + profile: protocol.AcceptProfile{ + ProtocolVersion: protocol.ProtocolVersionV1, + Env: packers.MediaTypeEncryptedMessage, + AcceptAnoncryptAlgorithms: []protocol.AcceptAnoncryptAlgorithms{protocol.AcceptAnoncryptECDHESA256KW}, + }, + }, + }, + { + desc: "Valid JWS accept profile", + accept: "iden3comm/v1;env=application/iden3comm-signed-json;alg=ES256K-R", + expected: expected{ + profile: protocol.AcceptProfile{ + ProtocolVersion: protocol.ProtocolVersionV1, + Env: packers.MediaTypeSignedMessage, + AcceptJwsAlgorithms: []protocol.AcceptJwsAlgorithms{protocol.AcceptJwsAlgorithmsES256KR}, + }, + }, + }, + { + desc: "Valid JWZ accept profile", + accept: "iden3comm/v1;env=application/iden3-zkp-json;circuitId=authV2,authV3;alg=groth16", + expected: expected{ + profile: protocol.AcceptProfile{ + ProtocolVersion: protocol.ProtocolVersionV1, + Env: packers.MediaTypeZKPMessage, + AcceptJwzAlgorithms: []protocol.AcceptJwzAlgorithms{protocol.AcceptJwzAlgorithmsGroth16}, + Circuits: []protocol.AcceptAuthCircuits{protocol.AcceptAuthCircuitsAuthV2, protocol.AcceptAuthCircuitsAuthV3}, + }, + }, + }, + { + desc: "Invalid accept profile", + accept: "iden3comm/v1", + expected: expected{ + err: errors.New("invalid accept profile value"), + }, + }, + { + desc: "Invalid protocol version profile", + accept: "iden3comm/v1000_000;env=application/iden3comm-plain-json", + expected: expected{ + err: errors.New("protocol version 'iden3comm/v1000_000' not supported"), + }, + }, + { + desc: "Invalid envelop param", + accept: "iden3comm/v1;application/iden3comm-plain-json", + expected: expected{ + err: errors.New("invalid accept profile 'env' parameter"), + }, + }, + { + desc: "Invalid envelop", + accept: "iden3comm/v1;env=application/iden3comm-rich-text", + expected: expected{ + err: errors.New("envelop 'application/iden3comm-rich-text' not supported"), + }, + }, + { + desc: "Invalid circuit ID", + accept: "iden3comm/v1;env=application/iden3-zkp-json;circuitId=authV2.5;alg=groth16", + expected: expected{ + err: errors.New("circuit 'authV2.5' not supported"), + }, + }, + { + desc: "Invalid alg", + accept: "iden3comm/v1;env=application/iden3-zkp-json;circuitId=authV2;alg=groth1", + expected: expected{ + err: errors.New("algorithm 'groth1' not supported for 'application/iden3-zkp-json'"), + }, + }, + { + desc: "Alg for plain message", + accept: "iden3comm/v1;env=application/iden3comm-plain-json;alg=someAlg", + expected: expected{ + err: errors.New("algorithm not supported for 'application/iden3comm-plain-json'"), + }, + }, + } { + t.Run(tc.desc, func(t *testing.T) { + profile, err := ParseAcceptProfile(tc.accept) + if tc.expected.err != nil { + assert.Equal(t, err.Error(), tc.expected.err.Error()) + } + assert.Equal(t, profile, tc.expected.profile) + }) + } +}