Commit eb7abbef authored by Sietse Ringers's avatar Sietse Ringers

feat: preliminary support for revocation

parent 72eee193
......@@ -57,19 +57,19 @@ type Issuer struct {
// CredentialType is a description of a credential type, specifying (a.o.) its name, issuer, and attributes.
type CredentialType struct {
ID string `xml:"CredentialID"`
Name TranslatedString `xml:"Name"`
ShortName TranslatedString `xml:"ShortName"`
IssuerID string `xml:"IssuerID"`
SchemeManagerID string `xml:"SchemeManager"`
IsSingleton bool `xml:"ShouldBeSingleton"`
DisallowDelete bool `xml:"DisallowDelete"`
Description TranslatedString
AttributeTypes []*AttributeType `xml:"Attributes>Attribute" json:"-"`
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"IssueSpecification"`
IssueURL TranslatedString `xml:"IssueURL"`
DeprecatedSince Timestamp
ID string `xml:"CredentialID"`
Name TranslatedString `xml:"Name"`
ShortName TranslatedString `xml:"ShortName"`
IssuerID string `xml:"IssuerID"`
SchemeManagerID string `xml:"SchemeManager"`
IsSingleton bool `xml:"ShouldBeSingleton"`
DisallowDelete bool `xml:"DisallowDelete"`
Description TranslatedString
AttributeTypes []*AttributeType `xml:"Attributes>Attribute" json:"-"`
SupportsRevocation bool
XMLVersion int `xml:"version,attr"`
XMLName xml.Name `xml:"IssueSpecification"`
IssueURL TranslatedString `xml:"IssueURL"`
Valid bool `xml:"-"`
}
......
......@@ -7,6 +7,15 @@ import (
type metaObjectIdentifier string
func (oi *metaObjectIdentifier) UnmarshalBinary(data []byte) error {
*oi = metaObjectIdentifier(data)
return nil
}
func (oi metaObjectIdentifier) MarshalBinary() (data []byte, err error) {
return []byte(oi), nil
}
// SchemeManagerIdentifier identifies a scheme manager. Equal to its ID. For example "irma-demo".
type SchemeManagerIdentifier struct {
metaObjectIdentifier
......
......@@ -49,24 +49,21 @@ func New(conf *server.Configuration) (*Server, error) {
}
func (s *Server) Stop() {
if err := s.conf.IrmaConfiguration.Close(); err != nil {
_ = server.LogWarning(err)
}
s.stopScheduler <- true
s.sessions.stop()
}
func (s *Server) verifyConfiguration(configuration *server.Configuration) error {
if s.conf.Logger == nil {
s.conf.Logger = server.NewLogger(s.conf.Verbose, s.conf.Quiet, s.conf.LogJSON)
}
server.Logger = s.conf.Logger
irma.Logger = s.conf.Logger
func (s *Server) verifyIrmaConf(configuration *server.Configuration) error {
if s.conf.IrmaConfiguration == nil {
var (
err error
exists bool
)
if s.conf.SchemesPath == "" {
s.conf.SchemesPath = server.DefaultSchemesPath() // Returns an existing path
s.conf.SchemesPath = irma.DefaultSchemesPath() // Returns an existing path
}
if exists, err = fs.PathExists(s.conf.SchemesPath); err != nil {
return server.LogError(err)
......@@ -86,6 +83,10 @@ func (s *Server) verifyConfiguration(configuration *server.Configuration) error
if err = s.conf.IrmaConfiguration.ParseFolder(); err != nil {
return server.LogError(err)
}
if err = fs.EnsureDirectoryExists(s.conf.RevocationPath); err != nil {
return server.LogError(err)
}
s.conf.IrmaConfiguration.RevocationPath = s.conf.RevocationPath
}
if len(s.conf.IrmaConfiguration.SchemeManagers) == 0 {
......@@ -104,6 +105,10 @@ func (s *Server) verifyConfiguration(configuration *server.Configuration) error
s.conf.SchemesUpdateInterval = 0
}
return nil
}
func (s *Server) verifyPrivateKeys(configuration *server.Configuration) error {
if s.conf.IssuerPrivateKeys == nil {
s.conf.IssuerPrivateKeys = make(map[irma.IssuerIdentifier]*gabi.PrivateKey)
}
......@@ -141,7 +146,32 @@ func (s *Server) verifyConfiguration(configuration *server.Configuration) error
return server.LogError(errors.Errorf("Private key %s-%d does not belong to corresponding public key", issid.String(), sk.Counter))
}
}
for issid := range s.conf.IrmaConfiguration.Issuers {
sk, err := s.conf.PrivateKey(issid)
if err != nil {
return server.LogError(err)
}
if sk == nil || !sk.RevocationSupported() {
continue
}
for credid, credtype := range s.conf.IrmaConfiguration.CredentialTypes {
if credtype.IssuerIdentifier() != issid || !credtype.SupportsRevocation {
continue
}
db, err := s.conf.IrmaConfiguration.RevocationDB(credid)
if err != nil {
return server.LogError(err)
}
if err = db.LoadCurrent(); err != nil {
return server.LogError(err)
}
}
}
return nil
}
func (s *Server) verifyURL(configuration *server.Configuration) error {
if s.conf.URL != "" {
if !strings.HasSuffix(s.conf.URL, "/") {
s.conf.URL = s.conf.URL + "/"
......@@ -159,7 +189,10 @@ func (s *Server) verifyConfiguration(configuration *server.Configuration) error
} else {
s.conf.Logger.Warn("No url parameter specified in configuration; unless an url is elsewhere prepended in the QR, the IRMA client will not be able to connect")
}
return nil
}
func (s *Server) verifyEmail(configuration *server.Configuration) error {
if s.conf.Email != "" {
// Very basic sanity checks
if !strings.Contains(s.conf.Email, "@") || strings.Contains(s.conf.Email, "\n") {
......@@ -170,6 +203,22 @@ func (s *Server) verifyConfiguration(configuration *server.Configuration) error
var x string
_ = t.Post("email", &x, s.conf.Email)
}
return nil
}
func (s *Server) verifyConfiguration(configuration *server.Configuration) error {
if s.conf.Logger == nil {
s.conf.Logger = server.NewLogger(s.conf.Verbose, s.conf.Quiet, s.conf.LogJSON)
}
server.Logger = s.conf.Logger
irma.Logger = s.conf.Logger
// loop to avoid repetetive err != nil line triplets
for _, f := range []func(*server.Configuration) error{s.verifyIrmaConf, s.verifyPrivateKeys, s.verifyURL, s.verifyEmail} {
if err := f(configuration); err != nil {
return err
}
}
return nil
}
......
package servercore
import (
"time"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/server"
"github.com/sirupsen/logrus"
......@@ -26,10 +30,18 @@ func (session *session) handleGetRequest(min, max *irma.ProtocolVersion) (irma.S
if session.status != server.StatusInitialized {
return nil, server.RemoteError(server.ErrorUnexpectedRequest, "Session already started")
}
session.markAlive()
session.markAlive()
logger := session.conf.Logger.WithFields(logrus.Fields{"session": session.token})
// we include the latest revocation records for the client here, as opposed to when the session
// was started, so that the client always gets the very latest revocation records
// TODO revocation database update mechanism
var err error
if err = session.request.Base().SetRevocationRecords(session.conf.IrmaConfiguration); err != nil {
return nil, session.fail(server.ErrorUnknown, err.Error()) // TODO error type
}
// Handle legacy clients that do not support condiscon, by attempting to convert the condiscon
// session request to the legacy session request format
legacy, legacyErr := session.request.Legacy()
......@@ -38,7 +50,6 @@ func (session *session) handleGetRequest(min, max *irma.ProtocolVersion) (irma.S
logger.Info("Using condiscon: backwards compatibility with legacy IRMA apps is disabled")
}
var err error
if session.version, err = session.chooseProtocolVersion(min, max); err != nil {
return nil, session.fail(server.ErrorProtocolVersion, "")
}
......@@ -143,8 +154,10 @@ func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentM
}
// Verify all proofs and check disclosed attributes, if any, against request
session.result.Disclosed, session.result.ProofStatus, err = commitments.Disclosure().VerifyAgainstDisjunctions(
session.conf.IrmaConfiguration, request.Disclose, request.GetContext(), request.GetNonce(nil), pubkeys, false)
now := time.Now()
session.result.Disclosed, session.result.ProofStatus, err = commitments.Disclosure().VerifyAgainstRequest(
session.conf.IrmaConfiguration, request, request.GetContext(), request.GetNonce(nil), pubkeys, &now, false,
)
if err != nil {
if err == irma.ErrorMissingPublicKey {
return nil, session.fail(server.ErrorUnknownPublicKey, "")
......@@ -174,10 +187,35 @@ func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentM
if err != nil {
return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
}
sig, err := issuer.IssueSignature(proof.U, attributes.Ints, commitments.Nonce2)
var witness *revocation.Witness
var nonrevAttr *big.Int
if session.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation {
db, err := session.conf.IrmaConfiguration.RevocationDB(cred.CredentialTypeID)
if err != nil {
return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
}
if db.Enabled() {
if witness, err = sk.RevocationGenerateWitness(&db.Current); err != nil {
return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
}
nonrevAttr = witness.E
if err = db.AddIssuanceRecord(&revocation.IssuanceRecord{
Key: cred.RevocationKey,
Attr: nonrevAttr,
Issued: time.Now().UnixNano(), // or (floored) cred issuance time?
ValidUntil: attributes.Expiry().UnixNano(),
}); err != nil {
return nil, session.fail(server.ErrorUnknown, "failed to save nonrevocation witness")
}
}
}
sig, err := issuer.IssueSignature(proof.U, attributes.Ints, nonrevAttr, commitments.Nonce2)
if err != nil {
return nil, session.fail(server.ErrorIssuanceFailed, err.Error())
}
sig.NonRevocationWitness = witness
sigs = append(sigs, sig)
}
......
......@@ -94,6 +94,29 @@ func (s *Server) validateIssuanceRequest(request *irma.IssuanceRequest) error {
}
cred.KeyCounter = int(privatekey.Counter)
// Check that the credential is consistent with irma_configuration
if err := cred.Validate(s.conf.IrmaConfiguration); err != nil {
return err
}
if s.conf.IrmaConfiguration.CredentialTypes[cred.CredentialTypeID].SupportsRevocation {
db, err := s.conf.IrmaConfiguration.RevocationDB(cred.CredentialTypeID)
if err != nil {
return err
}
if !db.Enabled() {
s.conf.Logger.WithFields(logrus.Fields{"cred": cred.CredentialTypeID}).Warn("revocation supported in scheme but not enabled")
} else {
if len(cred.RevocationKey) == 0 {
return errors.New("revocationKey field unset on revocable credential")
}
if exists, err := db.KeyExists([]byte(cred.RevocationKey)); err != nil {
return err
} else if exists {
return errors.New("revocationKey already used")
}
}
}
// Ensure the credential has an expiry date
defaultValidity := irma.Timestamp(time.Now().AddDate(0, 6, 0))
if cred.Validity == nil {
......
......@@ -9,6 +9,7 @@ import (
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/server"
"github.com/privacybydesign/keyproof/common"
"github.com/sirupsen/logrus"
"gopkg.in/antage/eventsource.v1"
)
......@@ -171,7 +172,7 @@ func (s *Server) newSession(action irma.Action, request irma.RequestorRequest) *
}
s.conf.Logger.WithFields(logrus.Fields{"session": ses.token}).Debug("New session started")
nonce, _ := gabi.RandomBigInt(gabi.DefaultSystemParameters[2048].Lstatzk)
nonce := common.RandomBigInt(new(big.Int).Lsh(big.NewInt(1), gabi.DefaultSystemParameters[2048].Lstatzk))
ses.request.Base().Nonce = nonce
ses.request.Base().Context = one
s.sessions.add(ses)
......
......@@ -91,7 +91,6 @@ func (th TestHandler) Cancelled() {
th.Failure(&irma.SessionError{Err: errors.New("Cancelled")})
}
func (th TestHandler) Failure(err *irma.SessionError) {
th.t.Logf("Session failed: %+v\n", *err)
select {
case th.c <- &SessionResult{Err: err}:
default:
......
......@@ -22,7 +22,7 @@ func TestManualKeyshareSession(t *testing.T) {
}
func TestRequestorIssuanceKeyshareSession(t *testing.T) {
testRequestorIssuance(t, true)
testRequestorIssuance(t, true, nil)
}
func TestKeyshareRegister(t *testing.T) {
......
......@@ -5,10 +5,16 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"crypto/rand"
"path/filepath"
"reflect"
"testing"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/test"
"github.com/privacybydesign/irmago/irmaclient"
......@@ -22,6 +28,7 @@ const (
sessionOptionUpdatedIrmaConfiguration sessionOption = 1 << iota
sessionOptionUnsatisfiableRequest
sessionOptionRetryPost
sessionOptionIgnoreClientError
)
type requestorSessionResult struct {
......@@ -62,7 +69,7 @@ func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *i
require.NoError(t, err)
client.NewSession(string(j), h)
clientResult := <-clientChan
if clientResult != nil {
if (len(options) == 0 || options[0] != sessionOptionIgnoreClientError) && clientResult != nil {
require.NoError(t, clientResult.Err)
}
......@@ -175,7 +182,7 @@ func testRequestorDisclosure(t *testing.T, request *irma.DisclosureRequest, opti
}
func TestRequestorIssuanceSession(t *testing.T) {
testRequestorIssuance(t, false)
testRequestorIssuance(t, false, nil)
}
func TestRequestorCombinedSessionMultipleAttributes(t *testing.T) {
......@@ -209,7 +216,7 @@ func TestRequestorCombinedSessionMultipleAttributes(t *testing.T) {
require.Equal(t, server.StatusDone, requestorSessionHelper(t, &ir, nil).Status)
}
func testRequestorIssuance(t *testing.T, keyshare bool) {
func testRequestorIssuance(t *testing.T, keyshare bool, client *irmaclient.Client) {
attrid := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
request := irma.NewIssuanceRequest([]*irma.CredentialRequest{{
CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard"),
......@@ -232,7 +239,7 @@ func testRequestorIssuance(t *testing.T, keyshare bool) {
})
}
result := requestorSessionHelper(t, request, nil)
result := requestorSessionHelper(t, request, client)
require.Nil(t, result.Err)
require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
require.NotEmpty(t, result.Disclosed)
......@@ -333,3 +340,83 @@ func TestOptionalDisclosure(t *testing.T) {
require.True(t, reflect.DeepEqual(args.disclosed, result.Disclosed))
}
}
func editDB(t *testing.T, path string, keystore revocation.Keystore, current bool, f func(*revocation.DB)) {
db, err := revocation.LoadDB(path, keystore)
require.NoError(t, err)
if current {
require.NoError(t, db.LoadCurrent())
}
f(db)
require.NoError(t, db.Close())
}
func revocationSession(t *testing.T, client *irmaclient.Client, options ...sessionOption) *requestorSessionResult {
attr := irma.NewAttributeTypeIdentifier("irma-demo.MijnOverheid.root.BSN")
req := irma.NewDisclosureRequest(attr)
req.Revocation = irma.RevocationSet{attr.CredentialTypeIdentifier(): struct{}{}}
result := requestorSessionHelper(t, req, client, options...)
require.Nil(t, result.Err)
return result
}
func TestRevocation(t *testing.T) {
// setup client, constants, and revocation key material
client, _ := parseStorage(t)
iss := irma.NewIssuerIdentifier("irma-demo.MijnOverheid")
cred := irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
dbPath := filepath.Join(testdata, "storage", "revocation", cred.String())
keystore := client.Configuration.RevocationKeystore(iss)
sk, err := client.Configuration.PrivateKey(iss)
require.NoError(t, err)
revsk, err := sk.RevocationKey()
require.NoError(t, err)
// enable revocation for our credential type by creating and saving an initial accumulator
editDB(t, dbPath, keystore, false, func(db *revocation.DB) {
require.NoError(t, db.EnableRevocation(revsk))
})
// issue MijnOverheid.root instance with revocation enabled
request := irma.NewIssuanceRequest([]*irma.CredentialRequest{{
RevocationKey: "12345", // once revocation is required for a credential type, this key is required
CredentialTypeID: irma.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root"),
Attributes: map[string]string{
"BSN": "299792458",
},
}})
result := requestorSessionHelper(t, request, client)
require.Nil(t, result.Err)
// perform disclosure session with nonrevocation proof
result = revocationSession(t, client)
require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
require.NotEmpty(t, result.Disclosed)
// revoke fake other credential
e, err := rand.Prime(rand.Reader, 207)
require.NoError(t, err)
editDB(t, dbPath, keystore, true, func(db *revocation.DB) {
require.NoError(t, db.AddIssuanceRecord(&revocation.IssuanceRecord{
Key: "fake",
Attr: big.Convert(e),
}))
require.NoError(t, db.Revoke(revsk, []byte("fake")))
})
// perform another disclosure session with nonrevocation proof
// client updates its witness to the new accumulator first
result = revocationSession(t, client)
require.Equal(t, irma.ProofStatusValid, result.ProofStatus)
require.NotEmpty(t, result.Disclosed)
// revoke our credential
editDB(t, dbPath, keystore, true, func(db *revocation.DB) {
require.NoError(t, db.Revoke(revsk, []byte("12345")))
})
// try to perform session with revoked credential
// client notices that is credential is revoked and aborts
result = revocationSession(t, client, sessionOptionIgnoreClientError)
require.Equal(t, result.Status, server.StatusCancelled)
}
......@@ -26,7 +26,7 @@ var (
)
func init() {
logger.Level = logrus.ErrorLevel
logger.Level = logrus.TraceLevel
logger.Formatter = &prefixed.TextFormatter{ForceFormatting: true, ForceColors: true}
}
......@@ -54,15 +54,12 @@ func StartIrmaServer(t *testing.T, updatedIrmaConf bool) {
irmaconf += "_updated"
}
logger := logrus.New()
logger.Level = logrus.ErrorLevel
logger.Formatter = &logrus.TextFormatter{}
var err error
irmaServer, err = irmaserver.New(&server.Configuration{
URL: "http://localhost:48680",
Logger: logger,
SchemesPath: filepath.Join(testdata, irmaconf),
URL: "http://localhost:48680",
Logger: logger,
SchemesPath: filepath.Join(testdata, irmaconf),
RevocationPath: filepath.Join(testdata, "storage", "revocation"),
})
require.NoError(t, err)
......@@ -76,6 +73,7 @@ func StartIrmaServer(t *testing.T, updatedIrmaConf bool) {
}
func StopIrmaServer() {
irmaServer.Stop()
_ = httpServer.Close()
}
......@@ -85,6 +83,7 @@ var IrmaServerConfiguration = &requestorserver.Configuration{
Logger: logger,
SchemesPath: filepath.Join(testdata, "irma_configuration"),
IssuerPrivateKeysPath: filepath.Join(testdata, "privatekeys"),
RevocationPath: filepath.Join(testdata, "storage", "revocation"),
},
DisableRequestorAuthentication: true,
Port: 48682,
......@@ -96,6 +95,7 @@ var JwtServerConfiguration = &requestorserver.Configuration{
Logger: logger,
SchemesPath: filepath.Join(testdata, "irma_configuration"),
IssuerPrivateKeysPath: filepath.Join(testdata, "privatekeys"),
RevocationPath: filepath.Join(testdata, "storage", "revocation"),
},
Port: 48682,
DisableRequestorAuthentication: false,
......
......@@ -88,8 +88,10 @@ func FindTestdataFolder(t *testing.T) string {
// ClearTestStorage removes any output from previously run tests to ensure a clean state;
// some of the tests don't like it when there is existing state in storage.
func ClearTestStorage(t *testing.T) {
path := filepath.Join(FindTestdataFolder(t), "storage", "test")
err := os.RemoveAll(path)
path := filepath.Join(FindTestdataFolder(t), "storage")
err := os.RemoveAll(filepath.Join(path, "test"))
checkError(t, err)
err = os.RemoveAll(filepath.Join(path, "revocation"))
checkError(t, err)
}
......@@ -98,12 +100,14 @@ func CreateTestStorage(t *testing.T) {
path := filepath.Join(FindTestdataFolder(t), "storage")
// EnsureDirectoryExists eventually uses mkdir from the OS which is not recursive
// so we have to create the temporary test storage by two function calls.
// so we have to create the temporary test storage by multiple function calls.
// We ignore any error possibly returned by creating the first one, because if it errors,
// then the second one certainly will as well.
_ = fs.EnsureDirectoryExists(path)
err := fs.EnsureDirectoryExists(filepath.Join(path, "test"))
checkError(t, err)
err = fs.EnsureDirectoryExists(filepath.Join(path, "revocation"))
checkError(t, err)
}
func SetupTestStorage(t *testing.T) {
......
......@@ -9,7 +9,6 @@ import (
"github.com/go-errors/errors"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/server"
"github.com/spf13/cobra"
)
......@@ -20,7 +19,7 @@ var downloadCmd = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
var path string
var urls []string
defaultIrmaconf := server.DefaultSchemesPath()
defaultIrmaconf := irma.DefaultSchemesPath()
if len(args) == 0 {
path = defaultIrmaconf
......@@ -100,7 +99,7 @@ func downloadSchemeManager(dest string, urls []string) error {
}
func downloadHelp() string {
defaultIrmaconf := server.DefaultSchemesPath()
defaultIrmaconf := irma.DefaultSchemesPath()
str := "The download command downloads and saves scheme managers given their URLs, saving it in path (i.e., an irma_configuration folder).\n\n"
if defaultIrmaconf != "" {
str += "If path is not given, the default path " + defaultIrmaconf + " is used.\n"
......
......@@ -4,15 +4,12 @@ import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
"os"
"io/ioutil"
"fmt"
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi/signed"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/spf13/cobra"
)
......@@ -47,16 +44,14 @@ var keygenCmd = &cobra.Command{
}
// Marshal keys
bts, err := x509.MarshalECPrivateKey(key)
pemEncoded, err := signed.MarshalPemPrivateKey(key)
if err != nil {
return err
}
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: bts})
bts, err = x509.MarshalPKIXPublicKey(&key.PublicKey)
pemEncodedPub, err := signed.MarshalPemPublicKey(&key.PublicKey)
if err != nil {
os.Exit(1)
return err
}
pemEncodedPub := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: bts})
// Save keys
if err = ioutil.WriteFile(skfile, pemEncoded, 0600); err != nil {
......
......@@ -11,7 +11,6 @@ import (
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/fs"
"github.com/privacybydesign/irmago/server"
"github.com/spf13/cobra"
)
......@@ -99,5 +98,5 @@ func prettyprint(ob interface{}) string {
func init() {
RootCmd.AddCommand(metaCmd)
metaCmd.Flags().StringP("irmaconf", "i", server.DefaultSchemesPath(), "path to irma_configuration")
metaCmd.Flags().StringP("irmaconf", "i", irma.DefaultSchemesPath(), "path to irma_configuration")