Environment setup¶
As described in the Proprietary authentication scheme chapter, the relationship between the in-browser JavaScript code and the SCWS service must be established using a specific Web Application Certificate provided by Idopte, and a challenge-response mutual authentication scheme. The functions decribed below must be called before any other API methods to achieve this.
Client-side¶
- static SCWS.findService(webAppCert, challenge)¶
Attempts to contact the local SCWS service.
The ports on which the service can potentially be listening are scanned, and the first from which a consistent answer can be obtained is selected. In order to authentify the local SCWS service, a random challenge should be given as a parameter. The local service will then sign it with its private key, and return the corresponding cryptogram that must be used to verify signature, using the corresponding Idopte public key. When successful, the SCWS returns a JavaScript object containing:
challenge
: an hexadecimal string containing the random challenge that must be used to compute the remote server signature, prior to callingSCWS.createEnvironment()
.cryptogram
: an hexadecimal string containing the cryptogram to verify (the challenge given as input signed by SCWS local service private key).keyID
: an integer corresponding to the index of the public key to use among the keys in the IdoptePublicKeys file (provided by Idopte).
- Arguments:
webAppCert – A character string identifying the web application server allowed to connect to the SCWS. The string is provided by Idopte and embeds the allowed origin URL and the public key used to check the server signature.
challenge – An hexadecimal string corresponding to the challenge to be signed by SCWS local service.
- Returns:
A
Promise
resolving to a JavaScript object as described above.
- static SCWS.createEnvironment(signature)¶
Creates the working environment on the SCWS side. Required before any operation.
Internally calls
SCWS.updateReaderList()
(thus intializing theSCWS.readers
array), and starts monitoring smart card reader events.- Arguments:
signature – Hexadecimal string containing the signature of the challenge returned from
SCWS.findService()
.
- Returns:
A
Promise
resolved when the environment is ready (the resolution value is undefined).
Below is an example usage of these functions, with the call to specific server-side generatechallenge``and ``verifycryptogramsignchallenge
scripts to generate a challenge, verify the cryptogram returned back and produce the required signature of challenge sent by local service:
Caution
Note that cryptogram verification and challenge signature are done in a unique script, which does not insure compatibility with middleware/SCWS API versions prior to 6.23.25.0 and 2.11, respectively. If you wish to insure compatibility with previous versions and do not need to use high level SCWS entry points (requestCertificates/requestPrivateKey and SignEncrypt entry points), you should use the old method for environment creation (one-sided authentication instead of mutual). You can find sample codes here: Old environment setup code samples. If you do not need to insure compatibility and you wish to use the newest SCWS functions, you should use sample codes given below.
var webappcert = "4HfLHzMS05>]T+PeNPw<X/qE2d+7^Fi4*tXK5XUSpj-5F?v220vO$U&V?<[}t1$T{SgWs]g1wFx0t^wKX88E=tBG:>Q=?r-f-mJ4nre!!s7cqOJ0/Os@Kw?HHaS>ewC+WPnDEbhxh:6MUtZIt0+D^Wa2eO?(&l.>A0MDw!JF2K0[8TM{W^[FLaq?oRb{WDRmEmo#oEoV3e<fhfH[+Cl7y)FJF0fMO28[.s=0JC@3ymR*]Zxq!{^$KgsBYg/7Ad.EHJiiea.!K@G&:H=1&T{K</+P7k]^-6GnEr*GM=?6{8Sk&w.VduRhSE!kr)QHYv$yi";
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("Challenge generation failed"));
}
};
req.open("GET", "generatechallenge", true);
req.send();
}).then(function(challenge){
SCWS.findService(webappcert, challenge).then(function(findServiceData) {
log("Connection to SCWS succeeded");
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("Cryptogram verification failed"));
}
};
req.open("GET", "verifycryptogramsignchallenge?keyID=" + findServiceData.keyID + "&cryptogram=" + findServiceData.cryptogram + "&rnd=" + findServiceData.challenge, true);
req.send();
}).then(function(signature) {
return SCWS.createEnvironment(signature);
});
});
});
This will return a Promise
resolved once the entire environment setup operation is done.
Environments can be explicitly destroyed using the following function:
- static SCWS.destroyEnvironment()¶
Destroys the environment created by
SCWS.createEnvironment()
.All readers, tokens, objects obtained through this environment are invalidated, and any subsequent method call or property access on these objects may trigger undefined behavior. After the environment is destroyed, a new environment may be created again using
SCWS.findService()
andSCWS.createEnvironment()
.Note that web applications are not mandated to destroy the environments. Environments are maintained alive within the underlying service through the event mechanism. If the web application is closed for some reason, this will be seen by the underlying service within a few minutes, and all the resources used by the environment will be automatically released.
The status of the underlying service can optionally be monitored by providing the following callback function:
- SCWS.onserviceunresponsive¶
Callback (can be assigned to a user function) used to receive notifications when the underlying service becomes unresponsive.
This may happen when the Smart Card Manager application is closed by the user (through the menu from the system tray icon).
The web application can eventually use this event to notify the user and ask him to restart the Smart Card Manager manually. The web application can attemp reconnecting to the service by calling
SCWS.findService()
andSCWS.createEnvironment()
again.
Server-side¶
The web application server must first produce a random challenge which will be signed by local service through SCWS.findService()
, and verify the cryptogram returned by the local service. Then, the web application server must be able to produce the signature from the challenge generated by the SCWS. This can be achieved with three scripts on the server side. Below are PHP and Java implementation examples of generatechallenge
, verifycryptogam
and signchallenge
scripts as required by the above client-side code.
Note that the Java sample code in this chapter assumes a REST application based on the Spring Boot framework, and requires the Bouncy Castle API to work (used for PEM parsing).
generatechallenge.php
:
<?php
session_start();
$challenge = random_bytes(16);
$_SESSION['challenge'] = $challenge;
echo bin2hex($challenge);
?>
generatechallenge.java
:
@GetMapping("/generatechallenge")
public String generateChallenge(HttpServletRequest request) {
SecureRandom random = new SecureRandom();
byte[] challenge = new byte[16];
random.nextBytes(challenge);
String challengeHex = Hex.toHexString(challenge);
HttpSession session = request.getSession();
session.setAttribute("challenge", challengeHex);
return challengeHex;
}
verifycryptogamsignchallenge.php
:
<?php
session_start();
$pubKeyID = $_GET["keyID"];
$pubKeysStr = file_get_contents("IdoptePublicKeys");
preg_match_all('/-----BEGIN PUBLIC KEY-----(.|\n)*?-----END PUBLIC KEY-----/', $pubKeysStr, $pubKeys);
$pubKeys = $pubKeys[0];
$pubKeysLen = count($pubKeys);
if ($pubKeyID >= $pubKeysLen) {
http_response_code(400);
}
else {
$k = $pubKeys[$pubKeyID];
if (str_contains($k, "!REVOKED!")) {
http_response_code(400);
}
else {
$challenge = $_SESSION['challenge'];
$output = "";
openssl_public_decrypt(hex2bin($_GET["cryptogram"]), $output, openssl_pkey_get_public($k));
if ($output == $challenge) {
$k = <<<EOT
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAMPMNNpbZZddeT/GTjU0PWuuN9VEGpxXJTAkmZY02o8238fQ2ynt
N40FVl08YksWBO/74XEjU30mAjuaz/FB2kkCAwEAAQJBALoMlsROSLCWD5q8EqCX
rS1e9IrgFfEtFZczkAWc33lo3FnFeFTXSMVCloNCBWU35od4zTOhdRPAWpQ1Mzxi
aCkCIQD9qjKjNvbDXjUcCNqdiJxPDlPGpa78yzyCCUA/+TNwVwIhAMWZoqZO3eWq
SCBTLelVQsg6CwJh9W7vlezvWxUni+ZfAiAopBAg3jmC66EOsMx12OFSOTVq6jiy
/8zd+KV2mnKHWQIgVpZiLZo1piQeAvwwDCUuZGr61Ap08C3QdsjUEssHhOUCIBee
72JZuJeABcv7lHhAWzsiCddVAkdnZKUo6ubaxw3u
-----END RSA PRIVATE KEY-----
EOT;
$output = "";
openssl_private_encrypt(hex2bin($_GET["rnd"]), $output, openssl_pkey_get_private($k));
echo bin2hex($output);
}
else {
http_response_code(500);
}
}
}
?>
verifycryptogamsignchallenge.java
:
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-----";
@GetMapping("/verifycryptogramsignchallenge")
public String verifyCryptogramSignChallenge(Integer keyID, String cryptogram, String rnd, HttpServletRequest request) {
try {
// Parse IdoptePublicKeys file and retrieve the public key to use for verification
String pubKeysStr = new String(Files.readAllBytes(Paths.get("IdoptePublicKeys")));
Pattern pattern = Pattern.compile("-----BEGIN PUBLIC KEY-----(.|\\n|\\r)*?-----END PUBLIC KEY-----");
Matcher matcher = pattern.matcher(pubKeysStr);
List<String> matches = new ArrayList<>();
while (matcher.find()) {
matches.add(matcher.group());
}
if (keyID >= matches.size()) {
throw new Exception("Invalid key ID");
}
String publicIdoKeyPEM = matches.get(keyID);
if (publicIdoKeyPEM.contains("!REVOKED!")) {
throw new Exception("Public key revoked");
}
// Prepare public key
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PemReader reader = new PemReader(new StringReader(publicIdoKeyPEM));
byte[] pubKey = reader.readPemObject().getContent();
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pubKey);
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
// Retrieve challenge generated in generateChallenge stored in session variable
HttpSession session = request.getSession();
String challengeHex = session.getAttribute("challenge").toString();
// Verify the cryptogram
Signature verifier = Signature.getInstance("NONEwithRSA");
verifier.initVerify(publicKey);
verifier.update(Hex.decode(challengeHex));
boolean verification = verifier.verify(Hex.decode(cryptogram));
if (!verification) {
throw new Exception("Cryptogram verification failed");
}
else {
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);
}
}
For this script to work, the IdoptePublicKeys
file provided by Idopte is required. The file format is described in the Proprietary authentication scheme chapter. Note that the file may evolve, but it should always be parsed in the way it is done in this sample code.
The key included in these scripts represents the Proprietary authentication scheme key generated on the customer’s side, whose public key is provided to Idopte when requesting the Web Application Certificate.
Caution
Note that above scripts do not check the origin of the request. This is volontary, as ensuring that the request effectively comes from a valid client is the responsibility of the web application developer, and depends on the architecture of the entire application. However, not checking the origin of the request before producing or verifying a signature is a security weakness and should be avoided. Standard methods such as cookies, or session mechanisms, can be used to solve this requirement.