Commit 645b8397 authored by Sietse Ringers's avatar Sietse Ringers
Browse files

Cleanup API

parent 9f8041f8
......@@ -22,15 +22,15 @@ const (
var (
metadataVersion = []byte{0x02}
versionField = MetadataField{1, 0}
signingDateField = MetadataField{3, 1}
validityField = MetadataField{2, 4}
keyCounterField = MetadataField{2, 6}
credentialID = MetadataField{16, 8}
versionField = metadataField{1, 0}
signingDateField = metadataField{3, 1}
validityField = metadataField{2, 4}
keyCounterField = metadataField{2, 6}
credentialID = metadataField{16, 8}
)
// MetadataField contains the length and offset of a field within a metadata attribute.
type MetadataField struct {
// metadataField contains the length and offset of a field within a metadata attribute.
type metadataField struct {
length int
offset int
}
......@@ -199,11 +199,11 @@ func (attr *MetadataAttribute) IsValid() bool {
return attr.IsValidOn(time.Now())
}
func (attr *MetadataAttribute) field(field MetadataField) []byte {
func (attr *MetadataAttribute) field(field metadataField) []byte {
return attr.Bytes()[field.offset : field.offset+field.length]
}
func (attr *MetadataAttribute) setField(field MetadataField, value []byte) {
func (attr *MetadataAttribute) setField(field metadataField, value []byte) {
if len(value) > field.length {
panic("Specified metadata field too large")
}
......@@ -252,10 +252,6 @@ type AttributeDisjunction struct {
// An AttributeDisjunctionList is a list of AttributeDisjunctions.
type AttributeDisjunctionList []*AttributeDisjunction
type DisjunctionListContainer interface {
DisjunctionList() AttributeDisjunctionList
}
// HasValues indicates if the attributes of this disjunction have values
// that should be satisfied.
func (disjunction *AttributeDisjunction) HasValues() bool {
......
......@@ -2,15 +2,15 @@ package irmago
import "github.com/mhe/gabi"
// Credential represents an IRMA credential, whose zeroth attribute
// credential represents an IRMA credential, whose zeroth attribute
// is always the secret key and the first attribute the metadata attribute.
type Credential struct {
type credential struct {
*gabi.Credential
*MetadataAttribute
}
func newCredential(gabicred *gabi.Credential) (cred *Credential) {
cred = &Credential{}
func newCredential(gabicred *gabi.Credential) (cred *credential) {
cred = &credential{}
cred.Credential = gabicred
cred.MetadataAttribute = MetadataFromInt(gabicred.Attributes[1])
cred.Pk = MetaStore.PublicKey(cred.CredentialType().IssuerIdentifier(), cred.KeyCounter())
......
// Package irmago is work in progress on an IRMA client in Go. It will:
//* (De)serialize credentials from/to storage
//* Be the client (like the IRMA Android app) in the IRMA protocol
// see (https://credentials.github.io/protocols/irma-protocol/).
// Package irmago is work in progress on an IRMA client in Go.
//It will (de)serialize credentials from/to storage,
// and be the client (like the IRMA Android app,
// https://github.com/credentials/irma_android_cardemu) in the IRMA protocol (see
// https://credentials.github.io/protocols/irma-protocol).
package irmago
......@@ -58,7 +58,7 @@ func parseAndroidStorage(t *testing.T) {
}
func verifyStoreIsUnmarshaled(t *testing.T) {
cred, err := Manager.Credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
cred, err := Manager.credential(NewCredentialTypeIdentifier("irma-demo.RU.studentCard"), 0)
assert.NoError(t, err, "could not fetch credential")
assert.NotNil(t, cred, "Credential should exist")
assert.NotNil(t, cred.Attributes[0], "Metadata attribute of irma-demo.RU.studentCard should not be nil")
......
......@@ -19,12 +19,12 @@ type CredentialManager struct {
secretkey *big.Int
storagePath string
attributes map[CredentialTypeIdentifier][]*AttributeList
credentials map[CredentialTypeIdentifier]map[int]*Credential
credentials map[CredentialTypeIdentifier]map[int]*credential
}
func newCredentialManager() *CredentialManager {
return &CredentialManager{
credentials: make(map[CredentialTypeIdentifier]map[int]*Credential),
credentials: make(map[CredentialTypeIdentifier]map[int]*credential),
}
}
......@@ -60,10 +60,10 @@ func (cm *CredentialManager) attrs(id CredentialTypeIdentifier) []*AttributeList
}
// creds returns cm.credentials[id], initializing it to an empty map if neccesary
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*Credential {
func (cm *CredentialManager) creds(id CredentialTypeIdentifier) map[int]*credential {
list, exists := cm.credentials[id]
if !exists {
list = make(map[int]*Credential)
list = make(map[int]*credential)
cm.credentials[id] = list
}
return list
......@@ -78,12 +78,12 @@ func (cm *CredentialManager) Attributes(id CredentialTypeIdentifier, counter int
return list[counter]
}
func (cm *CredentialManager) CredentialByID(id CredentialIdentifier) (cred *Credential, err error) {
return cm.Credential(id.Type, id.Index)
func (cm *CredentialManager) credentialByID(id CredentialIdentifier) (cred *credential, err error) {
return cm.credential(id.Type, id.Index)
}
// Credential returns the requested credential, or nil if we do not have it.
func (cm *CredentialManager) Credential(id CredentialTypeIdentifier, counter int) (cred *Credential, err error) {
// credential returns the requested credential, or nil if we do not have it.
func (cm *CredentialManager) credential(id CredentialTypeIdentifier, counter int) (cred *credential, err error) {
// If the requested credential is not in credential map, we check if its attributes were
// deserialized during Init(). If so, there should be a corresponding signature file,
// so we read that, construct the credential, and add it to the credential map
......@@ -176,19 +176,19 @@ func (cm *CredentialManager) ParseAndroidStorage() (err error) {
return
}
func (cm *CredentialManager) addCredential(cred *Credential) {
func (cm *CredentialManager) addCredential(cred *credential) {
id := cred.CredentialType().Identifier()
cm.attributes[id] = append(cm.attrs(id), NewAttributeListFromInts(cred.Attributes[1:]))
if _, exists := cm.credentials[id]; !exists {
cm.credentials[id] = make(map[int]*Credential)
cm.credentials[id] = make(map[int]*credential)
}
counter := len(cm.attributes[id]) - 1
cm.credentials[id][counter] = cred
}
// Add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) Add(cred *Credential) (err error) {
// add adds the specified credential to the CredentialManager.
func (cm *CredentialManager) add(cred *credential) (err error) {
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
......@@ -237,9 +237,9 @@ func (cm *CredentialManager) Candidates(disjunction *AttributeDisjunction) []*At
return candidates
}
func (cm *CredentialManager) CheckSatisfiability(disjunctions DisjunctionListContainer) AttributeDisjunctionList {
func (cm *CredentialManager) CheckSatisfiability(disjunctions AttributeDisjunctionList) AttributeDisjunctionList {
missing := make(AttributeDisjunctionList, 0, 5)
for _, disjunction := range disjunctions.DisjunctionList() {
for _, disjunction := range disjunctions {
if len(cm.Candidates(disjunction)) == 0 {
missing = append(missing, disjunction)
}
......@@ -271,7 +271,7 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre
return nil, err
}
// These indices will be used in the []*big.Int at gabi.Credential.Attributes,
// These indices will be used in the []*big.Int at gabi.credential.Attributes,
// which doesn't know about the secret key and metadata attribute, so +2
grouped[ici] = append(grouped[ici], index+2)
}
......@@ -281,7 +281,10 @@ func (cm *CredentialManager) groupCredentials(choice *DisclosureChoice) (map[Cre
type Session interface {
GetNonce() *big.Int
SetNonce(*big.Int)
GetContext() *big.Int
SetContext(*big.Int)
DisjunctionList() AttributeDisjunctionList
}
func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice) ([]gabi.ProofBuilder, error) {
......@@ -292,7 +295,7 @@ func (cm *CredentialManager) proofsBuilders(choice *DisclosureChoice) ([]gabi.Pr
builders := []gabi.ProofBuilder{}
for id, list := range todisclose {
cred, err := cm.CredentialByID(id)
cred, err := cm.credentialByID(id)
if err != nil {
return nil, err
}
......@@ -355,7 +358,7 @@ func (cm *CredentialManager) ConstructCredentials(msg []*gabi.IssueSignatureMess
}
for _, cred := range creds {
cm.Add(newCredential(cred))
cm.add(newCredential(cred))
}
return nil
......
// Package protocol implements the IRMA protocol.
// A new IRMA session is started with the NewSession() method
package protocol
......@@ -16,7 +16,7 @@ type ServiceProviderRequest struct {
Request *irmago.DisclosureRequest `json:"request"`
}
type SignatureServerRequest struct {
type SignatureRequestorRequest struct {
Request *irmago.SignatureRequest `json:"request"`
}
......@@ -29,9 +29,9 @@ type ServiceProviderJwt struct {
Request ServiceProviderRequest `json:"sprequest"`
}
type SignatureServerJwt struct {
type SignatureRequestorJwt struct {
ServerJwt
Request SignatureServerRequest `json:"absrequest"`
Request SignatureRequestorRequest `json:"absrequest"`
}
type IdentityProviderJwt struct {
......@@ -50,14 +50,14 @@ func NewServiceProviderJwt(servername string, dr *irmago.DisclosureRequest) *Ser
}
}
func NewSignatureServerJwt(servername string, sr *irmago.SignatureRequest) *SignatureServerJwt {
return &SignatureServerJwt{
func NewSignatureRequestorJwt(servername string, sr *irmago.SignatureRequest) *SignatureRequestorJwt {
return &SignatureRequestorJwt{
ServerJwt: ServerJwt{
ServerName: servername,
IssuedAt: irmago.Timestamp(time.Now()),
Type: "signature_request",
},
Request: SignatureServerRequest{Request: sr},
Request: SignatureRequestorRequest{Request: sr},
}
}
......@@ -72,14 +72,10 @@ func NewIdentityProviderJwt(servername string, ir *irmago.IssuanceRequest) *Iden
}
}
func (spr *ServiceProviderJwt) DisjunctionList() irmago.AttributeDisjunctionList {
return spr.Request.Request.Content
type RequestorJwt interface {
IrmaSession() irmago.Session
}
func (ssr *SignatureServerJwt) DisjunctionList() irmago.AttributeDisjunctionList {
return ssr.Request.Request.Content
}
func (ipr *IdentityProviderJwt) DisjunctionList() irmago.AttributeDisjunctionList {
return ipr.Request.Request.Disclose
}
func (jwt *ServiceProviderJwt) IrmaSession() irmago.Session { return jwt.Request.Request }
func (jwt *SignatureRequestorJwt) IrmaSession() irmago.Session { return jwt.Request.Request }
func (jwt *IdentityProviderJwt) IrmaSession() irmago.Session { return jwt.Request.Request }
package protocol
import (
"math/big"
"fmt"
"sort"
"strconv"
"strings"
"sort"
"fmt"
"encoding/json"
"encoding/base64"
"encoding/json"
"github.com/credentials/irmago"
"github.com/mhe/gabi"
......@@ -27,31 +23,26 @@ type Handler interface {
Failure(action Action, err *Error)
UnsatisfiableRequest(action Action, missing irmago.AttributeDisjunctionList)
AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, choice PermissionHandler)
AskVerificationPermission(request irmago.DisclosureRequest, ServerName string, choice PermissionHandler)
AskSignaturePermission(request irmago.SignatureRequest, ServerName string, choice PermissionHandler)
AskIssuancePermission(request irmago.IssuanceRequest, ServerName string, callback PermissionHandler)
AskVerificationPermission(request irmago.DisclosureRequest, ServerName string, callback PermissionHandler)
AskSignaturePermission(request irmago.SignatureRequest, ServerName string, callback PermissionHandler)
}
// A Session is an IRMA session.
type Session struct {
// A session is an IRMA session.
type session struct {
Action Action
Version Version
ServerURL string
Handler Handler
request irmago.DisjunctionListContainer
spRequest *ServiceProviderJwt
ipRequest *IdentityProviderJwt
ssRequest *SignatureServerJwt
transport *HTTPTransport
nonce *big.Int
context *big.Int
jwt RequestorJwt
irmaSession irmago.Session
transport *HTTPTransport
}
// Supported protocol versions. Minor version numbers should be reverse sorted.
var supportedVersions = map[int][]int{
2: []int{2, 1},
2: {2, 1},
}
func calcVersion(qr *Qr) (string, error) {
......@@ -90,14 +81,14 @@ func calcVersion(qr *Qr) (string, error) {
}
// NewSession creates and starts a new IRMA session.
func NewSession(qr *Qr, handler Handler) *Session {
func NewSession(qr *Qr, handler Handler) {
version, err := calcVersion(qr)
if err != nil {
handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorProtocolVersionNotSupported, error: err})
return nil
return
}
session := &Session{
session := &session{
Version: Version(version),
Action: Action(qr.Type),
ServerURL: qr.URL,
......@@ -114,7 +105,7 @@ func NewSession(qr *Qr, handler Handler) *Session {
fallthrough
default:
handler.Failure(ActionUnknown, &Error{ErrorCode: ErrorUnknownAction, error: nil, info: string(session.Action)})
return nil
return
}
if !strings.HasSuffix(session.ServerURL, "/") {
......@@ -123,41 +114,31 @@ func NewSession(qr *Qr, handler Handler) *Session {
go session.start()
return session
return
}
// start retrieves the first message in the IRMA protocol, checks if we can perform
// the request, and informs the user of the outcome.
func (session *Session) start() {
func (session *session) start() {
session.Handler.StatusUpdate(session.Action, StatusCommunicating)
// Get the first IRMA protocol message
// Get the first IRMA protocol message and parse it
info := &SessionInfo{}
err := session.transport.Get("jwt", info)
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorTransport, ApiError: err.(*TransportError).ApiErr})
return
}
session.nonce = info.Nonce
session.context = info.Context
jwtparts := strings.Split(info.Jwt, ".")
if jwtparts == nil || len(jwtparts) < 2 {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT})
return
}
headerbytes, err := base64.RawStdEncoding.DecodeString(jwtparts[0])
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
return
}
bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
return
}
var header struct {
Server string `json:"iss"`
}
......@@ -167,25 +148,25 @@ func (session *Session) start() {
return
}
// Deserialize JWT, and set session state
bodybytes, err := base64.RawStdEncoding.DecodeString(jwtparts[1])
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
return
}
switch session.Action {
case ActionDisclosing:
session.spRequest = &ServiceProviderJwt{}
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
jwt := &ServiceProviderJwt{}
err = json.Unmarshal([]byte(bodybytes), jwt)
session.jwt = jwt
case ActionSigning:
session.ssRequest = &SignatureServerJwt{}
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
jwt := &SignatureRequestorJwt{}
err = json.Unmarshal([]byte(bodybytes), jwt)
session.jwt = jwt
case ActionIssuing:
session.ipRequest = &IdentityProviderJwt{}
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
jwt := &IdentityProviderJwt{}
err = json.Unmarshal([]byte(bodybytes), jwt)
session.jwt = jwt
default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier
}
......@@ -193,38 +174,40 @@ func (session *Session) start() {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorInvalidJWT, error: err})
return
}
session.irmaSession = session.jwt.IrmaSession()
session.irmaSession.SetContext(info.Context)
session.irmaSession.SetNonce(info.Nonce)
if session.Action == ActionIssuing {
// Store which public keys the server will use
for _, credreq := range session.ipRequest.Request.Request.Credentials {
for _, credreq := range session.irmaSession.(*irmago.IssuanceRequest).Credentials {
credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
}
}
missing := irmago.Manager.CheckSatisfiability(session.request)
missing := irmago.Manager.CheckSatisfiability(session.irmaSession.DisjunctionList())
if len(missing) > 0 {
session.Handler.UnsatisfiableRequest(session.Action, missing)
return
}
// Ask for permission to execute the session
callback := PermissionHandler(func(proceed bool, choice *irmago.DisclosureChoice) {
go session.do(proceed, choice)
})
session.Handler.StatusUpdate(session.Action, StatusConnected)
switch session.Action {
case ActionDisclosing:
session.Handler.AskVerificationPermission(*session.spRequest.Request.Request, header.Server, callback)
session.Handler.AskVerificationPermission(*session.irmaSession.(*irmago.DisclosureRequest), header.Server, callback)
case ActionSigning:
session.Handler.AskSignaturePermission(*session.ssRequest.Request.Request, header.Server, callback)
session.Handler.AskSignaturePermission(*session.irmaSession.(*irmago.SignatureRequest), header.Server, callback)
case ActionIssuing:
session.Handler.AskIssuancePermission(*session.ipRequest.Request.Request, header.Server, callback)
session.Handler.AskIssuancePermission(*session.irmaSession.(*irmago.IssuanceRequest), header.Server, callback)
default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier
}
}
func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
func (session *session) do(proceed bool, choice *irmago.DisclosureChoice) {
if !proceed {
session.Handler.Cancelled(session.Action)
return
......@@ -235,11 +218,11 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
var err error
switch session.Action {
case ActionSigning:
message, err = irmago.Manager.Proofs(choice, session.ssRequest.Request.Request, true)
message, err = irmago.Manager.Proofs(choice, session.irmaSession, true)
case ActionDisclosing:
message, err = irmago.Manager.Proofs(choice, session.spRequest.Request.Request, false)
message, err = irmago.Manager.Proofs(choice, session.irmaSession, false)
case ActionIssuing:
message, err = irmago.Manager.IssueCommitments(choice, session.ipRequest.Request.Request)
message, err = irmago.Manager.IssueCommitments(choice, session.irmaSession.(*irmago.IssuanceRequest))
}
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
......@@ -270,7 +253,7 @@ func (session *Session) do(proceed bool, choice *irmago.DisclosureChoice) {
return
}
err = irmago.Manager.ConstructCredentials(response, session.ipRequest.Request.Request)
err = irmago.Manager.ConstructCredentials(response, session.irmaSession.(*irmago.IssuanceRequest))
if err != nil {
session.Handler.Failure(session.Action, &Error{ErrorCode: ErrorCrypto, error: err})
return
......
......@@ -101,7 +101,7 @@ func getDisclosureJwt(name string, id irmago.AttributeTypeIdentifier) interface{
}
func getSigningJwt(name string, id irmago.AttributeTypeIdentifier) interface{} {
return NewSignatureServerJwt(name, &irmago.SignatureRequest{
return NewSignatureRequestorJwt(name, &irmago.SignatureRequest{
Message: "test",
MessageType: "STRING",
DisclosureRequest: irmago.DisclosureRequest{
......
......@@ -94,26 +94,20 @@ func newIssuanceState(request *IssuanceRequest) (*issuanceState, error) {
}, nil
}
func (ir *IssuanceRequest) GetContext() *big.Int {
return ir.Context
}
func (ir *IssuanceRequest) GetNonce() *big.Int {
return ir.Nonce
}
func (dr *DisclosureRequest) GetContext() *big.Int {
return dr.Context
}
func (dr *DisclosureRequest) GetNonce() *big.Int {
return dr.Nonce
}
func (sr *SignatureRequest) GetContext() *big.Int {
return sr.Context
}
func (ir *IssuanceRequest) DisjunctionList() AttributeDisjunctionList { return ir.Disclose }
func (ir *IssuanceRequest) GetContext() *big.Int { return ir.Context }
func (ir *IssuanceRequest) SetContext(context *big.Int) { ir.Context = context }
func (ir *IssuanceRequest) GetNonce() *big.Int { return ir.Nonce }
func (ir *IssuanceRequest) SetNonce(nonce *big.Int) { ir.Nonce = nonce }
func (dr *DisclosureRequest) DisjunctionList() AttributeDisjunctionList { return dr.Content }
func (dr *DisclosureRequest) GetContext() *big.Int { return dr.Context }
func (dr *DisclosureRequest) SetContext(context *big.Int) { dr.Context = context }
func (dr *DisclosureRequest) GetNonce() *big.Int { return dr.Nonce }
func (dr *DisclosureRequest) SetNonce(nonce *big.Int) { dr.Nonce = nonce }
func (sr *SignatureRequest) DisjunctionList() AttributeDisjunctionList { return sr.Content }
func (sr *SignatureRequest) GetContext() *big.Int { return sr.Context }
func (sr *SessionRequest) SetContext(context *big.Int) { sr.Context = context }
func (sr *SessionRequest) SetNonce(nonce *big.Int) { sr.Nonce = nonce }
func (sr *SignatureRequest) GetNonce() *big.Int {
hashbytes := sha256.Sum256([]byte(sr.Message))
hashint := new(big.Int).SetBytes(hashbytes[:])
......
......@@ -95,7 +95,7 @@ func (cm *CredentialManager) saveFile(filepath string, content []byte) (err erro
return os.Rename(dir+"/"+tempfilename, filepath)
}
func (cm *CredentialManager) storeSignature(cred *Credential, counter int) (err error) {
func (cm *CredentialManager) storeSignature(cred *credential, counter int) (err error) {
if cred.CredentialType() == nil {
return errors.New("cannot add unknown credential type")
}
......
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