Commit eafa8900 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Support issuance

parent ebb610e8
......@@ -163,6 +163,16 @@ func (attr *MetadataAttribute) setValidityDuration(weeks int) {
attr.setField(validityField, shortToByte(weeks))
}
func (attr *MetadataAttribute) setExpiryDate(timestamp *Timestamp) error {
expiry := time.Time(*timestamp).Unix()
if expiry%ExpiryFactor != 0 {
return errors.New("Expiry date does not match an epoch boundary")
}
signing := attr.SigningDate().Unix()
attr.setValidityDuration(int((expiry - signing) / ExpiryFactor))
return nil
}
// CredentialType returns the credential type of the current instance
// using the MetaStore.
func (attr *MetadataAttribute) CredentialType() *CredentialType {
......
......@@ -12,7 +12,7 @@ type SchemeManagerIdentifier struct {
metaObjectIdentifier
}
// IssuerIdentifier identifies an inssuer. For example "irma-demo.RU".
// IssuerIdentifier identifies an issuer. For example "irma-demo.RU".
type IssuerIdentifier struct {
metaObjectIdentifier
}
......@@ -114,7 +114,7 @@ func (id AttributeTypeIdentifier) UnmarshalJSON(b []byte) error {
return nil
}
func (id CredentialTypeIdentifier) UnmarshalJSON(b []byte) error {
func (id *CredentialTypeIdentifier) UnmarshalJSON(b []byte) error {
var val string
err := json.Unmarshal(b, &val)
if err != nil {
......
......@@ -20,7 +20,6 @@ type CredentialManager struct {
storagePath string
attributes map[CredentialTypeIdentifier][]*AttributeList
credentials map[CredentialTypeIdentifier]map[int]*Credential
issuance issuanceState
}
func newCredentialManager() *CredentialManager {
......@@ -209,11 +208,11 @@ func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*At
candidates := make([]*AttributeIdentifier, 0, 10)
for _, attribute := range disjunction.Attributes {
credId := attribute.CredentialTypeIdentifier()
if !MetaStore.Contains(credId) {
credID := attribute.CredentialTypeIdentifier()
if !MetaStore.Contains(credID) {
continue
}
creds := cm.credentials[credId]
creds := cm.credentials[credID]
count := len(creds)
if count == 0 {
continue
......@@ -285,7 +284,7 @@ type sessionRequest interface {
GetContext() *big.Int
}
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request sessionRequest, issig bool) (gabi.ProofList, error) {
func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice, request sessionRequest) ([]gabi.ProofBuilder, error) {
todisclose, err := cm.groupCredentials(choice)
if err != nil {
return nil, err
......@@ -299,22 +298,63 @@ func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request sessionReq
}
builders = append(builders, cred.Credential.CreateDisclosureProofBuilder(list))
}
return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders, issig), nil
return builders, nil
}
type issuanceState struct {
builders []*gabi.CredentialBuilder
nonce2 *big.Int
func (cm *CredentialManager) Proofs(choice *DisclosureChoice, request sessionRequest, issig bool) (gabi.ProofList, error) {
builders, err := cm.proofsBuilders(choice, request)
if err != nil {
return nil, err
}
return gabi.BuildProofList(request.GetContext(), request.GetNonce(), builders, false), nil
}
func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request sessionRequest) (*gabi.IssueCommitmentMessage, error) {
cm.issuance = issuanceState{[]*gabi.CredentialBuilder{}, nil}
func (cm *CredentialManager) IssueCommitments(choice *DisclosureChoice, request *IssuanceRequest) (*gabi.IssueCommitmentMessage, error) {
state, err := newIssuanceState(request)
if err != nil {
return nil, err
}
request.state = state
proofBuilders := []gabi.ProofBuilder{}
for _, futurecred := range request.Credentials {
pk := MetaStore.PublicKey(futurecred.Credential.IssuerIdentifier(), futurecred.KeyCounter)
credBuilder := gabi.NewCredentialBuilder(pk, request.GetContext(), cm.secretkey, state.nonce2)
request.state.builders = append(request.state.builders, credBuilder)
proofBuilders = append(proofBuilders, credBuilder)
}
_, err := cm.groupCredentials(choice)
disclosures, err := cm.proofsBuilders(choice, request)
if err != nil {
return nil, err
}
proofBuilders = append(disclosures, proofBuilders...)
list := gabi.BuildProofList(request.GetContext(), request.GetNonce(), proofBuilders, false)
return &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: state.nonce2}, nil
}
func (cm *CredentialManager) ConstructCredentials(msg []*gabi.IssueSignatureMessage, request *IssuanceRequest) error {
if len(msg) != len(request.state.builders) {
return errors.New("Received unexpected amount of signatures")
}
creds := []*gabi.Credential{}
for i, sig := range msg {
attrs, err := request.Credentials[i].AttributeList()
if err != nil {
return err
}
cred, err := request.state.builders[i].ConstructCredential(sig, attrs.Ints)
if err != nil {
return err
}
creds = append(creds, cred)
}
for _, cred := range creds {
cm.addCredential(newCredential(cred))
}
return nil, nil
return nil
}
package protocol
import (
"encoding/json"
"fmt"
"math/big"
"github.com/credentials/irmago"
......@@ -83,3 +83,41 @@ func (e *Error) Error() string {
return string(e.ErrorCode)
}
}
/*
So apparently, in the old Java implementation we forgot to write a (de)serialization for the Java
equivalent of the type IssuerIdentifier. This means a Java IssuerIdentifier does not serialize to
a string, but to e.g. `{"identifier":"irma-demo.RU"}`.
This is a complex data type, so not suitable to act as keys in a JSON map. Consequentially,
Gson serializes the `json:"keys"` field not as a map, but as a list consisting of pairs where
the first item of the pair is a serialized IssuerIdentifier as above, and the second item
of the pair is the corresponding key counter from the original map.
This is a bit of a mess to have to deserialize. See below. In a future version of the protocol,
this will have to be fixed both in the Java world and here in Go.
*/
type jsonSessionInfo struct {
Jwt string `json:"jwt"`
Nonce *big.Int `json:"nonce"`
Context *big.Int `json:"context"`
Keys [][]interface{} `json:"keys"`
}
func (si *SessionInfo) UnmarshalJSON(b []byte) error {
temp := &jsonSessionInfo{}
err := json.Unmarshal(b, temp)
if err != nil {
return err
}
si.Jwt = temp.Jwt
si.Nonce = temp.Nonce
si.Context = temp.Context
si.Keys = make(map[irmago.IssuerIdentifier]int, len(temp.Keys))
for _, item := range temp.Keys {
idmap := item[0].(map[string]interface{})
id := irmago.NewIssuerIdentifier(idmap["identifier"].(string))
si.Keys[id] = int(item[1].(float64))
}
return nil
}
......@@ -69,7 +69,7 @@ func NewIdentityProviderJwt(servername string, ir irmago.IssuanceRequest) *Ident
ServerJwt: ServerJwt{
ServerName: servername,
IssuedAt: &now,
Type: "signature_request",
Type: "issue_request",
},
Request: IdentityProviderRequest{Request: ir},
}
......
package protocol
import (
"errors"
"math/big"
"strconv"
"strings"
......@@ -151,30 +150,38 @@ func (session *Session) start() {
var header struct {
Server string `json:"iss"`
}
json.Unmarshal([]byte(headerbytes), &header)
err = json.Unmarshal([]byte(headerbytes), &header)
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
return
}
switch session.Action {
case ActionDisclosing:
session.spRequest = &ServiceProviderJwt{}
json.Unmarshal([]byte(bodybytes), session.spRequest)
err = json.Unmarshal([]byte(bodybytes), session.spRequest)
session.spRequest.Request.Request.Context = session.context
session.spRequest.Request.Request.Nonce = session.nonce
session.request = session.spRequest
case ActionSigning:
session.ssRequest = &SignatureServerJwt{}
json.Unmarshal([]byte(bodybytes), session.ssRequest)
err = json.Unmarshal([]byte(bodybytes), session.ssRequest)
session.ssRequest.Request.Request.Context = session.context
session.ssRequest.Request.Request.Nonce = session.nonce
session.request = session.ssRequest
case ActionIssuing:
session.ipRequest = &IdentityProviderJwt{}
json.Unmarshal([]byte(bodybytes), session.ipRequest)
err = json.Unmarshal([]byte(bodybytes), session.ipRequest)
session.ipRequest.Request.Request.Context = session.context
session.ipRequest.Request.Request.Nonce = session.nonce
session.request = session.ipRequest
default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier
}
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
return
}
if session.Action == ActionIssuing {
// Store which public keys the server will use
......@@ -213,26 +220,50 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
}
session.Handler.StatusUpdate(session.Action, StatusCommunicating)
var proofs gabi.ProofList
var message interface{}
var err error
switch session.Action {
case ActionSigning:
proofs, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request, true)
message, err = irmago.Manager.Proofs(choice, &session.ssRequest.Request.Request, true)
case ActionDisclosing:
proofs, err = irmago.Manager.Proofs(choice, &session.spRequest.Request.Request, false)
message, err = irmago.Manager.Proofs(choice, &session.spRequest.Request.Request, false)
case ActionIssuing:
err = errors.New("Issuing not yet implemented")
message, err = irmago.Manager.IssueCommitments(choice, &session.ipRequest.Request.Request)
}
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
return
}
var response string
session.transport.Post("proofs", &response, proofs)
if response != "VALID" {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
return
switch session.Action {
case ActionSigning:
fallthrough
case ActionDisclosing:
response := ""
err = session.transport.Post("proofs", &response, message)
if err != nil {
session.Handler.Failure(session.Action,
&Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr, info: err.Error()})
return
}
if response != "VALID" {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorRejected, info: response})
return
}
case ActionIssuing:
response := []*gabi.IssueSignatureMessage{}
err = session.transport.Post("commitments", &response, message)
if err != nil {
session.Handler.Failure(session.Action,
&Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr, info: err.Error()})
return
}
err = irmago.Manager.ConstructCredentials(response, &session.ipRequest.Request.Request)
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
return
}
}
session.Handler.Success(session.Action)
......
......@@ -65,8 +65,6 @@ func (th TestHandler) Failure(action Action, err *Error) {
func (th TestHandler) UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList) {
th.c <- &Error{}
}
func (th TestHandler) AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, choice PermissionHandler) {
}
func (th TestHandler) AskVerificationPermission(request irmago.DisclosureRequest, ServerName string, callback PermissionHandler) {
choice := &irmago.DisclosureChoice{
Attributes: []*irmago.AttributeIdentifier{},
......@@ -80,8 +78,15 @@ func (th TestHandler) AskVerificationPermission(request irmago.DisclosureRequest
}
callback(true, choice)
}
func (th TestHandler) AskSignaturePermission(request irmago.SignatureRequest, ServerName string, choice PermissionHandler) {
th.AskVerificationPermission(request.DisclosureRequest, ServerName, choice)
func (th TestHandler) AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, callback PermissionHandler) {
dreq := irmago.DisclosureRequest{
SessionRequest: request.SessionRequest,
Content: request.Disclose,
}
th.AskVerificationPermission(dreq, ServerName, callback)
}
func (th TestHandler) AskSignaturePermission(request irmago.SignatureRequest, ServerName string, callback PermissionHandler) {
th.AskVerificationPermission(request.DisclosureRequest, ServerName, callback)
}
func getDisclosureJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
......@@ -110,6 +115,36 @@ func getSigningJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
})
}
func getIssuanceJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
expiry := irmago.Timestamp(irmago.NewMetadataAttribute().Expiry())
credid1 := irmago.NewCredentialTypeIdentifier("irma-demo.RU.studentCard")
credid2 := irmago.NewCredentialTypeIdentifier("irma-demo.MijnOverheid.root")
return NewIdentityProviderJwt(name, irmago.IssuanceRequest{
Credentials: []*irmago.CredentialRequest{
&irmago.CredentialRequest{
Validity: &expiry,
Credential: &credid1,
Attributes: map[string]string{
"university": "Radboud",
"studentCardNumber": "3.1415926535897932384626433832795028841971694",
"studentID": "s1234567",
"level": "42",
},
},
&irmago.CredentialRequest{
Validity: &expiry,
Credential: &credid2,
Attributes: map[string]string{
"BSN": "299792458",
},
},
},
Disclose: irmago.AttributeDisjunctionList{
&irmago.AttributeDisjunction{Label: "foo", Attributes: []irmago.AttributeTypeIdentifier{id}},
},
})
}
func TestSigningSession(t *testing.T) {
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
name := "testsigclient"
......@@ -126,6 +161,14 @@ func TestDisclosureSession(t *testing.T) {
sessionHelper(t, jwtcontents, "verification")
}
func TestIssuanceSession(t *testing.T) {
id := irmago.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
name := "testip"
jwtcontents := getIssuanceJwt(name, id)
sessionHelper(t, jwtcontents, "issue")
}
func sessionHelper(t *testing.T, jwtcontents interface{}, url string) {
parseMetaStore(t)
parseStorage(t)
......
......@@ -3,6 +3,7 @@ package protocol
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
......@@ -65,6 +66,7 @@ func (transport *HTTPTransport) request(url string, method string, result interf
if err != nil {
return &TransportError{Err: err.Error()}
}
fmt.Printf("POST: %s\n", string(marshaled))
reader = bytes.NewBuffer(marshaled)
}
}
......@@ -98,9 +100,11 @@ func (transport *HTTPTransport) request(url string, method string, result interf
if apierr.ErrorName == "" { // Not an ApiErrorMessage
return &TransportError{Status: res.StatusCode}
}
fmt.Printf("ERROR: %+v\n", apierr)
return &TransportError{Err: apierr.ErrorName, Status: res.StatusCode, ApiErr: apierr}
}
fmt.Printf("RESPONSE: %s\n", string(body))
err = json.Unmarshal(body, result)
if err != nil {
return &TransportError{Err: err.Error(), Status: res.StatusCode}
......
......@@ -3,15 +3,15 @@ package irmago
import (
"crypto/sha256"
"encoding/asn1"
"errors"
"fmt"
"log"
"math/big"
"strconv"
"time"
)
// Timestamp is a time.Time that marshals to Unix timestamps.
type Timestamp time.Time
"github.com/mhe/gabi"
)
type SessionRequest struct {
Context *big.Int `json:"nonce"`
......@@ -31,15 +31,67 @@ type SignatureRequest struct {
type IssuanceRequest struct {
SessionRequest
Credentials []CredentialRequest `json:"credentials"`
Credentials []*CredentialRequest `json:"credentials"`
Disclose AttributeDisjunctionList `json:"disclose"`
state *issuanceState
}
type CredentialRequest struct {
Validity *Timestamp
KeyCounter int
Credential CredentialTypeIdentifier
Attributes map[string]string
Validity *Timestamp `json:"validity"`
KeyCounter int `json:"keyCounter"`
Credential *CredentialTypeIdentifier `json:"credential"`
Attributes map[string]string `json:"attributes"`
}
// Timestamp is a time.Time that marshals to Unix timestamps.
type Timestamp time.Time
type issuanceState struct {
nonce2 *big.Int
builders []*gabi.CredentialBuilder
}
func (cr *CredentialRequest) AttributeList() (*AttributeList, error) {
meta := NewMetadataAttribute()
meta.setKeyCounter(cr.KeyCounter)
meta.setCredentialTypeIdentifier(cr.Credential.String())
meta.setSigningDate()
err := meta.setExpiryDate(cr.Validity)
if err != nil {
return nil, err
}
attrs := make([]*big.Int, len(cr.Attributes)+1, len(cr.Attributes)+1)
credtype := MetaStore.Credentials[*cr.Credential]
if credtype == nil {
return nil, errors.New("Unknown credential type")
}
if len(credtype.Attributes) != len(cr.Attributes) {
return nil, errors.New("Received unexpected amount of attributes")
}
attrs[0] = meta.Int
for i, attrtype := range credtype.Attributes {
if str, present := cr.Attributes[attrtype.ID]; present {
attrs[i+1] = new(big.Int).SetBytes([]byte(str))
} else {
return nil, errors.New("Unknown attribute")
}
}
return NewAttributeListFromInts(attrs), nil
}
func newIssuanceState(request *IssuanceRequest) (*issuanceState, error) {
nonce2, err := gabi.RandomBigInt(gabi.DefaultSystemParameters[4096].Lstatzk)
if err != nil {
return nil, err
}
return &issuanceState{
nonce2: nonce2,
builders: []*gabi.CredentialBuilder{},
}, nil
}
func (ir *IssuanceRequest) GetContext() *big.Int {
......
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