messages.go 5.46 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"
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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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)
}

// 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
40

Sietse Ringers's avatar
Sietse Ringers committed
41
42
43
// Action encodes the session type of an IRMA session (e.g., disclosing).
type Action string

Tomas's avatar
Tomas committed
44
45
// ErrorType are session errors.
type ErrorType string
Sietse Ringers's avatar
Sietse Ringers committed
46

Tomas's avatar
Tomas committed
47
48
// SessionError is a protocol error.
type SessionError struct {
Sietse Ringers's avatar
Sietse Ringers committed
49
	Err error
Tomas's avatar
Tomas committed
50
	ErrorType
Sietse Ringers's avatar
Sietse Ringers committed
51
	*ApiError
52
53
54
	Info          string
	Status        int
	StatusMessage string
Sietse Ringers's avatar
Sietse Ringers committed
55
56
57
58
59
60
61
62
63
64
65
}

// ApiError is an error message returned by the API server on errors.
type ApiError struct {
	Status      int    `json:"status"`
	ErrorName   string `json:"error"`
	Description string `json:"description"`
	Message     string `json:"message"`
	Stacktrace  string `json:"stacktrace"`
}

66
67
68
69
70
71
72
73
func (err *ApiError) Error() string {
	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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// 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")
98
	StatusManualStarted = Status("manualStarted")
Sietse Ringers's avatar
Sietse Ringers committed
99
100
)

Sietse Ringers's avatar
Sietse Ringers committed
101
102
// Actions
const (
103
104
105
106
107
	ActionSchemeManager = Action("schememanager")
	ActionDisclosing    = Action("disclosing")
	ActionSigning       = Action("signing")
	ActionIssuing       = Action("issuing")
	ActionUnknown       = Action("unknown")
Sietse Ringers's avatar
Sietse Ringers committed
108
109
110
111
112
)

// Protocol errors
const (
	// Protocol version not supported
Tomas's avatar
Tomas committed
113
	ErrorProtocolVersionNotSupported = ErrorType("protocolVersionNotSupported")
Sietse Ringers's avatar
Sietse Ringers committed
114
	// Error in HTTP communication
Tomas's avatar
Tomas committed
115
	ErrorTransport = ErrorType("transport")
Sietse Ringers's avatar
Sietse Ringers committed
116
	// Invalid client JWT in first IRMA message
Tomas's avatar
Tomas committed
117
	ErrorInvalidJWT = ErrorType("invalidJwt")
Sietse Ringers's avatar
Sietse Ringers committed
118
	// Unkown session type (not disclosing, signing, or issuing)
Tomas's avatar
Tomas committed
119
	ErrorUnknownAction = ErrorType("unknownAction")
Sietse Ringers's avatar
Sietse Ringers committed
120
	// Crypto error during calculation of our response (second IRMA message)
Tomas's avatar
Tomas committed
121
	ErrorCrypto = ErrorType("crypto")
Sietse Ringers's avatar
Sietse Ringers committed
122
	// Server rejected our response (second IRMA message)
Tomas's avatar
Tomas committed
123
	ErrorRejected = ErrorType("rejected")
Sietse Ringers's avatar
Sietse Ringers committed
124
	// (De)serializing of a message failed
Tomas's avatar
Tomas committed
125
	ErrorSerialization = ErrorType("serialization")
Sietse Ringers's avatar
Sietse Ringers committed
126
	// Error in keyshare protocol
Tomas's avatar
Tomas committed
127
	ErrorKeyshare = ErrorType("keyshare")
128
	// API server error
Tomas's avatar
Tomas committed
129
	ErrorApi = ErrorType("api")
130
	// Server returned unexpected or malformed response
Tomas's avatar
Tomas committed
131
	ErrorServerResponse = ErrorType("serverResponse")
132
	// Credential type not present in our Configuration
133
	ErrorUnknownCredentialType = ErrorType("unknownCredentialType")
134
	// Error during downloading of credential type, issuer, or public keys
135
	ErrorConfigurationDownload = ErrorType("configurationDownload")
136
137
	// IRMA requests refers to unknown scheme manager
	ErrorUnknownSchemeManager = ErrorType("unknownSchemeManager")
138
139
	// A session is requested involving a scheme manager that has some problem
	ErrorInvalidSchemeManager = ErrorType("invalidSchemeManager")
140
141
	// Recovered panic
	ErrorPanic = ErrorType("panic")
Sietse Ringers's avatar
Sietse Ringers committed
142
143
)

Tomas's avatar
Tomas committed
144
func (e *SessionError) Error() string {
145
146
147
148
149
150
151
152
	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
153
	if e.Err != nil {
154
155
		buffer.WriteString("\nDescription: ")
		buffer.WriteString(e.Err.Error())
Sietse Ringers's avatar
Sietse Ringers committed
156
	}
157
158
159
160
161
162
163
164
165
166
	if e.Status != 200 && e.StatusMessage != "" {
		buffer.WriteString("\nConnection: ")
		buffer.WriteString(e.StatusMessage)
	}
	if e.ApiError != nil {
		buffer.WriteString("\nIRMA server error: ")
		buffer.WriteString(e.ApiError.Error())
	}

	return buffer.String()
Sietse Ringers's avatar
Sietse Ringers committed
167
168
}

169
170
171
172
173
174
175
176
func (e *SessionError) Stack() string {
	if withStack, ok := e.Err.(*errors.Error); ok {
		return string(withStack.Stack())
	}

	return ""
}

177
func JwtDecode(jwt string, body interface{}) error {
Sietse Ringers's avatar
Sietse Ringers committed
178
179
	jwtparts := strings.Split(jwt, ".")
	if jwtparts == nil || len(jwtparts) < 2 {
180
		return errors.New("Not a JWT")
Sietse Ringers's avatar
Sietse Ringers committed
181
182
183
	}
	bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
	if err != nil {
184
		return err
Sietse Ringers's avatar
Sietse Ringers committed
185
	}
186
	return json.Unmarshal(bodybytes, body)
Sietse Ringers's avatar
Sietse Ringers committed
187
}
Sietse Ringers's avatar
Sietse Ringers committed
188

189
func ParseRequestorJwt(action Action, jwt string) (RequestorJwt, error) {
Sietse Ringers's avatar
Sietse Ringers committed
190
191
192
193
194
195
196
197
198
	var retval RequestorJwt
	switch action {
	case ActionDisclosing:
		retval = &ServiceProviderJwt{}
	case ActionSigning:
		retval = &SignatureRequestorJwt{}
	case ActionIssuing:
		retval = &IdentityProviderJwt{}
	default:
199
		return nil, errors.New("Invalid session type")
Sietse Ringers's avatar
Sietse Ringers committed
200
	}
201
	err := JwtDecode(jwt, retval)
Sietse Ringers's avatar
Sietse Ringers committed
202
	if err != nil {
203
		return nil, err
Sietse Ringers's avatar
Sietse Ringers committed
204
	}
205
	return retval, nil
Sietse Ringers's avatar
Sietse Ringers committed
206
}