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
}
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
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, "")
}
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.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
}
......
......@@ -129,14 +129,20 @@ func (session *session) eventSource() eventsource.EventSource {
// Other
func chooseProtocolVersion(min, max *irma.ProtocolVersion) (*irma.ProtocolVersion, error) {
if min.AboveVersion(maxProtocolVersion) || max.BelowVersion(minProtocolVersion) || max.BelowVersion(min) {
return nil, server.LogWarning(errors.Errorf("Protocol version negotiation failed, min=%s max=%s", min.String(), max.String()))
func (session *session) chooseProtocolVersion(minClient, maxClient *irma.ProtocolVersion) (*irma.ProtocolVersion, error) {
// Set our minimum supported version to 2.5 if condiscon compatibility is required
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
} else {
return max, nil
return maxClient, nil
}
}
......
......@@ -22,6 +22,7 @@ type session struct {
version *irma.ProtocolVersion
rrequest irma.RequestorRequest
request irma.SessionRequest
legacyCompatible bool // if the request is convertible to pre-condiscon format
status server.Status
prevStatus server.Status
......@@ -60,7 +61,7 @@ const (
var (
minProtocolVersion = irma.NewVersion(2, 4)
maxProtocolVersion = irma.NewVersion(2, 4)
maxProtocolVersion = irma.NewVersion(2, 5)
)
func (s *memorySessionStore) get(t string) *session {
......
......@@ -92,9 +92,12 @@ type session struct {
// We implement the handler for the keyshare protocol
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{
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 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
Handler: handler,
client: client,
}
session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
min := minVersion
// Check if the action is one of the supported types
switch session.Action {
......@@ -178,6 +183,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
session.request = &irma.DisclosureRequest{}
case irma.ActionSigning:
session.request = &irma.SignatureRequest{}
min = &irma.ProtocolVersion{2, 5} // New ABS format is not backwards compatible with old irma server
case irma.ActionIssuing:
session.request = &irma.IssuanceRequest{}
case irma.ActionUnknown:
......@@ -187,7 +193,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
return nil
}
session.transport.SetHeader(irma.MinVersionHeader, minVersion.String())
session.transport.SetHeader(irma.MinVersionHeader, min.String())
session.transport.SetHeader(irma.MaxVersionHeader, maxVersion.String())
if !strings.HasSuffix(session.ServerURL, "/") {
session.ServerURL += "/"
......
......@@ -462,6 +462,9 @@ func TestSessionRequests(t *testing.T) {
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.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 (
"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 {
Label string
Attributes legacyAttributeDisjunction
// LegacyDisjunction is a disjunction of attributes from before the condiscon feature,
// representing a list of attribute types one of which must be given by the user,
// 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
Content []attributeDisjunction `json:"content"`
Content []LegacyLabeledDisjunction `json:"content"`
}
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 []attributeDisjunction) (
func convertDisjunctions(disjunctions []LegacyLabeledDisjunction) (
condiscon AttributeConDisCon, labels map[int]TranslatedString,
) {
labels = make(map[int]TranslatedString)
......@@ -52,8 +113,7 @@ func checkType(typ, expected Action) error {
return nil
}
// Reuses AttributeCon.UnmarshalJSON()
func (l *legacyAttributeDisjunction) UnmarshalJSON(bts []byte) error {
func (l *LegacyDisjunction) UnmarshalJSON(bts []byte) error {
var err error
var lst []AttributeTypeIdentifier
if err = json.Unmarshal(bts, &lst); err == nil {
......@@ -74,6 +134,49 @@ func (l *legacyAttributeDisjunction) UnmarshalJSON(bts []byte) error {
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) {
var version int
if version, err = parseVersion(bts); err != nil {
......@@ -90,7 +193,7 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) {
return nil
}
var legacy legacyDisclosureRequest
var legacy LegacyDisclosureRequest
if err = json.Unmarshal(bts, &legacy); err != nil {
return err
}
......@@ -101,6 +204,25 @@ func (dr *DisclosureRequest) UnmarshalJSON(bts []byte) (err error) {
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) {
var version int
if version, err = parseVersion(bts); err != nil {
......@@ -128,10 +250,7 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) {
return nil
}
var legacy struct {
legacyDisclosureRequest
Message string `json:"message"`
}
var legacy LegacySignatureRequest
if err = json.Unmarshal(bts, &legacy); err != nil {
return err
}
......@@ -143,6 +262,23 @@ func (sr *SignatureRequest) UnmarshalJSON(bts []byte) (err error) {
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) {
var version int
if version, err = parseVersion(bts); err != nil {
......@@ -166,11 +302,7 @@ func (ir *IssuanceRequest) UnmarshalJSON(bts []byte) (err error) {
return nil
}
var legacy struct {
BaseRequest
Credentials []*CredentialRequest `json:"credentials"`
Disclose []attributeDisjunction `json:"disclose"`
}
var legacy LegacyIssuanceRequest
if err = json.Unmarshal(bts, &legacy); err != nil {
return err
}
......
......@@ -20,7 +20,7 @@ type BaseRequest struct {
// Denotes session type, must be "disclosing", "signing" or "issuing"
Type Action `json:"type"`
// Message version. Current version is 2.
Version int `json:"v"`
Version int `json:"v,omitempty"`
// Chosen by the IRMA server during the session
Context *big.Int `json:"context,omitempty"`
......@@ -85,6 +85,7 @@ type SessionRequest interface {
Disclosure() *DisclosureRequest
Identifiers() *IrmaIdentifierSet
Action() Action
Legacy() (SessionRequest, error)
}
// 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