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

import (
Sietse Ringers's avatar
Sietse Ringers committed
4
	"encoding/json"
5
	"errors"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"fmt"
7
8
	"math/big"

Sietse Ringers's avatar
Sietse Ringers committed
9
	"github.com/credentials/irmago"
Sietse Ringers's avatar
Sietse Ringers committed
10
11
)

12
13
14
15
16
17
18
19
20
// Status encodes the status of an IRMA session (e.g., connected).
type Status string

// Action encodes the session type of an IRMA session (e.g., disclosing).
type Action string

// Version encodes the IRMA protocol version of an IRMA session.
type Version string

21
22
23
// ErrorCode are session errors.
type ErrorCode string

Sietse Ringers's avatar
Sietse Ringers committed
24
// Error is a protocol error.
25
26
27
28
29
30
type Error struct {
	ErrorCode
	error
	info string
	*ApiError
}
31
32

// Statuses
Sietse Ringers's avatar
Sietse Ringers committed
33
const (
34
35
36
	StatusConnected     = Status("connected")
	StatusCommunicating = Status("communicating")
	StatusDone          = Status("done")
Sietse Ringers's avatar
Sietse Ringers committed
37
38
)

39
40
41
42
43
44
45
// Actions
const (
	ActionDisclosing = Action("disclosing")
	ActionSigning    = Action("signing")
	ActionIssuing    = Action("issuing")
	ActionUnknown    = Action("unknown")
)
Sietse Ringers's avatar
Sietse Ringers committed
46

47
48
// Protocol errors
const (
49
	// Protocol version not supported
50
	ErrorProtocolVersionNotSupported = ErrorCode("versionNotSupported")
51
	// Server URL invalid
52
	ErrorInvalidURL = ErrorCode("invalidUrl")
53
	// Error in HTTP communication
54
	ErrorTransport = ErrorCode("httpError")
55
	// Invalid client JWT in first IRMA message
56
	ErrorInvalidJWT = ErrorCode("invalidJwt")
57
	// Unkown session type (not disclosing, signing, or issuing)
58
	ErrorUnknownAction = ErrorCode("unknownAction")
59
	// Crypto error during calculation of our response (second IRMA message)
60
	ErrorCrypto = ErrorCode("cryptoResponseError")
61
	// Server rejected our response (second IRMA message)
62
	ErrorRejected = ErrorCode("rejectedByServer")
63
)
Sietse Ringers's avatar
Sietse Ringers committed
64
65

// Qr contains the data of an IRMA session QR.
Sietse Ringers's avatar
Sietse Ringers committed
66
type Qr struct {
67
68
69
70
71
72
73
74
75
76
77
78
	URL                string `json:"u"`
	ProtocolVersion    string `json:"v"`
	ProtocolMaxVersion string `json:"vmax"`
	Type               Action `json:"irmaqr"`
}

// A SessionInfo is the first message in the IRMA protocol.
type SessionInfo struct {
	Jwt     string                          `json:"jwt"`
	Nonce   *big.Int                        `json:"nonce"`
	Context *big.Int                        `json:"context"`
	Keys    map[irmago.IssuerIdentifier]int `json:"keys"`
Sietse Ringers's avatar
Sietse Ringers committed
79
80
}

81
82
83
84
func (e *Error) Error() string {
	if e.error != nil {
		return fmt.Sprintf("%s: %s", string(e.ErrorCode), e.error.Error())
	}
85
	return string(e.ErrorCode)
86
}
Sietse Ringers's avatar
Sietse Ringers committed
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106

/*
So apparently, in the old Java implementation we forgot to write a (de)serialization for the Java
equivalent of the type IssuerIdentifier. This means a Java IssuerIdentifier does not serialize to
a string, but to e.g. `{"identifier":"irma-demo.RU"}`.
This is a complex data type, so not suitable to act as keys in a JSON map. Consequentially,
Gson serializes the `json:"keys"` field not as a map, but as a list consisting of pairs where
the first item of the pair is a serialized IssuerIdentifier as above, and the second item
of the pair is the corresponding key counter from the original map.
This is a bit of a mess to have to deserialize. See below. In a future version of the protocol,
this will have to be fixed both in the Java world and here in Go.
*/

type jsonSessionInfo struct {
	Jwt     string          `json:"jwt"`
	Nonce   *big.Int        `json:"nonce"`
	Context *big.Int        `json:"context"`
	Keys    [][]interface{} `json:"keys"`
}

Sietse Ringers's avatar
Sietse Ringers committed
107
// UnmarshalJSON unmarshals session information.
Sietse Ringers's avatar
Sietse Ringers committed
108
109
110
111
112
113
114
115
116
117
118
119
func (si *SessionInfo) UnmarshalJSON(b []byte) error {
	temp := &jsonSessionInfo{}
	err := json.Unmarshal(b, temp)
	if err != nil {
		return err
	}

	si.Jwt = temp.Jwt
	si.Nonce = temp.Nonce
	si.Context = temp.Context
	si.Keys = make(map[irmago.IssuerIdentifier]int, len(temp.Keys))
	for _, item := range temp.Keys {
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
		var idmap map[string]interface{}
		var idstr string
		var counter float64
		var ok bool
		if idmap, ok = item[0].(map[string]interface{}); !ok {
			return errors.New("Failed to deserialize session info")
		}
		if idstr, ok = idmap["identifier"].(string); !ok {
			return errors.New("Failed to deserialize session info")
		}
		if counter, ok = item[1].(float64); !ok {
			return errors.New("Failed to deserialize session info")
		}
		id := irmago.NewIssuerIdentifier(idstr)
		si.Keys[id] = int(counter)
Sietse Ringers's avatar
Sietse Ringers committed
135
136
137
	}
	return nil
}