attributes.go 14.7 KB
Newer Older
1
package irma
2
3
4

import (
	"crypto/sha256"
5
	"encoding/binary"
Sietse Ringers's avatar
Sietse Ringers committed
6
	"encoding/hex"
7
	"encoding/json"
8
	"time"
9

Sietse Ringers's avatar
Sietse Ringers committed
10
	"github.com/go-errors/errors"
11
12
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/big"
13
14
)

15
16
const (
	// ExpiryFactor is the precision for the expiry attribute. Value is one week.
17
18
	ExpiryFactor   = 60 * 60 * 24 * 7
	metadataLength = 1 + 3 + 2 + 2 + 16
19
20
21
)

var (
Sietse Ringers's avatar
Sietse Ringers committed
22
23
24
25
26
	versionField     = metadataField{1, 0}
	signingDateField = metadataField{3, 1}
	validityField    = metadataField{2, 4}
	keyCounterField  = metadataField{2, 6}
	credentialID     = metadataField{16, 8}
27
28
)

Sietse Ringers's avatar
Sietse Ringers committed
29
30
// metadataField contains the length and offset of a field within a metadata attribute.
type metadataField struct {
31
32
33
34
	length int
	offset int
}

35
// metadataAttribute represents a metadata attribute. Contains the credential type, signing date, validity, and the public key counter.
36
type MetadataAttribute struct {
37
38
39
	Int  *big.Int
	pk   *gabi.PublicKey
	Conf *Configuration
40
41
}

42
43
44
// AttributeList contains attributes, excluding the secret key,
// providing convenient access to the metadata attribute.
type AttributeList struct {
45
	*MetadataAttribute `json:"-"`
46
	Ints               []*big.Int
Sietse Ringers's avatar
Sietse Ringers committed
47
	strings            []TranslatedString
48
	attrMap            map[AttributeTypeIdentifier]TranslatedString
49
	info               *CredentialInfo
50
	h                  string
51
52
}

53
// NewAttributeListFromInts initializes a new AttributeList from a list of bigints.
54
func NewAttributeListFromInts(ints []*big.Int, conf *Configuration) *AttributeList {
55
	return &AttributeList{
56
		Ints:              ints,
57
		MetadataAttribute: MetadataFromInt(ints[0], conf),
58
	}
59
60
}

61
func (al *AttributeList) Info() *CredentialInfo {
62
	if al.info == nil {
63
		al.info = NewCredentialInfo(al.Ints, al.Conf)
64
	}
65
	return al.info
66
67
}

68
func (al *AttributeList) Hash() string {
69
70
71
	if al.h == "" {
		bytes := []byte{}
		for _, i := range al.Ints {
72
			bytes = append(bytes, i.Bytes()...)
73
74
75
		}
		shasum := sha256.Sum256(bytes)
		al.h = hex.EncodeToString(shasum[:])
76
	}
77
	return al.h
78
79
}

80
81
82
83
84
85
func (al *AttributeList) Map(conf *Configuration) map[AttributeTypeIdentifier]TranslatedString {
	if al.attrMap == nil {
		al.attrMap = make(map[AttributeTypeIdentifier]TranslatedString)
		ctid := al.CredentialType().Identifier()
		strings := al.Strings()
		ct := conf.CredentialTypes[ctid]
86
87
		for i := range ct.AttributeTypes {
			attrid := ct.AttributeTypes[i].GetAttributeTypeIdentifier()
88
89
90
91
92
93
			al.attrMap[attrid] = strings[i]
		}
	}
	return al.attrMap
}

94
// Strings converts the current instance to human-readable strings.
Sietse Ringers's avatar
Sietse Ringers committed
95
func (al *AttributeList) Strings() []TranslatedString {
96
	if al.strings == nil {
Sietse Ringers's avatar
Sietse Ringers committed
97
		al.strings = make([]TranslatedString, len(al.Ints)-1)
98
99
100
101
		for i := range al.Ints[1:] { // skip metadata
			val := al.decode(i)
			if val == nil {
				continue
102
			}
Tomas's avatar
Tomas committed
103
			al.strings[i] = translateAttribute(val)
104
105
106
107
		}
	}
	return al.strings
}
108

Tomas's avatar
Tomas committed
109
110
111
112
113
// Localize raw attribute values (to be implemented)
func translateAttribute(attr *string) TranslatedString {
	if attr == nil {
		return nil
	}
114
115
116
117
118
	return map[string]string{
		"":   *attr, // raw value
		"en": *attr,
		"nl": *attr,
	}
Tomas's avatar
Tomas committed
119
120
}

121
func (al *AttributeList) decode(i int) *string {
122
123
124
125
126
127
128
129
130
	attr := al.Ints[i+1]
	metadataVersion := al.MetadataAttribute.Version()
	return decodeAttribute(attr, metadataVersion)
}

