Commit e2a426af authored by Sietse Ringers's avatar Sietse Ringers

feat: when revocation state cannot be updated, report to verifier but continue session

parent 67169d8a
......@@ -302,7 +302,7 @@ func (ar *AttributeRequest) Satisfy(attr AttributeTypeIdentifier, val *string) b
// Satisfy returns if each of the attributes specified by proofs and indices satisfies each of
// the contained AttributeRequests's. If so it also returns a list of the disclosed attribute values.
func (c AttributeCon) Satisfy(proofs gabi.ProofList, indices []*DisclosedAttributeIndex, conf *Configuration) (bool, []*DisclosedAttribute, error) {
func (c AttributeCon) Satisfy(proofs gabi.ProofList, indices []*DisclosedAttributeIndex, revocation map[int]*time.Time, conf *Configuration) (bool, []*DisclosedAttribute, error) {
if len(indices) < len(c) {
return false, nil, nil
}
......@@ -313,7 +313,7 @@ func (c AttributeCon) Satisfy(proofs gabi.ProofList, indices []*DisclosedAttribu
for j := range c {
index := indices[j]
attr, val, err := extractAttribute(proofs, index, conf)
attr, val, err := extractAttribute(proofs, index, revocation[index.CredentialIndex], conf)
if err != nil {
return false, nil, err
}
......@@ -340,9 +340,9 @@ func (dc AttributeDisCon) Validate() error {
// Satisfy returns true if the attributes specified by proofs and indices satisfies any one of the
// contained AttributeCon's. If so it also returns a list of the disclosed attribute values.
func (dc AttributeDisCon) Satisfy(proofs gabi.ProofList, indices []*DisclosedAttributeIndex, conf *Configuration) (bool, []*DisclosedAttribute, error) {
func (dc AttributeDisCon) Satisfy(proofs gabi.ProofList, indices []*DisclosedAttributeIndex, revocation map[int]*time.Time, conf *Configuration) (bool, []*DisclosedAttribute, error) {
for _, con := range dc {
satisfied, attrs, err := con.Satisfy(proofs, indices, conf)
satisfied, attrs, err := con.Satisfy(proofs, indices, revocation, conf)
if satisfied || err != nil {
return true, attrs, err
}
......@@ -371,7 +371,7 @@ func (cdc AttributeConDisCon) Validate(conf *Configuration) error {
// Satisfy returns true if each of the contained AttributeDisCon is satisfied by the specified disclosure.
// If so it also returns the disclosed attributes.
func (cdc AttributeConDisCon) Satisfy(disclosure *Disclosure, conf *Configuration) (bool, [][]*DisclosedAttribute, error) {
func (cdc AttributeConDisCon) Satisfy(disclosure *Disclosure, revocation map[int]*time.Time, conf *Configuration) (bool, [][]*DisclosedAttribute, error) {
if len(disclosure.Indices) < len(cdc) {
return false, nil, nil
}
......@@ -379,7 +379,7 @@ func (cdc AttributeConDisCon) Satisfy(disclosure *Disclosure, conf *Configuratio
complete := true
for i, discon := range cdc {
satisfied, attrs, err := discon.Satisfy(disclosure.Proofs, disclosure.Indices[i], conf)
satisfied, attrs, err := discon.Satisfy(disclosure.Proofs, disclosure.Indices[i], revocation, conf)
if err != nil {
return false, nil, err
}
......
......@@ -42,7 +42,11 @@ type (
RevocationSetting struct {
Mode RevocationMode `json:"mode"`
PostURLs []string `json:"post_urls" mapstructure:"post_urls"`
updated time.Time
// set to now whenever a new revocation record is received, or when the RA indicates
// there are no new records. Thus it specifies up to what time our nonrevocation
// guarantees lasts.
updated time.Time
}
// RevocationMode specifies for a given credential type what revocation operations are
......@@ -97,6 +101,12 @@ const (
// revocationUpdateCount specifies how many revocation records are attached to session requests
// for the client to update its revocation state.
revocationUpdateCount = 5
// revocationMaxAccumulatorAge is the default maximum for the 'accumulator age', which we
// define to be the amount of time since the last confirmation from the RA that the latest
// accumulator that we know is still the latest one: clients should prove nonrevocation
// against a 'younger' accumulator.
revocationMaxAccumulatorAge = 5 * time.Minute
)
// Revocation record methods
......@@ -340,11 +350,19 @@ func (rs *RevocationStorage) UpdateDB(typ CredentialTypeIdentifier) error {
if err != nil {
return err
}
return rs.AddRevocationRecords(records)
if err = rs.AddRevocationRecords(records); err != nil {
return err
}
// bump updated even if no new records were added
rs.getSettings(typ).updated = time.Now()
return nil
}
func (rs *RevocationStorage) UpdateIfOld(typ CredentialTypeIdentifier) error {
if rs.getSettings(typ).updated.Before(time.Now().Add(-5 * time.Minute)) {
// update 10 seconds before the maximum, to stay below it
if rs.getSettings(typ).updated.Before(time.Now().Add(-revocationMaxAccumulatorAge + 10*time.Second)) {
if err := rs.UpdateDB(typ); err != nil {
return err
}
......@@ -440,7 +458,18 @@ func (rs *RevocationStorage) SetRevocationRecords(b *BaseRequest) error {
b.RevocationUpdates = make(map[CredentialTypeIdentifier][]*RevocationRecord, len(b.Revocation))
for _, credid := range b.Revocation {
if err = rs.UpdateIfOld(credid); err != nil {
return err
updated := rs.getSettings(credid).updated
if !updated.IsZero() {
Logger.Warnf("failed to fetch revocation updates for %s, nonrevocation is guaranteed only until %s ago:",
credid, time.Now().Sub(updated).String())
Logger.Warn(err)
} else {
Logger.Errorf("revocation is disabled for %s: failed to fetch revocation updates and none are known locally", credid)
Logger.Warn(err)
// We can offer no nonrevocation guarantees at all while the requestor explicitly
// asked for it; fail the session by returning an error
return err
}
}
b.RevocationUpdates[credid], err = rs.LatestRevocationRecords(credid, revocationUpdateCount)
if err != nil {
......
......@@ -31,11 +31,12 @@ const (
// DisclosedAttribute represents a disclosed attribute.
type DisclosedAttribute struct {
RawValue *string `json:"rawvalue"`
Value TranslatedString `json:"value"` // Value of the disclosed attribute
Identifier AttributeTypeIdentifier `json:"id"`
Status AttributeProofStatus `json:"status"`
IssuanceTime Timestamp `json:"issuancetime"`
RawValue *string `json:"rawvalue"`
Value TranslatedString `json:"value"` // Value of the disclosed attribute
Identifier AttributeTypeIdentifier `json:"id"`
Status AttributeProofStatus `json:"status"`
IssuanceTime Timestamp `json:"issuancetime"`
NotRevokedBefore *Timestamp `json:"notrevokedbefore,omitempty"`
}
// ProofList is a gabi.ProofList with some extra methods.
......@@ -89,7 +90,7 @@ func (pl ProofList) Expired(configuration *Configuration, t *time.Time) bool {
return false
}
func extractAttribute(pl gabi.ProofList, index *DisclosedAttributeIndex, conf *Configuration) (*DisclosedAttribute, *string, error) {
func extractAttribute(pl gabi.ProofList, index *DisclosedAttributeIndex, notrevoked *time.Time, conf *Configuration) (*DisclosedAttribute, *string, error) {
if len(pl) < index.CredentialIndex {
return nil, nil, errors.New("Credential index out of range")
}
......@@ -101,7 +102,7 @@ func extractAttribute(pl gabi.ProofList, index *DisclosedAttributeIndex, conf *C
}
metadata := MetadataFromInt(proofd.ADisclosed[1], conf) // index 1 is metadata attribute
return parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex])
return parseAttribute(index.AttributeIndex, metadata, proofd.ADisclosed[index.AttributeIndex], notrevoked)
}
// VerifyProofs verifies the proofs cryptographically.
......@@ -111,22 +112,22 @@ func (pl ProofList) VerifyProofs(
publickeys []*gabi.PublicKey,
revRecords map[CredentialTypeIdentifier][]*RevocationRecord,
isSig bool,
) (bool, error) {
) (bool, map[int]*time.Time, error) {
// Empty proof lists are allowed (if consistent with the session request, which is checked elsewhere)
if len(pl) == 0 {
return true, nil
return true, nil, nil
}
if publickeys == nil {
var err error
publickeys, err = pl.ExtractPublicKeys(configuration)
if err != nil {
return false, err
return false, nil, err
}
}
if len(pl) != len(publickeys) {
return false, errors.New("Insufficient public keys to verify the proofs")
return false, nil, errors.New("Insufficient public keys to verify the proofs")
}
// Compute slice to inform gabi of which proofs should be verified to share the same secret key
......@@ -141,28 +142,29 @@ func (pl ProofList) VerifyProofs(
}
if !gabi.ProofList(pl).Verify(publickeys, context, nonce, isSig, keyshareServers) {
return false, nil
return false, nil, nil
}
// Perform per-proof verifications for each proof:
// - verify that any singleton credential occurs at most once in the prooflist
// - verify that all required nonrevocation proofs are present
singletons := map[CredentialTypeIdentifier]bool{}
for _, proof := range pl {
revocation := map[int]*time.Time{} // per proof, store up to what time it is known to be not revoked
for i, proof := range pl {
proofd, ok := proof.(*gabi.ProofD)
if !ok {
continue
}
typ := MetadataFromInt(proofd.ADisclosed[1], configuration).CredentialType()
if typ == nil {
return false, errors.New("Received unknown credential type")
return false, nil, errors.New("Received unknown credential type")
}
id := typ.Identifier()
if typ.IsSingleton {
if !singletons[id] { // Seen for the first time
singletons[id] = true
} else { // Seen for the second time
return false, nil
return false, nil, nil
}
}
......@@ -173,17 +175,24 @@ func (pl ProofList) VerifyProofs(
// OR a newer one included in the proofs itself.
r := revRecords[id]
if len(r) == 0 { // no nonrevocation proof was requested for this credential
return true, nil
return true, nil, nil
}
if !proofd.HasNonRevocationProof() {
return false, nil
return false, nil, nil
}
if proofd.NonRevocationProof.Accumulator.Index < r[len(r)-1].EndIndex {
return false, errors.New("nonrevocation proof used wrong accumulator")
ours, theirs := proofd.NonRevocationProof.Accumulator.Index, r[len(r)-1].EndIndex
if ours < theirs {
return false, nil, errors.New("nonrevocation proof used wrong accumulator")
}
if ours == theirs {
accAge := configuration.RevocationStorage.getSettings(id).updated
if time.Now().Sub(accAge) > revocationMaxAccumulatorAge {
revocation[i] = &accAge
}
}
}
return true, nil
return true, revocation, nil
}
func (d *Disclosure) extraIndices(condiscon AttributeConDisCon) []*DisclosedAttributeIndex {
......@@ -226,8 +235,8 @@ func (d *Disclosure) extraIndices(condiscon AttributeConDisCon) []*DisclosedAttr
// is included, then the first attributes in the returned slice match with the disjunction list in
// the disjunction list. The first return parameter of this function indicates whether or not all
// disjunctions (if present) are satisfied.
func (d *Disclosure) DisclosedAttributes(configuration *Configuration, condiscon AttributeConDisCon) (bool, [][]*DisclosedAttribute, error) {
complete, list, err := condiscon.Satisfy(d, configuration)
func (d *Disclosure) DisclosedAttributes(configuration *Configuration, condiscon AttributeConDisCon, revocation map[int]*time.Time) (bool, [][]*DisclosedAttribute, error) {
complete, list, err := condiscon.Satisfy(d, revocation, configuration)
if err != nil {
return false, nil, err
}
......@@ -235,7 +244,7 @@ func (d *Disclosure) DisclosedAttributes(configuration *Configuration, condiscon
var extra []*DisclosedAttribute
indices := d.extraIndices(condiscon)
for _, index := range indices {
attr, _, err := extractAttribute(d.Proofs, index, configuration)
attr, _, err := extractAttribute(d.Proofs, index, revocation[index.CredentialIndex], configuration)
if err != nil {
return false, nil, err
}
......@@ -249,7 +258,7 @@ func (d *Disclosure) DisclosedAttributes(configuration *Configuration, condiscon
return complete, list, nil
}
func parseAttribute(index int, metadata *MetadataAttribute, attr *big.Int) (*DisclosedAttribute, *string, error) {
func parseAttribute(index int, metadata *MetadataAttribute, attr *big.Int, notrevoked *time.Time) (*DisclosedAttribute, *string, error) {
var attrid AttributeTypeIdentifier
var attrval *string
credtype := metadata.CredentialType()
......@@ -269,11 +278,12 @@ func parseAttribute(index int, metadata *MetadataAttribute, attr *big.Int) (*Dis
status = AttributeProofStatusNull
}
return &DisclosedAttribute{
Identifier: attrid,
RawValue: attrval,
Value: NewTranslatedString(attrval),
Status: status,
IssuanceTime: Timestamp(metadata.SigningDate()),
Identifier: attrid,
RawValue: attrval,
Value: NewTranslatedString(attrval),
Status: status,
IssuanceTime: Timestamp(metadata.SigningDate()),
NotRevokedBefore: (*Timestamp)(notrevoked),
}, attrval, nil
}
......@@ -293,13 +303,13 @@ func (d *Disclosure) VerifyAgainstRequest(
}
// Cryptographically verify all included IRMA proofs
valid, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, revRecords, issig)
valid, revocation, err := ProofList(d.Proofs).VerifyProofs(configuration, context, nonce, publickeys, revRecords, issig)
if !valid || err != nil {
return nil, ProofStatusInvalid, err
}
// Next extract the contained attributes from the proofs, and match them to the signature request if present
allmatched, list, err := d.DisclosedAttributes(configuration, required)
allmatched, list, err := d.DisclosedAttributes(configuration, required, revocation)
if err != nil {
return nil, ProofStatusInvalid, 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