Commit 55e40826 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Merge branch 'refactor'

parents a5eb23a3 d9514702
......@@ -49,12 +49,6 @@
packages = ["."]
revision = "720887075201f3105c335f837431426d54891b7f"
[[projects]]
branch = "master"
name = "github.com/credentials/safeprime"
packages = ["."]
revision = "424faf641b4987fde58280730486e1035e057abc"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
......@@ -110,10 +104,14 @@
version = "v1.0"
[[projects]]
branch = "master"
branch = "refactor"
name = "github.com/mhe/gabi"
packages = ["."]
revision = "4f733a839b2b9234ef7c2610fbc8270bafe250c7"
packages = [
".",
"safeprime"
]
revision = "30dbdc3d708aee39573ef793505e78b6fb949b48"
source = "github.com/privacybydesign/gabi"
[[projects]]
branch = "master"
......@@ -197,6 +195,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "edc6335a88ff851653a42b9198c2f0f2e7f27502e18a9b48d2641de3b0bd69be"
inputs-digest = "463d26f53ec5d07c619bf97f2707460ffa512d6eddc1f8c109972e101981afab"
solver-name = "gps-cdcl"
solver-version = 1
......@@ -38,8 +38,9 @@
version = "1.0.0"
[[constraint]]
branch = "master"
branch = "refactor"
name = "github.com/mhe/gabi"
source = "github.com/privacybydesign/gabi"
[[constraint]]
name = "github.com/pkg/errors"
......
......@@ -5,13 +5,11 @@ import (
"encoding/binary"
"encoding/hex"
"encoding/json"
"math/big"
"time"
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"github.com/mhe/gabi/big"
)
const (
......@@ -20,24 +18,6 @@ const (
metadataLength = 1 + 3 + 2 + 2 + 16
)
type AttributeResult struct {
AttributeValue string `json:"value"` // Value of the disclosed attribute
AttributeId AttributeTypeIdentifier `json:"id"`
AttributeProofStatus AttributeProofStatus `json:"status"`
}
type AttributeResultList []*AttributeResult
// AttributeProofStatus is the proof status of a single attribute
type AttributeProofStatus string
const (
PRESENT = AttributeProofStatus("PRESENT") // Attribute is disclosed and matches the value
EXTRA = AttributeProofStatus("EXTRA") // Attribute is disclosed, but wasn't requested in request
MISSING = AttributeProofStatus("MISSING") // Attribute is NOT disclosed, but should be according to request
INVALID_VALUE = AttributeProofStatus("INVALID_VALUE") // Attribute is disclosed, but has invalid value according to request
)
var (
versionField = metadataField{1, 0}
signingDateField = metadataField{3, 1}
......@@ -65,6 +45,7 @@ type AttributeList struct {
*MetadataAttribute `json:"-"`
Ints []*big.Int
strings []TranslatedString
attrMap map[AttributeTypeIdentifier]TranslatedString
info *CredentialInfo
h string
}
......@@ -96,6 +77,20 @@ func (al *AttributeList) Hash() string {
return al.h
}
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]
for i := range ct.AttributeTypes {
attrid := ct.AttributeTypes[i].GetAttributeTypeIdentifier()
al.attrMap[attrid] = strings[i]
}
}
return al.attrMap
}
// Strings converts the current instance to human-readable strings.
func (al *AttributeList) Strings() []TranslatedString {
if al.strings == nil {
......@@ -105,12 +100,24 @@ func (al *AttributeList) Strings() []TranslatedString {
if val == nil {
continue
}
al.strings[i] = map[string]string{"en": *val, "nl": *val} // TODO
al.strings[i] = translateAttribute(val)
}
}
return al.strings
}
// Localize raw attribute values (to be implemented)
func translateAttribute(attr *string) TranslatedString {
if attr == nil {
return nil
}
return map[string]string{
"": *attr, // raw value
"en": *attr,
"nl": *attr,
}
}
func (al *AttributeList) decode(i int) *string {
attr := al.Ints[i+1]
metadataVersion := al.MetadataAttribute.Version()
......@@ -135,7 +142,7 @@ func (al *AttributeList) UntranslatedAttribute(identifier AttributeTypeIdentifie
if al.CredentialType().Identifier() != identifier.CredentialTypeIdentifier() {
return nil
}
for i, desc := range al.CredentialType().Attributes {
for i, desc := range al.CredentialType().AttributeTypes {
if desc.ID == string(identifier.Name()) {
return al.decode(i)
}
......@@ -143,12 +150,12 @@ func (al *AttributeList) UntranslatedAttribute(identifier AttributeTypeIdentifie
return nil
}
// Attribute returns the content of the specified attribute, or "" if not present in this attribute list.
// Attribute returns the content of the specified attribute, or nil if not present in this attribute list.
func (al *AttributeList) Attribute(identifier AttributeTypeIdentifier) TranslatedString {
if al.CredentialType().Identifier() != identifier.CredentialTypeIdentifier() {
return nil
}
for i, desc := range al.CredentialType().Attributes {
for i, desc := range al.CredentialType().AttributeTypes {
if desc.ID == string(identifier.Name()) {
return al.Strings()[i]
}
......@@ -334,68 +341,50 @@ type AttributeDisjunction struct {
Values map[AttributeTypeIdentifier]*string
selected *AttributeTypeIdentifier
}
// AttributeDisjunction with the disclosed value that is used to satisfy the disjunction
type DisclosedAttributeDisjunction struct {
AttributeDisjunction
DisclosedValue string
DisclosedId AttributeTypeIdentifier
ProofStatus AttributeProofStatus
value *string
index *int
}
// An AttributeDisjunctionList is a list of AttributeDisjunctions.
type AttributeDisjunctionList []*AttributeDisjunction
// Convert disjunction to a DisclosedAttributeDisjunction that contains disclosed attribute+value
func (disjunction *AttributeDisjunction) ToDisclosedAttributeDisjunction(ar *AttributeResult) *DisclosedAttributeDisjunction {
return &DisclosedAttributeDisjunction{
AttributeDisjunction: *disjunction,
DisclosedValue: ar.AttributeValue,
DisclosedId: ar.AttributeId,
ProofStatus: ar.AttributeProofStatus,
}
}
// 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
// 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
}
}
}
return false
return found
}
// Check whether specified attributedisjunction satisfy a list of disclosed attributes
// We return true if one of the attributes in the disjunction is satisfied
func (disjunction *AttributeDisjunction) SatisfyDisclosed(disclosed DisclosedCredentialList, conf *Configuration) (bool, *DisclosedAttributeDisjunction) {
var attributeResult *AttributeResult
for _, attr := range disjunction.Attributes {
requestedValue := disjunction.Values[attr]
var isSatisfied bool
isSatisfied, attributeResult = disclosed.isAttributeSatisfied(attr, requestedValue)
if isSatisfied {
return true, disjunction.ToDisclosedAttributeDisjunction(attributeResult)
}
// 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
}
// Nothing satisfied, attributeResult will contain the last attribute of the original request
// TODO: do we want this?
return false, disjunction.ToDisclosedAttributeDisjunction(attributeResult)
attr := disjunction.Attributes[*disjunction.index]
return !disjunction.HasValues() || disjunction.value == disjunction.Values[attr]
return false
}
// MatchesConfig returns true if all attributes contained in the disjunction are
......@@ -413,10 +402,10 @@ func (disjunction *AttributeDisjunction) MatchesConfig(conf *Configuration) bool
return true
}
// Satisfied indicates whether each contained attribute disjunction has a chosen attribute.
func (dl AttributeDisjunctionList) Satisfied() bool {
// satisfied indicates whether each contained attribute disjunction has a chosen attribute.
func (dl AttributeDisjunctionList) satisfied() bool {
for _, disjunction := range dl {
if !disjunction.Satisfied() {
if !disjunction.satisfied() {
return false
}
}
......@@ -458,27 +447,6 @@ func (disjunction *AttributeDisjunction) MarshalJSON() ([]byte, error) {
return json.Marshal(temp)
}
// Since we have a custom MarshalJSON for AttributeDisjunction, we also need one for every struct that extends AttributeDisjunction...
func (disclosedAttributeDisjunction *DisclosedAttributeDisjunction) MarshalJSON() ([]byte, error) {
temp := struct {
Label string `json:"label"`
Attributes []AttributeTypeIdentifier `json:"attributes"`
DisclosedValue string `json:"disclosedValue"`
DisclosedId AttributeTypeIdentifier `json:"disclosedId"`
ProofStatus AttributeProofStatus `json:"proofStatus"`
}{
Label: disclosedAttributeDisjunction.Label,
Attributes: disclosedAttributeDisjunction.Attributes,
DisclosedValue: disclosedAttributeDisjunction.DisclosedValue,
DisclosedId: disclosedAttributeDisjunction.DisclosedId,
ProofStatus: disclosedAttributeDisjunction.ProofStatus,
}
return json.Marshal(temp)
}
// UnmarshalJSON unmarshals an attribute disjunction from JSON.
func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
if disjunction.Values == nil {
......@@ -531,19 +499,3 @@ func (disjunction *AttributeDisjunction) UnmarshalJSON(bytes []byte) error {
return nil
}
func (al *AttributeResultList) String() string {
// TODO: pretty print?
str := "Attribute --- Value --- ProofStatus:"
for _, v := range *al {
str = str + "\n" + v.String()
}
return str
}
func (ar *AttributeResult) String() string {
return fmt.Sprintf("%v --- %v --- %v",
ar.AttributeId,
ar.AttributeValue,
ar.AttributeProofStatus)
}
package irma
import (
"math/big"
"fmt"
"strings"
"time"
"github.com/mhe/gabi/big"
)
// CredentialInfo contains all information of an IRMA credential.
type CredentialInfo struct {
CredentialTypeID CredentialTypeIdentifier // e.g., "irma-demo.RU.studentCard"
Name string // e.g., "studentCard"
IssuerID IssuerIdentifier // e.g., "RU"
SchemeManagerID SchemeManagerIdentifier // e.g., "irma-demo"
Index int // This is the Index-th credential instance of this type
SignedOn Timestamp // Unix timestamp
Expires Timestamp // Unix timestamp
Attributes []TranslatedString // Human-readable rendered attributes
Logo string // Path to logo on storage
Hash string // SHA256 hash over the attributes
ID string // e.g., "studentCard"
IssuerID string // e.g., "RU"
SchemeManagerID string // e.g., "irma-demo"
SignedOn Timestamp // Unix timestamp
Expires Timestamp // Unix timestamp
Attributes map[AttributeTypeIdentifier]TranslatedString // Human-readable rendered attributes
Hash string // SHA256 hash over the attributes
}
// A CredentialInfoList is a list of credentials (implements sort.Interface).
......@@ -34,20 +33,18 @@ func NewCredentialInfo(ints []*big.Int, conf *Configuration) *CredentialInfo {
id := credtype.Identifier()
issid := id.IssuerIdentifier()
return &CredentialInfo{
CredentialTypeID: NewCredentialTypeIdentifier(id.String()),
Name: id.Name(),
IssuerID: NewIssuerIdentifier(issid.Name()),
SchemeManagerID: NewSchemeManagerIdentifier(issid.SchemeManagerIdentifier().String()),
SignedOn: Timestamp(meta.SigningDate()),
Expires: Timestamp(meta.Expiry()),
Attributes: attrs.Strings(),
Logo: credtype.Logo(conf),
Hash: attrs.Hash(),
ID: id.Name(),
IssuerID: issid.Name(),
SchemeManagerID: issid.SchemeManagerIdentifier().Name(),
SignedOn: Timestamp(meta.SigningDate()),
Expires: Timestamp(meta.Expiry()),
Attributes: attrs.Map(conf),
Hash: attrs.Hash(),
}
}
func (ci CredentialInfo) GetCredentialType(conf *Configuration) *CredentialType {
return conf.CredentialTypes[ci.CredentialTypeID]
return conf.CredentialTypes[NewCredentialTypeIdentifier(fmt.Sprintf("%s.%s.%s", ci.SchemeManagerID, ci.IssuerID, ci.ID))]
}
// Returns true if credential is expired at moment of calling this function
......@@ -68,5 +65,5 @@ func (cl CredentialInfoList) Swap(i, j int) {
// Less implements sort.Interface.
func (cl CredentialInfoList) Less(i, j int) bool {
// TODO Decide on sorting, and if it depends on a irmago.TranslatedString, allow language choosing
return strings.Compare(cl[i].Name, cl[j].Name) > 0
return strings.Compare(cl[i].ID, cl[j].ID) > 0
}
......@@ -40,8 +40,7 @@ type Issuer struct {
SchemeManagerID string `xml:"SchemeManager"`
ContactAddress string
ContactEMail string
URL string `xml:"baseURL"`
XMLVersion int `xml:"version,attr"`
XMLVersion int `xml:"version,attr"`
Valid bool `xml:"-"`
}
......@@ -55,27 +54,34 @@ type CredentialType struct {
SchemeManagerID string `xml:"SchemeManager"`
IsSingleton bool `xml:"ShouldBeSingleton"`
Description TranslatedString
Attributes []AttributeDescription `xml:"Attributes>Attribute"`
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"IssueSpecification"`
AttributeTypes []*AttributeType `xml:"Attributes>Attribute" json:"-"`
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"IssueSpecification"`
Valid bool `xml:"-"`
}
// AttributeDescription is a description of an attribute within a credential type.
type AttributeDescription struct {
ID string `xml:"id,attr"`
Optional string `xml:"optional,attr" json:",omitempty"`
DisplayIndex *int `xml:"displayIndex,attr" json:",omitempty"`
Name TranslatedString
Description TranslatedString
// AttributeType is a description of an attribute within a credential type.
type AttributeType struct {
ID string `xml:"id,attr"`
Optional string `xml:"optional,attr" json:",omitempty"`
Name TranslatedString
Description TranslatedString
Index int `xml:"-"`
DisplayIndex *int `xml:"displayIndex,attr" json:",omitempty"`
// Taken from containing CredentialType
CredentialTypeID string `xml:"-"`
IssuerID string `xml:"-"`
SchemeManagerID string `xml:"-"`
}
func (ad AttributeDescription) GetAttributeTypeIdentifier(cred CredentialTypeIdentifier) AttributeTypeIdentifier {
return NewAttributeTypeIdentifier(cred.String() + "." + ad.ID)
func (ad AttributeType) GetAttributeTypeIdentifier() AttributeTypeIdentifier {
return NewAttributeTypeIdentifier(fmt.Sprintf("%s.%s.%s.%s", ad.SchemeManagerID, ad.IssuerID, ad.CredentialTypeID, ad.ID))
}
func (ad AttributeDescription) IsOptional() bool {
func (ad AttributeType) IsOptional() bool {
return ad.Optional == "true"
}
......@@ -85,7 +91,7 @@ func (ct *CredentialType) ContainsAttribute(ai AttributeTypeIdentifier) bool {
if ai.CredentialTypeIdentifier().String() != ct.Identifier().String() {
return false
}
for _, desc := range ct.Attributes {
for _, desc := range ct.AttributeTypes {
if desc.ID == ai.Name() {
return true
}
......@@ -99,7 +105,7 @@ func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
if ai.CredentialTypeIdentifier() != ct.Identifier() {
return -1, errors.New("Wrong credential type")
}
for i, description := range ct.Attributes {
for i, description := range ct.AttributeTypes {
if description.ID == ai.Name() {
return i, nil
}
......@@ -107,12 +113,12 @@ func (ct CredentialType) IndexOf(ai AttributeTypeIdentifier) (int, error) {
return -1, errors.New("Attribute identifier not found")
}
func (ct CredentialType) AttributeDescription(ai AttributeTypeIdentifier) *AttributeDescription {
func (ct CredentialType) AttributeType(ai AttributeTypeIdentifier) *AttributeType {
i, _ := ct.IndexOf(ai)
if i == -1 {
return nil
}
return &ct.Attributes[i]
return ct.AttributeTypes[i]
}
// TranslatedString is a map of translated strings.
......
......@@ -144,12 +144,11 @@ func (id IssuerIdentifier) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
// TODO enable this when updating protocol
//// UnmarshalText implements encoding.TextUnmarshaler.
//func (id *IssuerIdentifier) UnmarshalText(text []byte) error {
// *id = NewIssuerIdentifier(string(text))
// return nil
//}
// UnmarshalText implements encoding.TextUnmarshaler.
func (id *IssuerIdentifier) UnmarshalText(text []byte) error {
*id = NewIssuerIdentifier(string(text))
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (id CredentialTypeIdentifier) MarshalText() ([]byte, error) {
......
package sessiontest
import (
"encoding/json"
"testing"
"github.com/pkg/errors"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/irmaclient"
)
type TestClientHandler struct {
t *testing.T
c chan error
}
func (i *TestClientHandler) UpdateConfiguration(new *irma.IrmaIdentifierSet) {}
func (i *TestClientHandler) UpdateAttributes() {}
func (i *TestClientHandler) EnrollmentSuccess(manager irma.SchemeManagerIdentifier) {
select {
case i.c <- nil: // nop
default: // nop
}
}
func (i *TestClientHandler) EnrollmentFailure(manager irma.SchemeManagerIdentifier, err error) {
select {
case i.c <- err: // nop
default:
i.t.Fatal(err)
}
}
func (i *TestClientHandler) ChangePinSuccess(manager irma.SchemeManagerIdentifier) {
select {
case i.c <- nil: // nop
default: // nop
}
}
func (i *TestClientHandler) ChangePinFailure(manager irma.SchemeManagerIdentifier, err error) {
select {
case i.c <- err: //nop
default:
i.t.Fatal(err)
}
}
func (i *TestClientHandler) ChangePinIncorrect(manager irma.SchemeManagerIdentifier, attempts int) {
err := errors.New("incorrect pin")
select {
case i.c <- err: //nop
default:
i.t.Fatal(err)
}
}
func (i *TestClientHandler) ChangePinBlocked(manager irma.SchemeManagerIdentifier, timeout int) {
err := errors.New("blocked account")
select {
case i.c <- err: //nop
default:
i.t.Fatal(err)
}
}
type TestHandler struct {
t *testing.T
c chan *SessionResult
client *irmaclient.Client
}
func (th TestHandler) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
th.Failure(&irma.SessionError{Err: errors.New("KeyshareEnrollmentIncomplete")})
}
func (th TestHandler) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
th.Failure(&irma.SessionError{Err: errors.New("KeyshareBlocked")})
}
func (th TestHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
th.Failure(&irma.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
}
func (th TestHandler) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
th.Failure(&irma.SessionError{Err: errors.Errorf("Keyshare enrollment deleted for %s", manager.String())})
}
func (th TestHandler) StatusUpdate(action irma.Action, status irma.Status) {}
func (th TestHandler) Success(result string) {
th.c <- nil
}
func (th TestHandler) Cancelled() {
th.Failure(&irma.SessionError{Err: errors.New("Cancelled")})
}
func (th TestHandler) Failure(err *irma.SessionError) {
th.t.Logf("Session failed: %+v\n", *err)
select {
case th.c <- &SessionResult{Err: err}:
default:
th.t.Fatal(err)
}
}
func (th TestHandler) UnsatisfiableRequest(serverName string, missing irma.AttributeDisjunctionList) {
th.Failure(&irma.SessionError{
ErrorType: irma.ErrorType("UnsatisfiableRequest"),
})
}
func (th TestHandler) RequestVerificationPermission(request irma.DisclosureRequest, ServerName string, callback irmaclient.PermissionHandler) {
choice := &irma.DisclosureChoice{
Attributes: []*irma.AttributeIdentifier{},
}
var candidates []*irma.AttributeIdentifier
for _, disjunction := range request.Content {
candidates = th.client.Candidates(disjunction)
if len(candidates) == 0 {
th.Failure(&irma.SessionError{Err: errors.New("No disclosure candidates found")})
}
choice.Attributes = append(choice.Attributes, candidates[0])
}
callback(true, choice)
}
func (th TestHandler) RequestIssuancePermission(request irma.IssuanceRequest, ServerName string, callback irmaclient.PermissionHandler) {
dreq := irma.DisclosureRequest{
BaseRequest: request.BaseRequest,
Content: request.Disclose,
}
th.RequestVerificationPermission(dreq, ServerName, callback)
}
func (th TestHandler) RequestSignaturePermission(request irma.SignatureRequest, ServerName string, callback irmaclient.PermissionHandler) {