Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: a new API for extended disclosure proofs #245

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1b8e69e
feat: Custom session nonce in request
stenreijers Sep 12, 2022
a485865
Initial layout of an extended session disclosure result including all…
stenreijers Oct 5, 2022
8f50c40
Implemented the initial functions to create a DisclosureResult on Ses…
stenreijers Oct 5, 2022
60f271e
Fixed error with unmarshalling of session, because the proof containe…
stenreijers Oct 5, 2022
768ea72
Added API endpoints and written a simple test case for the disclosure…
stenreijers Oct 5, 2022
cc5329b
Added additional tests and moved Timestamp to root level, since this …
stenreijers Oct 5, 2022
a456489
Typo in nill result on disclosure endpoint
stenreijers Oct 6, 2022
aae4c82
fix: Added distributedKey flag to indicate that scheme uses a keyshar…
stenreijers Oct 15, 2022
fdf043a
fix: Timestamp requests are empty in testing mode, so no-nil check is…
stenreijers Nov 5, 2022
20208d1
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers Nov 5, 2022
961ff09
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers Nov 10, 2022
4fb3dd1
Updates to dockerfile
stenreijers Nov 21, 2022
0b0d5ff
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers Mar 3, 2023
98d13b5
fix: Refactoring from DisclosureResult to ExtendedResult inline with …
stenreijers Mar 3, 2023
e367e8c
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers Mar 27, 2023
bf146c1
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers May 16, 2023
ff98ef8
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
stenreijers Sep 11, 2023
d61d433
Merge branch 'master' into feature/new-api-for-extended-disclosure-pr…
Oct 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ testdata/tmp/*
!testdata/tmp/cardemu.xml
errors_*.go
vendor/
irma/irma
irma/irma
.vscode
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ ENV CGO_ENABLED=0
# Build irma CLI tool
COPY . /irmago
WORKDIR /irmago
RUN go build -a -ldflags '-extldflags "-static"' -o "/bin/irma" ./irma
RUN go build -buildvcs=false -a -ldflags '-extldflags "-static"' -o "/bin/irma" ./irma

# Create application user
RUN adduser -D -u 1000 -g irma irma
Expand Down
8 changes: 4 additions & 4 deletions attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func NewTranslatedString(attr *string) TranslatedString {
}
}

func decodeRandomBlind(attr *big.Int) *string {
func DecodeRandomBlind(attr *big.Int) *string {
if attr == nil {
return nil
}
Expand All @@ -170,14 +170,14 @@ func decodeRandomBlind(attr *big.Int) *string {
func (al *AttributeList) decode(i int) *string {
attr := al.Ints[i+1]
if al.CredentialType().AttributeTypes[i].RandomBlind {
return decodeRandomBlind(attr)
return DecodeRandomBlind(attr)
}
metadataVersion := al.MetadataAttribute.Version()
return decodeAttribute(attr, metadataVersion)
return DecodeAttribute(attr, metadataVersion)
}

// Decode attribute value into string according to metadataVersion
func decodeAttribute(attr *big.Int, metadataVersion byte) *string {
func DecodeAttribute(attr *big.Int, metadataVersion byte) *string {
bi := new(big.Int).Set(attr)
if metadataVersion >= 3 {
if bi.Bit(0) == 0 { // attribute does not exist
Expand Down
39 changes: 34 additions & 5 deletions internal/sessiontest/helper_dosession_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ type (

requestorSessionResult struct {
*server.SessionResult
clientResult *SessionResult
Missing [][]irmaclient.DisclosureCandidates
Dismisser irmaclient.SessionDismisser
clientResult *SessionResult
clientResultExtended *server.SessionResultExtended
Missing [][]irmaclient.DisclosureCandidates
Dismisser irmaclient.SessionDismisser
}
)

Expand All @@ -52,6 +53,7 @@ const (
optionPrePairingClient
optionPolling
optionNoSchemeAssets
optionGetResultExtended
)

func processOptions(options ...option) option {
Expand Down Expand Up @@ -189,6 +191,28 @@ func getSessionResult(t *testing.T, sesPkg *server.SessionPackage, serv stopper,
}
}

// getSessionResult retrieves the session result from the IRMA server or library.
func getSessionResultExtended(t *testing.T, sesPkg *server.SessionPackage, serv stopper, opts option) *server.SessionResultExtended {
waitSessionFinished(t, serv, sesPkg.Token, opts.enabled(optionWait))

switch s := serv.(type) {
case *IrmaServer:
result, err := s.irma.GetSessionResultExtended(sesPkg.Token)
require.NoError(t, err)
return result
default:
var res string
err := irma.NewHTTPTransport(requestorServerURL+"/session/"+string(sesPkg.Token), false).Get("result-extended", &res)
require.NoError(t, err)

clientResultExtended := &server.SessionResultExtended{}
err = json.Unmarshal([]byte(res), clientResultExtended)
require.NoError(t, err)

return clientResultExtended
}
}

func createSessionHandler(
t *testing.T,
opts option,
Expand Down Expand Up @@ -299,7 +323,7 @@ func doSession(

if opts.enabled(optionUnsatisfiableRequest) && !opts.enabled(optionWait) {
require.NotNil(t, clientResult)
return &requestorSessionResult{nil, nil, clientResult.Missing, dismisser}
return &requestorSessionResult{nil, nil, nil, clientResult.Missing, dismisser}
}

serverResult := getSessionResult(t, sesPkg, serv, useJWTs, opts)
Expand All @@ -311,7 +335,12 @@ func doSession(
require.NoError(t, err)
}

return &requestorSessionResult{serverResult, clientResult, nil, dismisser}
if opts.enabled(optionGetResultExtended) {
clientResultExtended := getSessionResultExtended(t, sesPkg, serv, opts)
return &requestorSessionResult{serverResult, clientResult, clientResultExtended, nil, dismisser}
}

return &requestorSessionResult{serverResult, clientResult, nil, nil, dismisser}
}

func doChainedSessions(
Expand Down
55 changes: 55 additions & 0 deletions internal/sessiontest/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ import (

func TestRequestorServer(t *testing.T) {
t.Run("DisclosureSession", apply(testDisclosureSession, RequestorServerConfiguration))
t.Run("SessionExtendedResult", apply(testSessionExtendedResult, RequestorServerConfiguration))
t.Run("NoAttributeDisclosureSession", apply(testNoAttributeDisclosureSession, RequestorServerConfiguration))
t.Run("EmptyDisclosure", apply(testEmptyDisclosure, RequestorServerConfiguration))
t.Run("SigningSession", apply(testSigningSession, RequestorServerConfiguration))
t.Run("SigningSessionResult", apply(testSigningSessionResult, RequestorServerConfiguration))
t.Run("IssuanceSession", apply(testIssuanceSession, RequestorServerConfiguration))
t.Run("MultipleIssuanceSession", apply(testMultipleIssuanceSession, RequestorServerConfiguration))
t.Run("DefaultCredentialValidity", apply(testDefaultCredentialValidity, RequestorServerConfiguration))
Expand Down Expand Up @@ -77,9 +79,11 @@ func TestIrmaServer(t *testing.T) {

// Tests also run against the requestor server
t.Run("DisclosureSession", apply(testDisclosureSession, IrmaServerConfiguration))
t.Run("SessionExtendedResult", apply(testSessionExtendedResult, IrmaServerConfiguration))
t.Run("NoAttributeDisclosureSession", apply(testNoAttributeDisclosureSession, IrmaServerConfiguration))
t.Run("EmptyDisclosure", apply(testEmptyDisclosure, IrmaServerConfiguration))
t.Run("SigningSession", apply(testSigningSession, IrmaServerConfiguration))
t.Run("SigningSessionResult", apply(testSigningSessionResult, IrmaServerConfiguration))
t.Run("IssuanceSession", apply(testIssuanceSession, IrmaServerConfiguration))
t.Run("MultipleIssuanceSession", apply(testMultipleIssuanceSession, IrmaServerConfiguration))
t.Run("IssuancePairing", apply(testIssuancePairing, IrmaServerConfiguration))
Expand Down Expand Up @@ -600,6 +604,34 @@ func testSigningSession(t *testing.T, conf interface{}, opts ...option) {
require.Equal(t, irma.ProofStatusValid, status)
}

func testSigningSessionResult(t *testing.T, conf interface{}, opts ...option) {
client, handler := parseStorage(t, opts...)
defer test.ClearTestStorage(t, client, handler.storage)
id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")

request := getSigningRequest(id)
var requestorSessionResult *requestorSessionResult
for _, opt := range []option{0, optionRetryPost} {
requestorSessionResult = doSession(t, request, client, nil, nil, nil, conf, append(opts, opt, optionGetResultExtended)...)

require.Nil(t, requestorSessionResult.Err)
require.Equal(t, irma.ProofStatusValid, requestorSessionResult.ProofStatus)
require.NotEmpty(t, requestorSessionResult.Disclosed)
require.Equal(t, id, requestorSessionResult.Disclosed[0][0].Identifier)
require.Equal(t, "456", requestorSessionResult.Disclosed[0][0].Value["en"])

// Ensure requestor session results reflect the session result values
require.Equal(t, irma.ProofStatusValid, requestorSessionResult.clientResultExtended.ProofStatus)
require.Len(t, requestorSessionResult.clientResultExtended.Credentials, 1)
require.Len(t, requestorSessionResult.clientResultExtended.Credentials[0].Attributes, 1)
require.Equal(t, id.CredentialTypeIdentifier().IssuerIdentifier(), requestorSessionResult.clientResultExtended.Credentials[0].Issuer.Identifier)
require.Equal(t, id.CredentialTypeIdentifier().SchemeManagerIdentifier(), requestorSessionResult.clientResultExtended.Credentials[0].Scheme.Identifier)
require.Equal(t, id, requestorSessionResult.clientResultExtended.Credentials[0].Attributes[0].Identifier)
require.Equal(t, "456", *requestorSessionResult.clientResultExtended.Credentials[0].Attributes[0].Value)
require.Equal(t, request.Message, *requestorSessionResult.clientResultExtended.Request.Message)
}
}

func testDisclosureSession(t *testing.T, conf interface{}, opts ...option) {
id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
request := getDisclosureRequest(id)
Expand All @@ -613,6 +645,29 @@ func testDisclosureSession(t *testing.T, conf interface{}, opts ...option) {
}
}

func testSessionExtendedResult(t *testing.T, conf interface{}, opts ...option) {
id := irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID")
request := getDisclosureRequest(id)
for _, opt := range []option{0, optionRetryPost} {
requestorSessionResult := doSession(t, request, nil, nil, nil, nil, conf, append(opts, opt, optionGetResultExtended)...)
require.Nil(t, requestorSessionResult.Err)
require.Equal(t, irma.ProofStatusValid, requestorSessionResult.ProofStatus)
require.Len(t, requestorSessionResult.Disclosed, 1)
require.Equal(t, id, requestorSessionResult.Disclosed[0][0].Identifier)
require.Equal(t, "456", requestorSessionResult.Disclosed[0][0].Value["en"])

// Ensure requestor session results reflect the session result values
require.Equal(t, irma.ProofStatusValid, requestorSessionResult.clientResultExtended.ProofStatus)
require.Nil(t, requestorSessionResult.clientResultExtended.Timestamp)
require.Len(t, requestorSessionResult.clientResultExtended.Credentials, 1)
require.Len(t, requestorSessionResult.clientResultExtended.Credentials[0].Attributes, 1)
require.Equal(t, id.CredentialTypeIdentifier().IssuerIdentifier(), requestorSessionResult.clientResultExtended.Credentials[0].Issuer.Identifier)
require.Equal(t, id.CredentialTypeIdentifier().SchemeManagerIdentifier(), requestorSessionResult.clientResultExtended.Credentials[0].Scheme.Identifier)
require.Equal(t, id, requestorSessionResult.clientResultExtended.Credentials[0].Attributes[0].Identifier)
require.Equal(t, "456", *requestorSessionResult.clientResultExtended.Credentials[0].Attributes[0].Value)
}
}

func testDisclosureMultipleAttrs(t *testing.T, conf interface{}, opts ...option) {
request := irma.NewDisclosureRequest(
irma.NewAttributeTypeIdentifier("irma-demo.RU.studentCard.studentID"),
Expand Down
4 changes: 2 additions & 2 deletions irmago_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -402,11 +402,11 @@ func TestAttributeDecoding(t *testing.T) {
expected := "male"

newAttribute, _ := new(big.Int).SetString("3670202571", 10)
newString := decodeAttribute(newAttribute, 3)
newString := DecodeAttribute(newAttribute, 3)
require.Equal(t, *newString, expected)

oldAttribute, _ := new(big.Int).SetString("1835101285", 10)
oldString := decodeAttribute(oldAttribute, 2)
oldString := DecodeAttribute(oldAttribute, 2)
require.Equal(t, *oldString, expected)
}

Expand Down
1 change: 1 addition & 0 deletions requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type BaseRequest struct {

// Set by the IRMA server during the session
Context *big.Int `json:"context,omitempty"`
Identifier *big.Int `json:"identifier,omitempty"`
Nonce *big.Int `json:"nonce,omitempty"`
ProtocolVersion *ProtocolVersion `json:"protocolVersion,omitempty"`

Expand Down
52 changes: 52 additions & 0 deletions server/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ import (
"strings"
"time"

"github.com/privacybydesign/gabi/big"

"github.com/bwesterb/go-atum"
"github.com/go-chi/chi/v5/middleware"
"github.com/go-errors/errors"
"github.com/golang-jwt/jwt/v4"
"github.com/privacybydesign/gabi"
irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -47,6 +51,54 @@ type SessionResult struct {
LegacySession bool `json:"-"` // true if request was started with legacy (i.e. pre-condiscon) session request
}

// SessionResultExtended contains an extended array of generalized credentials with the zkp
type SessionResultExtended struct {
Token irma.RequestorToken `json:"token"`
Status irma.ServerStatus `json:"status"`
Type irma.Action `json:"type"`
ProofStatus irma.ProofStatus `json:"proofStatus"`
Request SessionResultExtendedRequest `json:"request"`
Nonce *big.Int `json:"nonce"` // Actual nonce used in proofs
Timestamp *atum.Timestamp `json:"timestamp,omitempty"` // If present, timestamp is included in nonce (see ASN1ConvertSignatureNonce)
Credentials []SessionResultExtendedCredential `json:"credentials"`
}

// ResultExtended request input
type SessionResultExtendedRequest struct {
Identifier *big.Int `json:"identifier"` // Identifier assigned by requestor
Nonce *big.Int `json:"nonce,omitempty"` // If present, this nonce is used as base nonce
Message *string `json:"message,omitempty"` // If present, message is included in nonce (see ASN1ConvertSignatureNonce)
}

// ResultExtended credential
type SessionResultExtendedCredential struct {
Identifier irma.CredentialTypeIdentifier `json:"identifier"`
Issuer SessionResultExtendedCredentialIssuer `json:"issuer"`
Scheme SessionResultExtendedCredentialScheme `json:"scheme"`
IssuedAt time.Time `json:"issuedAt"`
ExpiresAt time.Time `json:"expiresAt"`
Attributes []SessionResultExtendedCredentialAttribute `json:"attributes"`
Proof gabi.ProofD `json:"proof"`
}

// ResultExtended scheme information
type SessionResultExtendedCredentialScheme struct {
Identifier irma.SchemeManagerIdentifier `json:"identifier"`
DistributedKey bool `json:"distributedKey"` // If true, the scheme uses a keyshare server
}

// ResultExtended issuer information
type SessionResultExtendedCredentialIssuer struct {
Identifier irma.IssuerIdentifier `json:"identifier"`
}

// ResultExtended attribute information
type SessionResultExtendedCredentialAttribute struct {
Identifier irma.AttributeTypeIdentifier `json:"identifier"`
Status irma.AttributeProofStatus `json:"status"`
Value *string `json:"value"`
}

// SessionHandler is a function that can handle a session result
// once an IRMA session has completed.
type SessionHandler func(*SessionResult)
Expand Down
4 changes: 2 additions & 2 deletions server/irmac/irmac.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
"context"
"encoding/base64"
"encoding/json"
irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"io"
"net/http"
"net/http/httptest"
"strings"

irma "github.com/privacybydesign/irmago"
"github.com/privacybydesign/irmago/internal/common"
"github.com/privacybydesign/irmago/server"
"github.com/privacybydesign/irmago/server/irmaserver"
)
Expand Down
17 changes: 17 additions & 0 deletions server/irmaserver/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,23 @@ func (s *Server) startNextSession(
func GetSessionResult(requestorToken irma.RequestorToken) (*server.SessionResult, error) {
return s.GetSessionResult(requestorToken)
}

// GetSessionResultExtended retrieves the disclosure result of the specified IRMA session.
func GetSessionResultExtended(requestorToken irma.RequestorToken) (*server.SessionResultExtended, error) {
return s.GetSessionResultExtended(requestorToken)
}

func (s *Server) GetSessionResultExtended(requestorToken irma.RequestorToken) (res *server.SessionResultExtended, err error) {
session, err := s.sessions.get(requestorToken)
defer func() { err = updateAndUnlock(session, err) }()
if err != nil {
return
}

res = session.ResultExtended
return
}

func (s *Server) GetSessionResult(requestorToken irma.RequestorToken) (res *server.SessionResult, err error) {
session, err := s.sessions.get(requestorToken)
defer func() { err = updateAndUnlock(session, err) }()
Expand Down
Loading