session.go 6.34 KB
Newer Older
1
2
3
4
package protocol

import (
	"encoding/json"
5
	"errors"
6
	"math/big"
7
	"strconv"
8
9
	"strings"

10
11
12
13
	"sort"

	"fmt"

14
	"github.com/credentials/irmago"
15
	"github.com/mhe/gabi"
16
17
)

18
type PermissionHandler func(proceed bool, choice *irmago.DisclosureChoice)
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

// A Handler contains callbacks for communication to the user.
type Handler interface {
	StatusUpdate(action Action, status Status)
	Success(action Action)
	Cancelled(action Action)
	Failure(action Action, error SessionError, info string)
	UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)

	AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler)
	AskVerificationPermission(request DisclosureRequest, ServerName string, choice PermissionHandler)
	AskSignaturePermission(request SignatureRequest, ServerName string, choice PermissionHandler)
}

// A Session is an IRMA session.
type Session struct {
	Action    Action
	Version   Version
	ServerURL string
	Handler   Handler

	request   irmago.DisjunctionListContainer
41
42
43
	spRequest *ServiceProviderJwt
	ipRequest *IdentityProviderJwt
	ssRequest *SignatureServerJwt
44

45
46
47
48
49
	transport *HTTPTransport
	nonce     *big.Int
	context   *big.Int
}

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
	2: []int{2, 1},
}

func calcVersion(qr *Qr) (string, error) {
	// Parse range supported by server
	minmajor, err := strconv.Atoi(string(qr.ProtocolVersion[0]))
	minminor, err := strconv.Atoi(string(qr.ProtocolVersion[2]))
	maxmajor, err := strconv.Atoi(string(qr.ProtocolMaxVersion[0]))
	maxminor, err := strconv.Atoi(string(qr.ProtocolMaxVersion[2]))
	if err != nil {
		return "", err
	}

	// Iterate supportedVersions in reverse sorted order (i.e. biggest major number first)
	keys := make([]int, 0, len(supportedVersions))
	for k, _ := range supportedVersions {
		keys = append(keys, k)
	}
	sort.Sort(sort.Reverse(sort.IntSlice(keys)))
	for _, major := range keys {
		for _, minor := range supportedVersions[major] {
			aboveMinimum := major > minmajor || (major == minmajor && minor >= minminor)
			underMaximum := major < maxmajor || (major == maxmajor && minor <= maxminor)
			if aboveMinimum && underMaximum {
				return fmt.Sprintf("%d.%d", major, minor), nil
			}
		}
	}
	return "", fmt.Errorf("No supported protocol version between %s and %s", qr.ProtocolVersion, qr.ProtocolMaxVersion)
}

83
// NewSession creates and starts a new IRMA session.
84
func NewSession(qr *Qr, handler Handler) *Session {
85
86
87
	version, err := calcVersion(qr)
	if err != nil {
		handler.Failure(ActionUnknown, ErrorProtocolVersionNotSupported, err.Error())
88
89
90
91
		return nil
	}

	session := &Session{
92
		Version:   Version(version),
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
		Action:    Action(qr.Type),
		ServerURL: qr.URL,
		Handler:   handler,
		transport: NewHTTPTransport(qr.URL),
	}

	// Check if the action is one of the supported types
	switch session.Action {
	case ActionDisclosing: // nop
	case ActionSigning: // nop
	case ActionIssuing: // nop
	case ActionUnknown:
		fallthrough
	default:
		handler.Failure(ActionUnknown, ErrorUnknownAction, string(session.Action))
		return nil
	}

	if !strings.HasSuffix(session.ServerURL, "/") {
		session.ServerURL += "/"
	}

	go session.start()

	return session
}

// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
func (session *Session) start() {
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)

	// Get the first IRMA protocol message
	info := &SessionInfo{}
	err := session.transport.Get("jwt", info)
	if err != nil {
		session.Handler.Failure(session.Action, ErrorTransport, err.Error())
		return
	}

	session.nonce = info.Nonce
	session.context = info.Context
	jwtparts := strings.Split(info.Jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
		session.Handler.Failure(session.Action, ErrorInvalidJWT, "")
		return
	}
	var header struct {
		Server string `json:"iss"`
	}
143
144
	json.Unmarshal([]byte(jwtparts[0]), &header)
	json.Unmarshal([]byte(jwtparts[1]), session.request)
145
146
147

	switch session.Action {
	case ActionDisclosing:
148
		session.spRequest = &ServiceProviderJwt{}
149
		session.request = session.spRequest
150
	case ActionSigning:
151
		session.ssRequest = &SignatureServerJwt{}
152
		session.request = session.ssRequest
153
	case ActionIssuing:
154
		session.ipRequest = &IdentityProviderJwt{}
155
		session.request = session.ipRequest
156
157
158
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
159

160
161
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
162
		for _, credreq := range session.request.(*IdentityProviderJwt).Request.Request.Credentials {
163
164
165
166
167
168
169
170
171
172
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

	missing := irmago.Manager.CheckSatisfiability(session.request)
	if len(missing) > 0 {
		session.Handler.UnsatisfiableRequest(session.Action, missing)
		return
	}

173
	callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
174
175
176
177
178
179
		go session.do(proceed, choice)
	})

	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
180
		session.Handler.AskVerificationPermission(session.spRequest.Request.Request, header.Server, callback)
181
	case ActionSigning:
182
		session.Handler.AskSignaturePermission(session.ssRequest.Request.Request, header.Server, callback)
183
	case ActionIssuing:
184
		session.Handler.AskIssuancePermission(session.ipRequest.Request.Request, header.Server, callback)
185
186
187
188
189
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

190
191
192
193
194
195
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219

	var proofs gabi.ProofList
	var err error
	switch session.Action {
	case ActionSigning:
		proofs, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request.Message)
	case ActionDisclosing:
		proofs, err = irmago.Manager.Proofs(choice, nil)
	case ActionIssuing:
		err = errors.New("Issuing not yet implemented")
	}
	if err != nil {
		session.Handler.Failure(session.Action, ErrorCrypto, err.Error())
		return
	}

	var response string
	session.transport.Post("proofs", &response, proofs)
	if response != "VALID" {
		session.Handler.Failure(session.Action, ErrorRejected, response)
		return
	}

	session.Handler.Success(session.Action)
220
}