session.go 4.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
package protocol

import (
	"encoding/json"
	"math/big"
	"strings"

	"github.com/credentials/irmago"
)

11
type PermissionHandler func(proceed bool, choice *irmago.DisclosureChoice)
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

// 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
	transport *HTTPTransport
	nonce     *big.Int
	context   *big.Int
}

// NewSession creates and starts a new IRMA session.
func NewSession(qr Qr, handler Handler) *Session {
	if qr.ProtocolVersion != "2.1" && qr.ProtocolVersion != "2.2" { // TODO version negotiation
		handler.Failure(ActionUnknown, ErrorProtocolVersionNotSupported, qr.ProtocolVersion)
		return nil
	}

	session := &Session{
		Version:   Version(qr.ProtocolVersion),
		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"`
	}
98
99
	json.Unmarshal([]byte(jwtparts[0]), &header)
	json.Unmarshal([]byte(jwtparts[1]), session.request)
100
101
102
103
104
105
106
107
108
109
110

	switch session.Action {
	case ActionDisclosing:
		session.request = &ServiceProviderRequest{}
	case ActionSigning:
		session.request = &SignatureServerRequest{}
	case ActionIssuing:
		session.request = &IdentityProviderRequest{}
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
111

112
113
114
115
116
117
118
119
120
121
122
123
124
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
		for _, credreq := range session.request.(*IdentityProviderRequest).Request.Request.Credentials {
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

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

125
	callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
		go session.do(proceed, choice)
	})

	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
		session.Handler.AskVerificationPermission(session.request.(*ServiceProviderRequest).Request.Request, header.Server, callback)
	case ActionSigning:
		session.Handler.AskSignaturePermission(session.request.(*SignatureServerRequest).Request.Request, header.Server, callback)
	case ActionIssuing:
		session.Handler.AskIssuancePermission(session.request.(*IdentityProviderRequest).Request.Request, header.Server, callback)
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

142
143
144
145
146
147
148
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}

	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
149
}