Commit 993f2380 authored by Sietse Ringers's avatar Sietse Ringers

feat: revocation servers that don't have another revocation server URL...

feat: revocation servers that don't have another revocation server URL configured are now authoritative
parent a6ce41b9
......@@ -52,12 +52,8 @@ func New(conf *server.Configuration, eventServer *sse.Server) (*Server, error) {
})
s.scheduler.Every(irma.RevocationParameters.RequestorUpdateInterval).Seconds().Do(func() {
for credid, credtype := range s.conf.IrmaConfiguration.CredentialTypes {
if !credtype.RevocationSupported() {
continue
}
settings := conf.RevocationSettings[credid]
if settings == nil || settings.ServerMode {
for credid, settings := range s.conf.RevocationSettings {
if settings.Authoritative() {
continue
}
if err := s.conf.IrmaConfiguration.Revocation.SyncIfOld(credid, settings.Tolerance/2); err != nil {
......
......@@ -255,7 +255,7 @@ func (s *Server) handleGetUpdateLatest(
func (s *Server) handlePostIssuanceRecord(
cred irma.CredentialTypeIdentifier, counter uint, message []byte,
) (string, *irma.RemoteError) {
if settings := s.conf.RevocationSettings[cred]; settings == nil || !settings.ServerMode {
if settings := s.conf.RevocationSettings[cred]; settings == nil || !settings.Authoritative() {
return "", server.RemoteError(server.ErrorInvalidRequest, "not supported by this server")
}
......
......@@ -33,6 +33,9 @@ func (session *session) setStatus(status server.Status) {
}
func (session *session) onUpdate() {
if session.sse == nil {
return
}
session.sse.SendMessage("session/"+session.clientToken,
sse.SimpleMessage(fmt.Sprintf(`"%s"`, session.status)),
)
......@@ -83,10 +86,8 @@ func (session *session) issuanceHandleRevocation(
}
// ensure the client always gets an up to date nonrevocation witness
if settings, ok := session.conf.RevocationSettings[id]; !ok || !settings.ServerMode {
if err := session.conf.IrmaConfiguration.Revocation.SyncDB(id); err != nil {
return nil, err
}
if err := session.conf.IrmaConfiguration.Revocation.SyncDB(id); err != nil {
return nil, err
}
rs := session.conf.IrmaConfiguration.Revocation
......
......@@ -101,8 +101,10 @@ func (s *memorySessionStore) stop() {
s.Lock()
defer s.Unlock()
for _, session := range s.requestor {
session.sse.CloseChannel("session/" + session.token)
session.sse.CloseChannel("session/" + session.clientToken)
if session.sse != nil {
session.sse.CloseChannel("session/" + session.token)
session.sse.CloseChannel("session/" + session.clientToken)
}
}
}
......@@ -137,8 +139,10 @@ func (s *memorySessionStore) deleteExpired() {
s.Lock()
for _, token := range expired {
session := s.requestor[token]
session.sse.CloseChannel("session/" + session.token)
session.sse.CloseChannel("session/" + session.clientToken)
if session.sse != nil {
session.sse.CloseChannel("session/" + session.token)
session.sse.CloseChannel("session/" + session.clientToken)
}
delete(s.client, session.clientToken)
delete(s.requestor, token)
}
......
......@@ -69,6 +69,8 @@ type Configuration struct {
initialized bool
assets string
readOnly bool
options ConfigurationOptions
}
// ConfigurationFileHash encodes the SHA256 hash of an authenticated
......@@ -132,17 +134,7 @@ func NewConfiguration(path string, opts ConfigurationOptions) (conf *Configurati
Path: path,
assets: opts.Assets,
readOnly: opts.ReadOnly,
}
conf.Scheduler = gocron.NewScheduler()
conf.Scheduler.Start()
conf.Revocation = &RevocationStorage{conf: conf}
if err = conf.Revocation.Load(
Logger.IsLevelEnabled(logrus.DebugLevel),
opts.RevocationDBType,
opts.RevocationDBConnStr,
opts.RevocationSettings,
); err != nil {
return nil, err
options: opts,
}
if conf.assets != "" { // If an assets folder is specified, then it must exist
......@@ -216,6 +208,21 @@ func (conf *Configuration) ParseFolder() (err error) {
if err != nil {
return
}
if conf.Revocation == nil {
conf.Scheduler = gocron.NewScheduler()
conf.Scheduler.Start()
conf.Revocation = &RevocationStorage{conf: conf}
if err = conf.Revocation.Load(
Logger.IsLevelEnabled(logrus.DebugLevel),
conf.options.RevocationDBType,
conf.options.RevocationDBConnStr,
conf.options.RevocationSettings,
); err != nil {
return err
}
}
conf.initialized = true
if mgrerr != nil {
return mgrerr
......
......@@ -49,8 +49,9 @@ type (
// RevocationClient offers an HTTP client to the revocation server endpoints.
RevocationClient struct {
Conf *Configuration
http *HTTPTransport
Conf *Configuration
Settings RevocationSettings
http *HTTPTransport
}
// RevocationKeys contains helper functions for retrieving revocation private and public keys
......@@ -376,7 +377,7 @@ func (rs *RevocationStorage) IssuanceRecords(id CredentialTypeIdentifier, key st
// and updating the revocation database on disk.
// If issued is not specified, i.e. passed the zero value, all credentials specified by key are revoked.
func (rs *RevocationStorage) Revoke(id CredentialTypeIdentifier, key string, issued time.Time) error {
if !rs.getSettings(id).ServerMode {
if !rs.getSettings(id).Authoritative() {
return errors.Errorf("cannot revoke %s", id)
}
return rs.sqldb.Transaction(func(tx sqlRevStorage) error {
......@@ -577,7 +578,7 @@ func (rs *RevocationStorage) SyncDB(id CredentialTypeIdentifier) error {
if ct == nil {
return errors.New("unknown credential type")
}
if settings, ok := rs.settings[id]; ok && settings.ServerMode {
if settings, ok := rs.settings[id]; ok && settings.Authoritative() {
return nil
}
......@@ -618,7 +619,7 @@ func (rs *RevocationStorage) SaveIssuanceRecord(id CredentialTypeIdentifier, rec
// Just store it if we are the revocation server for this credential type
settings := rs.getSettings(id)
if settings.ServerMode {
if settings.Authoritative() {
return rs.AddIssuanceRecord(rec)
}
......@@ -678,35 +679,43 @@ func (rs *RevocationStorage) listenUpdates(id CredentialTypeIdentifier, url stri
}
}
func updateURL(id CredentialTypeIdentifier, conf *Configuration, rs RevocationSettings) ([]string, error) {
settings := rs[id]
if settings != nil && settings.RevocationServerURL != "" {
return []string{settings.RevocationServerURL}, nil
} else {
credtype := conf.CredentialTypes[id]
if credtype == nil {
return nil, errors.New("unknown credential type")
}
if !credtype.RevocationSupported() {
return nil, errors.New("credential type does not support revocation")
}
return credtype.RevocationServers, nil
}
}
func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings map[CredentialTypeIdentifier]*RevocationSetting) error {
var t *CredentialTypeIdentifier
var ourtypes []CredentialTypeIdentifier
for id, s := range settings {
if s.ServerMode {
if s.RevocationServerURL != "" {
return errors.New("server_url cannot be combined with server mode")
if s.Authoritative() {
ourtypes = append(ourtypes, id)
}
ourtypes = append(ourtypes, id)
t = &id
}
if s.SSE {
url := s.RevocationServerURL
if url == "" {
if credtype := rs.conf.CredentialTypes[id]; credtype != nil {
if len(credtype.RevocationServers) > 0 {
url = credtype.RevocationServers[0]
}
}
}
if url == "" {
return errors.Errorf("revocation server of %s unknown", id.String())
urls, err := updateURL(id, rs.conf, rs.settings)
if err != nil {
return err
}
if rs.close == nil {
rs.close = make(chan struct{})
rs.events = make(chan *sseclient.Event)
go rs.receiveUpdates()
}
url = fmt.Sprintf("%srevocation/updateevents/%s", url, id.String())
url := fmt.Sprintf("%srevocation/updateevents/%s", urls[0], id.String())
go rs.listenUpdates(id, url)
}
}
......@@ -756,7 +765,7 @@ func (rs *RevocationStorage) Load(debug bool, dbtype, connstr string, settings m
id, settings.Tolerance)
}
}
rs.client = RevocationClient{Conf: rs.conf}
rs.client = RevocationClient{Conf: rs.conf, Settings: rs.settings}
rs.Keys = RevocationKeys{Conf: rs.conf}
return nil
}
......@@ -823,7 +832,7 @@ func (rs *RevocationStorage) getSettings(id CredentialTypeIdentifier) *Revocatio
}
func (rs *RevocationStorage) PostUpdate(id CredentialTypeIdentifier, update *revocation.Update) {
if rs.ServerSentEvents == nil || !rs.getSettings(id).ServerMode {
if rs.ServerSentEvents == nil || !rs.getSettings(id).Authoritative() {
return
}
Logger.WithField("credtype", id).Tracef("sending SSE update event")
......@@ -902,18 +911,26 @@ func (client RevocationClient) FetchUpdateFrom(id CredentialTypeIdentifier, pkco
}
func (client RevocationClient) FetchUpdateLatest(id CredentialTypeIdentifier, pkcounter uint, count uint64) (*revocation.Update, error) {
urls, err := updateURL(id, client.Conf, client.Settings)
if err != nil {
return nil, err
}
update := &revocation.Update{}
return update, client.getMultiple(
client.Conf.CredentialTypes[id].RevocationServers,
urls,
fmt.Sprintf("revocation/update/%s/%d/%d", id, count, pkcounter),
&update,
)
}
func (client RevocationClient) FetchUpdatesLatest(id CredentialTypeIdentifier, count uint64) (map[uint]*revocation.Update, error) {
urls, err := updateURL(id, client.Conf, client.Settings)
if err != nil {
return nil, err
}
update := map[uint]*revocation.Update{}
return update, client.getMultiple(
client.Conf.CredentialTypes[id].RevocationServers,
urls,
fmt.Sprintf("revocation/update/%s/%d", id, count),
&update,
)
......@@ -1071,6 +1088,10 @@ func (i *RevocationAttribute) UnmarshalCBOR(data []byte) error {
return cbor.Unmarshal(data, (*big.Int)(i))
}
func (s *RevocationSetting) Authoritative() bool {
return s.ServerMode && s.RevocationServerURL == ""
}
func (hash eventHash) Value() (driver.Value, error) {
return []byte(hash), nil
}
......
......@@ -313,6 +313,8 @@ func (conf *Configuration) verifyRevocation() error {
}
if settings.ServerMode {
conf.Logger.Info("revocation server mode enabled for " + credid.String())
}
if settings.Authoritative() {
conf.Logger.Info("Being the revocation server for a credential type comes with special responsibilities, a.o. that this server is always reachable online for any IRMA participant, and that the contents of the database is never deleted. Failure will lead to all IRMA apps being unable to disclose credentials of this type. Read more at https://irma.app/docs/revocation/#issuer-responsibilities.")
if err := conf.prepareRevocation(credid); err != nil {
return err
......
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