lucian.pantelimon
lucian.pantelimon

Reputation: 3669

Scalatra response HMAC calulation

I'm developing a web service using Scalatra and I want to use HMAC for bidirectional authentication.

So far, I've implemented the client authentication to the server: the client (an Android app) calculates a HMAC/SHA512 for each request using these parameters: a shared secret, the HTTP method, URL, some headers (timestamp, clientId etc) and the request body (if it's a POST or a PUT). This HMAC is then added to a specific header and the request is sent to the server (which validates the HMAC in the request header with a HMAC that it calculates the same as the client).

Now, I want to do the opposite thing: have the server authenticate to the client using the stored shared secret, the request HTTP method, URL and the response body.

So far, I've found that I can override renderResponse(actionResult: Any), renderResponseBody(actionResult: Any) or even renderPipeline and I've decided to go with overriding renderPipeline as it seems to be the easiest to handle.

In my overriden renderPipeline I transform the response body to a byte array (loading the served File in memory if serving a File), calculate the HMAC and add it to the response headers.

What I want to know is: are there cases when overriding renderPipeline this way would break either the authentication functionality presented above (like renderPipeline not being called or being called multiple times or the headers having been sent befor renderPipeline is called to render the body) or some other functionality in Scalatra?

As a note I do not calculate the HMAC when the action returns Unit and the response output is being written directly by the action.

Upvotes: 1

Views: 224

Answers (1)

mrnovalles
mrnovalles

Reputation: 353

I had exactly the same problem to solve. I used a trait extending Handler, just like it's done in the GZipSupport.scala and using this answer as a reference implementation.

I built a ServletOutputStreamCopier which holds a copy of the original OutputStream and every byte to both streams:

class ServletOutputStreamCopier(orig: ServletOutputStream) extends ServletOutputStream {
    val copy: ByteArrayOutputStream = new ByteArrayOutputStream(1024)

    override def write(b: Int): Unit = {
      orig.write(b)
      copy.write(b)
    }
    override def setWriteListener(writeListener: WriteListener): Unit = orig.setWriteListener(writeListener)

    override def isReady: Boolean = orig.isReady

    def getCopy: Array[Byte] = copy.toByteArray
}

Then a ResponseCopier, which is an HttpServletResponseWrapper with the previously defined ServletOutputStreamCopier and exposes the copy to the outside:

class ResponseCopier(res: HttpServletResponse, sos:   ServletOutputStreamCopier, w: PrintWriter) extends HttpServletResponseWrapper(res) {
    override def getOutputStream: ServletOutputStream = new ServletOutputStreamCopier(sos)

    override def getWriter: PrintWriter = w

    override def setContentLength(i: Int) = {}

    def getCopy: Array[Byte] = sos.getCopy
}

Finally the handle method takes care of adding the header after the Scalatra action was completed by using the callback ScalatraBase.onRenderedComplete.

trait SignedResponseSupport extends Handler {
  self: ScalatraBase =>

  abstract override def handle(req: HttpServletRequest, res: HttpServletResponse): Unit = {
    withRequestResponse(req, res) {
      val sosc = new ServletOutputStreamCopier(res.getOutputStream)
      val w = new PrintWriter(sosc)
      val wrapped = new ResponseCopier(response,sosc ,w)

      ScalatraBase.onRenderedCompleted { _ =>
        w.flush()
        w.close()
        val password = "secret-password"
        val signature = signResponseBody(wrapped.getCopy, password)
        wrapped.addHeader("X-Response-Signature", signature)
        }
      }
      super.handle(req, wrapped)
    }

  def signResponseBody(body: Array[Byte], password: String): String = {
    /*signing goes here*/
  }

}

Upvotes: 1

Related Questions