Asymmetric RSA encryption with JS and InterSystems IRIS

Primary tabs

Asymmetric cryptography is a cryptographic system that uses pairs of keys: public keys which may be disseminated widely, and private keys which are known only to the owner. The generation of such keys depends on cryptographic algorithms based on mathematical problems to produce one-way functions. Effective security only requires keeping the private key private; the public key can be openly distributed without compromising security.

In such a system, any person can encrypt a message using the receiver's public key, but that encrypted message can only be decrypted with the receiver's private key.

Robust authentication is also possible. A sender can combine a message with a private key to create a short digital signature on the message. Anyone with the sender's corresponding public key can combine the same message and the supposed digital signature associated with it to verify whether the signature was valid, i.e. made by the owner of the corresponding private key. (C) Wikipedia.

In this article, I would demonstrate how asymmetric encryption can be used with InterSystems IRIS and JavaScript.

We will build the app with a delegated authentication, that would encode credentials on a JS client and verify them inside InterSystems IRIS. This is more of a showcase for delegated authentication and asymmetric encryption than a recommended approach - OAuth authorization is much more robust and recommended for production use.

First of all, let's store our keys:

Class REST.Keys
{

Parameter PUBLICKEY = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlOJu6TyygqxfWT7eLtGDwajtN
FOb9I5XRb6khyfD1Yt3YiCgQWMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76
xFxdU6jE0NQ+Z+zEdhUTooNRaY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4
gwQco1KRMDSmXSMkDwIDAQAB
-----END PUBLIC KEY-----";

Parameter PRIVATEKEY = "-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDlOJu6TyygqxfWT7eLtGDwajtNFOb9I5XRb6khyfD1Yt3YiCgQ
WMNW649887VGJiGr/L5i2osbl8C9+WJTeucF+S76xFxdU6jE0NQ+Z+zEdhUTooNR
aY5nZiu5PgDB0ED/ZKBUSLKL7eibMxZtMlUDHjm4gwQco1KRMDSmXSMkDwIDAQAB
AoGAfY9LpnuWK5Bs50UVep5c93SJdUi82u7yMx4iHFMc/Z2hfenfYEzu+57fI4fv
xTQ//5DbzRR/XKb8ulNv6+CHyPF31xk7YOBfkGI8qjLoq06V+FyBfDSwL8KbLyeH
m7KUZnLNQbk8yGLzB3iYKkRHlmUanQGaNMIJziWOkN+N9dECQQD0ONYRNZeuM8zd
8XJTSdcIX4a3gy3GGCJxOzv16XHxD03GW6UNLmfPwenKu+cdrQeaqEixrCejXdAF
z/7+BSMpAkEA8EaSOeP5Xr3ZrbiKzi6TGMwHMvC7HdJxaBJbVRfApFrE0/mPwmP5
rN7QwjrMY+0+AbXcm8mRQyQ1+IGEembsdwJBAN6az8Rv7QnD/YBvi52POIlRSSIM
V7SwWvSK4WSMnGb1ZBbhgdg57DXaspcwHsFV7hByQ5BvMtIduHcT14ECfcECQATe
aTgjFnqE/lQ22Rk0eGaYO80cc643BXVGafNfd9fcvwBMnk0iGX0XRsOozVt5Azil
psLBYuApa66NcVHJpCECQQDTjI2AQhFc1yRnCU/YgDnSpJVm1nASoRUnU8Jfm3Oz
uku7JUXcVpt08DFSceCEX9unCuMcT72rAQlLpdZir876
-----END RSA PRIVATE KEY-----";

}

Use Managed Key Infrastructure to store keys in production systems.

Now let's go for REST brokers, we need two:

  • Public, to serve our public key
  • Private for authorization

Abstract broker:

Class REST.Abstract Extends %CSP.REST
{

Parameter UseSession As BOOLEAN = 1;

Parameter HandleCorsRequest = 1;

Parameter CHARSET = "UTF8";

}

Public broker (published as /auth/public REST app with unauthorized access):

Class REST.Public Extends REST.Abstract
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/key" Method="GET" Call="GetKey"/>
</Routes>
}

ClassMethod GetKey()
{
    #dim sc As %Status = $$$OK
    write ##class(REST.Keys).#PUBLICKEY
    quit sc
}

}

