...
 
Commits (19)
......@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.5.0-rc.4] - 2020-06-18
### Added
* Support for parallel sessions (e.g. issuance of missing credentials during a disclosure session) to `irmaclient`
### Fixed
* Several minor bugs in `irmaclient`
### Security
* The IRMA server now keeps issuer private keys in memory as short as possible
## [0.5.0-rc.3] - 2020-05-14
### Added
......@@ -81,6 +91,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Combined issuance-disclosure requests with two schemes one of which has a keyshare server now work as expected
- Various other bugfixes
[0.5.0-rc.4]: https://github.com/privacybydesign/irmago/compare/v0.5.0-rc.3...v0.5.0-rc.4
[0.5.0-rc.3]: https://github.com/privacybydesign/irmago/compare/v0.5.0-rc.2...v0.5.0-rc.3
[0.5.0-rc.2]: https://github.com/privacybydesign/irmago/compare/v0.5.0-rc.1...v0.5.0-rc.2
[0.5.0-rc.1]: https://github.com/privacybydesign/irmago/compare/v0.4.1...v0.5.0-rc.1
......
......@@ -45,7 +45,7 @@ type AttributeList struct {
*MetadataAttribute `json:"-"`
Ints []*big.Int
Revoked bool `json:",omitempty"`
RevocationSupported bool `json:,omitempty`
RevocationSupported bool `json:",omitempty"`
strings []TranslatedString
attrMap map[AttributeTypeIdentifier]TranslatedString
info *CredentialInfo
......@@ -207,17 +207,18 @@ func MetadataFromInt(i *big.Int, conf *Configuration) *MetadataAttribute {
func NewMetadataAttribute(version byte) *MetadataAttribute {
val := MetadataAttribute{new(big.Int), nil, nil}
val.setField(versionField, []byte{version})
val.setSigningDate()
val.setSigningDate(time.Now())
val.setKeyCounter(0)
val.setDefaultValidityDuration()
return &val
}
// Bytes returns this metadata attribute as a byte slice.
// Bigint's Bytes() method returns a big-endian byte slice, so add padding at begin.
func (attr *MetadataAttribute) Bytes() []byte {
bytes := attr.Int.Bytes()
if len(bytes) < metadataLength {
bytes = append(bytes, make([]byte, metadataLength-len(bytes))...)
bytes = append(make([]byte, metadataLength-len(bytes)), bytes...)
}
return bytes
}
......@@ -248,8 +249,8 @@ func (attr *MetadataAttribute) SigningDate() time.Time {
return time.Unix(timestamp, 0)
}
func (attr *MetadataAttribute) setSigningDate() {
attr.setField(signingDateField, shortToByte(uint(time.Now().Unix()/ExpiryFactor)))
func (attr *MetadataAttribute) setSigningDate(issuedAt time.Time) {
attr.setField(signingDateField, shortToByte(uint(issuedAt.Unix()/ExpiryFactor)))
}
// KeyCounter return the public key counter of the metadata attribute
......@@ -277,13 +278,14 @@ func (attr *MetadataAttribute) setDefaultValidityDuration() {
}
func (attr *MetadataAttribute) setExpiryDate(timestamp *Timestamp) error {
signingTimestamp := attr.SigningDate()
var expiry int64
if timestamp == nil {
expiry = time.Now().AddDate(0, 6, 0).Unix()
expiry = signingTimestamp.AddDate(0, 6, 0).Unix()
} else {
expiry = time.Time(*timestamp).Unix()
}
signing := attr.SigningDate().Unix()
signing := signingTimestamp.Unix()
if expiry-signing < 0 {
return errors.New("cannot set expired date")
}
......
......@@ -22,6 +22,11 @@ var Logger *logrus.Logger
// Only for use in unit tests.
var ForceHTTPS = true
const (
sessionChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
sessionTokenLength = 20
)
// AssertPathExists returns nil only if it has been successfully
// verified that all specified paths exists.
func AssertPathExists(paths ...string) error {
......@@ -264,3 +269,17 @@ func RandomBigInt(limit *big.Int) *big.Int {
type SSECtx struct {
Component, Arg string
}
func NewSessionToken() string {
r := make([]byte, sessionTokenLength)
_, err := rand.Read(r)
if err != nil {
panic(err)
}
b := make([]byte, sessionTokenLength)
for i := range b {
b[i] = sessionChars[r[i]%byte(len(sessionChars))]
}
return string(b)
}
......@@ -157,22 +157,38 @@ type SessionResult struct {
Missing [][]irmaclient.DisclosureCandidates
}
// UnsatisfiableTestHandler is a session handler that expects RequestVerificationPermission
// to be called for an unsatisfiable session. If called a second time, it checks
// that the session has beome satifsiable and finishes it.
type UnsatisfiableTestHandler struct {
TestHandler
called bool
}
func (th UnsatisfiableTestHandler) Success(result string) {
th.Failure(&irma.SessionError{ErrorType: irma.ErrorType("Unsatisfiable request succeeded")})
func (th *UnsatisfiableTestHandler) Success(result string) {
if !th.called {
th.Failure(&irma.SessionError{ErrorType: irma.ErrorType("Unsatisfiable request succeeded early")})
} else {
th.c <- nil
}
}
func (th UnsatisfiableTestHandler) RequestVerificationPermission(request *irma.DisclosureRequest, satisfiable bool, candidates [][]irmaclient.DisclosureCandidates, ServerName irma.TranslatedString, callback irmaclient.PermissionHandler) {
if satisfiable {
th.Failure(&irma.SessionError{ErrorType: irma.ErrorType("Unsatisfiable request succeeded")})
return
func (th *UnsatisfiableTestHandler) RequestVerificationPermission(request *irma.DisclosureRequest, satisfiable bool, candidates [][]irmaclient.DisclosureCandidates, ServerName irma.TranslatedString, callback irmaclient.PermissionHandler) {
if !th.called {
if satisfiable {
th.Failure(&irma.SessionError{ErrorType: irma.ErrorType("Unsatisfiable request succeeded")})
return
}
th.called = true
th.c <- &SessionResult{Missing: candidates}
} else {
th.TestHandler.RequestVerificationPermission(request, satisfiable, candidates, ServerName, callback)
}
th.c <- &SessionResult{Missing: candidates}
}
// Override TestHandler.Cancelled() so we can cancel future RequestVerificationPermission() invocations
func (th *UnsatisfiableTestHandler) Cancelled() {}
// ManualTestHandler embeds a TestHandler to inherit its methods.
// Below we overwrite the methods that require behaviour specific to manual settings.
type ManualTestHandler struct {
......
......@@ -26,11 +26,13 @@ const (
sessionOptionIgnoreError
sessionOptionReuseServer
sessionOptionClientWait
sessionOptionWait
)
type requestorSessionResult struct {
*server.SessionResult
Missing [][]irmaclient.DisclosureCandidates
Missing [][]irmaclient.DisclosureCandidates
Dismisser irmaclient.SessionDismisser
}
func processOptions(options ...sessionOption) sessionOption {
......@@ -54,7 +56,7 @@ func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *i
defer StopIrmaServer()
}
clientChan := make(chan *SessionResult)
clientChan := make(chan *SessionResult, 2)
serverChan := make(chan *server.SessionResult)
qr, token, err := irmaServer.StartSession(request, func(result *server.SessionResult) {
......@@ -64,7 +66,7 @@ func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *i
var h irmaclient.Handler
if opts&sessionOptionUnsatisfiableRequest > 0 {
h = &UnsatisfiableTestHandler{TestHandler{t, clientChan, client, nil, 0, ""}}
h = &UnsatisfiableTestHandler{TestHandler: TestHandler{t, clientChan, client, nil, 0, ""}}
} else {
var wait time.Duration = 0
if opts&sessionOptionClientWait > 0 {
......@@ -75,15 +77,15 @@ func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *i
j, err := json.Marshal(qr)
require.NoError(t, err)
client.NewSession(string(j), h)
dismisser := client.NewSession(string(j), h)
clientResult := <-clientChan
if opts&sessionOptionIgnoreError == 0 && clientResult != nil {
require.NoError(t, clientResult.Err)
}
if opts&sessionOptionUnsatisfiableRequest > 0 {
if opts&sessionOptionUnsatisfiableRequest > 0 && opts&sessionOptionWait == 0 {
require.NotNil(t, clientResult)
return &requestorSessionResult{nil, clientResult.Missing}
return &requestorSessionResult{nil, clientResult.Missing, dismisser}
}
serverResult := <-serverChan
......@@ -103,7 +105,7 @@ func requestorSessionHelper(t *testing.T, request irma.SessionRequest, client *i
require.NoError(t, err)
}
return &requestorSessionResult{serverResult, nil}
return &requestorSessionResult{serverResult, nil, dismisser}
}
// Check that nonexistent IRMA identifiers in the session request fail the session
......@@ -391,3 +393,45 @@ func TestClientDeveloperMode(t *testing.T) {
require.Equal(t, string(irma.ErrorHTTPS), string(serr.ErrorType))
require.Equal(t, "remote server does not use https", serr.Err.Error())
}
func TestParallelSessions(t *testing.T) {
client, handler := parseStorage(t)
defer test.ClearTestStorage(t, handler.storage)
StartIrmaServer(t, false, "")
defer StopIrmaServer()
// Ensure we don't have the requested attribute at first
require.NoError(t, client.RemoveStorage())
client.SetPreferences(irmaclient.Preferences{DeveloperMode: true})
// Start disclosure session for an attribute we don't have.
// sessionOptionWait makes this block until the IRMA server returns a result.
disclosure := make(chan *requestorSessionResult)
go func() {
disclosure <- requestorSessionHelper(t,
getDisclosureRequest(irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")),
client,
sessionOptionUnsatisfiableRequest, sessionOptionReuseServer, sessionOptionWait,
)
}()
// Wait for a bit then check that so far zero sessions have been done
time.Sleep(100 * time.Millisecond)
logs, err := client.LoadNewestLogs(100)
require.NoError(t, err)
require.Zero(t, len(logs))
// Issue credential containing above attribute
requestorSessionHelper(t, getIssuanceRequest(false), client, sessionOptionReuseServer)
// Running disclosure session should now finish using the new credential
result := <-disclosure
require.Nil(t, result.Err)
require.NotEmpty(t, result.Disclosed)
require.Equal(t, "s1234567", result.Disclosed[0][0].Value["en"])
// Two sessions should now have been done
logs, err = client.LoadNewestLogs(100)
require.NoError(t, err)
require.Len(t, logs, 2)
}
// +build !local_tests
package sessiontest
import (
......@@ -29,10 +31,6 @@ var (
revocationDbType, revocationDbStr = "mysql", "testuser:testpassword@tcp(127.0.0.1)/test"
revocationPkCounter uint = 2
revocationTestAttr = irma.NewAttributeTypeIdentifier("irma-demo.MijnOverheid.root.BSN")
revocationTestCred = revocationTestAttr.CredentialTypeIdentifier()
revKeyshareTestAttr = irma.NewAttributeTypeIdentifier("test.test.email.email")
revKeyshareTestCred = revKeyshareTestAttr.CredentialTypeIdentifier()
)
func testRevocation(t *testing.T, attr irma.AttributeTypeIdentifier, client *irmaclient.Client, handler irmaclient.ClientHandler) {
......@@ -82,6 +80,8 @@ func testRevocation(t *testing.T, attr irma.AttributeTypeIdentifier, client *irm
logger.Info("step 5")
result = revocationSession(t, client, request, sessionOptionUnsatisfiableRequest)
require.NotEmpty(t, result.Missing)
require.NotNil(t, result.Dismisser)
result.Dismisser.Dismiss()
// client revocation callback was called
require.NotNil(t, handler.(*TestClientHandler).revoked)
require.Equal(t, credid, handler.(*TestClientHandler).revoked.Type)
......@@ -696,11 +696,12 @@ func fakeMultipleRevocations(t *testing.T, count uint64, conf *irma.RevocationSt
func revocationConf(_ *testing.T) *server.Configuration {
return &server.Configuration{
URL: "http://localhost:48683",
Logger: logger,
EnableSSE: true,
DisableSchemesUpdate: true,
SchemesPath: filepath.Join(testdata, "irma_configuration"),
URL: "http://localhost:48683",
Logger: logger,
EnableSSE: true,
DisableSchemesUpdate: true,
SchemesPath: filepath.Join(testdata, "irma_configuration"),
IssuerPrivateKeysPath: filepath.Join(testdata, "privatekeys"),
RevocationSettings: irma.RevocationSettings{
revocationTestCred: {Authority: true},
revKeyshareTestCred: {Authority: true},
......
......@@ -13,7 +13,7 @@ import (
"github.com/privacybydesign/irmago/server/requestorserver"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"github.com/x-cray/logrus-prefixed-formatter"
prefixed "github.com/x-cray/logrus-prefixed-formatter"
)
var (
......@@ -24,6 +24,11 @@ var (
logger = logrus.New()
testdata = test.FindTestdataFolder(nil)
revocationTestAttr = irma.NewAttributeTypeIdentifier("irma-demo.MijnOverheid.root.BSN")
revocationTestCred = revocationTestAttr.CredentialTypeIdentifier()
revKeyshareTestAttr = irma.NewAttributeTypeIdentifier("test.test.email.email")
revKeyshareTestCred = revKeyshareTestAttr.CredentialTypeIdentifier()
)
func init() {
......@@ -139,8 +144,8 @@ var JwtServerConfiguration = &requestorserver.Configuration{
},
},
},
ListenAddress: "localhost",
Port: 48682,
ListenAddress: "localhost",
Port: 48682,
DisableRequestorAuthentication: false,
MaxRequestAge: 3,
Permissions: requestorserver.Permissions{
......
......@@ -306,8 +306,7 @@ func TestIrmaServerPrivateKeysFolder(t *testing.T) {
credid := irma.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
require.NotZero(t, len(irmaServerConfiguration.IrmaConfiguration.PrivateKeys))
sk, err := irmaServerConfiguration.IrmaConfiguration.PrivateKeyLatest(credid.IssuerIdentifier())
sk, err := irmaServerConfiguration.IrmaConfiguration.PrivateKeys.Latest(credid.IssuerIdentifier())
require.NoError(t, err)
require.NotNil(t, sk)
......@@ -328,8 +327,7 @@ func TestIrmaServerPrivateKeysFolder(t *testing.T) {
AttributeTypes: map[irma.AttributeTypeIdentifier]struct{}{},
}, downloaded)
require.NotZero(t, len(irmaServerConfiguration.IrmaConfiguration.PrivateKeys))
sk, err = irmaServerConfiguration.IrmaConfiguration.PrivateKeyLatest(credid.IssuerIdentifier())
sk, err = irmaServerConfiguration.IrmaConfiguration.PrivateKeys.Latest(credid.IssuerIdentifier())
require.NoError(t, err)
require.NotNil(t, sk)
}
......
......@@ -60,6 +60,7 @@ type Client struct {
Configuration *irma.Configuration
irmaConfigurationPath string
handler ClientHandler
sessions sessions
jobs chan func() // queue of jobs to run
jobsPause chan struct{} // sending pauses background jobs
......@@ -213,6 +214,8 @@ func New(
}
}
client.sessions = sessions{client: client, sessions: map[string]*session{}}
client.jobs = make(chan func(), 100)
client.initRevocation()
client.StartJobs()
......@@ -444,6 +447,9 @@ func (client *Client) RemoveStorage() error {
if err = client.storage.DeleteAll(); err != nil {
return err
}
if err = client.fileStorage.DeleteAll(); err != nil {
return err
}
// Client assumes there is always a secret key, so we have to load a new one
client.secretkey, err = client.storage.LoadSecretKey()
......@@ -563,17 +569,17 @@ func (client *Client) credCandidates(base *irma.BaseRequest, con irma.AttributeC
var candidates [][]*credCandidate
satisfiable := true
for _, credtype := range con.CredentialTypes() {
attrlistlist := client.attributes[credtype]
for _, credTypeID := range con.CredentialTypes() {
attrlistlist := client.attributes[credTypeID]
var c []*credCandidate
haveUsableCred := false
for _, attrlist := range attrlistlist {
satisfies, usable := client.satisfiesCon(base, attrlist, con)
if satisfies { // add it to the list, even if they are unusable
c = append(c, &credCandidate{Type: credtype, Hash: attrlist.Hash()})
}
if usable { // having one usable credential will do
haveUsableCred = true
c = append(c, &credCandidate{Type: credTypeID, Hash: attrlist.Hash()})
if usable { // having one usable credential will do
haveUsableCred = true
}
}
}
if !haveUsableCred {
......@@ -583,7 +589,15 @@ func (client *Client) credCandidates(base *irma.BaseRequest, con irma.AttributeC
}
if len(c) == 0 {
// No acceptable credentials found, add "empty" credential (i.e. without hash) to the candidates
c = append(c, &credCandidate{Type: credtype})
// Only add the credential if it is not deprecated.
credType := client.Configuration.CredentialTypes[credTypeID]
credDeprecatedSince := credType.DeprecatedSince
issuerDeprecatedSince := client.Configuration.Issuers[credType.IssuerIdentifier()].DeprecatedSince
now := irma.Timestamp(time.Now())
if (credDeprecatedSince.IsZero() || credDeprecatedSince.After(now)) &&
(issuerDeprecatedSince.IsZero() || issuerDeprecatedSince.After(now)) {
c = append(c, &credCandidate{Type: credTypeID})
}
satisfiable = false
}
candidates = append(candidates, c)
......@@ -927,10 +941,12 @@ func (client *Client) ConstructCredentials(msg []*gabi.IssueSignatureMessage, re
if sig.NonRevocationWitness != nil {
nonrevAttr = sig.NonRevocationWitness.E
}
issuedAt := time.Now()
attrs, err := request.Credentials[i-offset].AttributeList(
client.Configuration,
irma.GetMetadataVersion(request.Base().ProtocolVersion),
nonrevAttr,
issuedAt,
)
if err != nil {
return err
......
......@@ -20,4 +20,5 @@ func TestConvertingLegacyStorage(t *testing.T) {
// TestFreshStorage is not needed, because this test does not use an existing storage
t.Run("TestKeyshareEnrollmentRemoval", TestKeyshareEnrollmentRemoval)
t.Run("TestUpdatingStorage", TestUpdatingStorage)
t.Run("TestRemoveStorage", TestRemoveStorage)
}
......@@ -169,7 +169,7 @@ func TestCandidates(t *testing.T) {
require.True(t, attrs[0][0].Present())
// Require an attribute we do not have
disjunction[0][0] = irma.NewAttributeRequest("irma-demo.MijnOverheid.ageLower.over12")
disjunction[0][0] = irma.NewAttributeRequest("irma-demo.MijnOverheid.root.BSN")
attrs, satisfiable, err = client.candidatesDisCon(request.Base(), disjunction)
require.NoError(t, err)
require.False(t, satisfiable)
......@@ -324,7 +324,11 @@ func TestRemoveStorage(t *testing.T) {
client, handler := parseStorage(t)
defer test.ClearTestStorage(t, handler.storage)
bucketsBefore := map[string]bool{"attrs": true, "sigs": true, "userdata": true, "logs": true} // Test storage has 1 log
// Check whether we have logs in storage to know whether the logs bucket is there
logs, err := client.LoadNewestLogs(1)
require.NoError(t, err)
bucketsBefore := map[string]bool{"attrs": true, "sigs": true, "userdata": true, "logs": len(logs) > 0}
bucketsAfter := map[string]bool{"attrs": false, "sigs": false, "userdata": true, "logs": false} // Userdata should hold a new secret key
old_sk := *client.secretkey
......
......@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"io/ioutil"
"os"
"path/filepath"
"github.com/privacybydesign/gabi"
......@@ -134,3 +135,22 @@ func (f *fileStorage) LoadPreferences() (Preferences, error) {
func (f *fileStorage) LoadLogs() (logs []*LogEntry, err error) {
return logs, f.load(&logs, logsFile)
}
func (f *fileStorage) DeleteAll() error {
// Remove all legacy storage files
files := []string{skFile, attributesFile, kssFile, updatesFile, logsFile, preferencesFile}
for _, file := range files {
path := f.path(file)
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
return err
}
}
// Remove all legacy signatures
path := f.path(signaturesDir)
if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
......@@ -100,7 +100,7 @@ func (entry *LogEntry) GetIssuedCredentials(conf *irma.Configuration) (list irma
if err != nil {
return nil, err
}
return request.(*irma.IssuanceRequest).GetCredentialInfoList(conf, entry.Version)
return request.(*irma.IssuanceRequest).GetCredentialInfoList(conf, entry.Version, time.Time(entry.Time))
}
// GetSignedMessage gets the signed for a log entry
......
......@@ -14,6 +14,7 @@ import (
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
)
// This file contains the logic and state of performing IRMA sessions, communicates
......@@ -72,6 +73,7 @@ type session struct {
Version *irma.ProtocolVersion
ServerName irma.TranslatedString
token string
choice *irma.DisclosureChoice
attrIndices irma.DisclosedAttributeIndices
client *Client
......@@ -92,6 +94,11 @@ type session struct {
transport *irma.HTTPTransport
}
type sessions struct {
client *Client
sessions map[string]*session
}
// We implement the handler for the keyshare protocol
var _ keyshareSessionHandler = (*session)(nil)
......@@ -156,6 +163,7 @@ func (client *Client) newManualSession(request irma.SessionRequest, handler Hand
request: request,
prepRevocation: make(chan error),
}
client.sessions.add(session)
session.Handler.StatusUpdate(session.Action, irma.StatusManualStarted)
session.processSessionInfo()
......@@ -190,6 +198,7 @@ func (client *Client) newQrSession(qr *irma.Qr, handler Handler) SessionDismisse
client: client,
prepRevocation: make(chan error),
}
client.sessions.add(session)
session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
min := minVersion
......@@ -291,7 +300,8 @@ func (session *session) processSessionInfo() {
if session.Action == irma.ActionIssuing {
ir := session.request.(*irma.IssuanceRequest)
_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version)
issuedAt := time.Now()
_, err := ir.GetCredentialInfoList(session.client.Configuration, session.Version, issuedAt)
if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorUnknownIdentifier, Err: err})
return
......@@ -332,6 +342,15 @@ func (session *session) processSessionInfo() {
irma.Logger.Debug("starting candidate computation before revocation witnesses updating finished")
}
// Handle ClientReturnURL if one is found in the session request
if session.request.Base().ClientReturnURL != "" {
session.Handler.ClientReturnURLSet(session.request.Base().ClientReturnURL)
}
session.requestPermission()
}
func (session *session) requestPermission() {
candidates, satisfiable, err := session.client.Candidates(session.request)
if err != nil {
session.fail(&irma.SessionError{ErrorType: irma.ErrorCrypto, Err: err})
......@@ -340,11 +359,6 @@ func (session *session) processSessionInfo() {
session.Handler.StatusUpdate(session.Action, irma.StatusConnected)
// Handle ClientReturnURL if one is found in the session request
if session.request.Base().ClientReturnURL != "" {
session.Handler.ClientReturnURLSet(session.request.Base().ClientReturnURL)
}
// Ask for permission to execute the session
switch session.Action {
case irma.ActionDisclosing:
......@@ -392,6 +406,7 @@ func (session *session) doSession(proceed bool, choice *irma.DisclosureChoice) {
return
}
session.sendResponse(message)
session.finish(false)
} else {
var err error
session.builders, session.attrIndices, session.issuerProofNonce, err = session.getBuilders()
......@@ -496,7 +511,7 @@ func (session *session) sendResponse(message interface{}) {
if session.Action == irma.ActionIssuing {
session.client.handler.UpdateAttributes()
}
session.done = true
session.finish(false)
session.client.nonrevRepopulateCaches(session.request)
session.client.StartJobs()
session.Handler.Success(string(messageJson))
......@@ -547,6 +562,7 @@ func (session *session) checkKeyshareEnrollment() bool {
distributed := session.client.Configuration.SchemeManagers[id].Distributed()
_, enrolled := session.client.keyshareServers[id]
if distributed && !enrolled {
session.finish(false)
session.Handler.KeyshareEnrollmentMissing(id)
return false
}
......@@ -618,6 +634,7 @@ func (session *session) Distributed() bool {
func (session *session) recoverFromPanic() {
if e := recover(); e != nil {
session.finish(false)
if session.Handler != nil {
session.Handler.Failure(panicToError(e))
}
......@@ -642,13 +659,13 @@ func panicToError(e interface{}) *irma.SessionError {
// finish the session, by sending a DELETE to the server if there is one, and restarting local
// background jobs. This function is idempotent, doing nothing when called a second time. It
// returns whether or not it did something.
func (session *session) finish() bool {
func (session *session) finish(delete bool) bool {
if !session.done {
if session.IsInteractive() {
if delete && session.IsInteractive() {
session.transport.Delete()
}
session.client.nonrevRepopulateCaches(session.request)
session.client.StartJobs()
session.client.sessions.remove(session.token)
session.done = true
return true
}
......@@ -656,7 +673,7 @@ func (session *session) finish() bool {
}
func (session *session) fail(err *irma.SessionError) {
if session.finish() && err.ErrorType != irma.ErrorKeyshareUnenrolled {
if session.finish(true) && err.ErrorType != irma.ErrorKeyshareUnenrolled {
irma.Logger.Warn("client session error: ", err.Error())
err.Err = errors.Wrap(err.Err, 0)
session.Handler.Failure(err)
......@@ -664,7 +681,7 @@ func (session *session) fail(err *irma.SessionError) {
}
func (session *session) cancel() {
if session.finish() {
if session.finish(true) {
session.Handler.Cancelled()
}
}
......@@ -697,14 +714,17 @@ func (session *session) KeyshareCancelled() {
}
func (session *session) KeyshareEnrollmentIncomplete(manager irma.SchemeManagerIdentifier) {
session.finish(false)
session.Handler.KeyshareEnrollmentIncomplete(manager)
}
func (session *session) KeyshareEnrollmentDeleted(manager irma.SchemeManagerIdentifier) {
session.finish(false)
session.Handler.KeyshareEnrollmentDeleted(manager)
}
func (session *session) KeyshareBlocked(manager irma.SchemeManagerIdentifier, duration int) {
session.finish(false)
session.Handler.KeyshareBlocked(manager, duration)
}
......@@ -726,3 +746,23 @@ func (session *session) KeysharePin() {
func (session *session) KeysharePinOK() {
session.Handler.StatusUpdate(session.Action, irma.StatusCommunicating)
}
func (s sessions) remove(token string) {
last := s.sessions[token]
delete(s.sessions, token)
if last.Action == irma.ActionIssuing {
for _, session := range s.sessions {
session.requestPermission()
}
}
if len(s.sessions) == 0 {
s.client.StartJobs()
}
}
func (s sessions) add(session *session) {
session.token = common.NewSessionToken()
s.sessions[session.token] = session
}
......@@ -32,7 +32,6 @@ import (
"github.com/go-errors/errors"
"github.com/jasonlvhit/gocron"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/signed"
"github.com/privacybydesign/irmago/internal/common"
"github.com/sirupsen/logrus"
......@@ -46,9 +45,7 @@ type Configuration struct {
CredentialTypes map[CredentialTypeIdentifier]*CredentialType
AttributeTypes map[AttributeTypeIdentifier]*AttributeType
// Issuer private keys. If set (after calling ParseFolder()), will use these keys
// instead of keys in irma_configuration/$issuer/PrivateKeys.
PrivateKeys map[IssuerIdentifier]map[uint]*gabi.PrivateKey
PrivateKeys PrivateKeyRing
Revocation *RevocationStorage `json:"-"`
......@@ -162,7 +159,7 @@ func (conf *Configuration) clear() {
conf.publicKeys = make(map[IssuerIdentifier]map[uint]*gabi.PublicKey)
conf.reverseHashes = make(map[string]CredentialTypeIdentifier)
if conf.PrivateKeys == nil { // keep if already populated
conf.PrivateKeys = make(map[IssuerIdentifier]map[uint]*gabi.PrivateKey)
conf.PrivateKeys = &privateKeyRingMerge{}
}
}
......@@ -211,6 +208,14 @@ func (conf *Configuration) ParseFolder() (err error) {
return
}
if len(conf.PrivateKeys.(*privateKeyRingMerge).rings) == 0 {
ring, err := newPrivateKeyRingScheme(conf)
if err != nil {
return err
}
conf.PrivateKeys.(*privateKeyRingMerge).Add(ring)
}
if conf.Revocation == nil {
conf.Scheduler = gocron.NewScheduler()
conf.Scheduler.Start()
......@@ -331,42 +336,12 @@ func (conf *Configuration) ParseSchemeManagerFolder(dir string, manager *SchemeM
return
}
// PrivateKey returns the specified private key of the specified issuer if present; an error otherwise.
func (conf *Configuration) PrivateKey(id IssuerIdentifier, counter uint) (*gabi.PrivateKey, error) {
if _, haveIssuer := conf.PrivateKeys[id]; haveIssuer {
if sk := conf.PrivateKeys[id][counter]; sk != nil {
return sk, nil
}
}
path := fmt.Sprintf(privkeyPattern, conf.Path, id.SchemeManagerIdentifier().Name(), id.Name())
file := strings.Replace(path, "*", strconv.FormatUint(uint64(counter), 10), 1)
sk, err := gabi.NewPrivateKeyFromFile(file)
if err != nil {
return nil, err
}
if sk.Counter != counter {
return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
}
if conf.PrivateKeys[id] == nil {
conf.PrivateKeys[id] = make(map[uint]*gabi.PrivateKey)
}
conf.PrivateKeys[id][counter] = sk
return sk, nil
}
// PrivateKeyLatest returns the latest private key of the specified issuer.
func (conf *Configuration) PrivateKeyLatest(id IssuerIdentifier) (*gabi.PrivateKey, error) {
indices, err := conf.PrivateKeyIndices(id)
if err != nil {
return nil, err
}
if len(indices) == 0 {
return nil, errors.New("no private keys found")
func (conf *Configuration) AddPrivateKeyRing(ring PrivateKeyRing) error {
if err := validatePrivateKeyRing(ring, conf); err != nil {
return err
}
return conf.PrivateKey(id, indices[len(indices)-1])
conf.PrivateKeys.(*privateKeyRingMerge).Add(ring)
return nil
}
// PublicKey returns the specified public key, or nil if not present in the Configuration.
......@@ -550,40 +525,12 @@ func sorter(ints []uint) func(i, j int) bool {
return func(i, j int) bool { return ints[i] < ints[j] }
}
// unionset returns the concatenation of a and b, without duplicates, and sorted.
func unionset(a, b []uint) []uint {
m := map[uint]struct{}{}
for _, c := range [][]uint{a, b} {
for _, i := range c {
m[i] = struct{}{}
}
}
var ints []uint
for i := range m {
ints = append(ints, i)
}
sort.Slice(ints, sorter(ints))
return ints
}
func (conf *Configuration) PrivateKeyIndices(issuerid IssuerIdentifier) (i []uint, err error) {
filekeys, err := conf.matchKeyPattern(issuerid, privkeyPattern)
if err != nil {
return nil, err
}
var mapkeys []uint
for _, sk := range conf.PrivateKeys[issuerid] {
mapkeys = append(mapkeys, sk.Counter)
}
return unionset(filekeys, mapkeys), nil
}
func (conf *Configuration) PublicKeyIndices(issuerid IssuerIdentifier) (i []uint, err error) {
return conf.matchKeyPattern(issuerid, pubkeyPattern)
return matchKeyPattern(conf.Path, issuerid, pubkeyPattern)
}
func (conf *Configuration) matchKeyPattern(issuerid IssuerIdentifier, pattern string) (ints []uint, err error) {
pkpath := fmt.Sprintf(pattern, conf.Path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
func matchKeyPattern(path string, issuerid IssuerIdentifier, pattern string) (ints []uint, err error) {
pkpath := fmt.Sprintf(pattern, path, issuerid.SchemeManagerIdentifier().Name(), issuerid.Name())
files, err := filepath.Glob(pkpath)
if err != nil {
return
......@@ -1527,34 +1474,6 @@ func (conf *Configuration) ValidateKeys() error {
}
}
// Check private keys if any
indices, err = conf.PrivateKeyIndices(issuerid)
if err != nil {
return err
}
for _, i := range indices {
sk, err := conf.PrivateKey(issuerid, i)
if err != nil {
return err
}
if sk.Counter != i {
return errors.Errorf("Private key %d of issuer %s has wrong <Counter>", i, issuerid.String())
}
pk, err := conf.PublicKey(issuerid, i)
if err != nil {
return err
}
if pk == nil {
return errors.Errorf("Private key %d of issuer %s has no corresponding public key", i, issuerid.String())
}
if new(big.Int).Mul(sk.P, sk.Q).Cmp(pk.N) != 0 {
return errors.Errorf("Private key %d of issuer %s does not belong to corresponding public key", i, issuerid.String())
}
if sk.RevocationSupported() != pk.RevocationSupported() {
return errors.Errorf("revocation support of private key %d of issuer %s is not consistent with corresponding public key", i, issuerid.String())
}
}
// Check that the current public key supports enough attributes for all credential types
// issued by this issuer
for id, typ := range conf.CredentialTypes {
......
......@@ -666,3 +666,48 @@ func revoke(t *testing.T, acc *revocation.Accumulator, parent *revocation.Event,
require.NoError(t, err)
return acc, event
}
func TestPrivateKeyRings(t *testing.T) {
conf := parseConfiguration(t)
mo := NewIssuerIdentifier("irma-demo.MijnOverheid")
ru := NewIssuerIdentifier("irma-demo.RU")
tst := NewIssuerIdentifier("test.test")
schemering, err := newPrivateKeyRingScheme(conf)
require.NoError(t, err)
_, err = schemering.Get(mo, 2)
require.NoError(t, err)
_, err = schemering.Latest(mo)
require.NoError(t, err)
_, err = schemering.Get(ru, 2)
require.Error(t, err) // not present in scheme
_, err = schemering.Latest(ru)
require.Error(t, err) // not present in scheme
folderring, err := NewPrivateKeyRingFolder(filepath.Join(test.FindTestdataFolder(t), "privatekeys"), conf)
require.NoError(t, err)
_, err = folderring.Get(mo, 2)
require.Error(t, err) // not present in folder
_, err = folderring.Get(mo, 1)
require.NoError(t, err) // present in both
_, err = folderring.Get(ru, 2)
require.NoError(t, err)
_, err = folderring.Latest(ru)
require.NoError(t, err)
_, err = folderring.Get(tst, 3)
require.NoError(t, err)
_, err = folderring.Latest(tst)
require.NoError(t, err)
mergedring := privateKeyRingMerge{rings: []PrivateKeyRing{schemering, folderring}}
_, err = mergedring.Get(mo, 1)
require.NoError(t, err) // present in both
_, err = mergedring.Get(mo, 2)
require.NoError(t, err)
_, err = mergedring.Latest(mo)
require.NoError(t, err)
_, err = mergedring.Get(ru, 2)
require.NoError(t, err)
_, err = mergedring.Latest(ru)
require.NoError(t, err)
}
package irma
import (
goerrors "errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
)
type (
// PrivateKeyRing provides access to a set of private keys.
PrivateKeyRing interface {
// Latest returns the private key with the highest counter for the specified issuer, if any,
// or an error.
Latest(id IssuerIdentifier) (*gabi.PrivateKey, error)
// Get returns the specified private key, or an error.
Get(id IssuerIdentifier, counter uint) (*gabi.PrivateKey, error)
// Iterate executes the specified function on each private key of the specified issuer
// present in the ring. The private keys are offered to the function in no particular order,
// and the same key may be offered multiple times. Returns on the first error returned
// by the function.
Iterate(id IssuerIdentifier, f func(sk *gabi.PrivateKey) error) error
}
// PrivateKeyRingFolder represents a folder on disk containing private keys with filenames
// of the form scheme.issuer.xml and scheme.issuer.counter.xml.
PrivateKeyRingFolder struct {
path string
conf *Configuration
}
// privateKeyRingScheme provides access to private keys present in a scheme.
privateKeyRingScheme struct {
conf *Configuration
}
// privateKeyRingMerge is a merge of multiple key rings into one, provides access to the
// private keys of all of them.
privateKeyRingMerge struct {
rings []PrivateKeyRing
}
)
var (
ErrMissingPrivateKey = fmt.Errorf("issuer private key not found: %w", os.ErrNotExist)
)
func NewPrivateKeyRingFolder(path string, conf *Configuration) (*PrivateKeyRingFolder, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
ring := &PrivateKeyRingFolder{path, conf}
for _, file := range files {
filename := file.Name()
issuerid, counter, err := ring.parseFilename(filename)
if err != nil {
return nil, err
}
if issuerid == nil {
Logger.WithField("file", filename).Infof("Skipping non-private key file encountered in private keys path")
continue
}
sk, err := ring.readFile(filename, *issuerid)
if err != nil {
return nil, err
}
if counter != nil && *counter != sk.Counter {
return nil, errors.Errorf("private key %s has wrong counter %d in filename, should be %d", filename, counter, sk.Counter)
}
}
return ring, nil
}
func (_ *PrivateKeyRingFolder) parseFilename(filename string) (*IssuerIdentifier, *uint, error) {
// This regexp returns one of the following:
// [ "foo.bar.xml", "foo.bar", "", "" ] in case of "foo.bar.xml"
// [ "foo.bar.xml", "foo.bar", ".2", "2" ] in case of "foo.bar.2.xml"
// nil in case of other files.
matches := regexp.MustCompile(`^([^.]+\.[^.]+)(\.(\d+))?\.xml$`).FindStringSubmatch(filename)
if len(matches) != 4 {
return nil, nil, nil
}
issuerid := NewIssuerIdentifier(matches[1])
if matches[3] == "" {
return &issuerid, nil, nil
}
counter, err := strconv.ParseUint(matches[3], 10, 32)
if err != nil {
return nil, nil, err
}
c := uint(counter)
return &issuerid, &c, nil
}
func (p *PrivateKeyRingFolder) readFile(filename string, id IssuerIdentifier) (*gabi.PrivateKey, error) {
sk, err := gabi.NewPrivateKeyFromFile(filepath.Join(p.path, filename))
if err != nil {
return nil, err
}
if err = validatePrivateKey(id, sk, p.conf); err != nil {
return nil, err
}
return sk, nil
}
func (p *PrivateKeyRingFolder) Get(id IssuerIdentifier, counter uint) (*gabi.PrivateKey, error) {
sk, err := p.readFile(fmt.Sprintf("%s.%d.xml", id.String(), counter), id)
if err != nil && !goerrors.Is(err, os.ErrNotExist) {
return nil, err
}
if sk != nil {
return sk, nil
}
sk, err = p.readFile(fmt.Sprintf("%s.xml", id.String()), id)
if err != nil {
return nil, err
}
if counter != sk.Counter {
return nil, ErrMissingPrivateKey
}
return sk, nil
}
func (p *PrivateKeyRingFolder) Latest(id IssuerIdentifier) (*gabi.PrivateKey, error) {
var sk *gabi.PrivateKey
if err := p.Iterate(id, func(s *gabi.PrivateKey) error {
if sk == nil || s.Counter > sk.Counter {
sk = s
}
return nil
}); err != nil {
return nil, err
}
if sk == nil {
return nil, ErrMissingPrivateKey
}
return sk, nil
}
func (p *PrivateKeyRingFolder) Iterate(id IssuerIdentifier, f func(sk *gabi.PrivateKey) error) error {
files, err := filepath.Glob(filepath.Join(p.path, fmt.Sprintf("%s*", id.String())))
if err != nil {
return err
}
for _, file := range files {
sk, err := p.readFile(filepath.Base(file), id)
if err != nil {
return err
}
if err = f(sk); err != nil {
return err
}
}
return nil
}
func newPrivateKeyRingScheme(conf *Configuration) (*privateKeyRingScheme, error) {
ring := &privateKeyRingScheme{conf}
if err := validatePrivateKeyRing(ring, conf); err != nil {
return nil, err
}
return ring, nil
}
func (p *privateKeyRingScheme) counters(issuerid IssuerIdentifier) (i []uint, err error) {
return matchKeyPattern(p.conf.Path, issuerid, privkeyPattern)
}
func (p *privateKeyRingScheme) Get(id IssuerIdentifier, counter uint) (*gabi.PrivateKey, error) {
path := fmt.Sprintf(privkeyPattern, p.conf.Path, id.SchemeManagerIdentifier().Name(), id.Name())
file := strings.Replace(path, "*", strconv.FormatUint(uint64(counter), 10), 1)
sk, err := gabi.NewPrivateKeyFromFile(file)
if err != nil {
return nil, err
}
if sk.Counter != counter {
return nil, errors.Errorf("Private key %s of issuer %s has wrong <Counter>", file, id.String())
}
if err = validatePrivateKey(id, sk, p.conf); err != nil {
return nil, err
}
return sk, nil
}
func (p *privateKeyRingScheme) Latest(id IssuerIdentifier) (*gabi.PrivateKey, error) {
counters, err := p.counters(id)
if err != nil {
return nil, err
}
if len(counters) == 0 {
return nil, ErrMissingPrivateKey
}
return p.Get(id, counters[len(counters)-1])
}
func (p *privateKeyRingScheme) Iterate(id IssuerIdentifier, f func(sk *gabi.PrivateKey) error) error {
indices, err := p.counters(id)
if err != nil {
return err
}
for _, counter := range indices {
sk, err := p.Get(id, counter)
if err != nil {
return err
}
if err = f(sk); err != nil {
return err
}
}
return nil
}
func (p *privateKeyRingMerge) Add(ring PrivateKeyRing) {
p.rings = append(p.rings, ring)
}
func (p *privateKeyRingMerge) Get(id IssuerIdentifier, counter uint) (*gabi.PrivateKey, error) {
for _, ring := range p.rings {
sk, err := ring.Get(id, counter)
if err == nil {
return sk, nil
}
if !goerrors.Is(err, os.ErrNotExist) {
return nil, err
}
}
return nil, ErrMissingPrivateKey
}
func (p *privateKeyRingMerge) Latest(id IssuerIdentifier) (*gabi.PrivateKey, error) {
var sk *gabi.PrivateKey
for _, ring := range p.rings {
s, err := ring.Latest(id)
if err != nil && !goerrors.Is(err, os.ErrNotExist) {
return nil, err
}
if s != nil && (sk == nil || s.Counter > sk.Counter) {
sk = s
}
}
if sk == nil {
return nil, ErrMissingPrivateKey
}
return sk, nil
}
func (p *privateKeyRingMerge) Iterate(id IssuerIdentifier, f func(sk *gabi.PrivateKey) error) error {
for _, ring := range p.rings {
if err := ring.Iterate(id, f); err != nil {
return err
}
}
return nil
}
func validatePrivateKey(issuerid IssuerIdentifier, sk *gabi.PrivateKey, conf *Configuration) error {
if _, ok := conf.Issuers[issuerid]; !ok {
return errors.Errorf("Private key %d of issuer %s belongs to an unknown issuer", sk.Counter, issuerid.String())
}
pk, err := conf.PublicKey(issuerid, sk.Counter)
if err != nil {
return err
}
if pk == nil {
return errors.Errorf("Private key %d of issuer %s has no corresponding public key", sk.Counter, issuerid.String())
}
if new(big.Int).Mul(sk.P, sk.Q).Cmp(pk.N) != 0 {
return errors.Errorf("Private key %d of issuer %s does not belong to corresponding public key", sk.Counter, issuerid.String())
}
if sk.RevocationSupported() != pk.RevocationSupported() {
return errors.Errorf("revocation support of private key %d of issuer %s is not consistent with corresponding public key", sk.Counter, issuerid.String())
}
return nil
}
func validatePrivateKeyRing(ring PrivateKeyRing, conf *Configuration) error {
for issuerid := range conf.Issuers {
err := ring.Iterate(issuerid, func(sk *gabi.PrivateKey) error {
return validatePrivateKey(issuerid, sk, conf)
})
if err != nil {
return err
}
}
return nil
}
......@@ -558,8 +558,8 @@ func (dr *DisclosureRequest) Validate() error {
return nil
}
func (cr *CredentialRequest) Info(conf *Configuration, metadataVersion byte) (*CredentialInfo, error) {
list, err := cr.AttributeList(conf, metadataVersion, nil)
func (cr *CredentialRequest) Info(conf *Configuration, metadataVersion byte, issuedAt time.Time) (*CredentialInfo, error) {
list, err := cr.AttributeList(conf, metadataVersion, nil, issuedAt)
if err != nil {
return nil, err
}
......@@ -608,6 +608,7 @@ func (cr *CredentialRequest) AttributeList(
conf *Configuration,
metadataVersion byte,
revocationAttr *big.Int,
issuedAt time.Time,
) (*AttributeList, error) {
if err := cr.Validate(conf); err != nil {
return nil, err
......@@ -622,7 +623,7 @@ func (cr *CredentialRequest) AttributeList(
meta := NewMetadataAttribute(metadataVersion)
meta.setKeyCounter(cr.KeyCounter)
meta.setCredentialTypeIdentifier(cr.CredentialTypeID.String())
meta.setSigningDate()
meta.setSigningDate(issuedAt)
if err := meta.setExpiryDate(cr.Validity); err != nil {
return nil, err
}
......@@ -685,10 +686,14 @@ func (ir *IssuanceRequest) Identifiers() *IrmaIdentifierSet {
return ir.ids
}
func (ir *IssuanceRequest) GetCredentialInfoList(conf *Configuration, version *ProtocolVersion) (CredentialInfoList, error) {
func (ir *IssuanceRequest) GetCredentialInfoList(
conf *Configuration,
version *ProtocolVersion,
issuedAt time.Time,
) (CredentialInfoList, error) {
if ir.CredentialInfoList == nil {
for _, credreq := range ir.Credentials {
info, err := credreq.Info(conf, GetMetadataVersion(version))
info, err := credreq.Info(conf, GetMetadataVersion(version), issuedAt)
if err != nil {
return nil, err
}
......
......@@ -977,7 +977,7 @@ func (client RevocationClient) transport(forceHTTPS bool) *HTTPTransport {
}
func (rs RevocationKeys) PrivateKeyLatest(issid IssuerIdentifier) (*revocation.PrivateKey, error) {
sk, err := rs.Conf.PrivateKeyLatest(issid)
sk, err := rs.Conf.PrivateKeys.Latest(issid)
if err != nil {
return nil, err
}
......@@ -992,7 +992,7 @@ func (rs RevocationKeys) PrivateKeyLatest(issid IssuerIdentifier) (*revocation.P
}
func (rs RevocationKeys) PrivateKey(issid IssuerIdentifier, counter uint) (*revocation.PrivateKey, error) {
sk, err := rs.Conf.PrivateKey(issid, counter)
sk, err := rs.Conf.PrivateKeys.Get(issid, counter)
if err != nil {
return nil, err
}
......
......@@ -4,16 +4,13 @@ import (
"crypto/rsa"
"encoding/json"
"fmt"
"io/ioutil"
"path/filepath"
"regexp"
"strconv"
"strings"
"github.com/dgrijalva/jwt-go"
"github.com/go-errors/errors"
"github.com/privacybydesign/gabi"
"github.com/privacybydesign/gabi/big"
"github.com/privacybydesign/gabi/revocation"
irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/sirupsen/logrus"
......@@ -35,8 +32,6 @@ type Configuration struct {
SchemesUpdateInterval int `json:"schemes_update" mapstructure:"schemes_update"`
// Path to issuer private keys to parse
IssuerPrivateKeysPath string `json:"privkeys" mapstructure:"privkeys"`
// Issuer private keys
IssuerPrivateKeys map[irma.IssuerIdentifier]map[uint]*gabi.PrivateKey `json:"-"`
// URL at which the IRMA app can reach this server during sessions
URL string `json:"url" mapstructure:"url"`
// Required to be set to true if URL does not begin with https:// in production mode.
......@@ -118,7 +113,10 @@ func (conf *Configuration) Check() error {
func (conf *Configuration) HavePrivateKeys() bool {
var err error
for id := range conf.IrmaConfiguration.Issuers {
if _, err = conf.IrmaConfiguration.PrivateKeyLatest(id); err == nil {
if conf.IrmaConfiguration.SchemeManagers[id.SchemeManagerIdentifier()].Demo {
continue
}
if _, err = conf.IrmaConfiguration.PrivateKeys.Latest(id); err == nil {
return true
}
}
......@@ -183,11 +181,6 @@ func (conf *Configuration) verifyIrmaConf() error {
}
}
if len(conf.IssuerPrivateKeys) == 0 {
conf.IssuerPrivateKeys = make(map[irma.IssuerIdentifier]map[uint]*gabi.PrivateKey)
}
conf.IrmaConfiguration.PrivateKeys = conf.IssuerPrivateKeys
if len(conf.IrmaConfiguration.SchemeManagers) == 0 {
conf.Logger.Infof("No schemes found in %s, downloading default (irma-demo and pbdf)", conf.SchemesPath)
if err := conf.IrmaConfiguration.DownloadDefaultSchemes(); err != nil {
......@@ -205,102 +198,45 @@ func (conf *Configuration) verifyIrmaConf() error {
}
func (conf *Configuration) verifyPrivateKeys() error {
if conf.IssuerPrivateKeys == nil {
conf.IssuerPrivateKeys = make(map[irma.IssuerIdentifier]map[uint]*gabi.PrivateKey)
}
if conf.IssuerPrivateKeysPath != "" {
files, err := ioutil.ReadDir(conf.IssuerPrivateKeysPath)
if err != nil {
return err
}
for _, file := range files {
filename := file.Name()
dotcount := strings.Count(filename, ".")
if filepath.Ext(filename) != ".xml" || filename[0] == '.' || dotcount < 2 || dotcount > 3 {
conf.Logger.WithField("file", filename).Infof("Skipping non-private key file encountered in private keys path")
continue
}
base := strings.TrimSuffix(filename, filepath.Ext(filename))
counter := -1
var err error
if dotcount == 3 {
index := strings.LastIndex(base, ".")
counter, err = strconv.Atoi(base[index+1:])
if err != nil {
return err
}
base = base[:index]
}
issid := irma.NewIssuerIdentifier(base) // strip .xml
if _, ok := conf.IrmaConfiguration.Issuers[issid]; !ok {
return errors.Errorf("Private key %s belongs to an unknown issuer", filename)
}
sk, err := gabi.NewPrivateKeyFromFile(filepath.Join(conf.IssuerPrivateKeysPath, filename))
if err != nil {
return err
}
if counter >= 0 && uint(counter) != sk.Counter {
return errors.Errorf("private key %s has wrong counter %d in filename, should be %d", filename, counter, sk.Counter)
}
if len(conf.IssuerPrivateKeys[issid]) == 0 {
conf.IssuerPrivateKeys[issid] = map[uint]*gabi.PrivateKey{}
}
conf.IssuerPrivateKeys[issid][sk.Counter] = sk
}
if conf.IssuerPrivateKeysPath == "" {
return nil
}
for issid := range conf.IssuerPrivateKeys {
for _, sk := range conf.IssuerPrivateKeys[issid] {
pk, err := conf.IrmaConfiguration.PublicKey(issid, sk.Counter)
if err != nil {
return err
}
if pk == nil {
return errors.Errorf("Missing public key belonging to private key %s-%d", issid.String(), sk.Counter)
}
if new(big.Int).Mul(sk.P, sk.Q).Cmp(pk.N) != 0 {
return errors.Errorf("Private key %s-%d does not belong to corresponding public key", issid.String(), sk.Counter)
}
}
ring, err := irma.NewPrivateKeyRingFolder(conf.IssuerPrivateKeysPath, conf.IrmaConfiguration)
if err != nil {
return err
}
return nil
return conf.IrmaConfiguration.AddPrivateKeyRing(ring)
}
func (conf *Configuration) prepareRevocation(credid irma.CredentialTypeIdentifier) error {
sks, err := conf.IrmaConfiguration.PrivateKeyIndices(credid.IssuerIdentifier())
if err != nil {
return errors.WrapPrefix(err, "failed to load private key indices for revocation", 0)
}
if len(sks) == 0 {
return errors.Errorf("revocation server mode enabled for %s but no private key installed", credid)
}
rev := conf.IrmaConfiguration.Revocation
for _, skcounter := range sks {
isk, err := conf.IrmaConfiguration.PrivateKey(credid.IssuerIdentifier(), skcounter)
if err != nil {
return errors.WrapPrefix(err, fmt.Sprintf("failed to load private key %s-%d for revocation", credid, skcounter), 0)
}
var sk *revocation.PrivateKey
err := conf.IrmaConfiguration.PrivateKeys.Iterate(credid.IssuerIdentifier(), func(isk *gabi.PrivateKey) error {
if !isk.RevocationSupported()