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

Support manual disclosure sessions

parent 1fc0ae14
......@@ -4,6 +4,7 @@ import (
"encoding/json"
"testing"
"github.com/mhe/gabi"
"github.com/pkg/errors"
"github.com/privacybydesign/irmago"
)
......@@ -128,14 +129,20 @@ func (th TestHandler) RequestPin(remainingAttempts int, callback PinHandler) {
}
type SessionResult struct {
Err error
Result *irma.SignedMessage
Err error
SignatureResult *irma.SignedMessage
VerificationResult gabi.ProofList
}
// ManualTestHandler embeds a TestHandler to inherit its methods.
// Below we overwrite the methods that require behaviour specific to manual settings.
type ManualTestHandler struct {
TestHandler
action irma.Action
}
func (th *ManualTestHandler) StatusUpdate(action irma.Action, status irma.Status) {
th.action = action
}
func (th *ManualTestHandler) Success(result string) {
......@@ -143,9 +150,22 @@ func (th *ManualTestHandler) Success(result string) {
th.c <- nil
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{
Err: err,
ErrorType: irma.ErrorSerialization,
......@@ -153,17 +173,10 @@ func (th *ManualTestHandler) Success(result string) {
return
}
th.c <- &SessionResult{
Result: irmaSignedMessage,
}
th.c <- retval
}
func (th *ManualTestHandler) RequestSignaturePermission(request irma.SignatureRequest, requesterName string, ph PermissionHandler) {
var attributes []*irma.AttributeIdentifier
for _, cand := range request.Candidates {
attributes = append(attributes, cand[0])
}
c := irma.DisclosureChoice{attributes}
ph(true, &c)
th.RequestVerificationPermission(request.DisclosureRequest, requesterName, ph)
}
func (th *ManualTestHandler) RequestIssuancePermission(request irma.IssuanceRequest, issuerName string, ph PermissionHandler) {
ph(true, nil)
......@@ -174,5 +187,10 @@ func (th *ManualTestHandler) RequestSchemeManagerPermission(manager *irma.Scheme
th.Failure(&irma.SessionError{Err: errors.New("Unexpected session type")})
}
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
require.NoError(t, result.Err)
}
var verifyasRequest *irma.SignatureRequest
if verifyAs != "" {
verifyasRequest = &irma.SignatureRequest{}
switch h.action {
case irma.ActionDisclosing:
verifyasRequest := &irma.DisclosureRequest{}
err := json.Unmarshal([]byte(verifyAs), verifyasRequest)
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) {
......@@ -153,3 +163,31 @@ func TestManualSessionInvalidProof(t *testing.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
sigRequest := &irma.SignatureRequest{}
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")})
......@@ -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
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{
Action: irma.ActionSigning, // TODO hardcoded for now
Action: action,
Handler: handler,
client: client,
Version: minVersion,
ServerName: "Email request",
request: sigrequest,
ServerName: "",
request: request,
}
session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
......@@ -308,13 +313,7 @@ func (session *session) sendResponse(message interface{}) {
switch session.Action {
case irma.ActionSigning:
request, ok := session.request.(*irma.SignatureRequest)
if !ok {
session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
return
}
irmaSignature, err := request.SignatureFromMessage(message)
irmaSignature, err := session.request.(*irma.SignatureRequest).SignatureFromMessage(message)
if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Info: "Type assertion failed"})
return
......@@ -339,14 +338,21 @@ func (session *session) sendResponse(message interface{}) {
}
log, _ = session.createLogEntry(message.(gabi.ProofList)) // TODO err
case irma.ActionDisclosing:
var response disclosureResponse
if err = session.transport.Post("proofs", &response, message); err != nil {
session.fail(err.(*irma.SessionError))
messageJson, err = json.Marshal(message)
if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorSerialization, Err: err})
return
}
if response != "VALID" {
session.fail(&irma.SessionError{ErrorType: irma.ErrorRejected, Info: string(response)})
return
if session.IsInteractive() {
var response disclosureResponse
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
case irma.ActionIssuing:
......
......@@ -70,8 +70,8 @@ func (pl ProofList) extractPublicKeys(configuration *Configuration) ([]*gabi.Pub
return publicKeys, nil
}
// Verify the proofs cryptographically.
func (pl ProofList) Verify(configuration *Configuration, context *big.Int, nonce *big.Int, isSig bool) bool {
// VerifyProofs verifies the proofs cryptographically.
func (pl ProofList) VerifyProofs(configuration *Configuration, context *big.Int, nonce *big.Int, isSig bool) bool {
// Extract public keys
pks, err := pl.extractPublicKeys(configuration)
if err != nil {
......@@ -172,6 +172,35 @@ func (pl ProofList) DisclosedAttributes(configuration *Configuration, disjunctio
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
// (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
......@@ -208,29 +237,13 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature
}
// Now, cryptographically verify the IRMA disclosure proofs in the signature
proofList := ProofList(sm.Signature)
if !proofList.Verify(configuration, sm.Context, sm.GetNonce(), true) {
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
pl := ProofList(sm.Signature)
var required AttributeDisjunctionList
if request != nil {
disjunctions = request.Content
required = request.Content
}
allmatched, result.Attributes, err = proofList.DisclosedAttributes(configuration, disjunctions)
if err != nil {
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
result.Attributes, result.Status = pl.verifyAgainstDisjunctions(configuration, required, sm.Context, sm.GetNonce(), true)
if result.Status != ProofStatusValid {
return
}
......@@ -239,7 +252,7 @@ func (sm *SignedMessage) Verify(configuration *Configuration, request *Signature
if sm.Timestamp != nil {
t = time.Unix(sm.Timestamp.Time, 0)
}
expired, err := proofList.Expired(configuration, &t)
expired, err := pl.Expired(configuration, &t)
if err != nil {
result.Status = ProofStatusInvalidCrypto
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