Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IRMA
Github mirrors
irmago
Commits
e0f77546
Commit
e0f77546
authored
Aug 23, 2017
by
Sietse Ringers
Browse files
Add first part of IRMA protocol
parent
ad48738e
Changes
11
Hide whitespace changes
Inline
Side-by-side
attributes.go
View file @
e0f77546
...
...
@@ -3,9 +3,12 @@ package irmago
import
(
"crypto/sha256"
"encoding/binary"
"errors"
"math/big"
"time"
"encoding/json"
"github.com/mhe/gabi"
)
...
...
@@ -76,6 +79,15 @@ func (al *AttributeList) Strings() []string {
return
al
.
strings
}
func
(
al
*
AttributeList
)
Attribute
(
identifier
AttributeTypeIdentifier
)
string
{
for
i
,
desc
:=
range
al
.
CredentialType
()
.
Attributes
{
if
desc
.
ID
==
string
(
identifier
.
String
())
{
return
al
.
Strings
()[
i
]
}
}
return
""
}
// MetadataFromInt wraps the given Int
func
MetadataFromInt
(
i
*
big
.
Int
)
*
MetadataAttribute
{
return
&
MetadataAttribute
{
Int
:
i
}
...
...
@@ -209,3 +221,147 @@ func shortToByte(x int) []byte {
binary
.
BigEndian
.
PutUint16
(
bytes
,
uint16
(
x
))
return
bytes
}
// An AttributeDisjunction encapsulates a list of possible attributes, one
// of which should be disclosed.
type
AttributeDisjunction
struct
{
Label
string
Attributes
[]
AttributeTypeIdentifier
Values
map
[
AttributeTypeIdentifier
]
string
selected
*
AttributeTypeIdentifier
}
// An AttributeDisjunctionList is a list of AttributeDisjunctions.
type
AttributeDisjunctionList
[]
*
AttributeDisjunction
type
DisjunctionListContainer
interface
{
DisjunctionList
()
AttributeDisjunctionList
}
// 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
}
// Satisfied indicates if this disjunction has a valid chosen attribute
// to be disclosed.
func
(
disjunction
*
AttributeDisjunction
)
Satisfied
()
bool
{
if
disjunction
.
selected
==
nil
{
return
false
}
for
_
,
attr
:=
range
disjunction
.
Attributes
{
if
*
disjunction
.
selected
==
attr
{
return
true
}
}
return
false
}
// MatchesStore returns true if all attributes contained in the disjunction are
// present in the MetaStore.
func
(
disjunction
*
AttributeDisjunction
)
MatchesStore
()
bool
{
for
ai
:=
range
disjunction
.
Values
{
creddescription
,
exists
:=
MetaStore
.
Credentials
[
ai
.
CredentialTypeIdentifier
()]
if
!
exists
{
return
false
}
if
!
creddescription
.
ContainsAttribute
(
ai
)
{
return
false
}
}
return
true
}
// Satisfied indicates whether each contained attribute disjunction has a chosen attribute.
func
(
dl
AttributeDisjunctionList
)
Satisfied
()
bool
{
for
_
,
disjunction
:=
range
dl
{
if
!
disjunction
.
Satisfied
()
{
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
{
Label
string
`json:"label"`
Attributes
map
[
AttributeTypeIdentifier
]
string
`json:"attributes"`
}{
Label
:
disjunction
.
Label
,
Attributes
:
disjunction
.
Values
,
}
return
json
.
Marshal
(
temp
)
}
// UnmarshalJSON unmarshals an attribute disjunction from JSON.
func
(
disjunction
*
AttributeDisjunction
)
UnmarshalJSON
(
bytes
[]
byte
)
error
{
if
disjunction
.
Values
==
nil
{
disjunction
.
Values
=
make
(
map
[
AttributeTypeIdentifier
]
string
)
}
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"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
disjunction
.
Label
=
temp
.
Label
switch
temp
.
Attributes
.
(
type
)
{
case
map
[
string
]
interface
{}
:
temp
:=
struct
{
Label
string
`json:"label"`
Attributes
map
[
string
]
string
`json:"attributes"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
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"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
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
}
identifiers.go
View file @
e0f77546
...
...
@@ -24,6 +24,18 @@ type AttributeTypeIdentifier struct {
metaObjectIdentifier
}
type
CredentialIdentifier
struct
{
Type
CredentialTypeIdentifier
Index
int
Count
int
}
type
AttributeIdentifier
struct
{
Type
AttributeTypeIdentifier
Index
int
Count
int
}
func
(
oi
metaObjectIdentifier
)
Parent
()
string
{
str
:=
string
(
oi
)
return
str
[
:
strings
.
LastIndex
(
str
,
"."
)]
...
...
@@ -72,3 +84,7 @@ func (id CredentialTypeIdentifier) IssuerIdentifier() IssuerIdentifier {
func
(
id
AttributeTypeIdentifier
)
CredentialTypeIdentifier
()
CredentialTypeIdentifier
{
return
NewCredentialTypeIdentifier
(
id
.
Parent
())
}
func
(
id
AttributeTypeIdentifier
)
IsCredential
()
bool
{
return
strings
.
Count
(
id
.
String
(),
"."
)
==
2
}
irmago_test.go
View file @
e0f77546
...
...
@@ -7,6 +7,8 @@ import (
"testing"
"time"
"encoding/json"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
...
...
@@ -155,3 +157,45 @@ func TestMetadataCompatibility(t *testing.T) {
teardown
(
t
)
}
func
TestAttributeDisjunctionMarshaling
(
t
*
testing
.
T
)
{
disjunction
:=
AttributeDisjunction
{}
var
_
json
.
Unmarshaler
=
&
disjunction
var
_
json
.
Marshaler
=
&
disjunction
id
:=
NewAttributeTypeIdentifier
(
"MijnOverheid.ageLower.over18"
)
attrsjson
:=
`
{
"label": "Over 18",
"attributes": {
"MijnOverheid.ageLower.over18": "yes",
"Thalia.age.over18": "Yes"
}
}`
require
.
NoError
(
t
,
json
.
Unmarshal
([]
byte
(
attrsjson
),
&
disjunction
))
require
.
True
(
t
,
disjunction
.
HasValues
())
require
.
Contains
(
t
,
disjunction
.
Attributes
,
id
)
require
.
Contains
(
t
,
disjunction
.
Values
,
id
)
require
.
Equal
(
t
,
disjunction
.
Values
[
id
],
"yes"
)
disjunction
=
AttributeDisjunction
{}
attrsjson
=
`
{
"label": "Over 18",
"attributes": [
"MijnOverheid.ageLower.over18",
"Thalia.age.over18"
]
}`
require
.
NoError
(
t
,
json
.
Unmarshal
([]
byte
(
attrsjson
),
&
disjunction
))
require
.
False
(
t
,
disjunction
.
HasValues
())
require
.
Contains
(
t
,
disjunction
.
Attributes
,
id
)
require
.
True
(
t
,
disjunction
.
MatchesStore
())
require
.
False
(
t
,
disjunction
.
Satisfied
())
disjunction
.
selected
=
&
disjunction
.
Attributes
[
0
]
require
.
True
(
t
,
disjunction
.
Satisfied
())
}
manager.go
View file @
e0f77546
...
...
@@ -199,3 +199,47 @@ func (cm *CredentialManager) Add(cred *Credential) (err error) {
err
=
cm
.
storeAttributes
()
return
}
func
(
cm
*
CredentialManager
)
Candidates
(
disjunction
*
AttributeDisjunction
)
[]
*
AttributeIdentifier
{
candidates
:=
make
([]
*
AttributeIdentifier
,
10
)
for
_
,
attribute
:=
range
disjunction
.
Attributes
{
credId
:=
attribute
.
CredentialTypeIdentifier
()
if
!
MetaStore
.
Contains
(
credId
)
{
continue
}
creds
:=
cm
.
credentials
[
credId
]
count
:=
len
(
creds
)
if
count
==
0
{
continue
}
for
i
,
cred
:=
range
creds
{
id
:=
&
AttributeIdentifier
{
Type
:
attribute
,
Index
:
i
,
Count
:
count
}
if
attribute
.
IsCredential
()
{
candidates
=
append
(
candidates
,
id
)
}
else
{
attrs
:=
NewAttributeListFromInts
(
cred
.
Attributes
[
1
:
])
val
:=
attrs
.
Attribute
(
attribute
)
if
val
==
""
{
continue
}
if
!
disjunction
.
HasValues
()
||
val
==
disjunction
.
Values
[
attribute
]
{
candidates
=
append
(
candidates
,
id
)
}
}
}
}
return
candidates
}
func
(
cm
*
CredentialManager
)
CheckSatisfiability
(
disjunctions
DisjunctionListContainer
)
AttributeDisjunctionList
{
missing
:=
make
(
AttributeDisjunctionList
,
5
)
for
_
,
disjunction
:=
range
disjunctions
.
DisjunctionList
()
{
if
len
(
cm
.
Candidates
(
disjunction
))
==
0
{
missing
=
append
(
missing
,
disjunction
)
}
}
return
missing
}
protocol/attributes.go
View file @
e0f77546
package
protocol
import
(
"encoding/json"
"errors"
"github.com/credentials/irmago"
)
// An AttributeDisjunction encapsulates a list of possible attributes, one
// of which should be disclused.
type
AttributeDisjunction
struct
{
Label
string
Attributes
[]
irmago
.
AttributeTypeIdentifier
Values
map
[
irmago
.
AttributeTypeIdentifier
]
string
selected
*
irmago
.
AttributeTypeIdentifier
}
// 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
}
// Satisfied indicates if this disjunction has a valid chosen attribute
// to be disclosed.
func
(
disjunction
*
AttributeDisjunction
)
Satisfied
()
bool
{
if
disjunction
.
selected
==
nil
{
return
false
}
for
_
,
attr
:=
range
disjunction
.
Attributes
{
if
*
disjunction
.
selected
==
attr
{
return
true
}
}
return
false
}
// MatchesStore returns true if all attributes contained in the disjunction are
// present in the MetaStore.
func
(
disjunction
*
AttributeDisjunction
)
MatchesStore
()
bool
{
for
ai
:=
range
disjunction
.
Values
{
creddescription
,
exists
:=
irmago
.
MetaStore
.
Credentials
[
ai
.
CredentialTypeIdentifier
()]
if
!
exists
{
return
false
}
if
!
creddescription
.
ContainsAttribute
(
ai
)
{
return
false
}
}
return
true
}
// Satisfied indicates whether each contained attribute disjunction has a chosen attribute.
func
(
dl
AttributeDisjunctionList
)
Satisfied
()
bool
{
for
_
,
disjunction
:=
range
dl
{
if
!
disjunction
.
Satisfied
()
{
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
irmago
.
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
[]
irmago
.
AttributeTypeIdentifier
`json:"attributes"`
}{
Label
:
disjunction
.
Label
,
Attributes
:
disjunction
.
Attributes
,
}
return
json
.
Marshal
(
temp
)
}
temp
:=
struct
{
Label
string
`json:"label"`
Attributes
map
[
irmago
.
AttributeTypeIdentifier
]
string
`json:"attributes"`
}{
Label
:
disjunction
.
Label
,
Attributes
:
disjunction
.
Values
,
}
return
json
.
Marshal
(
temp
)
}
// UnmarshalJSON unmarshals an attribute disjunction from JSON.
func
(
disjunction
*
AttributeDisjunction
)
UnmarshalJSON
(
bytes
[]
byte
)
error
{
if
disjunction
.
Values
==
nil
{
disjunction
.
Values
=
make
(
map
[
irmago
.
AttributeTypeIdentifier
]
string
)
}
if
disjunction
.
Attributes
==
nil
{
disjunction
.
Attributes
=
make
([]
irmago
.
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"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
disjunction
.
Label
=
temp
.
Label
switch
temp
.
Attributes
.
(
type
)
{
case
map
[
string
]
interface
{}
:
temp
:=
struct
{
Label
string
`json:"label"`
Attributes
map
[
string
]
string
`json:"attributes"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
for
str
,
value
:=
range
temp
.
Attributes
{
id
:=
irmago
.
NewAttributeTypeIdentifier
(
str
)
disjunction
.
Attributes
=
append
(
disjunction
.
Attributes
,
id
)
disjunction
.
Values
[
id
]
=
value
}
case
[]
interface
{}
:
temp
:=
struct
{
Label
string
`json:"label"`
Attributes
[]
string
`json:"attributes"`
}{}
json
.
Unmarshal
(
bytes
,
&
temp
)
for
_
,
str
:=
range
temp
.
Attributes
{
disjunction
.
Attributes
=
append
(
disjunction
.
Attributes
,
irmago
.
NewAttributeTypeIdentifier
(
str
))
}
default
:
return
errors
.
New
(
"could not parse attribute disjunction: element 'attributes' was incorrect"
)
}
return
nil
}
protocol/messages.go
View file @
e0f77546
...
...
@@ -5,33 +5,68 @@ import (
"strconv"
"time"
"math/big"
"github.com/credentials/irmago"
)
// Session types.
// Timestamp is a time.Time that marshals to Unix timestamps.
type
Timestamp
time
.
Time
// 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
// SessionError are session errors.
type
SessionError
string
// Statuses
const
(
DISCLOSING
=
SessionType
(
"disclosing
"
)
ISSUING
=
SessionType
(
"issu
ing"
)
S
IGNING
=
SessionType
(
"signing
"
)
StatusConnected
=
Status
(
"connected
"
)
StatusCommunicating
=
Status
(
"communicat
ing"
)
S
tatusDone
=
Status
(
"done
"
)
)
// Timestamp is a time.Time that marshals to Unix timestamps.
type
Timestamp
time
.
Time
// Actions
const
(
ActionDisclosing
=
Action
(
"disclosing"
)
ActionSigning
=
Action
(
"signing"
)
ActionIssuing
=
Action
(
"issuing"
)
ActionUnknown
=
Action
(
"unknown"
)
)
// SessionType is a session type (DISCLOSING, ISSUING or SIGNING).
type
SessionType
string
// Protocol errors
const
(
ErrorProtocolVersionNotSupported
=
SessionError
(
"versionNotSupported"
)
ErrorInvalidURL
=
SessionError
(
"invalidUrl"
)
ErrorTransport
=
SessionError
(
"httpError"
)
ErrorInvalidJWT
=
SessionError
(
"invalidJwt"
)
ErrorUnknownAction
=
SessionError
(
"unknownAction"
)
)
// Qr contains the data of an IRMA session QR.
type
Qr
struct
{
URL
string
`json:"u"`
ProtocolVersion
string
`json:"v"`
ProtocolMaxVersion
string
`json:"vmax"`
Type
SessionType
`json:"irmaqr"`
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"`
}
// A DisclosureChoice contains the attributes chosen to be disclosed.
type
DisclosureChoice
struct
{
Request
SessionRequest
Attributes
[]
*
irmago
.
AttributeIdentifier
}
...
...
protocol/protocol_test.go
View file @
e0f77546
...
...
@@ -10,48 +10,6 @@ import (
"github.com/stretchr/testify/require"
)
func
TestAttributeDisjunctionMarshaling
(
t
*
testing
.
T
)
{
disjunction
:=
AttributeDisjunction
{}
var
_
json
.
Unmarshaler
=
&
disjunction
var
_
json
.
Marshaler
=
&
disjunction
id
:=
irmago
.
NewAttributeTypeIdentifier
(
"MijnOverheid.ageLower.over18"
)
attrsjson
:=
`
{
"label": "Over 18",
"attributes": {
"MijnOverheid.ageLower.over18": "yes",
"Thalia.age.over18": "Yes"
}
}`
require
.
NoError
(
t
,
json
.
Unmarshal
([]
byte
(
attrsjson
),
&
disjunction
))
require
.
True
(
t
,
disjunction
.
HasValues
())
require
.
Contains
(
t
,
disjunction
.
Attributes
,
id
)
require
.
Contains
(
t
,
disjunction
.
Values
,
id
)
require
.
Equal
(
t
,
disjunction
.
Values
[
id
],
"yes"
)
disjunction
=
AttributeDisjunction
{}
attrsjson
=
`
{
"label": "Over 18",
"attributes": [
"MijnOverheid.ageLower.over18",
"Thalia.age.over18"
]
}`
require
.
NoError
(
t
,
json
.
Unmarshal
([]
byte
(
attrsjson
),
&
disjunction
))
require
.
False
(
t
,
disjunction
.
HasValues
())
require
.
Contains
(
t
,
disjunction
.
Attributes
,
id
)
require
.
True
(
t
,
disjunction
.
MatchesStore
())
require
.
False
(
t
,
disjunction
.
Satisfied
())
disjunction
.
selected
=
&
disjunction
.
Attributes
[
0
]
require
.
True
(
t
,
disjunction
.
Satisfied
())
}
func
TestTimestamp
(
t
*
testing
.
T
)
{
mytime
:=
Timestamp
(
time
.
Unix
(
1500000000
,
0
))
timestruct
:=
struct
{
Time
*
Timestamp
}{
Time
:
&
mytime
}
...
...
@@ -92,3 +50,15 @@ func TestServiceProviderRequest(t *testing.T) {
require
.
NotNil
(
t
,
sprequest
.
Request
.
Request
.
Content
.
Find
(
irmago
.
NewAttributeTypeIdentifier
(
"irma-demo.RU.studentCard.studentID"
)))
}
func
TestTransport
(
t
*
testing
.
T
)
{
transport
:=
NewHTTPTransport
(
"https://xkcd.com"
)
obj
:=
&
struct
{
Num
int
`json:"num"`
Img
string
`json:"img"`
Title
string
`json:"title"`
}{}
err
:=
transport
.
Get
(
"614/info.0.json"
,
obj
)
require
.
NoError
(
t
,
err
)
}
protocol/requests.go
View file @
e0f77546
...
...
@@ -13,7 +13,7 @@ type SessionRequest struct {
type
DisclosureRequest
struct
{
SessionRequest
Content
AttributeDisjunctionList
`json:"content"`
Content
irmago
.
AttributeDisjunctionList
`json:"content"`
}
type
SignatureRequest
struct
{
...
...
@@ -37,8 +37,8 @@ type ServerRequest struct {
type
IssuanceRequest
struct
{
SessionRequest
Credentials
[]
CredentialRequest
`json:"credentials"`
Discose
[]
*
AttributeDisjunctionList
`json:"disclose"`
Credentials
[]
CredentialRequest
`json:"credentials"`
Disc
l
ose
irmago
.
AttributeDisjunctionList
`json:"disclose"`
}
type
ServiceProviderRequest
struct
{
...
...
@@ -61,3 +61,15 @@ type IdentityProviderRequest struct {
Request
IssuanceRequest
`json:"request"`
}
`json:"iprequest"`
}
func
(
spr
*
ServiceProviderRequest
)
DisjunctionList
()
irmago
.
AttributeDisjunctionList
{