Commit e841481d authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Disclosure now works, fixing many mistakes

parent 65876b5f
...@@ -227,7 +227,6 @@ func shortToByte(x int) []byte { ...@@ -227,7 +227,6 @@ func shortToByte(x int) []byte {
// A DisclosureChoice contains the attributes chosen to be disclosed. // A DisclosureChoice contains the attributes chosen to be disclosed.
type DisclosureChoice struct { type DisclosureChoice struct {
Session SessionRequest
Attributes []*AttributeIdentifier Attributes []*AttributeIdentifier
} }
......
...@@ -13,5 +13,6 @@ func newCredential(gabicred *gabi.Credential) (cred *Credential) { ...@@ -13,5 +13,6 @@ func newCredential(gabicred *gabi.Credential) (cred *Credential) {
cred = &Credential{} cred = &Credential{}
cred.Credential = gabicred cred.Credential = gabicred
cred.MetadataAttribute = MetadataFromInt(gabicred.Attributes[1]) cred.MetadataAttribute = MetadataFromInt(gabicred.Attributes[1])
cred.Pk = MetaStore.PublicKey(cred.CredentialType().IssuerIdentifier(), cred.KeyCounter())
return return
} }
...@@ -103,3 +103,23 @@ func (id AttributeTypeIdentifier) MarshalJSON() ([]byte, error) { ...@@ -103,3 +103,23 @@ func (id AttributeTypeIdentifier) MarshalJSON() ([]byte, error) {
func (id CredentialTypeIdentifier) MarshalJSON() ([]byte, error) { func (id CredentialTypeIdentifier) MarshalJSON() ([]byte, error) {
return json.Marshal(id.String()) return json.Marshal(id.String())
} }
func (id AttributeTypeIdentifier) UnmarshalJSON(b []byte) error {
var val string
err := json.Unmarshal(b, &val)
if err != nil {
return err
}
id.metaObjectIdentifier = metaObjectIdentifier(val)
return nil
}
func (id CredentialTypeIdentifier) UnmarshalJSON(b []byte) error {
var val string
err := json.Unmarshal(b, &val)
if err != nil {
return err
}
id.metaObjectIdentifier = metaObjectIdentifier(val)
return nil
}
...@@ -30,7 +30,7 @@ func parseMetaStore(t *testing.T) { ...@@ -30,7 +30,7 @@ func parseMetaStore(t *testing.T) {
} }
func parseStorage(t *testing.T) { func parseStorage(t *testing.T) {
exists, err := pathExists("testdata/storage/path") exists, err := PathExists("testdata/storage/path")
require.NoError(t, err, "pathexists() failed") require.NoError(t, err, "pathexists() failed")
if !exists { if !exists {
require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage") require.NoError(t, os.Mkdir("testdata/storage/test", 0755), "Could not create test storage")
......
...@@ -79,7 +79,7 @@ func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int ...@@ -79,7 +79,7 @@ func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int
} }
func (cm *CredentialManager) CredentialByID(id CredentialIdentifier) (cred *Credential, err error) { func (cm *CredentialManager) CredentialByID(id CredentialIdentifier) (cred *Credential, err error) {
return cm.Credential(id.Type, id.Count) return cm.Credential(id.Type, id.Index)
} }
// Credential returns the requested credential, or nil if we do not have it. // Credential returns the requested credential, or nil if we do not have it.
...@@ -122,7 +122,7 @@ func (cm *CredentialManager) Credential(id CredentialTypeIdentifier, counter int ...@@ -122,7 +122,7 @@ func (cm *CredentialManager) Credential(id CredentialTypeIdentifier, counter int
// and saving them to storage. // and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage. // CAREFUL: this method overwrites any existing secret keys and attributes on storage.
func (cm *CredentialManager) ParseAndroidStorage() (err error) { func (cm *CredentialManager) ParseAndroidStorage() (err error) {
exists, err := pathExists(cm.path(cardemuXML)) exists, err := PathExists(cm.path(cardemuXML))
if err != nil || !exists { if err != nil || !exists {
return return
} }
...@@ -238,7 +238,7 @@ func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*At ...@@ -238,7 +238,7 @@ func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*At
} }
func (cm *CredentialManager) CheckSatisfiability(disjunctions DisjunctionListContainer) AttributeDisjunctionList { func (cm *CredentialManager) CheckSatisfiability(disjunctions DisjunctionListContainer) AttributeDisjunctionList {
missing := make(AttributeDisjunctionList, 5) missing := make(AttributeDisjunctionList, 0, 5)
for _, disjunction := range disjunctions.DisjunctionList() { for _, disjunction := range disjunctions.DisjunctionList() {
if len(cm.Candidates(disjunction)) == 0 { if len(cm.Candidates(disjunction)) == 0 {
missing = append(missing, disjunction) missing = append(missing, disjunction)
...@@ -257,12 +257,11 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre ...@@ -257,12 +257,11 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre
// If this is the first attribute of its credential type that we encounter // If this is the first attribute of its credential type that we encounter
// in the disclosure choice, then there is no slice yet at grouped[ici] // in the disclosure choice, then there is no slice yet at grouped[ici]
var indices []int
if _, present := grouped[ici]; !present { if _, present := grouped[ici]; !present {
indices = []int{1} // Always include metadata indices := make([]int, 1, 1)
indices[0] = 1 // Always include metadata
grouped[ici] = indices grouped[ici] = indices
} }
indices = grouped[ici]
if identifier.IsCredential() { if identifier.IsCredential() {
continue // In this case we only disclose the metadata attribute, which is already handled continue // In this case we only disclose the metadata attribute, which is already handled
...@@ -274,7 +273,7 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre ...@@ -274,7 +273,7 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre
// These indices will be used in the []*big.Int at gabi.Credential.Attributes, // These indices will be used in the []*big.Int at gabi.Credential.Attributes,
// which doesn't know about the secret key and metadata attribute, so +2 // which doesn't know about the secret key and metadata attribute, so +2
indices = append(indices, index+2) grouped[ici] = append(grouped[ici], index+2)
} }
return grouped, nil return grouped, nil
...@@ -285,7 +284,7 @@ type SessionRequest interface { ...@@ -285,7 +284,7 @@ type SessionRequest interface {
GetContext() *big.Int GetContext() *big.Int
} }
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, message *string) (gabi.ProofList, error) { func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request SessionRequest) (gabi.ProofList, error) {
todisclose, err := cm.groupCredentials(choice) todisclose, err := cm.groupCredentials(choice)
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -300,5 +299,5 @@ func (cm *CredentialManager) Proofs(choice *DisclosureChoice, message *string) ( ...@@ -300,5 +299,5 @@ func (cm *CredentialManager) Proofs(choice *DisclosureChoice, message *string) (
builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list)) builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
} }
return gabi.BuildProofList(choice.Session.GetContext(), choice.Session.GetNonce(), builders), nil return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders), nil
} }
...@@ -22,8 +22,15 @@ type Action string ...@@ -22,8 +22,15 @@ type Action string
// Version encodes the IRMA protocol version of an IRMA session. // Version encodes the IRMA protocol version of an IRMA session.
type Version string type Version string
// SessionError are session errors. // ErrorCode are session errors.
type SessionError string type ErrorCode string
type Error struct {
ErrorCode
error
info string
*ApiError
}
// Statuses // Statuses
const ( const (
...@@ -43,19 +50,19 @@ const ( ...@@ -43,19 +50,19 @@ const (
// Protocol errors // Protocol errors
const ( const (
// Protocol version not supported // Protocol version not supported
ErrorProtocolVersionNotSupported = SessionError("versionNotSupported") ErrorProtocolVersionNotSupported = ErrorCode("versionNotSupported")
// Server URL invalid // Server URL invalid
ErrorInvalidURL = SessionError("invalidUrl") ErrorInvalidURL = ErrorCode("invalidUrl")
// Error in HTTP communication // Error in HTTP communication
ErrorTransport = SessionError("httpError") ErrorTransport = ErrorCode("httpError")
// Invalid client JWT in first IRMA message // Invalid client JWT in first IRMA message
ErrorInvalidJWT = SessionError("invalidJwt") ErrorInvalidJWT = ErrorCode("invalidJwt")
// Unkown session type (not disclosing, signing, or issuing) // Unkown session type (not disclosing, signing, or issuing)
ErrorUnknownAction = SessionError("unknownAction") ErrorUnknownAction = ErrorCode("unknownAction")
// Crypto error during calculation of our response (second IRMA message) // Crypto error during calculation of our response (second IRMA message)
ErrorCrypto = SessionError("cryptoResponseError") ErrorCrypto = ErrorCode("cryptoResponseError")
// Server rejected our response (second IRMA message) // Server rejected our response (second IRMA message)
ErrorRejected = SessionError("rejectedByServer") ErrorRejected = ErrorCode("rejectedByServer")
) )
// Qr contains the data of an IRMA session QR. // Qr contains the data of an IRMA session QR.
...@@ -74,6 +81,14 @@ type SessionInfo struct { ...@@ -74,6 +81,14 @@ type SessionInfo struct {
Keys map[irmago.IssuerIdentifier]int `json:"keys"` Keys map[irmago.IssuerIdentifier]int `json:"keys"`
} }
func (e *Error) Error() string {
if e.error != nil {
return fmt.Sprintf("%s: %s", string(e.ErrorCode), e.error.Error())
} else {
return string(e.ErrorCode)
}
}
// MarshalJSON marshals a timestamp. // MarshalJSON marshals a timestamp.
func (t *Timestamp) MarshalJSON() ([]byte, error) { func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix() ts := time.Time(*t).Unix()
......
package protocol package protocol
import ( import (
"encoding/json"
"errors" "errors"
"math/big" "math/big"
"strconv" "strconv"
...@@ -11,6 +10,10 @@ import ( ...@@ -11,6 +10,10 @@ import (
"fmt" "fmt"
"encoding/json"
"encoding/base64"
"github.com/credentials/irmago" "github.com/credentials/irmago"
"github.com/mhe/gabi" "github.com/mhe/gabi"
) )
...@@ -22,7 +25,7 @@ type Handler interface { ...@@ -22,7 +25,7 @@ type Handler interface {
StatusUpdate(action Action, status Status) StatusUpdate(action Action, status Status)
Success(action Action) Success(action Action)
Cancelled(action Action) Cancelled(action Action)
Failure(action Action, error SessionError, info string) Failure(action Action, err *Error)
UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList) UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)
AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler) AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler)
...@@ -84,7 +87,7 @@ func calcVersion(qr *Qr) (string, error) { ...@@ -84,7 +87,7 @@ func calcVersion(qr *Qr) (string, error) {
func NewSession(qr *Qr, handler Handler) *Session { func NewSession(qr *Qr, handler Handler) *Session {
version, err := calcVersion(qr) version, err := calcVersion(qr)
if err != nil { if err != nil {
handler.Failure(ActionUnknown, ErrorProtocolVersionNotSupported, err.Error()) handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorProtocolVersionNotSupported, error: err})
return nil return nil
} }
...@@ -104,7 +107,7 @@ func NewSession(qr *Qr, handler Handler) *Session { ...@@ -104,7 +107,7 @@ func NewSession(qr *Qr, handler Handler) *Session {
case ActionUnknown: case ActionUnknown:
fallthrough fallthrough
default: default:
handler.Failure(ActionUnknown, ErrorUnknownAction, string(session.Action)) handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, error: nil, info: string(session.Action)})
return nil return nil
} }
...@@ -126,7 +129,7 @@ func (session *Session) start() { ...@@ -126,7 +129,7 @@ func (session *Session) start() {
info := &SessionInfo{} info := &SessionInfo{}
err := session.transport.Get("jwt", info) err := session.transport.Get("jwt", info)
if err != nil { if err != nil {
session.Handler.Failure(session.Action, ErrorTransport, err.Error()) session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr})
return return
} }
...@@ -134,24 +137,40 @@ func (session *Session) start() { ...@@ -134,24 +137,40 @@ func (session *Session) start() {
session.context = info.Context session.context = info.Context
jwtparts := strings.Split(info.Jwt, ".") jwtparts := strings.Split(info.Jwt, ".")
if jwtparts == nil || len(jwtparts) < 2 { if jwtparts == nil || len(jwtparts) < 2 {
session.Handler.Failure(session.Action, ErrorInvalidJWT, "") session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
return return
} }
headerbytes, err := base64.RawStdEncoding.DecodeString(jwtparts[0])
bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
return
}
var header struct { var header struct {
Server string `json:"iss"` Server string `json:"iss"`
} }
json.Unmarshal([]byte(jwtparts[0]), &header) json.Unmarshal([]byte(headerbytes), &header)
json.Unmarshal([]byte(jwtparts[1]), session.request)
switch session.Action { switch session.Action {
case ActionDisclosing: case ActionDisclosing:
session.spRequest = &ServiceProviderJwt{} session.spRequest = &ServiceProviderJwt{}
json.Unmarshal([]byte(bodybytes), session.spRequest)
session.spRequest.Request.Request.Context = session.context
session.spRequest.Request.Request.Nonce = session.nonce
session.request = session.spRequest session.request = session.spRequest
case ActionSigning: case ActionSigning:
session.ssRequest = &SignatureServerJwt{} session.ssRequest = &SignatureServerJwt{}
json.Unmarshal([]byte(bodybytes), session.ssRequest)
session.ssRequest.Request.Request.Context = session.context
session.ssRequest.Request.Request.Nonce = session.nonce
session.request = session.ssRequest session.request = session.ssRequest
case ActionIssuing: case ActionIssuing:
session.ipRequest = &IdentityProviderJwt{} session.ipRequest = &IdentityProviderJwt{}
json.Unmarshal([]byte(bodybytes), session.ipRequest)
session.ipRequest.Request.Request.Context = session.context
session.ipRequest.Request.Request.Nonce = session.nonce
session.request = session.ipRequest session.request = session.ipRequest
default: default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier panic("Invalid session type") // does not happen, session.Action has been checked earlier
...@@ -159,7 +178,7 @@ func (session *Session) start() { ...@@ -159,7 +178,7 @@ func (session *Session) start() {
if session.Action == ActionIssuing { if session.Action == ActionIssuing {
// Store which public keys the server will use // Store which public keys the server will use
for _, credreq := range session.request.(*IdentityProviderJwt).Request.Request.Credentials { for _, credreq := range session.ipRequest.Request.Request.Credentials {
credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()] credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
} }
} }
...@@ -198,21 +217,21 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) { ...@@ -198,21 +217,21 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
var err error var err error
switch session.Action { switch session.Action {
case ActionSigning: case ActionSigning:
proofs, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request.Message) proofs, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request)
case ActionDisclosing: case ActionDisclosing:
proofs, err = irmago.Manager.Proofs(choice, nil) proofs, err = irmago.Manager.Proofs(choice, &session.spRequest.Request.Request)
case ActionIssuing: case ActionIssuing:
err = errors.New("Issuing not yet implemented") err = errors.New("Issuing not yet implemented")
} }
if err != nil { if err != nil {
session.Handler.Failure(session.Action, ErrorCrypto, err.Error()) session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
return return
} }
var response string var response string
session.transport.Post("proofs", &response, proofs) session.transport.Post("proofs", &response, proofs)
if response != "VALID" { if response != "VALID" {
session.Handler.Failure(session.Action, ErrorRejected, response) session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
return return
} }
......
package protocol package protocol
import ( import (
"encoding/json"
"testing"
"encoding/base64" "encoding/base64"
"encoding/json"
"fmt" "fmt"
"os"
"testing"
"github.com/credentials/irmago" "github.com/credentials/irmago"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// Helper functions copypasted from irmago. AFAIK there is no way in go
// to reuse irmago test methods here without copypasting.
func TestMain(m *testing.M) {
retCode := m.Run()
err := os.RemoveAll("../testdata/storage/test")
if err != nil {
fmt.Println("Could not delete test storage")
os.Exit(1)
}
os.Exit(retCode)
}
func parseMetaStore(t *testing.T) {
require.NoError(t, irmago.MetaStore.ParseFolder("../testdata/irma_configuration"), "MetaStore.ParseFolder() failed")
}
func parseStorage(t *testing.T) {
exists, err := irmago.PathExists("../testdata/storage/path")
require.NoError(t, err, "pathexists() failed")
if !exists {
require.NoError(t, os.Mkdir("../testdata/storage/test", 0755), "Could not create test storage")
}
require.NoError(t, irmago.Manager.Init("../testdata/storage/test"), "Manager.Init() failed")
}
func parseAndroidStorage(t *testing.T) {
require.NoError(t, irmago.Manager.ParseAndroidStorage(), "ParseAndroidStorage() failed")
}
func teardown(t *testing.T) {
require.NoError(t, os.RemoveAll("../testdata/storage/test"))
}
type TestHandler struct { type TestHandler struct {
t *testing.T t *testing.T
c chan *Error
} }
func (th TestHandler) StatusUpdate(action Action, status Status) {} func (th TestHandler) StatusUpdate(action Action, status Status) {}
func (th TestHandler) Success(action Action) {} func (th TestHandler) Success(action Action) {
th.c <- nil
}
func (th TestHandler) Cancelled(action Action) { func (th TestHandler) Cancelled(action Action) {
th.t.FailNow() th.c <- &Error{}
} }
func (th TestHandler) Failure(action Action, err SessionError, info string) { func (th TestHandler) Failure(action Action, err *Error) {
th.t.Fatal(string(err), info) th.c <- err
} }
func (th TestHandler) UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList) { func (th TestHandler) UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList) {
th.t.FailNow() th.c <- &Error{}
} }
func (th TestHandler) AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler) { func (th TestHandler) AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler) {
} }
...@@ -46,8 +84,13 @@ func (th TestHandler) AskSignaturePermission(request SignatureRequest, ServerNam ...@@ -46,8 +84,13 @@ func (th TestHandler) AskSignaturePermission(request SignatureRequest, ServerNam
} }
func TestSession(t *testing.T) { func TestSession(t *testing.T) {
parseMetaStore(t)
parseStorage(t)
parseAndroidStorage(t)
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID") id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
url := "https://demo.irmacard.org/tomcat/irma_api_server/api/v2/verification" //url := "https://demo.irmacard.org/tomcat/irma_api_server/api/v2/verification"
url := "http://localhost:8081/irma_api_server/api/v2/verification"
name := "testsp" name := "testsp"
spRequest := NewServiceProviderJwt(name, DisclosureRequest{ spRequest := NewServiceProviderJwt(name, DisclosureRequest{
...@@ -58,20 +101,26 @@ func TestSession(t *testing.T) { ...@@ -58,20 +101,26 @@ func TestSession(t *testing.T) {
}, },
}), }),
}) })
fmt.Printf("%+v\n", spRequest.Request.Request.Content[0])
headerbytes, err := json.Marshal(&map[string]string{"alg": "none", "typ": "JWT"}) headerbytes, err := json.Marshal(&map[string]string{"alg": "none", "typ": "JWT"})
require.NoError(t, err) require.NoError(t, err)
bodybytes, err := json.Marshal(spRequest) bodybytes, err := json.Marshal(spRequest)
require.NoError(t, err) require.NoError(t, err)
jwt := base64.StdEncoding.EncodeToString(headerbytes) + "." + base64.StdEncoding.EncodeToString(bodybytes) + "." jwt := base64.RawStdEncoding.EncodeToString(headerbytes) + "." + base64.RawStdEncoding.EncodeToString(bodybytes) + "."
fmt.Println(jwt)
qr, transportErr := StartSession(jwt, url) qr, transportErr := StartSession(jwt, url)
if transportErr != nil { if transportErr != nil {
fmt.Println(transportErr.(*TransportError).ApiErr) fmt.Println(transportErr.(*TransportError).ApiErr)
} }
require.NoError(t, transportErr) require.NoError(t, transportErr)
qr.URL = url + "/" + qr.URL
c := make(chan *Error)
NewSession(qr, TestHandler{t, c})
if err := <-c; err != nil {
t.Fatal(*err)
}
NewSession(qr, TestHandler{t}) teardown(t)
} }
...@@ -54,14 +54,20 @@ func (transport *HTTPTransport) request(url string, method string, result interf ...@@ -54,14 +54,20 @@ func (transport *HTTPTransport) request(url string, method string, result interf
panic("Cannot GET and also post an object") panic("Cannot GET and also post an object")
} }
var isstr bool
var reader io.Reader var reader io.Reader
if object != nil { if object != nil {
var objstr string
if objstr, isstr = object.(string); isstr {
reader = bytes.NewBuffer([]byte(objstr))
} else {
marshaled, err := json.Marshal(object) marshaled, err := json.Marshal(object)
if err != nil {