Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
IRMA
Github mirrors
irmago
Commits
2919f416
Commit
2919f416
authored
Sep 09, 2019
by
Sietse Ringers
Browse files
feat: send issuance records and add revocation api in irmaserver
parent
4953edc7
Changes
9
Hide whitespace changes
Inline
Side-by-side
internal/servercore/api.go
View file @
2919f416
...
...
@@ -45,13 +45,21 @@ func New(conf *server.Configuration) (*Server, error) {
s
.
sessions
.
deleteExpired
()
})
// TODO: how do we not update revocation state for credential types of which we are the authoritative server?
//s.scheduler.Every(5).Minutes().Do(func() {
// if err := s.conf.IrmaConfiguration.RevocationUpdateAll(); err != nil {
// s.conf.Logger.Error("failed to update revocation database:")
// _ = server.LogError(err)
// }
//})
s
.
scheduler
.
Every
(
5
)
.
Minutes
()
.
Do
(
func
()
{
for
credid
,
credtype
:=
range
s
.
conf
.
IrmaConfiguration
.
CredentialTypes
{
if
!
credtype
.
SupportsRevocation
()
{
continue
}
if
_
,
ours
:=
conf
.
RevocationServers
[
credid
];
ours
{
// TODO rethink this condition
continue
}
if
err
:=
s
.
conf
.
IrmaConfiguration
.
RevocationUpdateDB
(
credid
);
err
!=
nil
{
s
.
conf
.
Logger
.
Error
(
"failed to update revocation database for %s:"
,
credid
.
String
())
_
=
server
.
LogError
(
err
)
}
}
})
s
.
stopScheduler
=
s
.
scheduler
.
Start
()
...
...
@@ -132,8 +140,12 @@ func (s *Server) CancelSession(token string) error {
return
nil
}
func
(
s
*
Server
)
Revoke
(
credid
irma
.
CredentialTypeIdentifier
,
key
string
)
error
{
return
s
.
conf
.
IrmaConfiguration
.
Revoke
(
credid
,
key
)
}
func
ParsePath
(
path
string
)
(
token
,
noun
string
,
arg
[]
string
,
err
error
)
{
rev
:=
regexp
.
MustCompile
(
"-/revocation/(records)/?(.*)$"
)
rev
:=
regexp
.
MustCompile
(
"-/revocation/(records
|issuancerecord
)/?(.*)$"
)
matches
:=
rev
.
FindStringSubmatch
(
path
)
if
len
(
matches
)
==
3
{
args
:=
strings
.
Split
(
matches
[
2
],
"/"
)
...
...
@@ -381,13 +393,17 @@ func (s *Server) handleRevocationMessage(
}
return
server
.
JsonResponse
(
s
.
handlePostRevocationRecords
(
cred
,
records
))
}
//if noun == "revoke" && method == http.MethodPost {
// if len(args) != 1 {
// return server.JsonResponse(nil, server.RemoteError(server.ErrorInvalidRequest, "POST records expects 1 url arguments"))
// }
// cred := irma.NewCredentialTypeIdentifier(args[0])
// return server.JsonResponse(s.handleRevoke(cred, message))
//}
if
noun
==
"issuancerecord"
&&
method
==
http
.
MethodPost
{
if
len
(
args
)
!=
2
{
return
server
.
JsonResponse
(
nil
,
server
.
RemoteError
(
server
.
ErrorInvalidRequest
,
"POST issuancercord expects 2 url arguments"
))
}
cred
:=
irma
.
NewCredentialTypeIdentifier
(
args
[
0
])
counter
,
err
:=
strconv
.
Atoi
(
args
[
1
])
if
err
!=
nil
{
return
server
.
JsonResponse
(
nil
,
server
.
RemoteError
(
server
.
ErrorMalformedInput
,
err
.
Error
()))
}
return
server
.
JsonResponse
(
s
.
handlePostIssuanceRecord
(
cred
,
counter
,
message
))
}
return
server
.
JsonResponse
(
nil
,
server
.
RemoteError
(
server
.
ErrorInvalidRequest
,
""
))
}
internal/servercore/handle.go
View file @
2919f416
...
...
@@ -199,7 +199,7 @@ func (session *session) handlePostCommitments(commitments *irma.IssueCommitmentM
if
err
!=
nil
{
return
nil
,
session
.
fail
(
server
.
ErrorIssuanceFailed
,
err
.
Error
())
}
witness
,
nonrevAttr
,
err
:=
session
.
h
andleRevocation
(
cred
,
attributes
,
sk
)
witness
,
nonrevAttr
,
err
:=
session
.
issuanceH
andleRevocation
(
cred
,
attributes
,
sk
)
if
err
!=
nil
{
return
nil
,
session
.
fail
(
server
.
ErrorIssuanceFailed
,
err
.
Error
())
// TODO error type
}
...
...
@@ -245,32 +245,39 @@ func (s *Server) handleGetRevocationRecords(
return
records
,
nil
}
func
(
s
*
Server
)
handle
Revoke
(
cred
irma
.
CredentialTypeIdentifier
,
msg
signed
.
Messag
e
,
func
(
s
*
Server
)
handle
PostIssuanceRecord
(
cred
irma
.
CredentialTypeIdentifier
,
counter
int
,
message
[]
byt
e
,
)
(
string
,
*
irma
.
RemoteError
)
{
sk
,
err
:=
s
.
conf
.
IrmaConfiguration
.
PrivateKey
(
cred
.
IssuerIdentifier
())
if
_
,
ours
:=
s
.
conf
.
RevocationServers
[
cred
];
!
ours
{
return
""
,
server
.
RemoteError
(
server
.
ErrorInvalidRequest
,
"not supported by this server"
)
}
// Grab the counter-th issuer public key, with which the message should be signed,
// and verify and unmarshal the issuance record
pk
,
err
:=
s
.
conf
.
IrmaConfiguration
.
PublicKey
(
cred
.
IssuerIdentifier
(),
counter
)
if
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknown
,
err
.
Error
())
}
if
s
k
==
nil
{
if
p
k
==
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknownPublicKey
,
""
)
}
r
s
k
,
err
:=
s
k
.
RevocationKey
()
r
evp
k
,
err
:=
p
k
.
RevocationKey
()
if
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknown
,
err
.
Error
())
}
var
cmd
revocation
.
Command
if
err
=
signed
.
UnmarshalVerify
(
&
rsk
.
ECDSA
.
PublicKey
,
msg
,
&
cmd
);
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknown
,
err
.
Error
())
var
rec
revocation
.
IssuanceRecord
if
err
:=
signed
.
UnmarshalVerify
(
revpk
.
ECDSA
,
message
,
&
rec
);
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnauthorized
,
err
.
Error
())
}
// Insert the record into the database
db
,
err
:=
s
.
conf
.
IrmaConfiguration
.
RevocationDB
(
cred
)
if
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknown
,
err
.
Error
())
}
if
err
=
db
.
RevokeAttr
(
rsk
,
cmd
.
E
);
err
!=
nil
{
if
err
=
db
.
AddIssuanceRecord
(
&
rec
);
err
!=
nil
{
return
""
,
server
.
RemoteError
(
server
.
ErrorUnknown
,
err
.
Error
())
}
return
"OK"
,
nil
}
internal/servercore/helpers.go
View file @
2919f416
...
...
@@ -76,34 +76,43 @@ func (session *session) checkCache(message []byte, expectedStatus server.Status)
// Issuance helpers
func
(
session
*
session
)
h
andleRevocation
(
func
(
session
*
session
)
issuanceH
andleRevocation
(
cred
*
irma
.
CredentialRequest
,
attributes
*
irma
.
AttributeList
,
sk
*
gabi
.
PrivateKey
,
)
(
witness
*
revocation
.
Witness
,
nonrevAttr
*
big
.
Int
,
err
error
)
{
if
!
session
.
conf
.
IrmaConfiguration
.
CredentialTypes
[
cred
.
CredentialTypeID
]
.
SupportsRevocation
()
{
return
}
// ensure the client always gets an up to date nonrevocation witness
if
_
,
ours
:=
session
.
conf
.
RevocationServers
[
cred
.
CredentialTypeID
];
!
ours
{
// ensure the client always gets an up to date nonrevocation witness
// TODO: post IssuanceRecord to remote?
if
err
=
session
.
conf
.
IrmaConfiguration
.
RevocationUpdateDB
(
cred
.
CredentialTypeID
);
err
!=
nil
{
return
}
}
db
,
err
:=
session
.
conf
.
IrmaConfiguration
.
RevocationDB
(
cred
.
CredentialTypeID
)
if
err
!=
nil
{
return
}
if
db
.
Enabled
()
{
if
witness
,
err
=
sk
.
RevocationGenerateWitness
(
&
db
.
Current
);
err
!=
nil
{
return
}
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
{
if
!
db
.
Enabled
()
{
return
}
if
witness
,
err
=
sk
.
RevocationGenerateWitness
(
&
db
.
Current
);
err
!=
nil
{
return
}
nonrevAttr
=
witness
.
E
issrecord
:=
&
revocation
.
IssuanceRecord
{
Key
:
cred
.
RevocationKey
,
Attr
:
nonrevAttr
,
Issued
:
time
.
Now
()
.
UnixNano
(),
// or (floored) cred issuance time?
ValidUntil
:
attributes
.
Expiry
()
.
UnixNano
(),
}
err
=
session
.
conf
.
IrmaConfiguration
.
SendRevocationIssuanceRecord
(
cred
.
CredentialTypeID
,
issrecord
)
if
err
!=
nil
{
_
=
server
.
LogWarning
(
errors
.
WrapPrefix
(
err
,
"Failed to send issuance record to revocation server"
,
0
))
session
.
conf
.
Logger
.
Warn
(
"Storing issuance record locally"
)
if
err
=
db
.
AddIssuanceRecord
(
issrecord
);
err
!=
nil
{
return
nil
,
nil
,
err
}
}
...
...
internal/sessiontest/requestor_test.go
View file @
2919f416
...
...
@@ -6,14 +6,12 @@ import (
"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"
...
...
@@ -341,16 +339,6 @@ func TestOptionalDisclosure(t *testing.T) {
}
}
func
editDB
(
t
*
testing
.
T
,
path
string
,
keystore
revocation
.
Keystore
,
enabled
bool
,
f
func
(
*
revocation
.
DB
))
{
StopRevocationServer
()
db
,
err
:=
revocation
.
LoadDB
(
path
,
keystore
)
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
!
enabled
||
db
.
Enabled
())
f
(
db
)
require
.
NoError
(
t
,
db
.
Close
())
StartRevocationServer
(
t
)
}
func
revocationSession
(
t
*
testing
.
T
,
client
*
irmaclient
.
Client
,
options
...
sessionOption
)
*
requestorSessionResult
{
attr
:=
irma
.
NewAttributeTypeIdentifier
(
"irma-demo.MijnOverheid.root.BSN"
)
req
:=
irma
.
NewDisclosureRequest
(
attr
)
...
...
@@ -366,7 +354,6 @@ func TestRevocation(t *testing.T) {
client
,
_
:=
parseStorage
(
t
)
iss
:=
irma
.
NewIssuerIdentifier
(
"irma-demo.MijnOverheid"
)
cred
:=
irma
.
NewCredentialTypeIdentifier
(
"irma-demo.MijnOverheid.root"
)
dbPath
:=
filepath
.
Join
(
testdata
,
"tmp"
,
"issuer"
,
cred
.
String
())
keystore
:=
client
.
Configuration
.
RevocationKeystore
(
iss
)
sk
,
err
:=
client
.
Configuration
.
PrivateKey
(
iss
)
require
.
NoError
(
t
,
err
)
...
...
@@ -374,48 +361,45 @@ func TestRevocation(t *testing.T) {
require
.
NoError
(
t
,
err
)
// enable revocation for our credential type by creating and saving an initial accumulator
db
,
err
:=
revocation
.
LoadDB
(
filepath
.
Join
(
testdata
,
"tmp"
,
"issuer"
,
cred
.
String
()),
keystore
)
require
.
NoError
(
t
,
err
)
require
.
NoError
(
t
,
db
.
EnableRevocation
(
revsk
))
require
.
NoError
(
t
,
db
.
Close
())
// so StartRevocationServer() can open it again
StartRevocationServer
(
t
)
editDB
(
t
,
dbPath
,
keystore
,
false
,
func
(
db
*
revocation
.
DB
)
{
require
.
NoError
(
t
,
db
.
EnableRevocation
(
revsk
))
})
// issue MijnOverheid.root instance with revocation enabled
// issue
two
MijnOverheid.root instance
s
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"
)
,
RevocationKey
:
"
cred0
"
,
// once revocation is required for a credential type, this key is required
CredentialTypeID
:
cred
,
Attributes
:
map
[
string
]
string
{
"BSN"
:
"299792458"
,
},
}})
result
:=
requestorSessionHelper
(
t
,
request
,
client
)
require
.
Nil
(
t
,
result
.
Err
)
// issue second one which overwrites the first one, as our credtype is a singleton
// this is ok, as we use cred0 only to revoke it, to see if cred1 keeps working
request
.
Credentials
[
0
]
.
RevocationKey
=
"cred1"
result
=
requestorSessionHelper
(
t
,
request
,
client
)
require
.
Nil
(
t
,
result
.
Err
)
// perform disclosure session with nonrevocation proof
// perform disclosure session
(of cred1)
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"
)))
})
// revoke cred0
require
.
NoError
(
t
,
revocationServer
.
Revoke
(
cred
,
"cred0"
))
// perform another disclosure session with nonrevocation proof
// perform another disclosure session with nonrevocation proof
to see that cred1 still works
// 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"
)))
})
// revoke cred1
require
.
NoError
(
t
,
revocationServer
.
Revoke
(
cred
,
"cred1"
))
// try to perform session with revoked credential
// client notices that is credential is revoked and aborts
...
...
internal/sessiontest/server_test.go
View file @
2919f416
...
...
@@ -53,9 +53,9 @@ func StartRevocationServer(t *testing.T) {
var
err
error
revocationServer
,
err
=
irmaserver
.
New
(
&
server
.
Configuration
{
Logger
:
logger
,
SchemesPath
:
filepath
.
Join
(
testdata
,
"irma_configuration"
),
RevocationPath
:
filepath
.
Join
(
testdata
,
"tmp"
,
"issuer"
),
DisableSchemesUpdate
:
true
,
SchemesPath
:
filepath
.
Join
(
testdata
,
"irma_configuration"
),
RevocationPath
:
filepath
.
Join
(
testdata
,
"tmp"
,
"issuer"
),
// todo rename this path to revocation?
RevocationServers
:
map
[
irma
.
CredentialTypeIdentifier
]
server
.
RevocationServer
{
irma
.
NewCredentialTypeIdentifier
(
"irma-demo.MijnOverheid.root"
)
:
{},
},
...
...
@@ -84,10 +84,11 @@ func StartIrmaServer(t *testing.T, updatedIrmaConf bool) {
var
err
error
irmaServer
,
err
=
irmaserver
.
New
(
&
server
.
Configuration
{
URL
:
"http://localhost:48680"
,
Logger
:
logger
,
SchemesPath
:
filepath
.
Join
(
testdata
,
irmaconf
),
RevocationPath
:
filepath
.
Join
(
testdata
,
"tmp"
,
"revocation"
),
URL
:
"http://localhost:48680"
,
Logger
:
logger
,
DisableSchemesUpdate
:
true
,
SchemesPath
:
filepath
.
Join
(
testdata
,
irmaconf
),
RevocationPath
:
filepath
.
Join
(
testdata
,
"tmp"
,
"revocation"
),
})
require
.
NoError
(
t
,
err
)
...
...
irmaclient/client.go
View file @
2919f416
...
...
@@ -216,14 +216,23 @@ func (client *Client) addCredential(cred *credential) (err error) {
id
=
cred
.
CredentialType
()
.
Identifier
()
}
// Don't add duplicate creds
// If we receive a duplicate credential it should overwrite the previous one; remove it first
// (it makes no sense to possess duplicate credentials, but the new signature might contain new
// functionality such as a nonrevocation witness, so it does not suffice to just return here)
index
:=
-
1
for
_
,
attrlistlist
:=
range
client
.
attributes
{
for
_
,
attrs
:=
range
attrlistlist
{
for
i
,
attrs
:=
range
attrlistlist
{
if
attrs
.
Hash
()
==
cred
.
AttributeList
()
.
Hash
()
{
return
nil
index
=
i
break
}
}
}
if
index
!=
-
1
{
if
err
=
client
.
remove
(
id
,
index
,
false
);
err
!=
nil
{
return
err
}
}
// If this is a singleton credential type, ensure we have at most one by removing any previous instance
// If a credential already exists with exactly the same attribute values (except metadata), delete the previous credential
...
...
revocation.go
View file @
2919f416
...
...
@@ -8,6 +8,7 @@ import (
"github.com/go-errors/errors"
"github.com/hashicorp/go-multierror"
"github.com/privacybydesign/gabi/revocation"
"github.com/privacybydesign/gabi/signed"
)
func
(
conf
*
Configuration
)
RevocationKeystore
(
issuerid
IssuerIdentifier
)
revocation
.
Keystore
{
...
...
@@ -127,6 +128,37 @@ func (conf *Configuration) revocationUpdateDelayed(credid CredentialTypeIdentifi
return
nil
}
func
(
conf
*
Configuration
)
SendRevocationIssuanceRecord
(
cred
CredentialTypeIdentifier
,
rec
*
revocation
.
IssuanceRecord
,
)
error
{
credtype
:=
conf
.
CredentialTypes
[
cred
]
if
credtype
==
nil
{
return
errors
.
New
(
"unknown credential type"
)
}
if
credtype
.
RevocationServer
==
""
{
return
errors
.
New
(
"credential type has no revocation server"
)
}
sk
,
err
:=
conf
.
PrivateKey
(
cred
.
IssuerIdentifier
())
if
err
!=
nil
{
return
err
}
if
sk
==
nil
{
return
errors
.
New
(
"private key not found"
)
}
revsk
,
err
:=
sk
.
RevocationKey
()
if
err
!=
nil
{
return
err
}
message
,
err
:=
signed
.
MarshalSign
(
revsk
.
ECDSA
,
rec
)
if
err
!=
nil
{
return
err
}
return
NewHTTPTransport
(
credtype
.
RevocationServer
)
.
Post
(
fmt
.
Sprintf
(
"-/revocation/issuancerecord/%s/%d"
,
cred
,
sk
.
Counter
),
nil
,
[]
byte
(
message
),
)
}
func
(
conf
*
Configuration
)
Revoke
(
credid
CredentialTypeIdentifier
,
key
string
)
error
{
sk
,
err
:=
conf
.
PrivateKey
(
credid
.
IssuerIdentifier
())
if
err
!=
nil
{
...
...
server/irmaserver/main.go
View file @
2919f416
...
...
@@ -96,6 +96,13 @@ func (s *Server) CancelSession(token string) error {
return
s
.
Server
.
CancelSession
(
token
)
}
func
Revoke
(
credid
irma
.
CredentialTypeIdentifier
,
key
string
)
error
{
return
s
.
Revoke
(
credid
,
key
)
}
func
(
s
*
Server
)
Revoke
(
credid
irma
.
CredentialTypeIdentifier
,
key
string
)
error
{
return
s
.
Server
.
Revoke
(
credid
,
key
)
}
// SubscribeServerSentEvents subscribes the HTTP client to server sent events on status updates
// of the specified IRMA session.
func
SubscribeServerSentEvents
(
w
http
.
ResponseWriter
,
r
*
http
.
Request
,
token
string
,
requestor
bool
)
error
{
...
...
server/requestorserver/server.go
View file @
2919f416
...
...
@@ -330,7 +330,7 @@ func (s *Server) handleCreate(w http.ResponseWriter, r *http.Request) {
if
rrequest
!=
nil
{
s
.
handleCreateSession
(
w
,
requestor
,
rrequest
)
}
else
{
s
.
handle
CreateRevocation
(
w
,
requestor
,
revreq
)
s
.
handle
Revoke
(
w
,
requestor
,
revreq
)
}
}
...
...
@@ -391,7 +391,7 @@ func (s *Server) handleCreateStatic(w http.ResponseWriter, r *http.Request) {
server
.
WriteJson
(
w
,
qr
)
}
func
(
s
*
Server
)
handle
CreateRevocation
(
w
http
.
ResponseWriter
,
requestor
string
,
request
*
irma
.
RevocationRequest
)
{
func
(
s
*
Server
)
handle
Revoke
(
w
http
.
ResponseWriter
,
requestor
string
,
request
*
irma
.
RevocationRequest
)
{
allowed
,
reason
:=
s
.
conf
.
CanRevoke
(
requestor
,
request
.
CredentialType
)
if
!
allowed
{
s
.
conf
.
Logger
.
WithFields
(
logrus
.
Fields
{
"requestor"
:
requestor
,
"message"
:
reason
})
.
...
...
@@ -399,7 +399,7 @@ func (s *Server) handleCreateRevocation(w http.ResponseWriter, requestor string,
server
.
WriteError
(
w
,
server
.
ErrorUnauthorized
,
reason
)
return
}
if
err
:=
s
.
conf
.
IrmaConfiguration
.
Revoke
(
request
.
CredentialType
,
request
.
Key
);
err
!=
nil
{
if
err
:=
s
.
irmaserv
.
Revoke
(
request
.
CredentialType
,
request
.
Key
);
err
!=
nil
{
server
.
WriteError
(
w
,
server
.
ErrorUnknown
,
err
.
Error
())
}
server
.
WriteString
(
w
,
"OK"
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment