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

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

9
10
11
12
	"bytes"

	"fmt"

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

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

19
20
// ProtocolVersion encodes the IRMA protocol version of an IRMA session.
type ProtocolVersion struct {
21
22
	Major int
	Minor int
23
24
25
26
27
28
29
}

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

func (v *ProtocolVersion) String() string {
30
	return fmt.Sprintf("%d.%d", v.Major, v.Minor)
31
32
}

Tomas's avatar
Tomas committed
33
34
35
36
37
38
39
40
41
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")
	}
42
	if v.Major, err = strconv.Atoi(parts[0]); err != nil {
Tomas's avatar
Tomas committed
43
44
		return
	}
45
	v.Minor, err = strconv.Atoi(parts[1])
Tomas's avatar
Tomas committed
46
47
48
49
50
51
52
	return
}

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

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

Tomas's avatar
Tomas committed
61
62
63
64
65
66
67
68
69
// 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
70
71
72
// Action encodes the session type of an IRMA session (e.g., disclosing).
type Action string

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

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

Tomas's avatar
Tomas committed
85
86
// RemoteError is an error message returned by the API server on errors.
type RemoteError struct {
Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
91
92
93
	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
94
func (err *RemoteError) Error() string {
95
96
97
98
99
100
101
	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
102
103
104
105
106
107
// 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)
108
109
110
	Type               Action          `json:"irmaqr"`
	ProtocolVersion    ProtocolVersion `json:"v"`
	ProtocolMaxVersion ProtocolVersion `json:"vmax"`
Sietse Ringers's avatar
Sietse Ringers committed
111
112
113
114
115
116
}

// Statuses
const (
	StatusConnected     = Status("connected")
	StatusCommunicating = Status("communicating")
117
	StatusManualStarted = Status("manualStarted")
Sietse Ringers's avatar
Sietse Ringers committed
118
119
)

Sietse Ringers's avatar
Sietse Ringers committed
120
121
// Actions
const (
122
123
124
125
126
	ActionSchemeManager = Action("schememanager")
	ActionDisclosing    = Action("disclosing")
	ActionSigning       = Action("signing")
	ActionIssuing       = Action("issuing")
	ActionUnknown       = Action("unknown")
Sietse Ringers's avatar
Sietse Ringers committed
127
128
129
130
131
)

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

Tomas's avatar
Tomas committed
163
func (e *SessionError) Error() string {
164
165
166
167
168
169
170
171
	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
172
	if e.Err != nil {
173
174
		buffer.WriteString("\nDescription: ")
		buffer.WriteString(e.Err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
175
	}
Tomas's avatar
Tomas committed
176
177
	if e.RemoteStatus != 200 {
		buffer.WriteString("\nStatus code: ")
178
		buffer.WriteString(strconv.Itoa(e.RemoteStatus))
179
	}
Tomas's avatar
Tomas committed
180
	if e.RemoteError != nil {
181
		buffer.WriteString("\nIRMA server error: ")
Tomas's avatar
Tomas committed
182
		buffer.WriteString(e.RemoteError.Error())
183
184
185
	}

	return buffer.String()
Sietse Ringers's avatar
Sietse Ringers committed
186
187
}

Tomas's avatar
Tomas committed
188
189
190
191
192
193
194
195
func (e *SessionError) WrappedError() string {
	if e.Err == nil {
		return ""
	}

	return e.Err.Error()
}

196
197
198
199
200
201
202
203
func (e *SessionError) Stack() string {
	if withStack, ok := e.Err.(*errors.Error); ok {
		return string(withStack.Stack())
	}

	return ""
}

204
func JwtDecode(jwt string, body interface{}) error {
Sietse Ringers's avatar
Sietse Ringers committed
205
206
	jwtparts := strings.Split(jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
207
		return errors.New("Not a JWT")
Sietse Ringers's avatar
Sietse Ringers committed
208
209
210
	}
	bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
	if err != nil {
211
		return err
Sietse Ringers's avatar
Sietse Ringers committed
212
	}
213
	return json.Unmarshal(bodybytes, body)
Sietse Ringers's avatar
Sietse Ringers committed
214
}
Sietse Ringers's avatar
Sietse Ringers committed
215

216
func ParseRequestorJwt(action Action, jwt string) (RequestorJwt, error) {
Sietse Ringers's avatar
Sietse Ringers committed
217
218
219
220
221
222
223
224
225
	var retval RequestorJwt
	switch action {
	case ActionDisclosing:
		retval = &ServiceProviderJwt{}
	case ActionSigning:
		retval = &SignatureRequestorJwt{}
	case ActionIssuing:
		retval = &IdentityProviderJwt{}
	default:
226
		return nil, errors.New("Invalid session type")
Sietse Ringers's avatar
Sietse Ringers committed
227
	}
228
	err := JwtDecode(jwt, retval)
Sietse Ringers's avatar
Sietse Ringers committed
229
	if err != nil {
230
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
231
	}
232
	return retval, nil
Sietse Ringers's avatar
Sietse Ringers committed
233
}