Commit 0fd195ff authored by Koen van Ingen's avatar Koen van Ingen
Browse files

Initial signature verification

parent c10536c1
......@@ -59,6 +59,16 @@ func NewAttributeListFromInts(ints []*big.Int, conf *Configuration) *AttributeLi
}
}
// NewAttributeListFromInts initializes a new AttributeList from disclosed attributes of a prooflist
func NewAttributeListFromADisclosed(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int, conf *Configuration) (*AttributeList, error) {
ints, err := convertProofResponsesToInts(aResponses, aDisclosed)
if err != nil {
return nil, err
}
return NewAttributeListFromInts(ints, conf), nil
}
func (al *AttributeList) Info() *CredentialInfo {
if al.info == nil {
al.info = NewCredentialInfo(al.Ints, al.Conf)
......@@ -70,7 +80,9 @@ func (al *AttributeList) Hash() string {
if al.h == "" {
bytes := []byte{}
for _, i := range al.Ints {
bytes = append(bytes, i.Bytes()...)
if i != nil {
bytes = append(bytes, i.Bytes()...)
}
}
shasum := sha256.Sum256(bytes)
al.h = hex.EncodeToString(shasum[:])
......@@ -83,7 +95,11 @@ func (al *AttributeList) Strings() []TranslatedString {
if al.strings == nil {
al.strings = make([]TranslatedString, len(al.Ints)-1)
for index, num := range al.Ints[1:] { // skip metadata
al.strings[index] = map[string]string{"en": string(num.Bytes()), "nl": string(num.Bytes())} // TODO
if num == nil {
al.strings[index] = nil
} else {
al.strings[index] = map[string]string{"en": string(num.Bytes()), "nl": string(num.Bytes())} // TODO
}
}
}
return al.strings
......@@ -310,6 +326,45 @@ func (disjunction *AttributeDisjunction) Satisfied() bool {
return false
}
// Helper function to check if an attribute is satisfied against a list of disclosed attributes
// This is the case if:
// attribute is contained in disclosed AND if a value is present: equal to that value
func isAttributeSatisfied(attribute AttributeTypeIdentifier, value string, disclosed []*CredentialInfo, conf *Configuration) bool {
for _, cred := range disclosed {
credentialType := cred.GetCredentialType(conf)
index, err := credentialType.IndexOf(attribute)
if err != nil {
// Specified credential does not contain this attribute, move to next cred in list of disclosed credentials
continue
}
disclosedAttributeValue := cred.Attributes[index]
// If it contains this attribute, check if value match (it must be disclosed (i.e. not nil) and match the value)
// Attribute is Statiisfied if:
// - Attribute is disclosed (i.e. not nil)
// - Value is empty OR value equal to disclosedValue
if disclosedAttributeValue != nil && (value == "" || disclosedAttributeValue["en"] == value) { // TODO: fix translation/attr typing
return true
}
}
return false
}
// 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 []*CredentialInfo, conf *Configuration) bool {
for _, attr := range disjunction.Attributes {
value := disjunction.Values[attr]
if isAttributeSatisfied(attr, value, disclosed, conf) {
return true
}
}
return false
}
// MatchesConfig returns true if all attributes contained in the disjunction are
// present in the specified configuration.
func (disjunction *AttributeDisjunction) MatchesConfig(conf *Configuration) bool {
......
......@@ -7,16 +7,16 @@ import (
// CredentialInfo contains all information of an IRMA credential.
type CredentialInfo struct {
CredentialTypeID string // e.g., "irma-demo.RU.studentCard"
Name string // e.g., "studentCard"
IssuerID string // e.g., "RU"
SchemeManagerID string // 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
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
}
// A CredentialInfoList is a list of credentials (implements sort.Interface).
......@@ -29,25 +29,60 @@ func NewCredentialInfo(ints []*big.Int, conf *Configuration) *CredentialInfo {
return nil
}
attrs := make([]TranslatedString, len(credtype.Attributes))
for i := range credtype.Attributes {
val := string(ints[i+1].Bytes())
attrs[i] = TranslatedString(map[string]string{"en": val, "nl": val})
}
attrs := NewAttributeListFromInts(ints, conf)
id := credtype.Identifier()
issid := id.IssuerIdentifier()
return &CredentialInfo{
CredentialTypeID: id.String(),
CredentialTypeID: NewCredentialTypeIdentifier(id.String()),
Name: id.Name(),
IssuerID: issid.Name(),
SchemeManagerID: issid.SchemeManagerIdentifier().String(),
IssuerID: NewIssuerIdentifier(issid.Name()),
SchemeManagerID: NewSchemeManagerIdentifier(issid.SchemeManagerIdentifier().String()),
SignedOn: Timestamp(meta.SigningDate()),
Expires: Timestamp(meta.Expiry()),
Attributes: attrs,
Attributes: attrs.Strings(),
Logo: credtype.Logo(conf),
Hash: NewAttributeListFromInts(ints, conf).Hash(),
Hash: attrs.Hash(),
}
}
// Convert proof responses to Ints, adding nils for undislosed attributes
func convertProofResponsesToInts(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int) ([]*big.Int, error) {
var ints []*big.Int
length := len(aResponses) + len(aDisclosed)
for i := 1; i < length; i++ {
if aResponses[i] == nil {
if aDisclosed[i] == nil {
// If index not found in aResponses it must be in aDisclosed
return nil, &SessionError{
ErrorType: ErrorCrypto,
Info: fmt.Sprintf("Missing attribute index: %v", i),
} // TODO: error type?
}
ints = append(ints, aDisclosed[i])
} else {
// Don't include value of hidden attributes
ints = append(ints, nil)
}
}
return ints, nil
}
// NewAttributeListFromInts initializes a new AttributeList from disclosed attributes of a prooflist
func NewCredentialInfoFromADisclosed(aResponses map[int]*big.Int, aDisclosed map[int]*big.Int, conf *Configuration) (*CredentialInfo, error) {
ints, err := convertProofResponsesToInts(aResponses, aDisclosed)
if err != nil {
return nil, err
}
return NewCredentialInfo(ints, conf), nil
}
func (ci CredentialInfo) GetCredentialType(conf *Configuration) *CredentialType {
return conf.CredentialTypes[ci.CredentialTypeID]
}
// Len implements sort.Interface.
......
package irmaclient
import (
"github.com/privacybydesign/irmago"
"github.com/mhe/gabi"
"github.com/privacybydesign/irmago"
)
// credential represents an IRMA credential, whose zeroth attribute
......
package irmaclient
import (
"encoding/json"
"fmt"
"testing"
......@@ -14,19 +15,54 @@ type ManualSessionHandler struct {
pinHandler PinHandler
t *testing.T
c chan *irma.SessionError
sigRequest *irma.SignatureRequest
}
var client *Client
// Issue BSN credential using sessionHelper
func issue(t *testing.T, ms ManualSessionHandler) {
name := "testip"
jwtcontents := getIssuanceJwt(name)
sessionHandlerHelper(t, jwtcontents, "issue", client, &ms)
}
// Flip one bit in the proof string if invalidate is set to true
var invalidate bool
func corruptProofString(proof string) string {
if invalidate {
proofBytes := []byte(proof)
flipLoc := 15
if proofBytes[flipLoc] == 0x33 {
proofBytes[flipLoc] = 0x32
} else {
proofBytes[flipLoc] = 0x33
}
return string(proofBytes)
}
return proof
}
func TestManualSession(t *testing.T) {
invalidate = false
channel := make(chan *irma.SessionError)
client = parseStorage(t)
request := "{\"nonce\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
sigRequestJSON := []byte(request)
sigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
channel := make(chan *irma.SessionError)
manualSessionHandler := ManualSessionHandler{t: t, c: channel}
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
}
client.NewManualSession(request, &manualSessionHandler)
client.NewManualSession(request, &ms)
test.ClearTestStorage(t)
......@@ -36,14 +72,58 @@ func TestManualSession(t *testing.T) {
}
func TestManualKeyShareSession(t *testing.T) {
invalidate = false
channel := make(chan *irma.SessionError)
keyshareRequestString := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
keyshareRequestJSON := []byte(keyshareRequestString)
keyshareRequest := &irma.SignatureRequest{}
json.Unmarshal(keyshareRequestJSON, keyshareRequest)
manualSessionHandler := ManualSessionHandler{
t: t,
c: channel,
sigRequest: keyshareRequest,
}
client = parseStorage(t)
keyshareRequest := "{\"nonce\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
client.NewManualSession(keyshareRequestString, &manualSessionHandler)
teardown(t)
if err := <-channel; err != nil {
t.Fatal(*err)
}
}
func TestManualSessionMultiProof(t *testing.T) {
invalidate = false
client = parseStorage(t)
// First, we need to issue an extra credential (BSN)
is := ManualSessionHandler{t: t, c: make(chan *irma.SessionError)}
go issue(t, is)
if err := <-is.c; err != nil {
fmt.Println("Error during initial issueing!")
t.Fatal(*err)
}
// Request to sign with both BSN and StudentID
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]},{\"label\":\"BSN\",\"attributes\":[\"irma-demo.MijnOverheid.root.BSN\"]}]}"
channel := make(chan *irma.SessionError)
manualSessionHandler := ManualSessionHandler{t: t, c: channel}
sigRequestJSON := []byte(request)
sigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
}
client.NewManualSession(keyshareRequest, &manualSessionHandler)
client.NewManualSession(request, &ms)
test.ClearTestStorage(t)
......@@ -52,22 +132,71 @@ func TestManualKeyShareSession(t *testing.T) {
}
}
func TestManualSessionInvalidProof(t *testing.T) {
invalidate = true
channel := make(chan *irma.SessionError)
client = parseStorage(t)
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"messageType\":\"STRING\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
sigRequestJSON := []byte(request)
sigRequest := &irma.SignatureRequest{}
json.Unmarshal(sigRequestJSON, sigRequest)
ms := ManualSessionHandler{
t: t,
c: channel,
sigRequest: sigRequest,
}
client.NewManualSession(request, &ms)
teardown(t)
if err := <-channel; err.ErrorType != "Proof does not verify" {
t.Fatal(*err)
}
}
func (sh *ManualSessionHandler) Success(irmaAction irma.Action, result string) {
switch irmaAction {
case irma.ActionSigning:
// Make proof corrupt if we want to test invalid proofs
result = corruptProofString(result)
if !verifySig(client, result, sh.sigRequest) {
sh.c <- &irma.SessionError{
ErrorType: irma.ErrorType("Proof does not verify"),
}
return
}
}
sh.c <- nil
}
func (sh *ManualSessionHandler) UnsatisfiableRequest(irmaAction irma.Action, serverName string, missingAttributes irma.AttributeDisjunctionList) {
sh.t.Fail()
func (sh *ManualSessionHandler) UnsatisfiableRequest(irmaAction irma.Action, missingAttributes irma.AttributeDisjunctionList) {
// This function is called from main thread, which blocks go channel, so need go routine here
go func() {
sh.c <- &irma.SessionError{
ErrorType: irma.ErrorType("UnsatisfiableRequest"),
}
}()
}
// Done in irma bridge?
func (sh *ManualSessionHandler) StatusUpdate(irmaAction irma.Action, status irma.Status) {}
func (sh *ManualSessionHandler) RequestPin(remainingAttempts int, ph PinHandler) {
ph(true, "12345")
}
func (sh *ManualSessionHandler) RequestSignaturePermission(request irma.SignatureRequest, requesterName string, ph PermissionHandler) {
c := irma.DisclosureChoice{request.Candidates[0]}
var attributes []*irma.AttributeIdentifier
for _, cand := range request.Candidates {
attributes = append(attributes, cand[0])
}
c := irma.DisclosureChoice{attributes}
ph(true, &c)
}
func (sh *ManualSessionHandler) RequestIssuancePermission(request irma.IssuanceRequest, issuerName string, ph PermissionHandler) {
ph(true, nil)
}
// These handlers should not be called, fail test if they are called
func (sh *ManualSessionHandler) Cancelled(irmaAction irma.Action) {
......@@ -76,9 +205,6 @@ func (sh *ManualSessionHandler) Cancelled(irmaAction irma.Action) {
func (sh *ManualSessionHandler) KeyshareEnrollmentMissing(manager irma.SchemeManagerIdentifier) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.Errorf("Missing keyshare server %s", manager.String())})
}
func (sh *ManualSessionHandler) RequestIssuancePermission(request irma.IssuanceRequest, issuerName string, ph PermissionHandler) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Unexpected session type")})
}
func (sh *ManualSessionHandler) RequestSchemeManagerPermission(manager *irma.SchemeManager, callback func(proceed bool)) {
sh.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Unexpected session type")})
}
......
......@@ -209,6 +209,10 @@ func TestLargeAttribute(t *testing.T) {
}
func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Client) {
sessionHandlerHelper(t, jwtcontents, url, client, nil)
}
func sessionHandlerHelper(t *testing.T, jwtcontents interface{}, url string, client *Client, h Handler) {
init := client == nil
if init {
client = parseStorage(t)
......@@ -231,7 +235,10 @@ func sessionHelper(t *testing.T, jwtcontents interface{}, url string, client *Cl
qr.URL = url + "/" + qr.URL
c := make(chan *irma.SessionError)
client.NewSession(qr, TestHandler{t, c, client})
if h == nil {
h = TestHandler{t, c, client}
}
client.NewSession(qr, h)
if err := <-c; err != nil {
t.Fatal(*err)
......
package irmaclient
import (
"fmt"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
"github.com/privacybydesign/irmago"
"math/big"
)
func extractPublicKeys(client *Client, proofList *gabi.ProofList) ([]*gabi.PublicKey, error) {
var publicKeys []*gabi.PublicKey
for _, v := range *proofList {
switch v.(type) {
case *gabi.ProofD:
proof := v.(*gabi.ProofD)
metadata := irma.MetadataFromInt(proof.ADisclosed[1], client.Configuration) // index 1 is metadata attribute
publicKey, err := metadata.PublicKey()
if err != nil {
return nil, err
}
publicKeys = append(publicKeys, publicKey)
default:
return nil, errors.New("Cannot extract public key, not a disclosure proofD!")
}
}
return publicKeys, nil
}
func extractDisclosedCredentials(client *Client, proofList *gabi.ProofList) ([]*irma.CredentialInfo, error) {
var credentials []*irma.CredentialInfo
for _, v := range *proofList {
switch v.(type) {
case *gabi.ProofD:
proof := v.(*gabi.ProofD)
irmaCredentialInfo, err := irma.NewCredentialInfoFromADisclosed(proof.AResponses, proof.ADisclosed, client.Configuration)
if err != nil {
return nil, err
}
credentials = append(credentials, irmaCredentialInfo)
default:
return nil, errors.New("Cannot extract attributes from proof, not a disclosure proofD!")
}
}
return credentials, nil
}
func checkProofWithRequest(client *Client, proofList *gabi.ProofList, sigRequest *irma.SignatureRequest) bool {
credentials, err := extractDisclosedCredentials(client, proofList)
if err != nil {
fmt.Println(err)
return false
}
for _, content := range sigRequest.Content {
if !content.SatisfyDisclosed(credentials, client.Configuration) {
return false
}
}
return true
}
func verify(client *Client, proofList *gabi.ProofList, context *big.Int, nonce *big.Int, isSig bool) bool {
// Extract public keys
pks, err := extractPublicKeys(client, proofList)
if err != nil {
fmt.Printf("Error extracting public key: %v\n", err)
return false
}
return proofList.Verify(pks, context, nonce, true, isSig)
}
// Verify a signature proof and check if the attributes match the attributes in the original request
func verifySig(client *Client, proofString string, sigRequest *irma.SignatureRequest) bool {
// First, unmarshal proof and check if all the attributes in the proofstring match the signature request
var proofList gabi.ProofList
proofBytes := []byte(proofString)
err := proofList.UnmarshalJSON(proofBytes)
if err != nil {
fmt.Printf("Error unmarshalling JSON: %v\n", err)
return false
}
// Now, cryptographically verify the signature
if !verify(client, &proofList, sigRequest.GetContext(), sigRequest.GetNonce(), true) {
return false
}
// Finally, check whether attribute values in proof satisfy the original signature request
return checkProofWithRequest(client, &proofList, sigRequest)
}
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