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 {
// A DisclosureChoice contains the attributes chosen to be disclosed.
type DisclosureChoice struct {
Session SessionRequest
Attributes []*AttributeIdentifier
}
......
......@@ -13,5 +13,6 @@ func newCredential(gabicred *gabi.Credential) (cred *Credential) {
cred = &Credential{}
cred.Credential = gabicred
cred.MetadataAttribute = MetadataFromInt(gabicred.Attributes[1])
cred.Pk = MetaStore.PublicKey(cred.CredentialType().IssuerIdentifier(), cred.KeyCounter())
return
}
......@@ -103,3 +103,23 @@ func (id AttributeTypeIdentifier) MarshalJSON() ([]byte, error) {
func (id CredentialTypeIdentifier) MarshalJSON() ([]byte, error) {
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) {
}
func parseStorage(t *testing.T) {
exists, err := pathExists("testdata/storage/path")
exists, err := 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")
......
......@@ -79,7 +79,7 @@ func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int
}
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.
......@@ -122,7 +122,7 @@ func (cm *CredentialManager) Credential(id CredentialTypeIdentifier, counter int
// and saving them to storage.
// CAREFUL: this method overwrites any existing secret keys and attributes on storage.
func (cm *CredentialManager) ParseAndroidStorage() (err error) {
exists, err := pathExists(cm.path(cardemuXML))
exists, err := PathExists(cm.path(cardemuXML))
if err != nil || !exists {
return
}
......@@ -238,7 +238,7 @@ func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*At
}
func (cm *CredentialManager) CheckSatisfiability(disjunctions DisjunctionListContainer) AttributeDisjunctionList {
missing := make(AttributeDisjunctionList, 5)
missing := make(AttributeDisjunctionList, 0, 5)
for _, disjunction := range disjunctions.DisjunctionList() {
if len(cm.Candidates(disjunction)) == 0 {
missing = append(missing, disjunction)
......@@ -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
// in the disclosure choice, then there is no slice yet at grouped[ici]
var indices []int
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
}
indices = grouped[ici]
if identifier.IsCredential() {
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
// 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
indices = append(indices, index+2)
grouped[ici] = append(grouped[ici], index+2)
}
return grouped, nil
......@@ -285,7 +284,7 @@ type SessionRequest interface {
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)
if err != nil {
return nil, err
......@@ -300,5 +299,5 @@ func (cm *CredentialManager) Proofs(choice *DisclosureChoice, message *string) (
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
// Version encodes the IRMA protocol version of an IRMA session.
type Version string
// SessionError are session errors.
type SessionError string
// ErrorCode are session errors.
type ErrorCode string
type Error struct {
ErrorCode
error
info string
*ApiError
}
// Statuses
const (
......@@ -43,19 +50,19 @@ const (
// Protocol errors
const (
// Protocol version not supported
ErrorProtocolVersionNotSupported = SessionError("versionNotSupported")
ErrorProtocolVersionNotSupported = ErrorCode("versionNotSupported")
// Server URL invalid
ErrorInvalidURL = SessionError("invalidUrl")
ErrorInvalidURL = ErrorCode("invalidUrl")
// Error in HTTP communication
ErrorTransport = SessionError("httpError")
ErrorTransport = ErrorCode("httpError")
// Invalid client JWT in first IRMA message
ErrorInvalidJWT = SessionError("invalidJwt")
ErrorInvalidJWT = ErrorCode("invalidJwt")
// Unkown session type (not disclosing, signing, or issuing)
ErrorUnknownAction = SessionError("unknownAction")
ErrorUnknownAction = ErrorCode("unknownAction")
// Crypto error during calculation of our response (second IRMA message)
ErrorCrypto = SessionError("cryptoResponseError")
ErrorCrypto = ErrorCode("cryptoResponseError")
// Server rejected our response (second IRMA message)
ErrorRejected = SessionError("rejectedByServer")
ErrorRejected = ErrorCode("rejectedByServer")
)
// Qr contains the data of an IRMA session QR.
......@@ -74,6 +81,14 @@ type SessionInfo struct {
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.
func (t *Timestamp) MarshalJSON() ([]byte, error) {
ts := time.Time(*t).Unix()
......
package protocol
import (
"encoding/json"
"errors"
"math/big"
"strconv"
......@@ -11,6 +10,10 @@ import (
"fmt"
"encoding/json"
"encoding/base64"
"github.com/credentials/irmago"
"github.com/mhe/gabi"
)
......@@ -22,7 +25,7 @@ type Handler interface {
StatusUpdate(action Action, status Status)
Success(action Action)
Cancelled(action Action)
Failure(action Action, error SessionError, info string)
Failure(action Action, err *Error)
UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)
AskIssuancePermission(request IssuanceRequest, ServerName string, choice PermissionHandler)
......@@ -84,7 +87,7 @@ func calcVersion(qr *Qr) (string, error) {
func NewSession(qr *Qr, handler Handler) *Session {
version, err := calcVersion(qr)
if err != nil {
handler.Failure(ActionUnknown, ErrorProtocolVersionNotSupported, err.Error())
handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorProtocolVersionNotSupported, error: err})
return nil
}
......@@ -104,7 +107,7 @@ func NewSession(qr *Qr, handler Handler) *Session {
case ActionUnknown:
fallthrough
default:
handler.Failure(ActionUnknown, ErrorUnknownAction, string(session.Action))
handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, error: nil, info: string(session.Action)})
return nil
}
......@@ -126,7 +129,7 @@ func (session *Session) start() {
info := &SessionInfo{}
err := session.transport.Get("jwt", info)
if err != nil {
session.Handler.Failure(session.Action, ErrorTransport, err.Error())
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr})
return
}
......@@ -134,24 +137,40 @@ func (session *Session) start() {
session.context = info.Context
jwtparts := strings.Split(info.Jwt, ".")
if jwtparts == nil || len(jwtparts) < 2 {
session.Handler.Failure(session.Action, ErrorInvalidJWT, "")
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
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 {
Server string `json:"iss"`
}
json.Unmarshal([]byte(jwtparts[0]), &header)
json.Unmarshal([]byte(jwtparts[1]), session.request)
json.Unmarshal([]byte(headerbytes), &header)
switch session.Action {
case ActionDisclosing:
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
case ActionSigning:
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
case ActionIssuing:
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
default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier
......@@ -159,7 +178,7 @@ func (session *Session) start() {
if session.Action == ActionIssuing {
// 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()]
}
}
......@@ -198,21 +217,21 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
var err error
switch session.Action {
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:
proofs, err = irmago.Manager.Proofs(choice, nil)
proofs, err = irmago.Manager.Proofs(choice, &session.spRequest.Request.Request)
case ActionIssuing:
err = errors.New("Issuing not yet implemented")
}
if err != nil {
session.Handler.Failure(session.Action, ErrorCrypto, err.Error())
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
return
}
var response string
session.transport.Post("proofs", &response, proofs)
if response != "VALID" {
session.Handler.Failure(session.Action, ErrorRejected, response)
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
return
}
......
package protocol
import (
"encoding/json"
"testing"
"encoding/base64"
"encoding/json"
"fmt"
"os"
"testing"
"github.com/credentials/irmago"
"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 {
t *testing.T
c chan *Error
}
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) {
th.t.FailNow()
th.c <- &Error{}
}
func (th TestHandler) Failure(action Action, err SessionError, info string) {
th.t.Fatal(string(err), info)
func (th TestHandler) Failure(action Action, err *Error) {
th.c <- err
}
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) {
}
......@@ -46,8 +84,13 @@ func (th TestHandler) AskSignaturePermission(request SignatureRequest, ServerNam
}
func TestSession(t *testing.T) {
parseMetaStore(t)
parseStorage(t)
parseAndroidStorage(t)
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"
spRequest := NewServiceProviderJwt(name, DisclosureRequest{
......@@ -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"})
require.NoError(t, err)
bodybytes, err := json.Marshal(spRequest)
require.NoError(t, err)
jwt := base64.StdEncoding.EncodeToString(headerbytes) + "." + base64.StdEncoding.EncodeToString(bodybytes) + "."
fmt.Println(jwt)
jwt := base64.RawStdEncoding.EncodeToString(headerbytes) + "." + base64.RawStdEncoding.EncodeToString(bodybytes) + "."
qr, transportErr := StartSession(jwt, url)
if transportErr != nil {
fmt.Println(transportErr.(*TransportError).ApiErr)
}
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,13 +54,19 @@ func (transport *HTTPTransport) request(url string, method string, result interf
panic("Cannot GET and also post an object")
}
var isstr bool
var reader io.Reader
if object != nil {
marshaled, err := json.Marshal(object)
if err != nil {
return &TransportError{Err: err.Error()}
var objstr string
if objstr, isstr = object.(string); isstr {
reader = bytes.NewBuffer([]byte(objstr))
} else {
marshaled, err := json.Marshal(object)
if err != nil {
return &TransportError{Err: err.Error()}
}
reader = bytes.NewBuffer(marshaled)
}
reader = bytes.NewBuffer(marshaled)
}
req, err := http.NewRequest(method, transport.Server+url, reader)
......@@ -70,7 +76,11 @@ func (transport *HTTPTransport) request(url string, method string, result interf
req.Header.Set("User-Agent", "irmago")
if object != nil {
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
if isstr {
req.Header.Set("Content-Type", "text/plain; charset=UTF-8")
} else {
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
}
}
res, err := transport.client.Do(req)
......@@ -86,7 +96,7 @@ func (transport *HTTPTransport) request(url string, method string, result interf
apierr := &ApiError{}
json.Unmarshal(body, apierr)
if apierr.ErrorName == "" { // Not an ApiErrorMessage
return &TransportError{Err: err.Error(), Status: res.StatusCode}
return &TransportError{Status: res.StatusCode}
}
return &TransportError{Err: apierr.ErrorName, Status: res.StatusCode, ApiErr: apierr}
}
......
......@@ -23,7 +23,7 @@ const (
cardemuXML = "../cardemu.xml"
)
func pathExists(path string) (bool, error) {
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
......@@ -48,7 +48,7 @@ func (cm *CredentialManager) signatureFilename(id string, counter int) string {
// Setting it up in a properly protected location (e.g., with automatic
// backups to iCloud/Google disabled) is the responsibility of the user.
func (cm *CredentialManager) ensureStorageExists() (err error) {
exist, err := pathExists(cm.storagePath)
exist, err := PathExists(cm.storagePath)
if err != nil {
return
}
......@@ -56,7 +56,7 @@ func (cm *CredentialManager) ensureStorageExists() (err error) {
return errors.New("credential storage path does not exist")
}
exist, err = pathExists(cm.path(signaturesDir))
exist, err = PathExists(cm.path(signaturesDir))
if err != nil {
return err
}
......@@ -130,7 +130,7 @@ func (cm *CredentialManager) storeAttributes() (err error) {
func (cm *CredentialManager) loadSignature(id CredentialTypeIdentifier, counter int) (signature *gabi.CLSignature, err error) {
path := cm.signatureFilename(id.String(), counter)
exists, err := pathExists(path)
exists, err := PathExists(path)
if err != nil || !exists {
return
}
......@@ -143,7 +143,7 @@ func (cm *CredentialManager) loadSignature(id CredentialTypeIdentifier, counter
// loadSecretKey retrieves and returns the secret key from storage, or if no secret key
// was found in storage, it generates, saves, and returns a new secret key.
func (cm *CredentialManager) loadSecretKey() (*big.Int, error) {
exists, err := pathExists(cm.path(skFile))
exists, err := PathExists(cm.path(skFile))
if err != nil {
return nil, err
}
......@@ -170,7 +170,7 @@ func (cm *CredentialManager) loadAttributes() (list map[CredentialTypeIdentifier
list = make(map[CredentialTypeIdentifier][]*AttributeList)
temp := make(map[string][]*AttributeList)
exists, err := pathExists(cm.path(attributesFile))
exists, err := PathExists(cm.path(attributesFile))
if err != nil || !exists {
return
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment