messages.go 6.31 KB
Newer Older
1
package irma
Sietse Ringers's avatar
Sietse Ringers committed
2
3
4
5

import (
	"encoding/base64"
	"encoding/json"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"math/big"
7
	"strconv"
Sietse Ringers's avatar
Sietse Ringers committed
8
	"strings"
Sietse Ringers's avatar
Sietse Ringers committed
9

10
11
12
13
	"bytes"

	"fmt"

Sietse Ringers's avatar
Sietse Ringers committed
14
	"github.com/go-errors/errors"
Sietse Ringers's avatar
Sietse Ringers committed
15
16
)

Sietse Ringers's avatar
Sietse Ringers committed
17
18
19
// Status encodes the status of an IRMA session (e.g., connected).
type Status string

20
21
22
23
24
25
26
27
28
29
30
31
32
33
// ProtocolVersion encodes the IRMA protocol version of an IRMA session.
type ProtocolVersion struct {
	major int
	minor int
}

func NewVersion(major, minor int) *ProtocolVersion {
	return &ProtocolVersion{major, minor}
}

func (v *ProtocolVersion) String() string {
	return fmt.Sprintf("%d.%d", v.major, v.minor)
}

Tomas's avatar
Tomas committed
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func (v *ProtocolVersion) UnmarshalJSON(b []byte) (err error) {
	var str string
	if err := json.Unmarshal(b, &str); err != nil {
		return err
	}
	parts := strings.Split(str, ".")
	if len(parts) != 2 {
		return errors.New("Invalid protocol version number: not of form x.y")
	}
	if v.major, err = strconv.Atoi(parts[0]); err != nil {
		return
	}
	v.minor, err = strconv.Atoi(parts[1])
	return
}

func (v *ProtocolVersion) MarshalJSON() ([]byte, error) {
	return json.Marshal(v.String())
}

54
55
56
57
58
59
60
// Returns true if v is below the given version.
func (v *ProtocolVersion) Below(major, minor int) bool {
	if v.major < major {
		return true
	}
	return v.major == major && v.minor < minor
}
Sietse Ringers's avatar
Sietse Ringers committed
61

Tomas's avatar
Tomas committed
62
63
64
65
66
67
68
69
70
// GetMetadataVersion maps a chosen protocol version to a metadata version that
// the server will use.
func GetMetadataVersion(v *ProtocolVersion) byte {
	if v.Below(2, 3) {
		return 0x02 // no support for optional attributes
	}
	return 0x03 // current version
}

Sietse Ringers's avatar
Sietse Ringers committed
71
72
73
// Action encodes the session type of an IRMA session (e.g., disclosing).
type Action string

Tomas's avatar
Tomas committed
74
75
// ErrorType are session errors.
type ErrorType string
Sietse Ringers's avatar
Sietse Ringers committed
76

Tomas's avatar
Tomas committed
77
78
// SessionError is a protocol error.
type SessionError struct {
Sietse Ringers's avatar
Sietse Ringers committed
79
	Err error
Tomas's avatar
Tomas committed
80
	ErrorType
Tomas's avatar
Tomas committed
81
82
83
	Info         string
	RemoteError  *RemoteError
	RemoteStatus int
Sietse Ringers's avatar
Sietse Ringers committed
84
85
}

Tomas's avatar
Tomas committed
86
87
// RemoteError is an error message returned by the API server on errors.
type RemoteError struct {
Sietse Ringers's avatar
Sietse Ringers committed
88
89
90
91
92
93
94
	Status      int    `json:"status"`
	ErrorName   string `json:"error"`
	Description string `json:"description"`
	Message     string `json:"message"`
	Stacktrace  string `json:"stacktrace"`
}

Tomas's avatar
Tomas committed
95
func (err *RemoteError) Error() string {
96
97
98
99
100
101
102
	var msg string
	if err.Message != "" {
		msg = fmt.Sprintf(" (%s)", err.Message)
	}
	return fmt.Sprintf("%s%s: %s", err.ErrorName, msg, err.Description)
}

Sietse Ringers's avatar
Sietse Ringers committed
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// Qr contains the data of an IRMA session QR (as generated by irma_js),
// suitable for NewSession().
type Qr struct {
	// Server with which to perform the session
	URL string `json:"u"`
	// Session type (disclosing, signing, issuing)
	Type               Action `json:"irmaqr"`
	ProtocolVersion    string `json:"v"`
	ProtocolMaxVersion string `json:"vmax"`
}

// A SessionInfo is the first message in the IRMA protocol (i.e., GET on the server URL),
// containing the session request info.
type SessionInfo struct {
	Jwt     string                   `json:"jwt"`
	Nonce   *big.Int                 `json:"nonce"`
	Context *big.Int                 `json:"context"`
	Keys    map[IssuerIdentifier]int `json:"keys"`
}

// Statuses
const (
	StatusConnected     = Status("connected")
	StatusCommunicating = Status("communicating")
127
	StatusManualStarted = Status("manualStarted")
Sietse Ringers's avatar
Sietse Ringers committed
128
129
)

Sietse Ringers's avatar
Sietse Ringers committed
130
131
// Actions
const (
132
133
134
135
136
	ActionSchemeManager = Action("schememanager")
	ActionDisclosing    = Action("disclosing")
	ActionSigning       = Action("signing")
	ActionIssuing       = Action("issuing")
	ActionUnknown       = Action("unknown")
Sietse Ringers's avatar
Sietse Ringers committed
137
138
139
140
141
)

