Commit 183c7256 authored by Sietse Ringers's avatar Sietse Ringers

Add initial support for obtaining and verifying timestamps in ABS sessions

parent f884e022
......@@ -3,22 +3,26 @@ package irma
import (
"crypto/sha256"
"encoding/asn1"
"github.com/mhe/gabi"
"log"
"math/big"
"github.com/bwesterb/go-atum"
"github.com/mhe/gabi"
)
// IrmaSignedMessage is a message signed with an attribute-based signature
// The 'realnonce' will be calculated as: SigRequest.GetNonce() = ASN1(sha256(message), sha256(nonce))
// // TODO: remove pointer from Signature
type IrmaSignedMessage struct {
Signature *gabi.ProofList `json:"signature"`
Nonce *big.Int `json:"nonce"`
Context *big.Int `json:"context"`
Message string `json:"message"`
Timestamp *atum.Timestamp `json:"timestamp"`
}
func (im *IrmaSignedMessage) GetNonce() *big.Int {
return ASN1ConvertSignatureNonce(im.Message, im.Nonce)
return ASN1ConvertSignatureNonce(im.Message, im.Nonce, im.Timestamp)
}
func (im *IrmaSignedMessage) MatchesNonceAndContext(request *SignatureRequest) bool {
......@@ -29,13 +33,17 @@ func (im *IrmaSignedMessage) MatchesNonceAndContext(request *SignatureRequest) b
// Convert a Nonce to a nonce of a signature session
// (with the message already hashed into it).
func ASN1ConvertSignatureNonce(message string, nonce *big.Int) *big.Int {
hashbytes := sha256.Sum256([]byte(message))
hashint := new(big.Int).SetBytes(hashbytes[:])
// TODO the 2 should be abstracted away
asn1bytes, err := asn1.Marshal([]interface{}{big.NewInt(2), nonce, hashint})
func ASN1ConvertSignatureNonce(message string, nonce *big.Int, timestamp *atum.Timestamp) *big.Int {
msgHash := sha256.Sum256([]byte(message))
tohash := []interface{}{nonce, new(big.Int).SetBytes(msgHash[:])}
if timestamp != nil {
tohash = append(tohash, timestamp.Sig.Data)
}
// TODO remove the 2, or keep backwards compatible?
tohash = append([]interface{}{big.NewInt(int64(len(tohash)))}, tohash...)
asn1bytes, err := asn1.Marshal(tohash)
if err != nil {
log.Print(err) // TODO? does this happen?
log.Print(err) // TODO
}
asn1hash := sha256.Sum256(asn1bytes)
return new(big.Int).SetBytes(asn1hash[:])
......
......@@ -547,6 +547,24 @@ func (client *Client) Proofs(choice *irma.DisclosureChoice, request irma.IrmaSes
if err != nil {
return nil, err
}
if issig {
var sigs []*big.Int
var disclosed [][]*big.Int
var s *big.Int
var d []*big.Int
for _, builder := range builders {
s, d = builder.(*gabi.DisclosureProofBuilder).TimestampRequestContributions()
sigs = append(sigs, s)
disclosed = append(disclosed, d)
}
r := request.(*irma.SignatureRequest)
r.Timestamp, err = irma.GetTimestamp(r.Message, sigs, disclosed)
if err != nil {
return nil, err
}
}
return builders.BuildProofList(request.GetContext(), request.GetNonce(), issig), nil
}
......
......@@ -82,7 +82,7 @@ func TestManualSession(t *testing.T) {
result := <-ms.resultChannel
if ps := result.ProofStatus; ps != irma.VALID {
t.Logf("Invalid proof result: %v Expected: %v", ps, irma.VALID)
t.Fail()
t.Fatal()
}
if attrStatus := result.ToAttributeResultList()[0].AttributeProofStatus; attrStatus != irma.PRESENT {
t.Logf("Invalid attribute result value: %v Expected: %v", attrStatus, irma.PRESENT)
......@@ -293,7 +293,6 @@ func (sh *ManualSessionHandler) Success(irmaAction irma.Action, result string) {
// Make proof corrupt if we want to test invalid proofs
resultBytes := corruptAndConvertProofString(result)
irmaSignedMessage := &irma.IrmaSignedMessage{}
json.Unmarshal(resultBytes, irmaSignedMessage)
if err := json.Unmarshal(resultBytes, irmaSignedMessage); err != nil {
sh.errorChannel <- &irma.SessionError{
......
......@@ -7,6 +7,8 @@ import (
"time"
"encoding/json"
"github.com/bwesterb/go-atum"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
......@@ -56,7 +58,8 @@ type DisclosureRequest struct {
// A SignatureRequest is a a request to sign a message with certain attributes.
type SignatureRequest struct {
DisclosureRequest
Message string `json:"message"`
Message string `json:"message"`
Timestamp *atum.Timestamp `json:"-"`
}
// An IssuanceRequest is a request to issue certain credentials,
......@@ -289,7 +292,7 @@ func (dr *DisclosureRequest) SetNonce(nonce *big.Int) { dr.Nonce = nonce }
// GetNonce returns the nonce of this signature session
// (with the message already hashed into it).
func (sr *SignatureRequest) GetNonce() *big.Int {
return ASN1ConvertSignatureNonce(sr.Message, sr.Nonce)
return ASN1ConvertSignatureNonce(sr.Message, sr.Nonce, sr.Timestamp)
}
// Convert fields in JSON string to BigInterger if they are string
......@@ -346,6 +349,7 @@ func (sr *SignatureRequest) SignatureFromMessage(message interface{}) (*IrmaSign
Nonce: sr.Nonce,
Context: sr.Context,
Message: sr.Message,
Timestamp: sr.Timestamp,
}, nil
}
......
package irma
import (
"crypto/sha256"
"encoding/asn1"
"errors"
"math/big"
"github.com/bwesterb/go-atum"
"github.com/mhe/gabi"
)
// GetTimestamp GETs a signed timestamp (a signature over the current time and the parameters)
// over the message to be signed, the randomized signatures over the attributes, and the disclosed
// attributes, for in attribute-based signature sessions.
func GetTimestamp(message string, sigs []*big.Int, disclosed [][]*big.Int) (*atum.Timestamp, error) {
nonce, err := TimestampRequest(message, sigs, disclosed)
if err != nil {
return nil, err
}
alg := atum.Ed25519
return atum.SendRequest(TimestampServerURL, atum.Request{
Nonce: nonce,
PreferredSigAlg: &alg,
})
}
// TimestampRequest computes the nonce to be signed by a timestamp server, given a message to be signed
// in an attribute-based signature session along with the randomized signatures over the attributes
// and the disclosed attributes.
func TimestampRequest(message string, sigs []*big.Int, disclosed [][]*big.Int) ([]byte, error) {
msgHash := sha256.Sum256([]byte(message))
bts, err := asn1.Marshal(struct {
Sigs []*big.Int
MsgHash []byte
Disclosed [][]*big.Int
}{
sigs, msgHash[:], disclosed,
})
if err != nil {
return nil, err
}
hashed := sha256.Sum256(bts)
return hashed[:], nil
}
const TimestampServerURL = "https://metrics.privacybydesign.foundation/atum"
// Given an IrmaSignedMessage, verify the timestamp over the signed message, disclosed attributes,
// and rerandomized CL-signatures.
func VerifyTimestamp(irmaSignature *IrmaSignedMessage, message string, conf *Configuration) error {
if irmaSignature.Timestamp.ServerUrl != TimestampServerURL {
return errors.New("Untrusted timestamp server")
}
// Extract the disclosed attributes and randomized CL-signatures from the proofs in order to
// construct the nonce that should be signed by the timestamp server.
zero := big.NewInt(0)
size := len(*irmaSignature.Signature)
sigs := make([]*big.Int, size)
disclosed := make([][]*big.Int, size)
for i, proof := range *irmaSignature.Signature {
proofd := proof.(*gabi.ProofD)
meta := MetadataFromInt(proofd.ADisclosed[1], conf)
sigs[i] = proofd.A
// TODO check for nil
attrcount := len(meta.CredentialType().Attributes) + 2 // plus secret key and metadata
disclosed[i] = make([]*big.Int, attrcount)
for j := 0; j < attrcount; j++ {
val, ok := proofd.ADisclosed[j]
if !ok {
disclosed[i][j] = zero
} else {
disclosed[i][j] = val
}
}
}
bts, err := TimestampRequest(message, sigs, disclosed)
if err != nil {
return err
}
valid, err := irmaSignature.Timestamp.Verify(bts)
if err != nil {
return err
}
if !valid {
return errors.New("Timestamp signature invalid")
}
return nil
}
......@@ -14,10 +14,14 @@ type ProofStatus string
const (
VALID = ProofStatus("VALID")
EXPIRED = ProofStatus("EXPIRED")
INVALID_CRYPTO = ProofStatus("INVALID_CRYPTO")
INVALID_TIMESTAMP = ProofStatus("INVALID_TIMESTAMP")
UNMATCHED_REQUEST = ProofStatus("UNMATCHED_REQUEST")
MISSING_ATTRIBUTES = ProofStatus("MISSING_ATTRIBUTES")
// The contained attributes are currently expired, but it is not certain if they already were expired
// during creation of the ABS.
EXPIRED = ProofStatus("EXPIRED")
)
// ProofResult is a result of a complete proof, containing all the disclosed attributes and corresponding request
......@@ -103,10 +107,10 @@ func (disclosed DisclosedCredentialList) createAndCheckSignatureProofResult(conf
return &signatureProofResult
}
// Returns true if one of the disclosed credentials is expired
func (disclosed DisclosedCredentialList) IsExpired() bool {
// Returns true if one of the disclosed credentials is expired at the specified time
func (disclosed DisclosedCredentialList) IsExpired(t time.Time) bool {
for _, cred := range disclosed {
if cred.IsExpired() {
if cred.IsExpired(t) {
return true
}
}
......@@ -141,8 +145,8 @@ func (proofResult *ProofResult) ContainsAttribute(attrId AttributeTypeIdentifier
return false
}
func (cred *DisclosedCredential) IsExpired() bool {
return cred.metadataAttribute.Expiry().Before(time.Now())
func (cred *DisclosedCredential) IsExpired(t time.Time) bool {
return cred.metadataAttribute.Expiry().Before(t)
}
func NewDisclosedCredentialFromADisclosed(aDisclosed map[int]*big.Int, configuration *Configuration) *DisclosedCredential {
......@@ -228,8 +232,8 @@ func addExtraAttributes(disclosed DisclosedCredentialList, proofResult *ProofRes
}
// Check an gabi prooflist against a signature proofrequest
func checkProofWithRequest(configuration *Configuration, proofList gabi.ProofList, sigRequest *SignatureRequest) *SignatureProofResult {
disclosed, err := extractDisclosedCredentials(configuration, proofList)
func checkProofWithRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
disclosed, err := extractDisclosedCredentials(configuration, *irmaSignature.Signature)
if err != nil {
fmt.Println(err)
......@@ -249,9 +253,21 @@ func checkProofWithRequest(configuration *Configuration, proofList gabi.ProofLis
}
// If all disjunctions are satisfied, check if a credential is expired
if disclosed.IsExpired() {
signatureProofResult.ProofStatus = EXPIRED
return signatureProofResult
if irmaSignature.Timestamp == nil {
if disclosed.IsExpired(time.Now()) {
// At least one of the contained attributes has currently expired. We don't know the
// creation time of the ABS so we can't ascertain that the attributes were still valid then.
// Otherwise the signature is valid.
signatureProofResult.ProofStatus = EXPIRED
return signatureProofResult
}
} else {
if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
// The ABS contains attributes that were expired at the time of creation of the ABS.
// This must not happen and in this case the signature is invalid
signatureProofResult.ProofStatus = INVALID_CRYPTO
return signatureProofResult
}
}
// All disjunctions satisfied and nothing expired, proof is valid!
......@@ -272,7 +288,19 @@ func verify(configuration *Configuration, proofList gabi.ProofList, context *big
// Verify a signature proof and check if the attributes match the attributes in the original request
func VerifySig(configuration *Configuration, irmaSignature *IrmaSignedMessage, sigRequest *SignatureRequest) *SignatureProofResult {
// First, check if nonce and context of the signature match those of the signature request
// First, verify the timestamp, if any
if irmaSignature.Timestamp != nil {
if err := VerifyTimestamp(irmaSignature, sigRequest.Message, configuration); err != nil {
return &SignatureProofResult{
ProofResult: &ProofResult{
ProofStatus: INVALID_TIMESTAMP,
},
}
}
sigRequest.Timestamp = irmaSignature.Timestamp
}
// Then, check if nonce and context of the signature match those of the signature request
if !irmaSignature.MatchesNonceAndContext(sigRequest) {
return &SignatureProofResult{
ProofResult: &ProofResult{
......@@ -291,12 +319,19 @@ func VerifySig(configuration *Configuration, irmaSignature *IrmaSignedMessage, s
}
// Finally, check whether attribute values in proof satisfy the original signature request
return checkProofWithRequest(configuration, *irmaSignature.Signature, sigRequest)
return checkProofWithRequest(configuration, irmaSignature, sigRequest)
}
// Verify a signature cryptographically, but do not check/compare with a signature request
func VerifySigWithoutRequest(configuration *Configuration, irmaSignature *IrmaSignedMessage) (ProofStatus, DisclosedCredentialList) {
// First, cryptographically verify the signature
// First, verify the timestamp, if any
if irmaSignature.Timestamp != nil {
if err := VerifyTimestamp(irmaSignature, irmaSignature.Message, configuration); err != nil {
return INVALID_TIMESTAMP, nil
}
}
// Cryptographically verify the signature
if !verify(configuration, *irmaSignature.Signature, irmaSignature.Context, irmaSignature.GetNonce(), true) {
return INVALID_CRYPTO, nil
}
......@@ -309,8 +344,14 @@ func VerifySigWithoutRequest(configuration *Configuration, irmaSignature *IrmaSi
return INVALID_CRYPTO, nil
}
if disclosed.IsExpired() {
return EXPIRED, disclosed
if irmaSignature.Timestamp == nil {
if disclosed.IsExpired(time.Now()) {
return EXPIRED, disclosed
}
} else {
if disclosed.IsExpired(time.Unix(irmaSignature.Timestamp.Time, 0)) {
return INVALID_CRYPTO, nil
}
}
return VALID, disclosed
......
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