Commit b8f27664 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

feat: backwards compatibility with pre-condiscon session requests in irmaclient and server

parent 4eb4d8fe
...@@ -28,14 +28,30 @@ func (session *session) handleGetRequest(min, max *irma.ProtocolVersion) (irma.S ...@@ -28,14 +28,30 @@ func (session *session) handleGetRequest(min, max *irma.ProtocolVersion) (irma.S
} }
session.markAlive() session.markAlive()
logger := session.conf.Logger.WithFields(logrus.Fields{"session": session.token})
// Handle legacy clients that do not support condiscon, by attempting to convert the condiscon
// session request to the legacy session request format
legacy, legacyErr := session.request.Legacy()
session.legacyCompatible = legacyErr == nil
if legacyErr != nil {
logger.Info("Using condiscon: backwards compatibility with legacy IRMA apps is disabled")
}
var err error var err error
if session.version, err = chooseProtocolVersion(min, max); err != nil { if session.version, err = session.chooseProtocolVersion(min, max); err != nil {
return nil, session.fail(server.ErrorProtocolVersion, "") return nil, session.fail(server.ErrorProtocolVersion, "")
} }
session.conf.Logger.WithFields(logrus.Fields{"session": session.token, "version": session.version.String()}).Debugf("Protocol version negotiated") logger.WithFields(logrus.Fields{"version": session.version.String()}).Debugf("Protocol version negotiated")
session.request.Base().ProtocolVersion = session.version session.request.Base().ProtocolVersion = session.version
session.setStatus(server.StatusConnected) session.setStatus(server.StatusConnected)
if session.version.Below(2, 5) {
logger.Info("Returning legacy session format")
legacy.Base().ProtocolVersion = session.version
return legacy, nil
}
return session.request, nil return session.request, nil
} }
......
...@@ -129,14 +129,20 @@ func (session *session) eventSource() eventsource.EventSource { ...@@ -129,14 +129,20 @@ func (session *session) eventSource() eventsource.EventSource {
// Other // Other
func chooseProtocolVersion(min, max *irma.ProtocolVersion) (*irma.ProtocolVersion, error) { func (session *session) chooseProtocolVersion(minClient, maxClient *irma.ProtocolVersion) (*irma.ProtocolVersion, error) {
if min.AboveVersion(maxProtocolVersion) || max.BelowVersion(minProtocolVersion) || max.BelowVersion(min) { // Set our minimum supported version to 2.5 if condiscon compatibility is required
return nil, server.LogWarning(errors.Errorf("Protocol version negotiation failed, min=%s max=%s", min.String(), max.String())) minServer := minProtocolVersion
if !session.legacyCompatible {
minServer = &irma.ProtocolVersion{2, 5}
} }
if max.AboveVersion(maxProtocolVersion) {
if minClient.AboveVersion(maxProtocolVersion) || maxClient.BelowVersion(minServer) || maxClient.BelowVersion(minClient) {
return nil, server.LogWarning(errors.Errorf("Protocol version negotiation failed, min=%s max=%s minServer=%s maxServer=%s", minClient.String(), maxClient.String(), minServer.String(), maxProtocolVersion.String()))
}
if maxClient.AboveVersion(maxProtocolVersion) {
return maxProtocolVersion, nil return maxProtocolVersion, nil
} else { } else {
return max, nil return maxClient, nil
} }
} }
......
...@@ -16,12 +16,13 @@ import ( ...@@ -16,12 +16,13 @@ import (
type session struct { type session struct {
sync.Mutex sync.Mutex
action irma.Action action irma.Action
token string token string
clientToken string clientToken string
version *irma.ProtocolVersion version *irma.ProtocolVersion
rrequest irma.RequestorRequest rrequest irma.RequestorRequest
request irma.SessionRequest request irma.SessionRequest
legacyCompatible bool // if the request is convertible to pre-condiscon format
status server.Status status server.Status
prevStatus server.Status prevStatus server.Status
...@@ -60,7 +61,7 @@ const ( ...@@ -60,7 +61,7 @@ const (
var ( var (
minProtocolVersion = irma.NewVersion(2, 4) minProtocolVersion = irma.NewVersion(2, 4)
maxProtocolVersion = irma.NewVersion(2, 4) maxProtocolVersion = irma.NewVersion(2, 5)
) )
func (s *memorySessionStore) get(t string) *session { func (s *memorySessionStore) get(t string) *session {
......
...@@ -92,9 +92,12 @@ type session struct { ...@@ -92,9 +92,12 @@ type session struct {
// We implement the handler for the keyshare protocol // We implement the handler for the keyshare protocol
var _ keyshareSessionHandler = (*session)(nil) var _ keyshareSessionHandler = (*session)(nil)
// Supported protocol versions. Minor version numbers should be reverse sorted. // Supported protocol versions. Minor version numbers should be sorted.
var supportedVersions = map[int][]int{ var supportedVersions = map[int][]int{
2: {4}, 2: {
4, // old protocol with legacy session requests
5, // introduces condiscon feature
},
} }
var minVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][0]} var minVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][0]}
var maxVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][len(supportedVersions[2])-1]} var maxVersion = &irma.ProtocolVersion{Major: 2, Minor: supportedVersions[2][len(supportedVersions[2])-1]}
...@@ -170,7 +173,9 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse ...@@ -170,7 +173,9 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
Handler: handler, Handler: handler,
client: client, client: client,
} }
session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating) session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
min := minVersion
// Check if the action is one of the supported types // Check if the action is one of the supported types
switch session.Action { switch session.Action {
...@@ -178,6 +183,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse ...@@ -178,6 +183,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
session.request = &irma.DisclosureRequest{} session.request = &irma.DisclosureRequest{}
case irma.ActionSigning: case irma.ActionSigning:
session.request = &irma.SignatureRequest{} session.request = &irma.SignatureRequest{}
min = &irma.ProtocolVersion{2, 5} // New ABS format is not backwards compatible with old irma server
case irma.ActionIssuing: case irma.ActionIssuing:
session.request = &irma.IssuanceRequest{} session.request = &irma.IssuanceRequest{}
case irma.ActionUnknown: case irma.ActionUnknown:
...@@ -187,7 +193,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse ...@@ -187,7 +193,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
return nil return nil
} }
session.transport.SetHeader(irma.MinVersionHeader, minVersion.String()) session.transport.SetHeader(irma.MinVersionHeader, min.String())
session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String()) session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
if !strings.HasSuffix(session.ServerURL, "/") { if !strings.HasSuffix(session.ServerURL, "/") {
session.ServerURL += "/" session.ServerURL += "/"
......
...@@ -462,6 +462,9 @@ func TestSessionRequests(t *testing.T) { ...@@ -462,6 +462,9 @@ func TestSessionRequests(t *testing.T) {
require.NoError(t, json.Unmarshal([]byte(tst.currentJson), tst.current)) require.NoError(t, json.Unmarshal([]byte(tst.currentJson), tst.current))
require.True(t, reflect.DeepEqual(tst.old, tst.expected), "Legacy %s did not unmarshal to expected value", reflect.TypeOf(tst.old).String()) require.True(t, reflect.DeepEqual(tst.old, tst.expected), "Legacy %s did not unmarshal to expected value", reflect.TypeOf(tst.old).String())
require.True(t, reflect.DeepEqual(tst.current, tst.expected), "%s did not unmarshal to expected value", reflect.TypeOf(tst.old).String()) require.True(t, reflect.DeepEqual(tst.current, tst.expected), "%s did not unmarshal to expected value", reflect.TypeOf(tst.old).String())
_, err := tst.expected.Legacy()
require.NoError(t, err)
} }
} }
......
...@@ -6,19 +6,80 @@ import ( ...@@ -6,19 +6,80 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
) )
type legacyAttributeDisjunction []AttributeRequest // This file contains compatibility code for the legacy, pre-condiscon session requests,
// which supported only condis requests.
//
// Old requests can always be converted to new requests, and this is automatically done by the JSON
// unmarshaler when it encounters a legacy session request.
// New requests can be converted to old requests only if all inner conjunctions contain exactly
// 1 attribute. This can be done using the Legacy() method.
//
// Droppping compatibility with pre-condiscon session requests should thus be more or less:
// 1. delete this file
// 2. solve all compiler errors by removing the lines on which they occur
// 3. adjust the minimal supported protocol version in irmaclient and server
type attributeDisjunction struct { // LegacyDisjunction is a disjunction of attributes from before the condiscon feature,
Label string // representing a list of attribute types one of which must be given by the user,
Attributes legacyAttributeDisjunction // possibly requiring specific values. (C.f. AttributeCon, also defined as []AttributeRequest,
// which is only satisfied if all listed attributes are given by the user.)
type LegacyDisjunction []AttributeRequest
type LegacyLabeledDisjunction struct {
Label string `json:"label"`
Attributes LegacyDisjunction `json:"attributes"`
} }
type legacyDisclosureRequest struct { type LegacyDisclosureRequest struct {
BaseRequest BaseRequest
Content []attributeDisjunction `json:"content"` Content []LegacyLabeledDisjunction `json:"content"`
} }
func convertDisjunctions(disjunctions []attributeDisjunction) ( func (dr *LegacyDisclosureRequest) Validate() error { panic("not implemented") }
func (dr *LegacyDisclosureRequest) Disclosure() *DisclosureRequest { panic("not implemented") }
func (dr *LegacyDisclosureRequest) Identifiers() *IrmaIdentifierSet { panic("not implemented") }
func (dr *LegacyDisclosureRequest) Base() *BaseRequest { return &dr.BaseRequest }
func (dr *LegacyDisclosureRequest) Action() Action { return dr.Type }
func (dr *LegacyDisclosureRequest) Legacy() (SessionRequest, error) { return dr, nil }
type LegacySignatureRequest struct {
LegacyDisclosureRequest
Message string `json:"message"`
}
type LegacyIssuanceRequest struct {
BaseRequest
Credentials []*CredentialRequest `json:"credentials"`
Disclose []LegacyLabeledDisjunction `json:"disclose"`
}
func (ir *LegacyIssuanceRequest) Validate() error { panic("not implemented") }
func (ir *LegacyIssuanceRequest) Disclosure() *DisclosureRequest { panic("not implemented") }
func (ir *LegacyIssuanceRequest) Identifiers() *IrmaIdentifierSet { panic("not implemented") }
func (ir *LegacyIssuanceRequest) Base() *BaseRequest { return &ir.BaseRequest }
func (ir *LegacyIssuanceRequest) Action() Action { return ir.Type }
func (ir *LegacyIssuanceRequest) Legacy() (SessionRequest, error) { return ir, nil }
func convertConDisCon(cdc AttributeConDisCon, labels map[int]TranslatedString) ([]LegacyLabeledDisjunction, error) {
var disjunctions []LegacyLabeledDisjunction
for i, dis := range cdc {
l := LegacyLabeledDisjunction{}
for _, con := range dis {
if len(con) != 1 {
return nil, errors.New("request not convertible to legacy request")
}
l.Attributes = append(l.Attributes, AttributeRequest{Type: con[0].Type, Value: con[0].Value})
}
l.Label = labels[i]["en"]
if l.Label == "" {
l.Label = l.Attributes[0].Type.Name()
}
disjunctions = append(disjunctions, l)
}
return disjunctions, nil
}
func convertDisjunctions(disjunctions []LegacyLabeledDisjunction) (
condiscon AttributeConDisCon, labels map[int]TranslatedString, condiscon AttributeConDisCon, labels map[int]TranslatedString,
) { ) {
labels = make(map[int]TranslatedString) labels = make(map[int]TranslatedString)
...@@ -52,8 +113,7 @@ func checkType(typ, expected Action) error { ...@@ -52,8 +113,7 @@ func checkType(typ, expected Action) error {
return nil return nil
} }
// Reuses AttributeCon.UnmarshalJSON() func (l *LegacyDisjunction) UnmarshalJSON(bts []byte) error {
func (l *legacyAttributeDisjunction) UnmarshalJSON(bts []byte) error {
var err error var err error
var lst []AttributeTypeIdentifier var lst []AttributeTypeIdentifier
if err = json.Unmarshal(bts, &lst); err == nil { if err = json.Unmarshal(bts, &lst); err == nil {
...@@ -74,6 +134,49 @@ func (l *legacyAttributeDisjunction) UnmarshalJSON(bts []byte) error { ...@@ -74,6 +134,49 @@ func (l *legacyAttributeDisjunction) UnmarshalJSON(bts []byte) error {
return errors.New("Failed to unmarshal legacy attribute conjunction") return errors.New("Failed to unmarshal legacy attribute conjunction")
} }
func (l *LegacyDisjunction) MarshalJSON() ([]byte, error) {
hasvalues := false
for _, r := range *l {
if r.Value != nil {
hasvalues = true
break
}
}
var tmp interface{}
if hasvalues {
m := map[AttributeTypeIdentifier]*string{}
for _, r := range *l {
m[r.Type] = r.Value
}
tmp = m
} else {
var m []AttributeTypeIdentifier
for _, r := range *l {
m = append(m, r.Type)
}
tmp = m
}
return json.Marshal(tmp)
}
func (dr *DisclosureRequest) Legacy() (SessionRequest, error) {
disjunctions, err := convertConDisCon(dr.Disclose, dr.Labels)
if err != nil {
return nil, err
}
return &LegacyDisclosureRequest{
BaseRequest: BaseRequest{
Type: dr.Type,
Context: dr.Context,
Nonce: dr.Nonce,
ProtocolVersion: dr.ProtocolVersion,
},
Content: disjunctions,
}, nil
}
func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) { func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) {
var version int var version int
if version, err = parseVersion(bts); err != nil { if version, err = parseVersion(bts); err != nil {
...@@ -90,7 +193,7 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) { ...@@ -90,7 +193,7 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) {
return nil return nil
} }
var legacy legacyDisclosureRequest var legacy LegacyDisclosureRequest
if err = json.Unmarshal(bts, &legacy); err != nil { if err = json.Unmarshal(bts, &legacy); err != nil {
return err return err
} }
...@@ -101,6 +204,25 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) { ...@@ -101,6 +204,25 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) {
return checkType(legacy.Type, ActionDisclosing) return checkType(legacy.Type, ActionDisclosing)
} }
func (sr *SignatureRequest) Legacy() (SessionRequest, error) {
disjunctions, err := convertConDisCon(sr.Disclose, sr.Labels)
if err != nil {
return nil, err
}
return &LegacySignatureRequest{
Message: sr.Message,
LegacyDisclosureRequest: LegacyDisclosureRequest{
BaseRequest: BaseRequest{
Type: sr.Type,
Context: sr.Context,
Nonce: sr.Nonce,
ProtocolVersion: sr.ProtocolVersion,
},
Content: disjunctions,
},
}, nil
}
func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) { func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) {
var version int var version int
if version, err = parseVersion(bts); err != nil { if version, err = parseVersion(bts); err != nil {
...@@ -128,10 +250,7 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) { ...@@ -128,10 +250,7 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) {
return nil return nil
} }
var legacy struct { var legacy LegacySignatureRequest
legacyDisclosureRequest
Message string `json:"message"`
}
if err = json.Unmarshal(bts, &legacy); err != nil { if err = json.Unmarshal(bts, &legacy); err != nil {
return err return err
} }
...@@ -143,6 +262,23 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) { ...@@ -143,6 +262,23 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) {
return checkType(legacy.Type, ActionSigning) return checkType(legacy.Type, ActionSigning)
} }
func (ir *IssuanceRequest) Legacy() (SessionRequest, error) {
disjunctions, err := convertConDisCon(ir.Disclose, ir.Labels)
if err != nil {
return nil, err
}
return &LegacyIssuanceRequest{
BaseRequest: BaseRequest{
Type: ir.Type,
Context: ir.Context,
Nonce: ir.Nonce,
ProtocolVersion: ir.ProtocolVersion,
},
Credentials: ir.Credentials,
Disclose: disjunctions,
}, nil
}
func (ir *IssuanceRequest) UnmarshalJSON(bts []byte) (err error) { func (ir *IssuanceRequest) UnmarshalJSON(bts []byte) (err error) {
var version int var version int
if version, err = parseVersion(bts); err != nil { if version, err = parseVersion(bts); err != nil {
...@@ -166,11 +302,7 @@ func (ir *IssuanceRequest) UnmarshalJSON(bts []byte) (err error) { ...@@ -166,11 +302,7 @@ func (ir *IssuanceRequest) UnmarshalJSON(bts []byte) (err error) {
return nil return nil
} }
var legacy struct { var legacy LegacyIssuanceRequest
BaseRequest
Credentials []*CredentialRequest `json:"credentials"`
Disclose []attributeDisjunction `json:"disclose"`
}
if err = json.Unmarshal(bts, &legacy); err != nil { if err = json.Unmarshal(bts, &legacy); err != nil {
return err return err
} }
......
...@@ -20,7 +20,7 @@ type BaseRequest struct { ...@@ -20,7 +20,7 @@ type BaseRequest struct {
// Denotes session type, must be "disclosing", "signing" or "issuing" // Denotes session type, must be "disclosing", "signing" or "issuing"
Type Action `json:"type"` Type Action `json:"type"`
// Message version. Current version is 2. // Message version. Current version is 2.
Version int `json:"v"` Version int `json:"v,omitempty"`
// Chosen by the IRMA server during the session // Chosen by the IRMA server during the session
Context *big.Int `json:"context,omitempty"` Context *big.Int `json:"context,omitempty"`
...@@ -85,6 +85,7 @@ type SessionRequest interface { ...@@ -85,6 +85,7 @@ type SessionRequest interface {
Disclosure() *DisclosureRequest Disclosure() *DisclosureRequest
Identifiers() *IrmaIdentifierSet Identifiers() *IrmaIdentifierSet
Action() Action Action() Action
Legacy() (SessionRequest, error)
} }
// Timestamp is a time.Time that marshals to Unix timestamps. // Timestamp is a time.Time that marshals to Unix timestamps.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment