Sample codeΒΆ

The sample code in this chapter assumes a REST application based on the Spring Boot framework.

The application follows the sequence diagram shown in the introduction chapter. It consists of four main REST entry points on the server controller:

  • the sign_env_challenge entry point that takes care of the SCWS environment creation (prerequisite for allowing the application to contact the SCWS)

  • the init_session entry point that simply instantiates a SCSSSession object, not yet bound to a specific client

  • the process_session entry point that takes care of the APDU command relaying mechanism

  • the exec_session entry point that proceed with the operation that must be actually made on the token, from the server side (this entry point contains the application-specific logic).

@RestController
public class ProcController implements ExternalAuthenticateHandler, MutualAuthenticateHandler {

        /* This is the private key for SCWS environment creation
        * (should correspond to the public key for which the webappcert has been generated)
        * Note: the webappcert is in the javascript code */
        private static final String privateKeyPEM =
                        "-----BEGIN RSA PRIVATE KEY-----\r\n" +
                        "MIIBOgIBAAJBAMPMNNpbZZddeT/GTjU0PWuuN9VEGpxXJTAkmZY02o8238fQ2ynt\r\n" +
                        "N40FVl08YksWBO/74XEjU30mAjuaz/FB2kkCAwEAAQJBALoMlsROSLCWD5q8EqCX\r\n" +
                        "rS1e9IrgFfEtFZczkAWc33lo3FnFeFTXSMVCloNCBWU35od4zTOhdRPAWpQ1Mzxi\r\n" +
                        "aCkCIQD9qjKjNvbDXjUcCNqdiJxPDlPGpa78yzyCCUA/+TNwVwIhAMWZoqZO3eWq\r\n" +
                        "SCBTLelVQsg6CwJh9W7vlezvWxUni+ZfAiAopBAg3jmC66EOsMx12OFSOTVq6jiy\r\n" +
                        "/8zd+KV2mnKHWQIgVpZiLZo1piQeAvwwDCUuZGr61Ap08C3QdsjUEssHhOUCIBee\r\n" +
                        "72JZuJeABcv7lHhAWzsiCddVAkdnZKUo6ubaxw3u\r\n" +
                        "-----END RSA PRIVATE KEY-----";

        /**
        * Entry point for generating the signature, for the initial environment creation.
        * (called by the client between SCWS.findService and SCWS.createEnvironment).
        * @param rnd the challenge to sign (coming from the client-side SCWS).
        * @return the cryptogram that will authorize environment creation.
        */
        @GetMapping("/sign_env_challenge")
        public String signEnvChallenge(String rnd) {
                try {
                        PrivateKey privateKey;
                        try (PEMParser pemParser = new PEMParser(new StringReader(privateKeyPEM))) {
                                JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
                                Object object = pemParser.readObject();
                                KeyPair kp = converter.getKeyPair((PEMKeyPair)object);
                                privateKey = kp.getPrivate();
                        }
                        Signature signer = Signature.getInstance("NONEwithRSA","BC");
                        signer.initSign(privateKey);
                        signer.update(Hex.decode(rnd));
                        byte[] signature = signer.sign();
                        return Hex.toHexString(signature);
                } catch (Exception e) {
                        e.printStackTrace();
                        throw new RuntimeException(e.getMessage(), e);
                }
        }

        /**
        * Create a {@link SCSSSession} object.
        * @return the handle of created {@link SCSSSession} object.
        * @throws SCMException
        * @throws IOException
        */
        @GetMapping("/init_session")
        public String startTest() throws SCMException, IOException {
                final SCSSSession s = new SCSSSession();
                return s.getHandle();
        }

        /**
        * Process session and allow communication between ServerSide SmartCard and ClientSide SmartCard middlewares.
        * @param id the handle of the {@link SCSSSession} object to communicate with.
        * @param body the body of HTTP request
        * @return the result of processed session.
        * @throws SCMException
        * @throws IOException
        */
        @PostMapping("/process_session/{id}")
        public String processSession(@PathVariable String id, @RequestBody String body) throws SCMException, IOException {
                return SCSSSession.process(id, body);
        }

        /**
        * Execute session. Once {@link SCSSSession} object is created and session is managed with {@link SCSSSession#process}, call this to use methods and attributes from
        * {@link Token} object.
        * @param id the handle of {@link SCSSSession} object.
        * @return some result
        * @throws SCMException
        * @throws IOException
        */
        @GetMapping("/exec_session/{id}")
        public String execSession(@PathVariable String id) throws SCMException, IOException {
                SCSSSession s = SCSSSession.getSession(id);

                try {
                        s.init();

                        /* -------------------------------------------------------------------- */
                        // here lies the server-side application logic.
                        // Operations are made by calling Token methods on the `s` object.
                        /* -------------------------------------------------------------------- */

                }
                catch (Exception e){
                        return "Operation failed";
                }
                finally {
                        s.close();
                }
                return "Operation OK";
        }

}

On the client, most of the logic is typically dedicated to establishing the connection with the SCWS and obtaining a Token instance from the reader (this process is done in the init() function below). The interesting bit is located at the end of the sample, in the test() function: this opens the server-side session.

window.webappcert = "4SyF8E6?J4{SVYI)psu*Xb0t#E=1oJdCH}B[Y8){oDbd%GP-L&ZI*KtJl@P?<:t$Tvv[NPS9>6Mqkpnmg^O1Zkn[Zo#czsNjEe:ASWVef!l-I$=e02=*YAmCkmvm1iifG])b[EFTe2>-:Hmk.KPTX9S0CQPVV41h3>S0>QyDEq/Bb?u#Zc^i7v]JDE@jgy@*bbc=59z!woxg6nRpqTxix&o8>5J8D6lzoLkuw=Q8MreUlGfHfBt(&q8!8Eh>BBL.6A)S.&IBkyKcR%$V@Zb$.!0aJjM1<iEMzoN)2!j^GtkU:d>A/Rx=u*@Q7W6$kk[zM";

window.connections = [];
window.connecting = false;

/* boilerplate function for performing a ajax request */
function req(method, url, contents) {
        return new Promise(function(resolve, reject) {
                var req = new XMLHttpRequest();
                req.onreadystatechange = function () {
                        if (this.readyState === 4) {
                                if (this.status === 200)
                                        resolve(req.responseText);
                                else
                                        reject(new Error("App server request failed"));
                        }
                };
                req.open(method, url, true);
                req.send(contents);
        });
}

function reqGet(url) {
        return req("GET", url);
}

function reqPost(url, contents) {
        return req("POST", url, contents);
}

/* connects to the SCWS, then tries to find smart cards in readers and connect to them */
function init() {
        /* connecting to SCWS */
        console.log("Connecting to SCWS...");
        SCWS.findService(window.webappcert).then(function(challenge) {
                console.log("Connection to SCWS succeeded");
                return reqGet("sign_env_challenge?rnd=" + challenge)
                .then(function(signature) {
                        return SCWS.createEnvironment(signature);
                });
        }).then(function() {
                console.log("SCWS environment created successfully");
                /* check all readers already connected */
                var readers = SCWS.readers;
                if (readers.length == 0) {
                        console.log("No readers found");
                }
                else {
                        for (var i = 0; i < readers.length; i++)
                                checkReader(readers[i]);
                }
                document.getElementById("initbtn").style.display = "none";
                updateStatus();
                /* register event callbacks for checking readers changes */
                SCWS.onreaderadded = function(reader) {
                        console.log("Reader inserted: '" + reader.name + "'");
                        checkReader(reader);
                }
                SCWS.onreaderremoved = function(reader) {
                        console.log("Reader removed: '" + reader.name + "'");
                        checkReader(reader);
                }
                SCWS.onreaderstatechanged = function(reader) {
                        console.log("Reader state changed: '" + reader.name + "'");
                        checkReader(reader);
                }
        }).catch(function(err) {
                console.log("ERROR: " + err.message);
        });
}

function updateStatus()
{
        // update GUI elements to show connection state
}

/**
* Called when a reader needs to be scanned for objects.
*/
function checkReader(reader) {
        /* trying to find reader from existing connections */
        for (var connidx = 0; connidx < window.connections.length; connidx++) {
                if (window.connections[connidx].reader == reader)
                        break;
        }
        if (connidx == window.connections.length) {
                if (reader.status == "ok" && reader.cardPresent) {
                        /* reader not found in existing connections, and reader contains card: connecting */
                        console.log("Connecting to reader '" + reader.name + "'...");
                        var connection = { reader: reader };
                        window.connections.push(connection);
                        window.connecting = true;
                        updateStatus();
                        reader.connect().then(function(token) {
                                connection.token = token;
                                connection.items = [];
                                console.log("Getting objects from reader '" + reader.name + "'...");
                                return token.getObjects();
                        }).then(function(items) {
                                connection.items = items;
                                window.connecting = false;
                                updateStatus();
                        }).catch(function(err) {
                                console.log("ERROR: " + err.message);
                                window.connecting = false;
                                updateStatus();
                        });
                }
        }
        else {
                var conn = connections[connidx];
                if (reader.status != "ok" || !reader.cardPresent) {
                        /* reader found in existing connections, but contains no card: removing */
                        if (conn.token) {
                                console.log("Disconnecting from reader '" + reader.name + "'.");
                                conn.token.disconnect();
                        }
                        window.connections.splice(connidx, 1);
                        updateStatus();
                }
        }
}

function showError(err) {
        document.getElementById("errmsg").textContent = err.message || err;
}

/* Function that actually opens the server session */
function test() {
        /* first, create the SCSSSession instance on the server */
        reqGet("init_session").then(function(sessHandle) {
                /* process the session (session handle is used in the URL) */
                window.connections[0].token.processServerSession("process_session/" + sessHandle).then(function() {
                        console.log("OK");
                        /* at that point, the session is fully completed */
                }, function() {
                        console.log("error");
                });
                /* concurrently with the processServerSession call, the REST entry point that actually performs the operation is called, also giving the session handle */
                reqGet("exec_session/" + sessHandle).then(function(result) {
                        console.log(result);
                        /* although the REST operation is completed, the session may not be yet finished at that point */
                });
        });
}