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

Add type and protocolversion fields to sessionrequest for stricter validation


Co-authored-by: Tomas's avatarConfiks <confiks@scriptbase.org>
parent d267aed0
......@@ -755,7 +755,7 @@ func (client *Client) keyshareEnrollWorker(managerID irma.SchemeManagerIdentifie
// If the session succeeds or fails, the keyshare server is stored to disk or
// removed from the client by the keyshareEnrollmentHandler.
client.keyshareServers[managerID] = kss
client.NewSession(qr, &keyshareEnrollmentHandler{
client.newQrSession(qr, &keyshareEnrollmentHandler{
client: client,
pin: pin,
kss: kss,
......
......@@ -452,7 +452,7 @@ func TestDownloadSchemeManager(t *testing.T) {
URL: "http://localhost:48681/irma_configuration/irma-demo",
}
c := make(chan *irma.SessionError)
client.NewSession(qr, TestHandler{t, c, client})
client.newQrSession(qr, TestHandler{t, c, client})
if err := <-c; err != nil {
t.Fatal(*err)
}
......
......@@ -67,7 +67,7 @@ func createManualSessionHandler(request string, invalidRequest string, t *testin
func TestManualSession(t *testing.T) {
invalidate = false
request := "{\"nonce\": 42, \"context\": 1337, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
request := "{\"nonce\": 42, \"context\": 1337, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
......@@ -95,7 +95,7 @@ func TestManualSession(t *testing.T) {
func TestManualSessionUnsatisfiable(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
......@@ -113,8 +113,8 @@ func TestManualSessionUnsatisfiable(t *testing.T) {
func TestManualSessionInvalidNonce(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 1, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 1, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
......@@ -138,8 +138,8 @@ func TestManualSessionInvalidNonce(t *testing.T) {
func TestManualSessionInvalidRequest(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.university\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.university\"]}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
......@@ -175,8 +175,8 @@ func TestManualSessionInvalidRequest(t *testing.T) {
func TestManualSessionInvalidAttributeValue(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"456\"}}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"456\"}}]}"
invalidRequest := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":{\"irma-demo.RU.studentCard.studentID\": \"123\"}}]}"
ms := createManualSessionHandler(request, invalidRequest, t)
......@@ -204,7 +204,7 @@ func TestManualSessionInvalidAttributeValue(t *testing.T) {
func TestManualKeyShareSession(t *testing.T) {
invalidate = false
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"test.test.mijnirma.email\"]}]}"
ms := createManualSessionHandler(request, request, t)
......@@ -237,7 +237,7 @@ func TestManualSessionMultiProof(t *testing.T) {
}
// Request to sign with both BSN and StudentID
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]},{\"label\":\"BSN\",\"attributes\":[\"irma-demo.MijnOverheid.root.BSN\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]},{\"label\":\"BSN\",\"attributes\":[\"irma-demo.MijnOverheid.root.BSN\"]}]}"
ms := createManualSessionHandler(request, request, t)
......@@ -268,7 +268,7 @@ func TestManualSessionMultiProof(t *testing.T) {
func TestManualSessionInvalidProof(t *testing.T) {
invalidate = true
request := "{\"nonce\": 0, \"context\": 0, \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
request := "{\"nonce\": 0, \"context\": 0, \"type\": \"signing\", \"message\":\"I owe you everything\",\"content\":[{\"label\":\"Student number (RU)\",\"attributes\":[\"irma-demo.RU.studentCard.studentID\"]}]}"
ms := createManualSessionHandler(request, request, t)
client = parseStorage(t)
......
......@@ -83,44 +83,21 @@ var supportedVersions = map[int][]int{
// either an irma.QR or *irma.QR; a string that contains a JSON-serialized irma.QR;
// or a string that contains a serialized *irma.SignatureRequest.
// In any other case it calls the Failure method of the specified Handler.
func (client *Client) NewSession(info interface{}, handler Handler) SessionDismisser {
parsed := map[string]interface{}{}
func (client *Client) NewSession(sessionrequest string, handler Handler) SessionDismisser {
bts := []byte(sessionrequest)
switch x := info.(type) {
case *irma.Qr: // Just start the session directly
return client.newQrSession(x, handler)
case irma.Qr:
return client.newQrSession(&x, handler)
// We assume the string contains a JSON object
// Deserialize it into a temp to see which fields it contains, and infer from that what kind of object it is
case string:
bts := []byte(x)
if err := json.Unmarshal(bts, &parsed); err != nil {
handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
return nil
}
if _, isqr := parsed["irmaqr"]; isqr {
qr := &irma.Qr{}
if err := json.Unmarshal(bts, qr); err != nil {
handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
return nil
}
return client.newQrSession(qr, handler)
}
// Try to deserialize it as a Qr or SignatureRequest
qr := &irma.Qr{}
if err := irma.UnmarshalValidate(bts, qr); err == nil {
return client.newQrSession(qr, handler)
}
if _, isSigRequest := parsed["message"]; isSigRequest {
sigrequest := &irma.SignatureRequest{}
if err := json.Unmarshal([]byte(x), sigrequest); err != nil {
handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: err})
return nil
}
return client.newManualSession(sigrequest, handler)
}
sigrequest := &irma.SignatureRequest{}
if err := irma.UnmarshalValidate(bts, sigrequest); err == nil {
return client.newManualSession(sigrequest, handler)
}
handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Info specified of unsupported type")})
handler.Failure(irma.ActionUnknown, &irma.SessionError{Err: errors.New("Session request could not be parsed")})
return nil
}
......
......@@ -344,7 +344,7 @@ func sessionHandlerHelper(t *testing.T, jwtcontents interface{}, url string, cli
if h == nil {
h = TestHandler{t, c, client}
}
client.NewSession(qr, h)
client.newQrSession(qr, h)
if err := <-c; err != nil {
t.Fatal(*err)
......
......@@ -3,6 +3,7 @@ package irma
import (
"encoding/base64"
"encoding/json"
"net/url"
"strconv"
"strings"
......@@ -91,6 +92,22 @@ type RemoteError struct {
Stacktrace string `json:"stacktrace"`
}
type Validator interface {
Validate() error
}
// UnmarshalValidate json.Unmarshal's data, and validates it using the
// Validate() method if dest implements the Validator interface.
func UnmarshalValidate(data []byte, dest interface{}) error {
if err := json.Unmarshal(data, dest); err != nil {
return err
}
if v, ok := dest.(Validator); ok {
return v.Validate()
}
return nil
}
func (err *RemoteError) Error() string {
var msg string
if err.Message != "" {
......@@ -231,3 +248,23 @@ func ParseRequestorJwt(action Action, jwt string) (RequestorJwt, error) {
}
return retval, nil
}
func (qr *Qr) Validate() error {
if _, err := url.ParseRequestURI(qr.URL); err != nil {
return errors.Errorf("Invalid URL: %s", err.Error())
}
if qr.URL == "" {
return errors.New("No URL specified")
}
switch qr.Type {
case ActionDisclosing: // nop
case ActionIssuing: // nop
case ActionSigning: // nop
case ActionSchemeManager: // nop
default:
return errors.New("Unsupported session type")
}
return nil
}
......@@ -17,15 +17,16 @@ import (
// BaseRequest contains the context and nonce for an IRMA session.
type BaseRequest struct {
Context *big.Int `json:"context"`
Nonce *big.Int `json:"nonce"`
RequestorName string `json:"requestorName"`
Candidates [][]*AttributeIdentifier `json:"-"`
Context *big.Int `json:"context"`
Nonce *big.Int `json:"nonce"`
RequestorName string `json:"requestorName"`
Type Action `json:"type"`
Choice *DisclosureChoice `json:"-"`
Ids *IrmaIdentifierSet `json:"-"`
Candidates [][]*AttributeIdentifier `json:"-"`
Choice *DisclosureChoice `json:"-"`
Ids *IrmaIdentifierSet `json:"-"`
version *ProtocolVersion
Version *ProtocolVersion `json:"protocolVersion"`
}
func (sr *BaseRequest) SetCandidates(candidates [][]*AttributeIdentifier) {
......@@ -44,12 +45,12 @@ func (sr *BaseRequest) SetDisclosureChoice(choice *DisclosureChoice) {
// ...
func (sr *BaseRequest) SetVersion(v *ProtocolVersion) {
sr.version = v
sr.Version = v
}
// ...
func (sr *BaseRequest) GetVersion() *ProtocolVersion {
return sr.version
return sr.Version
}
func (sr *BaseRequest) GetRequestorName() string {
......@@ -287,6 +288,16 @@ func (ir *IssuanceRequest) GetNonce() *big.Int { return ir.Nonce }
// SetNonce sets the nonce of this session.
func (ir *IssuanceRequest) SetNonce(nonce *big.Int) { ir.Nonce = nonce }
func (ir *IssuanceRequest) Validate() error {
if ir.Type != ActionIssuing {
return errors.New("Not an issuance request")
}
if len(ir.Credentials) == 0 {
return errors.New("Empty issuance request")
}
return nil
}
func (dr *DisclosureRequest) Identifiers() *IrmaIdentifierSet {
if dr.Ids == nil {
dr.Ids = &IrmaIdentifierSet{
......@@ -321,6 +332,21 @@ func (dr *DisclosureRequest) GetNonce() *big.Int { return dr.Nonce }
// SetNonce sets the nonce of this session.
func (dr *DisclosureRequest) SetNonce(nonce *big.Int) { dr.Nonce = nonce }
func (dr *DisclosureRequest) Validate() error {
if dr.Type != ActionDisclosing {
return errors.New("Not a disclosure request")
}
if len(dr.Content) == 0 {
return errors.New("Disclosure request had no attributes")
}
for _, disjunction := range dr.Content {
if len(disjunction.Attributes) == 0 {
return errors.New("Disclosure request had an empty disjunction")
}
}
return nil
}
// GetNonce returns the nonce of this signature session
// (with the message already hashed into it).
func (sr *SignatureRequest) GetNonce() *big.Int {
......@@ -350,14 +376,14 @@ func convertFieldsToBigInt(jsonString []byte, fieldNames []string) ([]byte, erro
// Custom Unmarshalling to support both json with string and int fields for nonce and context
// i.e. {"nonce": "42", "context": "1337", ... } and {"nonce": 42, "context": 1337, ... }
func (sr *SignatureRequest) UnmarshalJSON(b []byte) error {
type SignatureRequestTemp SignatureRequest // To avoid 'recursive unmarshalling'
type signatureRequestTemp SignatureRequest // To avoid 'recursive unmarshalling'
fixedRequest, err := convertFieldsToBigInt(b, []string{"nonce", "context"})
if err != nil {
return err
}
var result SignatureRequestTemp
var result signatureRequestTemp
err = json.Unmarshal(fixedRequest, &result)
if err != nil {
return err
......@@ -385,6 +411,24 @@ func (sr *SignatureRequest) SignatureFromMessage(message interface{}) (*SignedMe
}, nil
}
func (sr *SignatureRequest) Validate() error {
if sr.Type != ActionSigning {
return errors.New("Not a signature request")
}
if sr.Message == "" {
return errors.New("Signature request had empty message")
}
if len(sr.Content) == 0 {
return errors.New("Disclosure request had no attributes")
}
for _, disjunction := range sr.Content {
if len(disjunction.Attributes) == 0 {
return errors.New("Disclosure request had an empty disjunction")
}
}
return nil
}
// Check if Timestamp is before other Timestamp. Used for checking expiry of attributes
func (t Timestamp) Before(u Timestamp) bool {
return time.Time(t).Before(time.Time(u))
......
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