// Decode attribute value into string according to metadataVersion
func decodeAttribute(attr *big.Int, metadataVersion byte) *string {
	bi := new(big.Int).Set(attr)
	if metadataVersion >= 3 {
131
132
133
134
135
136
137
138
139
140
141
		if bi.Bit(0) == 0 { // attribute does not exist
			return nil
		}
		bi.Rsh(bi, 1)
	}
	str := string(bi.Bytes())
	return &str
}

// UntranslatedAttribute decodes the bigint corresponding to the specified attribute.
func (al *AttributeList) UntranslatedAttribute(identifier AttributeTypeIdentifier) *string {
142
	if al.CredentialType().Identifier() != identifier.CredentialTypeIdentifier() {
143
		return nil
144
	}
145
	for i, desc := range al.CredentialType().AttributeTypes {
146
		if desc.ID == string(identifier.Name()) {
147
			return al.decode(i)
148
149
		}
	}
150
	return nil
151
152
}

153
// Attribute returns the content of the specified attribute, or nil if not present in this attribute list.
Sietse Ringers's avatar
Sietse Ringers committed
154
155
156
157
func (al *AttributeList) Attribute(identifier AttributeTypeIdentifier) TranslatedString {
	if al.CredentialType().Identifier() != identifier.CredentialTypeIdentifier() {
		return nil
	}
158
	for i, desc := range al.CredentialType().AttributeTypes {
Sietse Ringers's avatar
Sietse Ringers committed
159
160
161
162
163
164
165
		if desc.ID == string(identifier.Name()) {
			return al.Strings()[i]
		}
	}
	return nil
}

166
// MetadataFromInt wraps the given Int
167
168
func MetadataFromInt(i *big.Int, conf *Configuration) *MetadataAttribute {
	return &MetadataAttribute{Int: i, Conf: conf}
169
170
171
}

// NewMetadataAttribute constructs a new instance containing the default values:
172
// provided version as versionField
173
174
175
// now as signing date
// 0 as keycounter
// ValidityDefault (half a year) as default validity.
176
func NewMetadataAttribute(version byte) *MetadataAttribute {
177
	val := MetadataAttribute{new(big.Int), nil, nil}
178
	val.setField(versionField, []byte{version})
179
180
	val.setSigningDate()
	val.setKeyCounter(0)
181
	val.setDefaultValidityDuration()
182
183
184
185
186
187
188
189
190
191
192
193
194
195
	return &val
}

// Bytes returns this metadata attribute as a byte slice.
func (attr *MetadataAttribute) Bytes() []byte {
	bytes := attr.Int.Bytes()
	if len(bytes) < metadataLength {
		bytes = append(bytes, make([]byte, metadataLength-len(bytes))...)
	}
	return bytes
}

// PublicKey extracts identifier of the Idemix public key with which this instance was signed,
// and returns this public key.
196
func (attr *MetadataAttribute) PublicKey() (*gabi.PublicKey, error) {
197
	if attr.pk == nil {
198
		var err error
199
		attr.pk, err = attr.Conf.PublicKey(attr.CredentialType().IssuerIdentifier(), attr.KeyCounter())
200
201
202
		if err != nil {
			return nil, err
		}
203
	}
204
	return attr.pk, nil
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
}

// Version returns the metadata version of this instance
func (attr *MetadataAttribute) Version() byte {
	return attr.field(versionField)[0]
}

// SigningDate returns the time at which this instance was signed
func (attr *MetadataAttribute) SigningDate() time.Time {
	bytes := attr.field(signingDateField)
	bytes = bytes[1:] // The signing date field is one byte too long
	timestamp := int64(binary.BigEndian.Uint16(bytes)) * ExpiryFactor
	return time.Unix(timestamp, 0)
}

func (attr *MetadataAttribute) setSigningDate() {
	attr.setField(signingDateField, shortToByte(int(time.Now().Unix()/ExpiryFactor)))
}

// KeyCounter return the public key counter of the metadata attribute
func (attr *MetadataAttribute) KeyCounter() int {
	return int(binary.BigEndian.Uint16(attr.field(keyCounterField)))
}

func (attr *MetadataAttribute) setKeyCounter(i int) {
	attr.setField(keyCounterField, shortToByte(i))
}

// ValidityDuration returns the amount of epochs during which this instance is valid
func (attr *MetadataAttribute) ValidityDuration() int {
	return int(binary.BigEndian.Uint16(attr.field(validityField)))
}

func (attr *MetadataAttribute) setValidityDuration(weeks int) {
	attr.setField(validityField, shortToByte(weeks))
}

242
243
244
245
func (attr *MetadataAttribute) setDefaultValidityDuration() {
	attr.setExpiryDate(nil)
}

