Application SecurityCode Fighters

Veracode API: Getting things done with AWS Lambda and AWS API Gateway

Every day at Conviso both dev and sre teams are working together facing challenges to make Conviso Platform a more complete solution.

You can also listen to the audio version of this article:

This time we needed to integrate with Veracode, a well-known application security platform, in order to extract scan results via API and centralize them within Conviso Platform.

At first glance, it was not a hard task, as we knew that Veracode provided a REST API to do the job, nevertheless, it was not as simple as we expected IT to be.

Due to this year’s update in Veracode API authentication mechanism, there was no more Basic-Auth working to communicate with the API. The only way was using HMAC Authentication header that also did not seem to be a big challenge for us as we have worked with it in the past.

Our main problem was that the libraries that Veracode suggested to be used, were not compatible with our backend language, and therefore it wasn’t the solution we were seeking to use in production.

Having to install new dependencies was not a path that we wanted to take.

However, the major challenge was creating the HMAC Authentication header without the need to install dependencies. After digging a bit on Google, we found a collection of open-source scripts listed on Veracode’s Github official account.

We found out one that happened to be compatible with our backend language that at first sight seemed to be a problem-solver, but after doing some testing it was generating a faulty HMAC authentication header, hence, no successful communication with Veracode API.

After some more investigation, we’ve ended up finding a Javascript code that thankfully was generating a valid HMAC Authentication header, just what we needed.

Having solved the first problem we had to find a way to use it in our environment.

As we work with AWS daily, we decided to create a Lambda function and create an endpoint with the API Gateway service, so we can later call it from our application.

For those who are not familiar with these services, here you can read about them:

Bear in mind that the lambda function was created in Node.js and can be triggered by creating an endpoint through AWS API Gateway which you can find in the following link:

/*
---------------------------------------
Author: Daniel Arenas
Company: Conviso Application Security
Last Update: 01/11/2019 
Version: 1.0
Based on: https://gist.githubusercontent.com/mrpinghe/f44479f2270ea36bf3b7cc958cc76cc0
Sample Event:
Findings API
{
  "body": {
    "api_id": "API_ID_HERE",
    "api_key": "API_KEY_HERE",
    "request_type": "GET",
    "host": "api.veracode.com",
    "endpoint": "/appsec/v1/applications"
  }
}
XML API
{
  "body": {
    "api_id": "API_ID_HERE",
    "api_key": "API_KEY_HERE",
    "request_type": "GET",
    "host": "analysiscenter.veracode.com",
    "endpoint": "/api/5.0/getapplist.do"
  }
}
With Params:
{
  "body": {
    "api_id": "API_ID_HERE",
    "api_key": "API_KEY_HERE",
    "request_type": "GET",
    "host": "analysiscenter.veracode.com",
    "endpoint": "/api/5.0/detailedreport.do?build_id=X"
  }
}
---------------------------------------
*/

const crypto = require('crypto');
const http = require('https');

const preFix = "VERACODE-HMAC-SHA-256";
const verStr = "vcode_request_version_1";

exports.handler = async (event, context, callback) => {
    
    var requestBody = event['body'];
    
    if(checkJSON(requestBody))
        requestBody = JSON.parse(requestBody);
    
    const id = requestBody['api_id'];
    const key = requestBody['api_key'];
    const endpoint = requestBody['endpoint'];
    const http_method = requestBody['request_type'];
    const host = requestBody['host'];
    
    return new Promise((resolve, reject) => {
      generateHeader(host, endpoint, http_method, id, key)
      .then(authToken =>{
            performRequest(authToken,http_method,host, endpoint)
            .then((output) => {
                sendResponse(output,callback);
            }).catch(reject);
        }).catch(reject);  
    });
};

var hmac256 = (data, key, format) => {
	var hash = crypto.createHmac('sha256', key).update(data);
	// no format = Buffer / byte array
	return hash.digest(format);
};

var getByteArray = (hex) => {
	var bytes = [];

	for(var i = 0; i < hex.length-1; i+=2){
	    bytes.push(parseInt(hex.substr(i, 2), 16));
	}

	// signed 8-bit integer array (byte array)
	return Int8Array.from(bytes);
};

var generateHeader = (host, url, method, id, key) =>{
    
    return new Promise((resolve, reject) => {
        var data = `id=${id}&host=${host}&url=${url}&method=${method}`;
    	var timestamp = (new Date().getTime()).toString();
    	var nonce = crypto.randomBytes(16).toString("hex");
    
    	// calculate signature
    	var hashedNonce = hmac256(getByteArray(nonce), getByteArray(key));
    	var hashedTimestamp = hmac256(timestamp, hashedNonce);
    	var hashedVerStr = hmac256(verStr, hashedTimestamp);
    	var signature = hmac256(data, hashedVerStr, 'hex');
    	
    	var hmac_header = `${preFix} id=${id},ts=${timestamp},nonce=${nonce},sig=${signature}`;
    	
    	resolve(hmac_header);
    });
};

var performRequest = (hmacAuthToken,requestType,host, endpoint) => {
    
    return new Promise((resolve, reject) => {
        
        var headers = { 
            Authorization: hmacAuthToken,
        }   
    
        var options = { 
            method: requestType,
            host: host,
            path: endpoint,
            contentType: 'application/json',
            headers: headers
        };
        
        const req = http.request(options, (res) => {
        var chunks = [];

        res.on("data", function (chunk) {
            chunks.push(chunk);
        });
        
        res.on("end", function () {
            var api_result = Buffer.concat(chunks).toString();
            if(checkJSON(api_result)){
                api_result = JSON.parse(api_result);   
            }
            
            var r = {
                "status": res.statusCode,
                "message": api_result
            };
            
            resolve(r);
          });
        });

        req.on('error', (e) => {
          reject(e.message);
        });
        
        req.end();
    });
};

var sendResponse = (output, callback) => {
  
  var result;
  var response = output;
  
  var response_message = response['message'];
  var response_status = response['status'];
  
  var r = createResponse(response_status,response_message);
  callback(null, r);
};

var createResponse = (status, result) => {
  var responseBody = result;
  
  var response = {
    "statusCode": status,
    "headers": { 'Content-Type': 'application/json' },
    "body":  JSON.stringify(responseBody)
  };
  
  return response;
};

var checkJSON = (str) => {
    try {
        JSON.parse(str);
    } catch (e) {
        return false;
    }
    return true;
}

In a nutshell, this approach has the objective to help those who work with AWS and do not wish to rely upon specific libraries or languages when facing to integrate with Veracode API.

Reference links for this article:

Related posts
Application Security

The Importance of Supply Chain to Application Security

When we think about software development, we usually think about complex technical concepts…
Read more
Application Security

What is WAAP (Web Application and API Protection)

Welcome to the world of Web Application and API Protection (WAAP), an advanced security approach…
Read more
Application Security

The challenges in application security in the use of artificial intelligence by developers

As artificial intelligence (AI) becomes more and more present in our daily lives, it has become…
Read more

Deixe um comentário