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

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

	"github.com/credentials/irmago"
10
	"github.com/mhe/gabi"
11
12
)

13
type PermissionHandler func(proceed bool, choice *irmago.DisclosureChoice)
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

// 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
36
37
38
	spRequest *ServiceProviderJwt
	ipRequest *IdentityProviderJwt
	ssRequest *SignatureServerJwt
39

40
41
42
43
44
45
	transport *HTTPTransport
	nonce     *big.Int
	context   *big.Int
}

// NewSession creates and starts a new IRMA session.
46
func NewSession(qr *Qr, handler Handler) *Session {
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
98
99
100
101
102
103
	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"`
	}
104
105
	json.Unmarshal([]byte(jwtparts[0]), &header)
	json.Unmarshal([]byte(jwtparts[1]), session.request)
106
107
108

	switch session.Action {
	case ActionDisclosing:
109
		session.spRequest = &ServiceProviderJwt{}
110
		session.request = session.spRequest
111
	case ActionSigning:
112
		session.ssRequest = &SignatureServerJwt{}
113
		session.request = session.ssRequest
114
	case ActionIssuing:
115
		session.ipRequest = &IdentityProviderJwt{}
116
		session.request = session.ipRequest
117
118
119
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
120

121
122
	if session.Action == ActionIssuing {
		// Store which public keys the server will use
123
		for _, credreq := range session.request.(*IdentityProviderJwt).Request.Request.Credentials {
124
125
126
127
128
129
130
131
132
133
			credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
		}
	}

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

134
	callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
135
136
137
138
139
140
		go session.do(proceed, choice)
	})

	session.Handler.StatusUpdate(session.Action, StatusConnected)
	switch session.Action {
	case ActionDisclosing:
141
		session.Handler.AskVerificationPermission(session.spRequest.Request.Request, header.Server, callback)
142
	case ActionSigning:
143
		session.Handler.AskSignaturePermission(session.ssRequest.Request.Request, header.Server, callback)
144
	case ActionIssuing:
145
		session.Handler.AskIssuancePermission(session.ipRequest.Request.Request, header.Server, callback)
146
147
148
149
150
	default:
		panic("Invalid session type") // does not happen, session.Action has been checked earlier
	}
}

151
152
153
154
155
156
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
	if !proceed {
		session.Handler.Cancelled(session.Action)
		return
	}
	session.Handler.StatusUpdate(session.Action, StatusCommunicating)
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180

	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)
181
}