Commit 903aa88c authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Support manual disclosure sessions

parent 1fc0ae14
...@@ -4,6 +4,7 @@ import ( ...@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"testing" "testing"
"github.com/mhe/gabi"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/privacybydesign/irmago" "github.com/privacybydesign/irmago"
) )
...@@ -128,14 +129,20 @@ func (th TestHandler) RequestPin(remainingAttempts int, callback PinHandler) { ...@@ -128,14 +129,20 @@ func (th TestHandler) RequestPin(remainingAttempts int, callback PinHandler) {
} }
type SessionResult struct { type SessionResult struct {
Err error Err error
Result *irma.SignedMessage SignatureResult *irma.SignedMessage
VerificationResult gabi.ProofList
} }
// ManualTestHandler embeds a TestHandler to inherit its methods. // ManualTestHandler embeds a TestHandler to inherit its methods.
// Below we overwrite the methods that require behaviour specific to manual settings. // Below we overwrite the methods that require behaviour specific to manual settings.
type ManualTestHandler struct { type ManualTestHandler struct {
TestHandler TestHandler
action irma.Action
}
func (th *ManualTestHandler) StatusUpdate(action irma.Action, status irma.Status) {
th.action = action
} }
func (th *ManualTestHandler) Success(result string) { func (th *ManualTestHandler) Success(result string) {
...@@ -143,9 +150,22 @@ func (th *ManualTestHandler) Success(result string) { ...@@ -143,9 +150,22 @@ func (th *ManualTestHandler) Success(result string) {
th.c <- nil th.c <- nil
return return
} }
irmaSignedMessage := &irma.SignedMessage{}
if err := json.Unmarshal([]byte(result), irmaSignedMessage); err != nil { var err error
retval := &SessionResult{}
switch th.action {
case irma.ActionSigning:
retval.SignatureResult = &irma.SignedMessage{}
err = json.Unmarshal([]byte(result), retval.SignatureResult)
case irma.ActionDisclosing:
proofs := []*gabi.ProofD{}
err = json.Unmarshal([]byte(result), &proofs)
retval.VerificationResult = make(gabi.ProofList, len(proofs))
for i, proof := range proofs {
retval.VerificationResult[i] = proof
}
}
if err != nil {
th.Failure(&irma.SessionError{ th.Failure(&irma.SessionError{
Err: err, Err: err,
ErrorType: irma.ErrorSerialization, ErrorType: irma.ErrorSerialization,
...@@ -153,17 +173,10 @@ func (th *ManualTestHandler) Success(result string) { ...@@ -153,17 +173,10 @@ func (th *ManualTestHandler) Success(result string) {
return return
} }
th.c <- &SessionResult{ th.c <- retval
Result: irmaSignedMessage,
}
} }
func (th *ManualTestHandler) RequestSignaturePermission(request irma.SignatureRequest, requesterName string, ph PermissionHandler) { func (th *ManualTestHandler) RequestSignaturePermission(request irma.SignatureRequest, requesterName string, ph PermissionHandler) {
var attributes []*irma.AttributeIdentifier th.RequestVerificationPermission(request.DisclosureRequest, requesterName, ph)
for _, cand := range request.Candidates {
attributes = append(attributes, cand[0])
}
c := irma.DisclosureChoice{attributes}
ph(true, &c)
} }
func (th *ManualTestHandler) RequestIssuancePermission(request irma.IssuanceRequest, issuerName string, ph PermissionHandler) { func (th *ManualTestHandler) RequestIssuancePermission(request irma.IssuanceRequest, issuerName string, ph PermissionHandler) {
ph(true, nil) ph(true, nil)
...@@ -174,5 +187,10 @@ func (th *ManualTestHandler) RequestSchemeManagerPermission(manager *irma.Scheme ...@@ -174,5 +187,10 @@ func (th *ManualTestHandler) RequestSchemeManagerPermission(manager *irma.Scheme
th.Failure(&irma.SessionError{Err: errors.New("Unexpected session type")}) th.Failure(&irma.SessionError{Err: errors.New("Unexpected session type")})
} }
func (th *ManualTestHandler) RequestVerificationPermission(request irma.DisclosureRequest, verifierName string, ph PermissionHandler) { func (th *ManualTestHandler) RequestVerificationPermission(request irma.DisclosureRequest, verifierName string, ph PermissionHandler) {
th.Failure(&irma.SessionError{Err: errors.New("Unexpected session type")}) var attributes []*irma.AttributeIdentifier
for _, cand := range request.Candidates {
attributes = append(attributes, cand[0])
}
c := irma.DisclosureChoice{attributes}
ph(true, &c)
} }
...@@ -35,19 +35,29 @@ func manualSessionHelper(t *testing.T, client *Client, h *ManualTestHandler, req ...@@ -35,19 +35,29 @@ func manualSessionHelper(t *testing.T, client *Client, h *ManualTestHandler, req
require.NoError(t, result.Err) require.NoError(t, result.Err)
} }
var verifyasRequest *irma.SignatureRequest switch h.action {
if verifyAs != "" { case irma.ActionDisclosing:
verifyasRequest = &irma.SignatureRequest{} verifyasRequest := &irma.DisclosureRequest{}
err := json.Unmarshal([]byte(verifyAs), verifyasRequest) err := json.Unmarshal([]byte(verifyAs), verifyasRequest)
require.NoError(t, err) require.NoError(t, err)
return irma.ProofList(result.VerificationResult).Verify(client.Configuration, verifyasRequest)
case irma.ActionSigning:
var verifyasRequest *irma.SignatureRequest
if verifyAs != "" {
verifyasRequest = &irma.SignatureRequest{}
err := json.Unmarshal([]byte(verifyAs), verifyasRequest)
require.NoError(t, err)
}
if corrupt {
// Interesting: modifying C results in INVALID_CRYPTO; modifying A or an attribute results in INVALID_TIMESTAMP
i := result.SignatureResult.Signature[0].(*gabi.ProofD).C
i.Add(i, big.NewInt(16))
}
return result.SignatureResult.Verify(client.Configuration, verifyasRequest)
default:
return nil
} }
if corrupt {
// Interesting: modifying C results in INVALID_CRYPTO; modifying A or an attribute results in INVALID_TIMESTAMP
i := result.Result.Signature[0].(*gabi.ProofD).C
i.Add(i, big.NewInt(16))
}
return result.Result.Verify(client.Configuration, verifyasRequest)
} }
func TestManualSession(t *testing.T) { func TestManualSession(t *testing.T) {
...@@ -153,3 +163,31 @@ func TestManualSessionInvalidProof(t *testing.T) { ...@@ -153,3 +163,31 @@ func TestManualSessionInvalidProof(t *testing.T) {
test.ClearTestStorage(t) test.ClearTestStorage(t)
} }
func TestManualDisclosureSession(t *testing.T) {
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"disclosing\", \"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(t, nil)
result := manualSessionHelper(t, nil, ms, request, request, false)
require.Equal(t, irma.AttributeProofStatusPresent, result.Attributes[0].Status)
require.Equal(t, "456", result.Attributes[0].Value["en"])
require.Equal(t, irma.ProofStatusValid, result.Status)
test.ClearTestStorage(t)
}
// Test if proof verification fails with status 'MISSING_ATTRIBUTES' if we provide it with a non-matching disclosure request
func TestManualDisclosureSessionInvalidRequest(t *testing.T) {
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"disclosing\", \"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"type\": \"disclosing\", \"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.university\"]}]}"
ms := createManualSessionHandler(t, nil)
result := manualSessionHelper(t, nil, ms, request, invalidRequest, false)
require.Equal(t, irma.ProofStatusMissingAttributes, result.Status)
// First attribute result is MISSING, because it is in the request but not disclosed
require.Equal(t, irma.AttributeProofStatusMissing, result.Attributes[0].Status)
// Second attribute result is EXTRA, since it is disclosed, but not matching the sigrequest
require.Equal(t, irma.AttributeProofStatusExtra, result.Attributes[1].Status)
test.ClearTestStorage(t)
}
...@@ -104,7 +104,12 @@ func (client *Client) NewSession(sessionrequest string, handler Handler) Session ...@@ -104,7 +104,12 @@ func (client *Client) NewSession(sessionrequest string, handler Handler) Session
sigRequest := &irma.SignatureRequest{} sigRequest := &irma.SignatureRequest{}
if err := irma.UnmarshalValidate(bts, sigRequest); err == nil { if err := irma.UnmarshalValidate(bts, sigRequest); err == nil {
return client.newManualSession(sigRequest, handler) return client.newManualSession(sigRequest, handler, irma.ActionSigning)
}
disclosureRequest := &irma.DisclosureRequest{}
if err := irma.UnmarshalValidate(bts, disclosureRequest); err == nil {
return client.newManualSession(disclosureRequest, handler, irma.ActionDisclosing)
} }
handler.Failure(&irma.SessionError{Err: errors.New("Session request could not be parsed")}) handler.Failure(&irma.SessionError{Err: errors.New("Session request could not be parsed")})
...@@ -112,14 +117,14 @@ func (client *Client) NewSession(sessionrequest string, handler Handler) Session ...@@ -112,14 +117,14 @@ func (client *Client) NewSession(sessionrequest string, handler Handler) Session
} }
// newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to // newManualSession starts a manual session, given a signature request in JSON and a handler to pass messages to
func (client *Client) newManualSession(sigrequest *irma.SignatureRequest, handler Handler) SessionDismisser { func (client *Client) newManualSession(request irma.SessionRequest, handler Handler, action irma.Action) SessionDismisser {
session := &session{ session := &session{
Action: irma.ActionSigning, // TODO hardcoded for now Action: action,
Handler: handler, Handler: handler,
client: client, client: client,
Version: minVersion, Version: minVersion,
ServerName: "Email request", ServerName: "",
request: sigrequest, request: request,
} }
session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted) session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
...@@ -308,13 +313,7 @@ func (session *session) sendResponse(message interface{}) { ...@@ -308,13 +313,7 @@ func (session *session) sendResponse(message interface{}) {
switch session.Action { switch session.Action {
case irma.ActionSigning: case irma.ActionSigning:
request, ok := session.request.(*irma.SignatureRequest) irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message)
if !ok {
session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
return
}
irmaSignature, err := request.SignatureFromMessage(message)
if err != nil { if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"}) session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
return return
...@@ -339,14 +338,21 @@ func (session *session) sendResponse(message interface{}) { ...@@ -339,14 +338,21 @@ func (session *session) sendResponse(message interface{}) {
} }
log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
case irma.ActionDisclosing: case irma.ActionDisclosing:
var response disclosureResponse messageJson, err = json.Marshal(message)
if err = session.transport.Post("proofs", &response, message); err != nil { if err != nil {
session.fail(err.(*irma.SessionError)) session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
return return
} }
if response != "VALID" { if session.IsInteractive() {
session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)}) var response disclosureResponse
return if err = session.transport.Post("proofs", &response, message); err != nil {
session.fail(err.(*irma.SessionError))
return
}
if response != "VALID" {
session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
return
}
} }
log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
case irma.ActionIssuing: case irma.ActionIssuing:
......
...@@ -70,8 +70,8 @@ func (pl ProofList) extractPublicKeys(configuration *Configuration) ([]*gabi.Pub ...@@ -70,8 +70,8 @@ func (pl ProofList) extractPublicKeys(configuration *Configuration) ([]*gabi.Pub
return publicKeys, nil return publicKeys, nil
} }
// Verify the proofs cryptographically. // VerifyProofs verifies the proofs cryptographically.
func (pl ProofList) Verify(configuration *Configuration, context *big.Int, nonce *big.Int, isSig bool) bool { func (pl ProofList) VerifyProofs(configuration *Configuration, context *big.Int, nonce *big.Int, isSig bool) bool {
// Extract public keys // Extract public keys
pks, err := pl.extractPublicKeys(configuration) pks, err := pl.extractPublicKeys(configuration)
if err != nil { if err != nil {
...@@ -172,6 +172,35 @@ func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctio ...@@ -172,6 +172,35 @@ func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctio
return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil return len(disjunctions) == 0 || disjunctions.satisfied(), list, nil
} }
func (pl ProofList) verifyAgainstDisjunctions(configuration *Configuration, required AttributeDisjunctionList, nonce, context *big.Int, issig bool) ([]*DisclosedAttribute, ProofStatus) {
// Cryptographically verify the IRMA disclosure proofs in the signature
if !pl.VerifyProofs(configuration, nonce, context, issig) {
return nil, ProofStatusInvalidCrypto
}
// Next extract the contained attributes from the proofs, and match them to the signature request if present
allmatched, list, err := pl.DisclosedAttributes(configuration, required)
if err != nil {
return nil, ProofStatusInvalidCrypto
}
// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
// This status takes priority over 'EXPIRED'
if !allmatched {
return list, ProofStatusMissingAttributes
}
return list, ProofStatusValid
}
func (pl ProofList) Verify(configuration *Configuration, request *DisclosureRequest) *VerificationResult {
list, status := pl.verifyAgainstDisjunctions(configuration, request.Content, request.Nonce, request.Context, false)
return &VerificationResult{
Attributes: list,
Status: status,
}
}
// Verify the attribute-based signature, optionally against a corresponding signature request. If the request is present // Verify the attribute-based signature, optionally against a corresponding signature request. If the request is present
// (i.e. not nil), then the first attributes in the returned result match with the disjunction list in the request // (i.e. not nil), then the first attributes in the returned result match with the disjunction list in the request
// (that is, the i'th attribute in the result should satisfy the i'th disjunction in the request). If the request is not // (that is, the i'th attribute in the result should satisfy the i'th disjunction in the request). If the request is not
...@@ -208,29 +237,13 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature ...@@ -208,29 +237,13 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature
} }
// Now, cryptographically verify the IRMA disclosure proofs in the signature // Now, cryptographically verify the IRMA disclosure proofs in the signature
proofList := ProofList(sm.Signature) pl := ProofList(sm.Signature)
if !proofList.Verify(configuration, sm.Context, sm.GetNonce(), true) { var required AttributeDisjunctionList
result.Status = ProofStatusInvalidCrypto
return
}
// Next extract the contained attributes from the signature, and match them to the signature request if present
var allmatched bool
var err error
var disjunctions AttributeDisjunctionList
if request != nil { if request != nil {
disjunctions = request.Content required = request.Content
} }
allmatched, result.Attributes, err = proofList.DisclosedAttributes(configuration, disjunctions) result.Attributes, result.Status = pl.verifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), true)
if err != nil { if result.Status != ProofStatusValid {
result.Status = ProofStatusInvalidCrypto
return
}
// Return MISSING_ATTRIBUTES as proofstatus if one of the disjunctions in the request (if present) is not satisfied
// This status takes priority over 'EXPIRED'
if !allmatched {
result.Status = ProofStatusMissingAttributes
return return
} }
...@@ -239,7 +252,7 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature ...@@ -239,7 +252,7 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature
if sm.Timestamp != nil { if sm.Timestamp != nil {
t = time.Unix(sm.Timestamp.Time, 0) t = time.Unix(sm.Timestamp.Time, 0)
} }
expired, err := proofList.Expired(configuration, &t) expired, err := pl.Expired(configuration, &t)
if err != nil { if err != nil {
result.Status = ProofStatusInvalidCrypto result.Status = ProofStatusInvalidCrypto
return 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