Sietse Ringers's avatar
Sietse Ringers committed
246
func (attr *MetadataAttribute) setExpiryDate(timestamp *Timestamp) error {
247
	var expiry int64
Sietse Ringers's avatar
Sietse Ringers committed
248
	if timestamp == nil {
249
		expiry = time.Now().AddDate(0, 6, 0).Unix()
250
251
	} else {
		expiry = time.Time(*timestamp).Unix()
Sietse Ringers's avatar
Sietse Ringers committed
252
	}
Sietse Ringers's avatar
Sietse Ringers committed
253
254
255
256
257
	signing := attr.SigningDate().Unix()
	attr.setValidityDuration(int((expiry - signing) / ExpiryFactor))
	return nil
}

258
// CredentialType returns the credential type of the current instance
259
// using the Configuration.
260
func (attr *MetadataAttribute) CredentialType() *CredentialType {
261
	return attr.Conf.hashToCredentialType(attr.field(credentialID))
262
263
}

264
func (attr *MetadataAttribute) setCredentialTypeIdentifier(id string) {
265
266
267
268
	bytes := sha256.Sum256([]byte(id))
	attr.setField(credentialID, bytes[:16])
}

269
270
271
272
func (attr *MetadataAttribute) CredentialTypeHash() []byte {
	return attr.field(credentialID)
}

273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
// Expiry returns the expiry date of this instance
func (attr *MetadataAttribute) Expiry() time.Time {
	expiry := attr.SigningDate().Unix() + int64(attr.ValidityDuration()*ExpiryFactor)
	return time.Unix(expiry, 0)
}

// IsValidOn returns whether this instance is still valid at the given time
func (attr *MetadataAttribute) IsValidOn(t time.Time) bool {
	return attr.Expiry().After(t)
}

// IsValid returns whether this instance is valid.
func (attr *MetadataAttribute) IsValid() bool {
	return attr.IsValidOn(time.Now())
}

289
290
291
292
293
294
295
// FloorToEpochBoundary returns the greatest time not greater than the argument
// that falls on the boundary of an epoch for attribute validity or expiry,
// of which the value is defined by ExpiryFactor (one week).
func FloorToEpochBoundary(t time.Time) time.Time {
	return time.Unix((t.Unix()/ExpiryFactor)*ExpiryFactor, 0)
}

Sietse Ringers's avatar
Sietse Ringers committed
296
func (attr *MetadataAttribute) field(field metadataField) []byte {
297
298
299
	return attr.Bytes()[field.offset : field.offset+field.length]
}

Sietse Ringers's avatar
Sietse Ringers committed
300
func (attr *MetadataAttribute) setField(field metadataField, value []byte) {
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
	if len(value) > field.length {
		panic("Specified metadata field too large")
	}

	bytes := attr.Bytes()

	// Push the value to the right within the field. Graphical representation:
	// --xxxXXX----
	// "-" indicates a byte of another field
	// "X" is a byte of the value and "x" of our field
	// In this example, our field has offset 2, length 6,
	// but the specified value is only 3 bytes long.
	startindex := field.length - len(value)
	for i := 0; i < field.length; i++ {
		if i < startindex {
			bytes[i+field.offset] = 0
		} else {
			bytes[i+field.offset] = value[i-startindex]
		}
	}

	attr.Int.SetBytes(bytes)
}

func shortToByte(x int) []byte {
	bytes := make([]byte, 2)
	binary.BigEndian.PutUint16(bytes, uint16(x))
	return bytes
}
330

331
332
333
334
335
// A DisclosureChoice contains the attributes chosen to be disclosed.
type DisclosureChoice struct {
	Attributes []*AttributeIdentifier
}

336
337
338
339
340
// An AttributeDisjunction encapsulates a list of possible attributes, one
// of which should be disclosed.
type AttributeDisjunction struct {
	Label      string
	Attributes []AttributeTypeIdentifier
341
	Values     map[AttributeTypeIdentifier]*string
342
343

	selected *AttributeTypeIdentifier
344
345
	value    *string
	index    *int
346
347
}

348
349
350
351
352
353
354
355
356
// An AttributeDisjunctionList is a list of AttributeDisjunctions.
type AttributeDisjunctionList []*AttributeDisjunction

// HasValues indicates if the attributes of this disjunction have values
// that should be satisfied.
func (disjunction *AttributeDisjunction) HasValues() bool {
	return disjunction.Values != nil && len(disjunction.Values) != 0
}

357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
// attemptSatisfy tries to match the specified attribute type and value against the current disjunction,
// returning true if the disjunction contains the specified attribute type. Note that if the disjunction
// has required values, then it is only considered satisfied by the specified attribute type and value
// if the required value matches the specified value.
func (disjunction *AttributeDisjunction) attemptSatisfy(id AttributeTypeIdentifier, value *string) bool {
	var found bool
	for index, attr := range disjunction.Attributes {
		if attr == id {
			found = true
			disjunction.selected = &id
			disjunction.index = &index
			disjunction.value = value
			if !disjunction.HasValues() || disjunction.Values[id] == value {
				return true
			}
372
373
		}
	}
374
	return found
375
376
}

