Commit 1131c29d authored by Sietse Ringers's avatar Sietse Ringers
Browse files

IRMA server sessions are now started with instances of irma.RequestorRequest

That is, irma.ServiceProviderRequest, irma.SignatureRequestorRequest, or irma.IdentityProviderRequest.
These structs contain new fields with which the requestor can configure the session
(they have in fact always been present in session request JWTs). Functions and endpoints
that start an IRMA session now support one of these; or an instance of irma.SessionRequest;
or JSON-serialized versions of any of those.
parent cb1c0ea9
......@@ -120,24 +120,51 @@ func WriteString(w http.ResponseWriter, str string) {
w.Write([]byte(str))
}
// ParseSessionRequest tries to parse the specified bytes as an
// disclosure request, a signature request, and an issuance request, in that order.
// Returns an error if none of the attempts work.
func ParseSessionRequest(bts []byte) (request irma.SessionRequest, err error) {
request = &irma.DisclosureRequest{}
if err = irma.UnmarshalValidate(bts, request); err == nil {
return request, nil
func ParseSessionRequest(request interface{}) (irma.RequestorRequest, error) {
switch r := request.(type) {
case irma.RequestorRequest:
return r, nil
case irma.SessionRequest:
return wrapSessionRequest(r)
case string:
return ParseSessionRequest([]byte(r))
case []byte:
var attempts = []irma.Validator{&irma.ServiceProviderRequest{}, &irma.SignatureRequestorRequest{}, &irma.IdentityProviderRequest{}}
t, err := tryUnmarshalJson(r, attempts)
if err == nil {
return t.(irma.RequestorRequest), nil
}
attempts = []irma.Validator{&irma.DisclosureRequest{}, &irma.SignatureRequest{}, &irma.IssuanceRequest{}}
t, err = tryUnmarshalJson(r, attempts)
if err == nil {
return wrapSessionRequest(t.(irma.SessionRequest))
}
return nil, errors.New("Failed to JSON unmarshal request bytes")
default:
return nil, errors.New("Invalid request type")
}
request = &irma.SignatureRequest{}
if err = irma.UnmarshalValidate(bts, request); err == nil {
return request, nil
}
func wrapSessionRequest(request irma.SessionRequest) (irma.RequestorRequest, error) {
switch r := request.(type) {
case *irma.DisclosureRequest:
return &irma.ServiceProviderRequest{Request: r}, nil
case *irma.SignatureRequest:
return &irma.SignatureRequestorRequest{Request: r}, nil
case *irma.IssuanceRequest:
return &irma.IdentityProviderRequest{Request: r}, nil
default:
return nil, errors.New("Invalid session type")
}
request = &irma.IssuanceRequest{}
if err = irma.UnmarshalValidate(bts, request); err == nil {
return request, nil
}
func tryUnmarshalJson(bts []byte, attempts []irma.Validator) (irma.Validator, error) {
for _, a := range attempts {
if err := irma.UnmarshalValidate(bts, a); err == nil {
return a, nil
}
}
Logger.Warn("Failed to parse as session request: ", string(bts))
return nil, errors.New("Invalid or disabled session type")
return nil, errors.New("")
}
func LocalIP() (string, error) {
......
......@@ -104,31 +104,26 @@ func Initialize(configuration *server.Configuration) error {
return nil
}
func StartSession(request irma.SessionRequest) (*irma.Qr, string, error) {
if err := request.Validate(); err != nil {
func StartSession(req interface{}) (*irma.Qr, string, error) {
rrequest, err := server.ParseSessionRequest(req)
if err != nil {
return nil, "", server.LogError(err)
}
action := irma.ActionUnknown
switch request.(type) {
case *irma.DisclosureRequest:
action = irma.ActionDisclosing
case *irma.SignatureRequest:
action = irma.ActionSigning
case *irma.IssuanceRequest:
action = irma.ActionIssuing
request := rrequest.SessionRequest()
action := request.Action()
if action == irma.ActionIssuing {
if err := validateIssuanceRequest(request.(*irma.IssuanceRequest)); err != nil {
return nil, "", server.LogError(err)
}
default:
return nil, "", server.LogError(errors.New("Invalid session type"))
}
session := newSession(action, request)
session := newSession(action, rrequest)
conf.Logger.Infof("%s session started, token %s", action, session.token)
if conf.Logger.IsLevelEnabled(logrus.DebugLevel) {
conf.Logger.Debug("Session request: ", server.ToJson(request))
conf.Logger.Debug("Session request: ", server.ToJson(rrequest))
} else {
logPurgedRequest(request)
logPurgedRequest(rrequest)
}
return &irma.Qr{
Type: action,
......@@ -145,6 +140,15 @@ func GetSessionResult(token string) *server.SessionResult {
return session.result
}
func GetRequest(token string) irma.RequestorRequest {
session := sessions.get(token)
if session == nil {
conf.Logger.Warn("Session request requested of unknown session ", token)
return nil
}
return session.rrequest
}
func CancelSession(token string) error {
session := sessions.get(token)
if session == nil {
......
......@@ -130,26 +130,25 @@ func chooseProtocolVersion(min, max *irma.ProtocolVersion) (*irma.ProtocolVersio
}
// logPurgedRequest logs the request excluding any attribute values.
func logPurgedRequest(request irma.SessionRequest) {
func logPurgedRequest(request irma.RequestorRequest) {
// We want to log as much as possible of the request, but no attribute values.
// We cannot just remove them from the request parameter as that would break the calling code.
// So we create a deep copy of the request from which we can then safely remove whatever we want to.
// Ugly hack alert: the easiest way to do this seems to be to convert it to JSON and then back.
// As we do not know the precise type of request (may be *irma.DisclosureRequest,
// *irma.SignatureRequest, or *irma.IssuanceRequest), we use reflection to create a new instance
// As we do not know the precise type of request, we use reflection to create a new instance
// of the same type as request, into which we then unmarshal our copy.
cpy := reflect.New(reflect.TypeOf(request).Elem()).Interface()
bts, _ := json.Marshal(request)
_ = json.Unmarshal(bts, cpy)
// Remove required attribute values from any attributes to be disclosed
attrs := cpy.(irma.SessionRequest).ToDisclose()
attrs := cpy.(irma.RequestorRequest).SessionRequest().ToDisclose()
for _, disjunction := range attrs {
disjunction.Values = nil
}
// Remove attribute values from attributes to be issued
if isreq, ok := cpy.(*irma.IssuanceRequest); ok {
for _, cred := range isreq.Credentials {
if isreq, ok := cpy.(*irma.IdentityProviderRequest); ok {
for _, cred := range isreq.Request.Credentials {
cred.Attributes = nil
}
}
......
......@@ -14,10 +14,11 @@ import (
type session struct {
sync.Mutex
action irma.Action
token string
version *irma.ProtocolVersion
request irma.SessionRequest
action irma.Action
token string
version *irma.ProtocolVersion
rrequest irma.RequestorRequest
request irma.SessionRequest
status server.Status
lastActive time.Time
......@@ -112,11 +113,12 @@ func (s memorySessionStore) deleteExpired() {
var one *big.Int = big.NewInt(1)
func newSession(action irma.Action, request irma.SessionRequest) *session {
func newSession(action irma.Action, request irma.RequestorRequest) *session {
token := newSessionToken()
s := &session{
action: action,
request: request,
rrequest: request,
request: request.SessionRequest(),
lastActive: time.Now(),
token: token,
result: &server.SessionResult{
......@@ -126,8 +128,8 @@ func newSession(action irma.Action, request irma.SessionRequest) *session {
}
s.setStatus(server.StatusInitialized)
nonce, _ := gabi.RandomBigInt(gabi.DefaultSystemParameters[2048].Lstatzk)
request.SetNonce(nonce)
request.SetContext(one)
s.request.SetNonce(nonce)
s.request.SetContext(one)
sessions.add(token, s)
return s
}
......
......@@ -28,7 +28,7 @@ func Initialize(configuration *server.Configuration) error {
// StartSession starts an IRMA session, running the handler on completion, if specified.
// The session token (the second return parameter) can be used in GetSessionResult()
// and CancelSession().
func StartSession(request irma.SessionRequest, handler SessionHandler) (*irma.Qr, string, error) {
func StartSession(request interface{}, handler SessionHandler) (*irma.Qr, string, error) {
qr, token, err := core.StartSession(request)
if err != nil {
return nil, "", err
......@@ -44,6 +44,10 @@ func GetSessionResult(token string) *server.SessionResult {
return core.GetSessionResult(token)
}
func GetRequest(token string) irma.RequestorRequest {
return core.GetRequest(token)
}
// CancelSession cancels the specified IRMA session.
func CancelSession(token string) error {
return core.CancelSession(token)
......
......@@ -27,7 +27,7 @@ type Authenticator interface {
// it was not able to successfully authenticate the request).
Authenticate(
headers http.Header, body []byte,
) (applies bool, request irma.SessionRequest, requestor string, err *irma.RemoteError)
) (applies bool, request irma.RequestorRequest, requestor string, err *irma.RemoteError)
}
type AuthenticationMethod string
......@@ -55,7 +55,7 @@ var authenticators map[AuthenticationMethod]Authenticator
func (NilAuthenticator) Authenticate(
headers http.Header, body []byte,
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
if headers.Get("Authentication") != "" || !strings.HasPrefix(headers.Get("Content-Type"), "application/json") {
return false, nil, "", nil
}
......@@ -72,7 +72,7 @@ func (NilAuthenticator) Initialize(name string, requestor Requestor) error {
func (hauth *HmacAuthenticator) Authenticate(
headers http.Header, body []byte,
) (applies bool, request irma.SessionRequest, requestor string, err *irma.RemoteError) {
) (applies bool, request irma.RequestorRequest, requestor string, err *irma.RemoteError) {
return jwtAuthenticate(headers, body, jwt.SigningMethodHS256.Name, hauth.hmackeys)
}
......@@ -100,7 +100,7 @@ func (hauth *HmacAuthenticator) Initialize(name string, requestor Requestor) err
func (pkauth *PublicKeyAuthenticator) Authenticate(
headers http.Header, body []byte,
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
return jwtAuthenticate(headers, body, jwt.SigningMethodRS256.Name, pkauth.publickeys)
}
......@@ -123,7 +123,7 @@ func (pkauth *PublicKeyAuthenticator) Initialize(name string, requestor Requesto
func (pskauth *PresharedKeyAuthenticator) Authenticate(
headers http.Header, body []byte,
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
auth := headers.Get("Authentication")
if auth == "" || !strings.HasPrefix(headers.Get("Content-Type"), "application/json") {
return false, nil, "", nil
......@@ -176,7 +176,7 @@ func jwtKeyExtractor(publickeys map[string]interface{}) func(token *jwt.Token) (
// jwtAuthenticate is a helper function for JWT-based authenticators that verifies and parses JWTs.
func jwtAuthenticate(
headers http.Header, body []byte, signatureAlg string, keys map[string]interface{},
) (bool, irma.SessionRequest, string, *irma.RemoteError) {
) (bool, irma.RequestorRequest, string, *irma.RemoteError) {
// Read JWT and check its type
if headers.Get("Authorization") != "" || !strings.HasPrefix(headers.Get("Content-Type"), "text/plain") {
return false, nil, "", nil
......@@ -217,7 +217,7 @@ func jwtAuthenticate(
}
requestor := claims.Issuer // presence is ensured by jwtKeyExtractor
return true, parsedJwt.SessionRequest(), requestor, nil
return true, parsedJwt.RequestorRequest(), requestor, nil
}
func jwtSignatureAlg(j string) (string, error) {
......
......@@ -138,13 +138,14 @@ func handleCreate(w http.ResponseWriter, r *http.Request) {
// We do this by feeding the HTTP POST details to all known authenticators, and see if
// one of them is applicable and able to authenticate the request.
var (
rrequest irma.RequestorRequest
request irma.SessionRequest
requestor string
rerr *irma.RemoteError
applies bool
)
for _, authenticator := range authenticators {
applies, request, requestor, rerr = authenticator.Authenticate(r.Header, body)
applies, rrequest, requestor, rerr = authenticator.Authenticate(r.Header, body)
if applies || rerr != nil {
break
}
......@@ -163,6 +164,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request) {
// Authorize request: check if the requestor is allowed to verify or issue
// the requested attributes or credentials
request = rrequest.SessionRequest()
if request.Action() == irma.ActionIssuing {
allowed, reason := conf.CanIssue(requestor, request.(*irma.IssuanceRequest).Credentials)
if !allowed {
......@@ -184,7 +186,7 @@ func handleCreate(w http.ResponseWriter, r *http.Request) {
}
// Everything is authenticated and parsed, we're good to go!
qr, _, err := irmarequestor.StartSession(request, nil)
qr, _, err := irmarequestor.StartSession(rrequest, nil)
if err != nil {
server.WriteError(w, server.ErrorInvalidRequest, err.Error())
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