And private broker (published as /auth/private REST app with Password and Delegated access):

Class REST.Private Extends REST.Abstract
{

XData UrlMap [ XMLNamespace = "http://www.intersystems.com/urlmap" ]
{
<Routes>
<Route Url="/random" Method="GET" Call="GetRandom"/>
</Routes>
}

ClassMethod GetRandom()
{
    #dim sc As %Status = $$$OK
    write $random(100)
    quit sc
}

}

That's it. /random is a protected call.

 

And here is our ZUAUTHENTICATE routine, it accepts authorization in an XAUTH header, where value is RSA encrypted <user>$c(10)<pass>$c(10)<timestamp>.

ZAUTHENTICATE(ServiceName,Namespace,Username,Password,Credentials,Properties) PUBLIC {
    #include %occStatus
    quit $$$OK
}

GetCredentials(ServiceName,Namespace,Username,Password,Credentials) Public {
    #include %occErrors
    #include %occStatus
    
    #dim separator As %Char = $c(10)
    #dim %request As %CSP.Request

    set ciphertext = %request.GetCgiEnv("HTTP_XAUTH")

    quit:ciphertext="" $$$ERROR($$$GeneralError, "No XAuth header")

    set text = $SYSTEM.Encryption.RSADecrypt($system.Encryption.Base64Decode(ciphertext), ##class(REST.Keys).#PRIVATEKEY,"",2)

    quit:text="" $$$ERROR($$$GeneralError, "Unable to decrypt")
    quit:$l(text, separator)'=3 $$$ERROR($$$GeneralError, "Wrong plaintext structure")
    
    set time = $p(text, separator, 3)
    
    set limit = 10
    set diff = $system.SQL.DATEDIFF("s", $tr(time,"TZ", "  "), $SYSTEM.Util.LocalWithZTIMEZONEtoUTC($H))
    quit:diff>limit $$$ERROR($$$GeneralError, "Old request:" _ diff)
    
    set Username = $p(text, separator, 1)
    set Password = $p(text, separator, 2)
        
    quit $$$OK
 }

Note that the REST package must be mapped to %SYS namespace.

Now for a client. We're using the JSEncrypt library for RSA encryption and sending two requests - first to get the key and second (authorized request) to get a random value from our private broker.

<!doctype html>
<html>
  <head>
    <title>InterSystems IRIS - JavaScript RSA Encryption</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jsencrypt/2.3.1/jsencrypt.min.js"></script>
    <script type="text/javascript">
    
    function getKey()
    {
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.onreadystatechange = function() {
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
                document.getElementById('pubkey').value = xmlHttp.responseText;
        }
        var url = '/auth/public/key';
        xmlHttp.open("GET", url, true);
        xmlHttp.send(null);
    };
    
    function doAuth() {
        // Encrypt with the public key...
        var encrypt = new JSEncrypt();
        encrypt.setPublicKey(document.getElementById('pubkey').value);
        var encrypted = encrypt.encrypt(document.getElementById('user').value + "\n" + document.getElementById('pass').value + "\n" + new Date().toISOString());

        var xmlHttp = new XMLHttpRequest();
        
        var url = '/auth/private/random';
        xmlHttp.onreadystatechange = function() {
            if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
                document.getElementById('randomVal').innerHTML = xmlHttp.responseText;
        }
        xmlHttp.open("GET", url, true);
        xmlHttp.setRequestHeader("XAuth", encrypted)
        xmlHttp.send(null);
    }
    
    
    </script>
  </head>
  <body>
    <label for="pubkey">Public Key</label><br/>
    <textarea id="pubkey" rows="15" cols="65"></textarea><input type="button" value="Get Key" onclick="getKey();" /><br/>

    User:<br>
    <input id="user" type="text" name="user" value="_SYSTEM"><br>
    Pass:<br>
    <input id="pass"  type="password" name="pass" value="SYS"><br><br>
    <input type="button" value="Auth" onclick="doAuth();" />

    <br/><br/>
    <label>Random: </label><label id="randomVal"></label><br/>
    
  </body>
</html>

With this, we successfully encrypted our plaintext on a client and decrypted it with InterSystems IRIS.

Get the code here.