377
378
379
380
381
// satisfied indicates if this disjunction has a valid attribute type and value selected,
// matching one of the attributes in the disjunction and possibly also the corresponding required value.
func (disjunction *AttributeDisjunction) satisfied() bool {
	if disjunction.index == nil {
		return false
382
383
	}

384
385
386
387
	attr := disjunction.Attributes[*disjunction.index]
	return !disjunction.HasValues() || disjunction.value == disjunction.Values[attr]

	return false
388
389
}

390
391
392
// MatchesConfig returns true if all attributes contained in the disjunction are
// present in the specified configuration.
func (disjunction *AttributeDisjunction) MatchesConfig(conf *Configuration) bool {
393
	for ai := range disjunction.Values {
394
		creddescription, exists := conf.CredentialTypes[ai.CredentialTypeIdentifier()]
395
396
397
398
399
400
401
402
403
404
		if !exists {
			return false
		}
		if !creddescription.ContainsAttribute(ai) {
			return false
		}
	}
	return true
}

405
406
// satisfied indicates whether each contained attribute disjunction has a chosen attribute.
func (dl AttributeDisjunctionList) satisfied() bool {
407
	for _, disjunction := range dl {
408
		if !disjunction.satisfied() {
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
			return false
		}
	}
	return true
}

// Find searches for and returns the disjunction that contains the specified attribute identifier, or nil if not found.
func (dl AttributeDisjunctionList) Find(ai AttributeTypeIdentifier) *AttributeDisjunction {
	for _, disjunction := range dl {
		for _, attr := range disjunction.Attributes {
			if attr == ai {
				return disjunction
			}
		}
	}
	return nil
}

// MarshalJSON marshals the disjunction to JSON.
func (disjunction *AttributeDisjunction) MarshalJSON() ([]byte, error) {
	if !disjunction.HasValues() {
		temp := struct {
			Label      string                    `json:"label"`
			Attributes []AttributeTypeIdentifier `json:"attributes"`
		}{
			Label:      disjunction.Label,
			Attributes: disjunction.Attributes,
		}
		return json.Marshal(temp)
	}

	temp := struct {
441
442
		Label      string                              `json:"label"`
		Attributes map[AttributeTypeIdentifier]*string `json:"attributes"`
443
444
445
446
447
	}{
		Label:      disjunction.Label,
		Attributes: disjunction.Values,
	}
	return json.Marshal(temp)
448
449
}

450
451
452
// UnmarshalJSON unmarshals an attribute disjunction from JSON.
func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
	if disjunction.Values == nil {
453
		disjunction.Values = make(map[AttributeTypeIdentifier]*string)
454
455
456
457
458
459
460
461
462
463
464
465
	}
	if disjunction.Attributes == nil {
		disjunction.Attributes = make([]AttributeTypeIdentifier, 0, 3)
	}

	// We don't know if the json element "attributes" is a list, or a map.
	// So we unmarshal it into a temporary struct that has interface{} as the
	// type of "attributes", so that we can check which of the two it is.
	temp := struct {
		Label      string      `json:"label"`
		Attributes interface{} `json:"attributes"`
	}{}
Sietse Ringers's avatar
Sietse Ringers committed
466
467
468
	if err := json.Unmarshal(bytes, &temp); err != nil {
		return err
	}
469
470
471
472
473
	disjunction.Label = temp.Label

	switch temp.Attributes.(type) {
	case map[string]interface{}:
		temp := struct {
474
475
			Label      string             `json:"label"`
			Attributes map[string]*string `json:"attributes"`
476
		}{}
Sietse Ringers's avatar
Sietse Ringers committed
477
478
479
		if err := json.Unmarshal(bytes, &temp); err != nil {
			return err
		}
480
481
482
483
484
485
486
487
488
489
		for str, value := range temp.Attributes {
			id := NewAttributeTypeIdentifier(str)
			disjunction.Attributes = append(disjunction.Attributes, id)
			disjunction.Values[id] = value
		}
	case []interface{}:
		temp := struct {
			Label      string   `json:"label"`
			Attributes []string `json:"attributes"`
		}{}
Sietse Ringers's avatar
Sietse Ringers committed
490
491
492
		if err := json.Unmarshal(bytes, &temp); err != nil {
			return err
		}
493
494
495
496
497
498
499
500
501
		for _, str := range temp.Attributes {
			disjunction.Attributes = append(disjunction.Attributes, NewAttributeTypeIdentifier(str))
		}
	default:
		return errors.New("could not parse attribute disjunction: element 'attributes' was incorrect")
	}

	return nil
}