nuts-foundation/nuts-auth

View on GitHub
pkg/services/irma/signer.go

Summary

Maintainability
A
0 mins
Test Coverage
/*
 * Nuts auth
 * Copyright (C) 2020. Nuts community
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package irma

import (
    "encoding/base64"
    "encoding/json"
    "fmt"
    "os"
    "strings"

    "github.com/mdp/qrterminal/v3"
    "github.com/nuts-foundation/nuts-auth/logging"
    "github.com/nuts-foundation/nuts-auth/pkg/contract"
    "github.com/nuts-foundation/nuts-auth/pkg/services"
    "github.com/pkg/errors"
    irmago "github.com/privacybydesign/irmago"
    "github.com/privacybydesign/irmago/server"
)

// SessionPtr should be made private when v0 is removed
type SessionPtr struct {
    ID         string
    QrCodeInfo irmago.Qr `json:"sessionPtr"`
}

// SessionID returns the SessionID of the SessionPtr
func (s SessionPtr) SessionID() string {
    return s.ID
}

// Payload renders the IrmaQRCode as json according to irmago.Qr
func (s SessionPtr) Payload() []byte {
    jsonResult, _ := json.Marshal(s.QrCodeInfo)
    return jsonResult
}

// MarshalJSON marshals a custom session pointer json object for the IRMA means.
func (s SessionPtr) MarshalJSON() ([]byte, error) {
    return json.Marshal(struct {
        QrCodeInfo irmago.Qr `json:"clientPtr"`
        ID         string    `json:"sessionID"`
    }{QrCodeInfo: s.QrCodeInfo, ID: s.ID})
}

// NutsIrmaSignedContract is the type of proof used in an Irma VP
const NutsIrmaSignedContract = "NutsIrmaSignedContract"

// StartSigningSession accepts a rawContractText and creates an IRMA signing session.
func (v Service) StartSigningSession(rawContractText string) (contract.SessionPointer, error) {
    // Put the template in an IRMA envelope
    signatureRequest := irmago.NewSignatureRequest(rawContractText)
    schemeManager := v.IrmaServiceConfig.IrmaSchemeManager

    c, err := contract.ParseContractString(rawContractText, v.ContractTemplates)
    if err != nil {
        return nil, err
    }

    var attributes irmago.AttributeCon
    for _, att := range c.Template.SignerAttributes {
        // Checks if attribute name start with a dot, if so, add the configured scheme manager.
        if strings.Index(att, ".") == 0 {
            att = fmt.Sprintf("%s%s", schemeManager, att)
        }
        attributes = append(attributes, irmago.NewAttributeRequest(att))
    }
    signatureRequest.Disclose = irmago.AttributeConDisCon{
        irmago.AttributeDisCon{
            attributes,
        },
    }

    // Start an IRMA session
    sessionPointer, token, err := v.IrmaSessionHandler.StartSession(signatureRequest, func(result *server.SessionResult) {
        logging.Log().Debugf("session done, result: %s", server.ToJson(result))
    })
    if err != nil {
        return nil, fmt.Errorf("error while creating session: %w", err)
    }
    logging.Log().Debugf("session created with token: %s", token)

    // Return the sessionPointer and sessionId
    challenge := SessionPtr{
        ID:         token,
        QrCodeInfo: *sessionPointer,
    }
    jsonResult := challenge.Payload()
    printQrCode(string(jsonResult))

    return challenge, nil
}

func (v Service) SigningSessionStatus(sessionID string) (contract.SigningSessionResult, error) {
    if result := v.IrmaSessionHandler.GetSessionResult(sessionID); result != nil {
        var (
            token string
        )
        if result.Signature != nil {
            c, err := contract.ParseContractString(result.Signature.Message, v.ContractTemplates)
            if err != nil {
                return nil, err
            }
            sic := &SignedIrmaContract{IrmaContract: *result.Signature, contract: c}

            le, err := v.legalEntityFromContract(sic)
            if err != nil {
                return nil, fmt.Errorf("could not create JWT for given session: %w", err)
            }

            token, err = v.CreateIdentityTokenFromIrmaContract(sic, le)
            if err != nil {
                return nil, err
            }
        }
        result := SigningSessionResult{SessionResult: *result, NutsAuthToken: token}
        logging.Log().Info(result.NutsAuthToken)
        return result, nil
    }
    return nil, services.ErrSessionNotFound
}

// SigningSessionResult implements the SigningSessionResult interface and contains the
// SigningSessionResult from the IRMA means.
type SigningSessionResult struct {
    server.SessionResult
    // NutsAuthToken contains the JWT if the sessionStatus is DONE
    NutsAuthToken string `json:"nuts_auth_token"`
}

// Status returns the IRMA signing status
func (s SigningSessionResult) Status() string {
    return string(s.SessionResult.Status)
}

// VerifiablePresentation returns an IRMA implementation of the contract.VerifiablePresentation interface.
func (s SigningSessionResult) VerifiablePresentation() (contract.VerifiablePresentation, error) {

    js, err := json.Marshal(s.Signature)
    if err != nil {
        return nil, errors.Wrap(err, "failed to create NutsIrmaPresentation")
    }
    b64 := base64.StdEncoding.EncodeToString(js)

    return VerifiablePresentation{
        VerifiablePresentationBase: contract.VerifiablePresentationBase{
            Context: []string{contract.VerifiableCredentialContext},
            Type:    []contract.VPType{contract.VerifiablePresentationType, VerifiablePresentationType},
        },
        Proof: VPProof{
            Proof: contract.Proof{
                Type: NutsIrmaSignedContract,
            },
            ProofValue: b64,
        },
    }, nil
}

func printQrCode(qrcode string) {
    config := qrterminal.Config{
        HalfBlocks: false,
        BlackChar:  qrterminal.WHITE,
        WhiteChar:  qrterminal.BLACK,
        Level:      qrterminal.M,
        Writer:     os.Stdout,
        QuietZone:  1,
    }
    qrterminal.GenerateWithConfig(qrcode, config)
}