session.go 7.23 KB
Newer Older
1
2
3
package protocol

import (
4
	"errors"
5
	"math/big"
6
	"strconv"
7
8
	"strings"

9
10
11
12
	"sort"

	"fmt"

13
14
15
16
	"encoding/json"

	"encoding/base64"

17
	"github.com/credentials/irmago"
18
	"github.com/mhe/gabi"
19
20
)

21
type PermissionHandler func(proceed bool, choice *irmago.DisclosureChoice)
22
23
24
25
26
27

// A Handler contains callbacks for communication to the user.
type Handler interface {
	StatusUpdate(action Action, status Status)
	Success(action Action)
	Cancelled(action Action)
28
	Failure(action Action, err *Error)
29
30
	UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)

31
32
33
	AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, choice PermissionHandler)
	AskVerificationPermission(request irmago.DisclosureRequest, ServerName string, choice PermissionHandler)
	AskSignaturePermission(request irmago.SignatureRequest, ServerName string, choice PermissionHandler)
34
35
36
37
38
39
40
41
42
43
}

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

	request   irmago.DisjunctionListContainer
44
45
46
	spRequest *ServiceProviderJwt
	ipRequest *IdentityProviderJwt
	ssRequest *SignatureServerJwt
47

48
49
50
51
52
	transport *HTTPTransport
	nonce     *big.Int
	context   *big.Int
}

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
83
84
85
// 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)
}

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

	session := &Session{
95
		Version:   Version(version),
96
97
98
99
100
101
102
103
104
105
106
107
108
109
		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:
110
		handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, error: nil, info: string(session.Action)})
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
		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 {
132
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr})
133
134
135
136
137
138
139
		return
	}

	session.nonce = info.Nonce
	session.context = info.Context
	jwtparts := strings.Split(info.Jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
140
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
141
142
		return
	}
143
144
145
146
147
148
149
150

	headerbytes, err := base64.RawStdEncoding.DecodeString(jwtparts[0])
	bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
	if err != nil {
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
		return
	}

151
152
153
	var header struct {
		Server string `json:"iss"`
	}
154
	json.Unmarshal([]byte(headerbytes), &header)
155
156
157

	switch session.Action {
	case ActionDisclosing:
158
		session.spRequest = &ServiceProviderJwt{}
159
160
161
		json.Unmarshal([]byte(bodybytes), session.spRequest)
		session.spRequest.Request.Request.Context = session.context
		session.spRequest.Request.Request.Nonce = session.nonce
162
		session.request = session.spRequest
163
	case ActionSigning:
164
		session.ssRequest = &SignatureServerJwt{}
165
166
167
		json.Unmarshal([]byte(bodybytes), session.ssRequest)
		session.ssRequest.Request.Request.Context = session.context
		session.ssRequest.Request.Request.Nonce = session.nonce
168
		session.request = session.ssRequest
169
	case ActionIssuing:
170
		session.ipRequest = &IdentityProviderJwt{}
171
172
173
		json.Unmarshal([]byte(bodybytes), session.ipRequest)
		session.ipRequest.Request.Request.Context = session.context
		session.ipRequest.Request.Request.Nonce = session.nonce
174
		session.request = session.ipRequest
175
176
177
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
178

179
180
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
181
		for _, credreq := range session.ipRequest.Request.Request.Credentials {
182
183
184
185
186
187
188
189
190
191
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

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

192
	callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
193
194
195
196
197
198
		go session.do(proceed, choice)
	})

	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
199
		session.Handler.AskVerificationPermission(session.spRequest.Request.Request, header.Server, callback)
200
	case ActionSigning:
201
		session.Handler.AskSignaturePermission(session.ssRequest.Request.Request, header.Server, callback)
202
	case ActionIssuing:
203
		session.Handler.AskIssuancePermission(session.ipRequest.Request.Request, header.Server, callback)
204
205
206
207
208
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

209
210
211
212
213
214
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
215
216
217
218
219

	var proofs gabi.ProofList
	var err error
	switch session.Action {
	case ActionSigning:
220
		proofs, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request, true)
221
	case ActionDisclosing:
222
		proofs, err = irmago.Manager.Proofs(choice, &session.spRequest.Request.Request, false)
223
224
225
226
	case ActionIssuing:
		err = errors.New("Issuing not yet implemented")
	}
	if err != nil {
227
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
228
229
230
231
232
233
		return
	}

	var response string
	session.transport.Post("proofs", &response, proofs)
	if response != "VALID" {
234
		session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
235
236
237
238
		return
	}

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