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

Adding session logs

parent a2a7f94f
......@@ -112,12 +112,12 @@ func (id *SchemeManagerIdentifier) UnmarshalText(text []byte) error {
return nil
}
// MarshalText implements encoding.TextMarshaler.
func (id IssuerIdentifier) MarshalText() ([]byte, error) {
return []byte(id.String()), nil
}
// TODO enable this when updating protocol
//// MarshalText implements encoding.TextMarshaler.
//func (id IssuerIdentifier) MarshalText() ([]byte, error) {
// return []byte(id.String()), nil
//}
//
//// UnmarshalText implements encoding.TextUnmarshaler.
//func (id *IssuerIdentifier) UnmarshalText(text []byte) error {
// *id = NewIssuerIdentifier(string(text))
......
......@@ -174,13 +174,33 @@ func TestAndroidParse(t *testing.T) {
}
func TestUnmarshaling(t *testing.T) {
parseStorage(t)
manager := parseStorage(t)
// Do session so we can examine its log item later
logs, err := manager.Logs()
require.NoError(t, err)
jwt := getIssuanceJwt("testip", NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"))
sessionHelper(t, jwt, "issue", manager)
newmanager, err := NewCredentialManager("testdata/storage/test", "testdata/irma_configuration", "testdata/oldstorage", nil)
require.NoError(t, err)
verifyManagerIsUnmarshaled(t, newmanager)
verifyCredentials(t, newmanager)
verifyKeyshareIsUnmarshaled(t, newmanager)
newlogs, err := newmanager.Logs()
require.NoError(t, err)
require.True(t, len(newlogs) == len(logs)+1)
entry := newlogs[len(newlogs)-1]
require.NotNil(t, entry)
sessionjwt, _, err := entry.Jwt()
require.NoError(t, err)
require.Equal(t, "testip", sessionjwt.(*IdentityProviderJwt).ServerName)
response, err := entry.GetResponse()
require.NoError(t, err)
require.NotEmpty(t, response.(*IssuanceLog).Proofs)
teardown(t)
}
......
......@@ -382,7 +382,7 @@ func (ks *keyshareSession) Finish(challenge *big.Int, responses map[SchemeManage
ks.sessionHandler.KeyshareError(err)
return
}
message := gabi.IssueCommitmentMessage{Proofs: list, Nonce2: ks.session.(*IssuanceRequest).state.nonce2}
message := &gabi.IssueCommitmentMessage{Proofs: list, Nonce2: ks.session.(*IssuanceRequest).state.nonce2}
for _, response := range responses {
message.ProofPjwt = response
break
......
......@@ -4,10 +4,16 @@ import (
"encoding/json"
"math/big"
"strings"
"time"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
// Legacy from the old Android app, and from the protocol that will be updated
// in the future
func (pki *publicKeyIdentifier) MarshalJSON() ([]byte, error) {
temp := struct {
Issuer map[string]string `json:"issuer"`
......@@ -82,3 +88,110 @@ func (si *SessionInfo) UnmarshalJSON(b []byte) error {
}
return nil
}
const (
androidLogVerificationType = "verification"
androidLogIssueType = "issue"
androidLogSignatureType = "signature"
androidLogRemoveType = "remove"
androidLogTimeFormat = "January 2, 2006 3:04:05 PM MST -07:00"
)
type androidLogEnvelope struct {
Type string `json:"type"`
Value json.RawMessage `json:"value"`
}
func (env *androidLogEnvelope) Parse() (interface{}, error) {
switch env.Type {
case androidLogVerificationType:
val := &androidLogVerification{}
return val, json.Unmarshal(env.Value, val)
case androidLogIssueType:
val := &androidLogIssuance{}
return val, json.Unmarshal(env.Value, val)
case androidLogSignatureType:
val := &androidLogSignature{}
return val, json.Unmarshal(env.Value, val)
case androidLogRemoveType:
val := &androidLogRemoval{}
return val, json.Unmarshal(env.Value, val)
default:
return nil, errors.New("Invalid Android log type")
}
}
type androidLogEntry struct {
Time string `json:"timestamp"`
Credential struct {
Identifier CredentialTypeIdentifier `json:"identifier"`
} `json:"credential"`
}
func (entry *androidLogEntry) GetTime() Timestamp {
// An example date directly from cardemu.xml: September 29, 2017 11:12:57 AM GMT+02:00
// Unfortunately, the seemingly appropriate format parameter for time.Parse, with
// "MST-07:00" at the end, makes time.Parse emit an error: "GMT+02" gets to be
// interpreted as the timezone, i.e. as MST, and then nothing gets mapped onto "-07".
// So, we put a space between "GMT" and "+02:00".
fixed := strings.Replace(entry.Time, "+", " +", 1)
parsed, _ := time.Parse(androidLogTimeFormat, fixed)
return Timestamp(parsed)
}
type androidLogIssuance struct {
androidLogEntry
}
type androidLogRemoval struct {
androidLogEntry
}
type androidLogVerification struct {
androidLogEntry
Disclosed map[string]bool `json:"attributeDisclosed"`
}
type androidLogSignature struct {
androidLogVerification
Message string `json:"message"`
}
// TODO remove on protocol upgrade
type logSessionInfo struct {
Jwt string `json:"jwt"`
Nonce *big.Int `json:"nonce"`
Context *big.Int `json:"context"`
Keys map[string]int `json:"keys"`
}
// TODO remove on protocol upgrade
func (entry *LogEntry) MarshalJSON() ([]byte, error) {
resp := entry.raw
if len(resp) == 0 {
if bytes, err := json.Marshal(entry.Response); err == nil {
resp = json.RawMessage(bytes)
} else {
return nil, err
}
}
temp := &jsonLogEntry{
Type: entry.Type,
Time: entry.Time,
Response: resp,
SessionInfo: &logSessionInfo{
Jwt: entry.SessionInfo.Jwt,
Nonce: entry.SessionInfo.Nonce,
Context: entry.SessionInfo.Context,
Keys: make(map[string]int),
},
}
for iss, count := range entry.SessionInfo.Keys {
temp.SessionInfo.Keys[iss.String()] = count
}
return json.Marshal(temp)
}
package irmago
import (
"encoding/json"
"time"
"github.com/go-errors/errors"
"github.com/mhe/gabi"
)
type LogEntry struct {
Type Action
Time Timestamp // Time at which the session was completed
SessionInfo *SessionInfo // Message that started the session
Response interface{} // Session-type specific info, parsed on-demand, use .GetResponse()
raw json.RawMessage
}
type RemovalLog struct {
Credential CredentialTypeIdentifier
}
type VerificationLog struct {
Proofs []*gabi.ProofD
}
type IssuanceLog struct {
Proofs []*gabi.ProofD
AttributeList []*AttributeList
}
type SigningLog struct {
Proofs []*gabi.ProofD
Message []byte
MessageType string
}
func (session *session) createLogEntry(response gabi.ProofList) (*LogEntry, error) {
entry := &LogEntry{
Type: session.Action,
Time: Timestamp(time.Now()),
SessionInfo: session.info,
}
proofs := []*gabi.ProofD{}
for _, proof := range response {
if proofd, isproofd := proof.(*gabi.ProofD); isproofd {
proofs = append(proofs, proofd)
}
}
switch entry.Type {
case ActionDisclosing:
item := &VerificationLog{Proofs: proofs}
entry.Response = item
case ActionIssuing:
item := &IssuanceLog{Proofs: proofs}
for _, req := range session.jwt.(*IdentityProviderJwt).Request.Request.Credentials {
list, err := req.AttributeList(session.credManager.Store)
if err != nil {
continue // TODO?
}
item.AttributeList = append(item.AttributeList, list)
}
entry.Response = item
case ActionSigning:
item := SigningLog{Proofs: proofs}
item.Message = []byte(session.jwt.(*SignatureRequestorJwt).Request.Request.Message)
item.MessageType = session.jwt.(*SignatureRequestorJwt).Request.Request.MessageType
entry.Response = item
default:
return nil, errors.New("Invalid log type")
}
return entry, nil
}
func (entry *LogEntry) Jwt() (RequestorJwt, string, error) {
return parseRequestorJwt(entry.Type, entry.SessionInfo.Jwt)
}
func (entry *LogEntry) GetResponse() (interface{}, error) {
if entry.Response == nil {
switch entry.Type {
case ActionDisclosing:
entry.Response = &VerificationLog{}
case ActionIssuing:
entry.Response = &IssuanceLog{}
case ActionSigning:
entry.Response = &SigningLog{}
case Action("removal"):
entry.Response = &RemovalLog{}
default:
return nil, errors.New("Invalid log type")
}
err := json.Unmarshal(entry.raw, entry.Response)
if err != nil {
return nil, err
}
}
return entry.Response, nil
}
type jsonLogEntry struct {
Type Action
Time Timestamp
SessionInfo *logSessionInfo
Response json.RawMessage
}
func (entry *LogEntry) UnmarshalJSON(bytes []byte) error {
var err error
temp := &jsonLogEntry{}
if err = json.Unmarshal(bytes, temp); err != nil {
return err
}
*entry = LogEntry{
Type: temp.Type,
Time: temp.Time,
SessionInfo: &SessionInfo{
Jwt: temp.SessionInfo.Jwt,
Nonce: temp.SessionInfo.Nonce,
Context: temp.SessionInfo.Context,
Keys: make(map[IssuerIdentifier]int),
},
raw: temp.Response,
}
// TODO remove on protocol upgrade
for iss, count := range temp.SessionInfo.Keys {
entry.SessionInfo.Keys[NewIssuerIdentifier(iss)] = count
}
return nil
}
......@@ -18,6 +18,7 @@ type CredentialManager struct {
credentials map[CredentialTypeIdentifier]map[int]*credential
keyshareServers map[SchemeManagerIdentifier]*keyshareServer
paillierKeyCache *paillierPrivateKey
logs []*LogEntry
irmaConfigurationPath string
androidStoragePath string
......@@ -409,3 +410,19 @@ func (cm *CredentialManager) KeyshareRemove(manager SchemeManagerIdentifier) err
delete(cm.keyshareServers, manager)
return cm.storeKeyshareServers()
}
func (cm *CredentialManager) addLogEntry(entry *LogEntry) error {
cm.logs = append(cm.logs, entry)
return cm.storeLogs()
}
func (cm *CredentialManager) Logs() ([]*LogEntry, error) {
if cm.logs == nil || len(cm.logs) == 0 {
var err error
cm.logs, err = cm.loadLogs()
if err != nil {
return nil, err
}
}
return cm.logs, nil
}
......@@ -130,3 +130,22 @@ func jwtDecode(jwt string, body interface{}) (string, error) {
}
return header.Issuer, json.Unmarshal(bodybytes, body)
}
func parseRequestorJwt(action Action, jwt string) (RequestorJwt, string, error) {
var retval RequestorJwt
switch action {
case ActionDisclosing:
retval = &ServiceProviderJwt{}
case ActionSigning:
retval = &SignatureRequestorJwt{}
case ActionIssuing:
retval = &IdentityProviderJwt{}
default:
return nil, "", errors.New("Invalid session type")
}
server, err := jwtDecode(jwt, retval)
if err != nil {
return nil, "", err
}
return retval, server, nil
}
......@@ -36,6 +36,7 @@ type session struct {
ServerURL string
Handler Handler
info *SessionInfo
credManager *CredentialManager
jwt RequestorJwt
irmaSession IrmaSession
......@@ -132,35 +133,27 @@ func (session *session) start() {
session.Handler.StatusUpdate(session.Action, StatusCommunicating)
// Get the first IRMA protocol message and parse it
info := &SessionInfo{}
Err := session.transport.Get("jwt", info)
session.info = &SessionInfo{}
Err := session.transport.Get("jwt", session.info)
if Err != nil {
session.fail(Err.(*Error))
return
}
switch session.Action {
case ActionDisclosing:
session.jwt = &ServiceProviderJwt{}
case ActionSigning:
session.jwt = &SignatureRequestorJwt{}
case ActionIssuing:
session.jwt = &IdentityProviderJwt{}
default:
panic("Invalid session type") // does not happen, session.Action has been checked earlier
}
server, err := jwtDecode(info.Jwt, session.jwt)
var server string
var err error
session.jwt, server, err = parseRequestorJwt(session.Action, session.info.Jwt)
if err != nil {
session.fail(&Error{ErrorCode: ErrorInvalidJWT, Err: err})
return
}
session.irmaSession = session.jwt.IrmaSession()
session.irmaSession.SetContext(info.Context)
session.irmaSession.SetNonce(info.Nonce)
session.irmaSession.SetContext(session.info.Context)
session.irmaSession.SetNonce(session.info.Nonce)
if session.Action == ActionIssuing {
// Store which public keys the server will use
for _, credreq := range session.irmaSession.(*IssuanceRequest).Credentials {
credreq.KeyCounter = info.Keys[credreq.Credential.IssuerIdentifier()]
credreq.KeyCounter = session.info.Keys[credreq.Credential.IssuerIdentifier()]
}
}
......@@ -253,7 +246,9 @@ func (session *session) KeyshareError(err error) {
type disclosureResponse string
func (session *session) sendResponse(message interface{}) {
var log *LogEntry
var err error
switch session.Action {
case ActionSigning:
fallthrough
......@@ -267,6 +262,7 @@ func (session *session) sendResponse(message interface{}) {
session.fail(&Error{ErrorCode: ErrorRejected, Info: string(response)})
return
}
log, err = session.createLogEntry(message.(gabi.ProofList)) // TODO err
case ActionIssuing:
response := []*gabi.IssueSignatureMessage{}
if err = session.transport.Post("commitments", &response, message); err != nil {
......@@ -277,7 +273,9 @@ func (session *session) sendResponse(message interface{}) {
session.fail(&Error{Err: err, ErrorCode: ErrorCrypto})
return
}
log, err = session.createLogEntry(message.(*gabi.IssueCommitmentMessage).Proofs) // TODO err
}
session.credManager.addLogEntry(log) // TODO err
session.Handler.Success(session.Action)
}
......@@ -20,6 +20,7 @@ const (
kssFile = "kss"
paillierFile = "paillier"
updatesFile = "updates"
logsFile = "logs"
signaturesDir = "sigs"
)
......@@ -288,6 +289,15 @@ func (cm *CredentialManager) storePaillierKeys() (err error) {
return
}
func (cm *CredentialManager) storeLogs() (err error) {
bts, err := json.Marshal(cm.logs)
if err != nil {
return
}
err = saveFile(cm.path(logsFile), bts)
return
}
func (cm *CredentialManager) loadSignature(attrs *AttributeList) (signature *gabi.CLSignature, err error) {
sigpath := cm.signatureFilename(attrs)
exists, err := PathExists(sigpath)
......@@ -402,3 +412,20 @@ func (cm *CredentialManager) loadPaillierKeys() (key *paillierPrivateKey, err er
}
return
}
func (cm *CredentialManager) loadLogs() (logs []*LogEntry, err error) {
logs = []*LogEntry{}
exists, err := PathExists(cm.path(logsFile))
if err != nil || !exists {
return
}
bytes, err := ioutil.ReadFile(cm.path(logsFile))
if err != nil {
return nil, err
}
err = json.Unmarshal(bytes, &logs)
if err != nil {
return nil, err
}
return
}
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