// Protocol errors
const (
	// Protocol version not supported
Tomas's avatar
Tomas committed
142
	ErrorProtocolVersionNotSupported = ErrorType("protocolVersionNotSupported")
Sietse Ringers's avatar
Sietse Ringers committed
143
	// Error in HTTP communication
Tomas's avatar
Tomas committed
144
	ErrorTransport = ErrorType("transport")
Sietse Ringers's avatar
Sietse Ringers committed
145
	// Invalid client JWT in first IRMA message
Tomas's avatar
Tomas committed
146
	ErrorInvalidJWT = ErrorType("invalidJwt")
Sietse Ringers's avatar
Sietse Ringers committed
147
	// Unkown session type (not disclosing, signing, or issuing)
Tomas's avatar
Tomas committed
148
	ErrorUnknownAction = ErrorType("unknownAction")
Sietse Ringers's avatar
Sietse Ringers committed
149
	// Crypto error during calculation of our response (second IRMA message)
Tomas's avatar
Tomas committed
150
	ErrorCrypto = ErrorType("crypto")
Sietse Ringers's avatar
Sietse Ringers committed
151
	// Server rejected our response (second IRMA message)
Tomas's avatar
Tomas committed
152
	ErrorRejected = ErrorType("rejected")
Sietse Ringers's avatar
Sietse Ringers committed
153
	// (De)serializing of a message failed
Tomas's avatar
Tomas committed
154
	ErrorSerialization = ErrorType("serialization")
Sietse Ringers's avatar
Sietse Ringers committed
155
	// Error in keyshare protocol
Tomas's avatar
Tomas committed
156
	ErrorKeyshare = ErrorType("keyshare")
157
	// API server error
Tomas's avatar
Tomas committed
158
	ErrorApi = ErrorType("api")
159
	// Server returned unexpected or malformed response
Tomas's avatar
Tomas committed
160
	ErrorServerResponse = ErrorType("serverResponse")
161
	// Credential type not present in our Configuration
162
	ErrorUnknownCredentialType = ErrorType("unknownCredentialType")
163
	// Error during downloading of credential type, issuer, or public keys
164
	ErrorConfigurationDownload = ErrorType("configurationDownload")
165
166
	// IRMA requests refers to unknown scheme manager
	ErrorUnknownSchemeManager = ErrorType("unknownSchemeManager")
167
168
	// A session is requested involving a scheme manager that has some problem
	ErrorInvalidSchemeManager = ErrorType("invalidSchemeManager")
169
170
	// Recovered panic
	ErrorPanic = ErrorType("panic")
Sietse Ringers's avatar
Sietse Ringers committed
171
172
)

Tomas's avatar
Tomas committed
173
func (e *SessionError) Error() string {
174
175
176
177
178
179
180
181
	var buffer bytes.Buffer
	typ := e.ErrorType
	if typ == "" {
		typ = ErrorType("unknown")
	}

	buffer.WriteString("Error type: ")
	buffer.WriteString(string(typ))
Sietse Ringers's avatar
Sietse Ringers committed
182
	if e.Err != nil {
183
184
		buffer.WriteString("\nDescription: ")
		buffer.WriteString(e.Err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
185
	}
Tomas's avatar
Tomas committed
186
187
	if e.RemoteStatus != 200 {
		buffer.WriteString("\nStatus code: ")
188
		buffer.WriteString(strconv.Itoa(e.RemoteStatus))
189
	}
Tomas's avatar
Tomas committed
190
	if e.RemoteError != nil {
191
		buffer.WriteString("\nIRMA server error: ")
Tomas's avatar
Tomas committed
192
		buffer.WriteString(e.RemoteError.Error())
193
194
195
	}

	return buffer.String()
Sietse Ringers's avatar
Sietse Ringers committed
196
197
}

Tomas's avatar
Tomas committed
198
199
200
201
202
203
204
205
func (e *SessionError) WrappedError() string {
	if e.Err == nil {
		return ""
	}

	return e.Err.Error()
}

206
207
208
209
210
211
212
213
func (e *SessionError) Stack() string {
	if withStack, ok := e.Err.(*errors.Error); ok {
		return string(withStack.Stack())
	}

	return ""
}

214
func JwtDecode(jwt string, body interface{}) error {
Sietse Ringers's avatar
Sietse Ringers committed
215
216
	jwtparts := strings.Split(jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
217
		return errors.New("Not a JWT")
Sietse Ringers's avatar
Sietse Ringers committed
218
219
220
	}
	bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
	if err != nil {
221
		return err
Sietse Ringers's avatar
Sietse Ringers committed
222
	}
223
	return json.Unmarshal(bodybytes, body)
Sietse Ringers's avatar
Sietse Ringers committed
224
}
Sietse Ringers's avatar
Sietse Ringers committed
225

226
func ParseRequestorJwt(action Action, jwt string) (RequestorJwt, error) {
Sietse Ringers's avatar
Sietse Ringers committed
227
228
229
230
231
232
233
234
235
	var retval RequestorJwt
	switch action {
	case ActionDisclosing:
		retval = &ServiceProviderJwt{}
	case ActionSigning:
		retval = &SignatureRequestorJwt{}
	case ActionIssuing:
		retval = &IdentityProviderJwt{}
	default:
236
		return nil, errors.New("Invalid session type")
Sietse Ringers's avatar
Sietse Ringers committed
237
	}
238
	err := JwtDecode(jwt, retval)
Sietse Ringers's avatar
Sietse Ringers committed
239
	if err != nil {
240
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
241
	}
242
	return retval, nil
Sietse Ringers's avatar
Sietse Ringers committed
243
}