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

Add support to irma session for remote server

parent 528ab31c
......@@ -2,6 +2,7 @@ package fs
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"io/ioutil"
"os"
......@@ -136,3 +137,34 @@ func CopyDirectory(src, dest string) error {
}),
)
}
func ReadKey(key string) ([]byte, error) {
var bts []byte
if stat, err := os.Stat(key); err == nil {
if stat.IsDir() {
return nil, errors.New("cannot read key from a directory")
}
bts, err = ioutil.ReadFile(key)
if err != nil {
return nil, err
}
} else {
bts = []byte(key)
}
return bts, nil
}
func Base64Decode(b []byte) ([]byte, error) {
var (
err error
bts []byte
encodings = []*base64.Encoding{base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding, base64.StdEncoding}
)
for _, encoding := range encodings {
err = nil
if bts, err = encoding.DecodeString(string(b)); err == nil {
break
}
}
return bts, err
}
......@@ -10,9 +10,11 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/dgrijalva/jwt-go"
"github.com/go-errors/errors"
"github.com/mdp/qrterminal"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/server"
"github.com/privacybydesign/irmago/server/irmarequestor"
"github.com/spf13/cobra"
......@@ -31,7 +33,8 @@ var sessionCmd = &cobra.Command{
Short: "Perform an IRMA disclosure, issuance or signature session",
Example: `irma session --disclose irma-demo.MijnOverheid.root.BSN
irma session --sign irma-demo.MijnOverheid.root.BSN --message message
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN`,
irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose irma-demo.MijnOverheid.root.BSN
irma session --disclose irma-demo.MijnOverheid.root.BSN --server http://localhost:48680 --authmethod psk --key presharedkey`,
Run: func(cmd *cobra.Command, args []string) {
request, irmaconfig, err := configure(cmd)
if err != nil {
......@@ -41,12 +44,16 @@ irma session --issue irma-demo.MijnOverheid.ageLower=yes,yes,yes,no --disclose i
var result *server.SessionResult
serverurl, _ := cmd.Flags().GetString("server")
noqr, _ := cmd.Flags().GetBool("noqr")
flags := cmd.Flags()
if serverurl == "" {
port, _ := cmd.Flags().GetInt("port")
privatekeysPath, _ := cmd.Flags().GetString("privatekeys")
port, _ := flags.GetInt("port")
privatekeysPath, _ := flags.GetString("privatekeys")
result, err = libraryRequest(request, irmaconfig, port, privatekeysPath, noqr)
} else {
result, err = serverRequest(request, serverurl, noqr)
authmethod, _ := flags.GetString("authmethod")
key, _ := flags.GetString("key")
name, _ := flags.GetString("name")
result, err = serverRequest(request, serverurl, authmethod, key, name, noqr)
}
if err != nil {
die("Session failed", err)
......@@ -93,15 +100,14 @@ func libraryRequest(
func serverRequest(
request irma.SessionRequest,
serverurl string,
serverurl, authmethod, key, name string,
noqr bool,
) (*server.SessionResult, error) {
logger.Debug("Server URL: ", serverurl)
qr := &irma.Qr{}
// Start session at server
transport := irma.NewHTTPTransport(serverurl)
if err := transport.Post("session", qr, request); err != nil {
qr, transport, err := postRequest(serverurl, request, name, authmethod, key)
if err != nil {
return nil, err
}
......@@ -111,18 +117,17 @@ func serverRequest(
return nil, errors.WrapPrefix(err, "Failed to print QR", 0)
}
token := qr.URL[strings.LastIndex(qr.URL, "/")+1:]
statuschan := make(chan server.Status)
// Wait untill client connects
go poll(token, server.StatusInitialized, transport, statuschan)
go poll(server.StatusInitialized, transport, statuschan)
status := <-statuschan
if status != server.StatusConnected {
return nil, errors.Errorf("Unexpected status: %s", status)
}
// Wait untill client finishes
go poll(token, server.StatusConnected, transport, statuschan)
go poll(server.StatusConnected, transport, statuschan)
status = <-statuschan
if status != server.StatusDone {
return nil, errors.Errorf("Unexpected status: %s", status)
......@@ -130,12 +135,62 @@ func serverRequest(
// Retrieve session result
result := &server.SessionResult{}
if err := transport.Get(fmt.Sprintf("session/%s/result", token), result); err != nil {
if err := transport.Get("result", result); err != nil {
return nil, errors.WrapPrefix(err, "Failed to get session result", 0)
}
return result, nil
}
func postRequest(serverurl string, request irma.SessionRequest, name, authmethod, key string) (*irma.Qr, *irma.HTTPTransport, error) {
var (
err error
sk interface{}
qr = &irma.Qr{}
transport = irma.NewHTTPTransport(serverurl)
)
switch authmethod {
case "none":
err = transport.Post("session", qr, request)
case "psk":
transport.SetHeader("Authentication", key)
err = transport.Post("session", qr, request)
case "hmac", "rsa":
var (
jwtalg jwt.SigningMethod
jwtstr string
bts []byte
)
if bts, err = fs.ReadKey(key); err != nil {
return nil, nil, err
}
if authmethod == "hmac" {
jwtalg = jwt.SigningMethodHS256
if sk, err = fs.Base64Decode(bts); err != nil {
return nil, nil, err
}
}
if authmethod == "rsa" {
jwtalg = jwt.SigningMethodRS256
if sk, err = jwt.ParseRSAPrivateKeyFromPEM(bts); err != nil {
return nil, nil, err
}
}
if jwtstr, err = irma.SignedRequestorJwt(request, jwtalg, sk, name); err != nil {
return nil, nil, err
}
logger.Debug("Session request JWT: ", jwtstr)
err = transport.Post("session", qr, jwtstr)
default:
return nil, nil, errors.New("Invalid authentication method (must be none, psk, hmac or rsa)")
}
token := qr.URL[strings.LastIndex(qr.URL, "/")+1:]
transport.Server += fmt.Sprintf("session/%s/", token)
return qr, transport, err
}
// Configuration functions
func configureServer(port int, privatekeysPath string, irmaconfig *irma.Configuration) error {
......@@ -191,20 +246,20 @@ func configure(cmd *cobra.Command) (irma.SessionRequest, *irma.Configuration, er
// Helper functions
// poll recursively polls the session status until a status different from initialStatus is received.
func poll(t string, initialStatus server.Status, transport *irma.HTTPTransport, statuschan chan server.Status) {
func poll(initialStatus server.Status, transport *irma.HTTPTransport, statuschan chan server.Status) {
// First we wait
<-time.NewTimer(pollInterval).C
// Get session status
var status string
if err := transport.Get(fmt.Sprintf("session/%s/status", t), &status); err != nil {
if err := transport.Get("status", &status); err != nil {
_ = server.LogFatal(err)
}
status = strings.Trim(status, `"`)
// If the status has not yet changed, schedule another poll
if server.Status(status) == initialStatus {
go poll(t, initialStatus, transport, statuschan)
go poll(initialStatus, transport, statuschan)
} else {
logger.Trace("Stopped polling, new status ", status)
statuschan <- server.Status(status)
......@@ -375,6 +430,9 @@ func init() {
flags.CountP("verbose", "v", "verbose (repeatable)")
flags.StringP("server", "s", "", "Server to post request to (leave blank to use builtin library)")
flags.StringP("authmethod", "a", "none", "Authentication method to server (none, psk, rsa, hmac)")
flags.String("key", "", "Key to sign request with")
flags.String("name", "", "Requestor name")
flags.StringArray("disclose", nil, "Add an attribute disjunction (comma-separated)")
flags.StringArray("issue", nil, "Add a credential to issue")
......
package irmaserver
import (
"encoding/base64"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/go-errors/errors"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/server"
)
......@@ -83,16 +81,14 @@ func (hauth *HmacAuthenticator) Initialize(name string, requestor Requestor) err
return errors.Errorf("Requestor %s has no authentication key", name)
}
var err error
var bts []byte
// We accept any of the base64 encodings
encodings := []*base64.Encoding{base64.StdEncoding, base64.RawStdEncoding, base64.URLEncoding, base64.RawURLEncoding}
for _, encoding := range encodings {
err = nil
if bts, err = encoding.DecodeString(requestor.AuthenticationKey); err == nil {
break
}
// Read file contents or string contents
bts, err := fs.ReadKey(requestor.AuthenticationKey)
if err != nil {
return err
}
// We accept any of the base64 encodings
bts, err = fs.Base64Decode(bts)
if err != nil {
return errors.WrapPrefix(err, "Failed to base64 decode hmac key of requestor "+name, 0)
}
......@@ -109,16 +105,9 @@ func (pkauth *PublicKeyAuthenticator) Authenticate(
}
func (pkauth *PublicKeyAuthenticator) Initialize(name string, requestor Requestor) error {
var bts []byte
var err error
if strings.HasPrefix(requestor.AuthenticationKey, "-----BEGIN") {
bts = []byte(requestor.AuthenticationKey)
}
if _, err := os.Stat(requestor.AuthenticationKey); err == nil {
bts, err = ioutil.ReadFile(requestor.AuthenticationKey)
if err != nil {
return err
}
bts, err := fs.ReadKey(requestor.AuthenticationKey)
if err != nil {
return err
}
if len(bts) == 0 {
return errors.Errorf("Requestor %s has invalid public key", name)
......@@ -154,7 +143,11 @@ func (pskauth *PresharedKeyAuthenticator) Initialize(name string, requestor Requ
if requestor.AuthenticationKey == "" {
return errors.Errorf("Requestor %s has no authentication key", name)
}
pskauth.presharedkeys[requestor.AuthenticationKey] = name
bts, err := fs.ReadKey(requestor.AuthenticationKey)
if err != nil {
return err
}
pskauth.presharedkeys[string(bts)] = name
return nil
}
......@@ -166,12 +159,13 @@ func jwtKeyExtractor(publickeys map[string]interface{}) func(token *jwt.Token) (
var ok bool
kid, ok := token.Header["kid"]
if !ok {
return nil, errors.New("No kid jwt header found")
kid = token.Claims.(*jwt.StandardClaims).Issuer
}
requestor, ok := kid.(string)
if !ok {
return nil, errors.New("kid jwt header was not a string")
return nil, errors.New("requestor name was not a string")
}
token.Claims.(*jwt.StandardClaims).Issuer = requestor
if pk, ok := publickeys[requestor]; ok {
return pk, nil
}
......@@ -205,7 +199,7 @@ func jwtAuthenticate(
// Verify JWT signature. We do not yet store the JWT contents here, because we need to know the session type first
// before we can construct a struct instance of the appropriate type into which to unmarshal the JWT contents.
claims := &jwt.StandardClaims{}
token, err := jwt.ParseWithClaims(requestorJwt, claims, jwtKeyExtractor(keys))
_, err = jwt.ParseWithClaims(requestorJwt, claims, jwtKeyExtractor(keys))
if err != nil {
return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
}
......@@ -222,7 +216,7 @@ func jwtAuthenticate(
return true, nil, "", server.RemoteError(server.ErrorInvalidRequest, err.Error())
}
requestor := token.Header["kid"].(string) // presence in Header and type is already checked by jwtKeyExtractor
requestor := claims.Issuer // presence is ensured by jwtKeyExtractor
return true, parsedJwt.SessionRequest(), requestor, nil
}
......
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