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 AppSec Flow a more complete solution.

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 AppSec Flow.

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

Great Place to Work: Conviso is among the 15 best companies in Paraná in its category

Conviso has been named on the 2021 list of Best Workplaces in the state of Parana, Brazil. The…
Read more
Code FightersTech

An introduction to secure code review on Go applications

We have a new application or module written in the Go language that we want to analyze. So how do we…
Read more
Application SecurityCode Fighters

Bank malware mitigations

Malware (Bank malware mitigations) is the name for a program designed to mistreat its users. Viruses…
Read more

Deixe um comentário