Background Primer:

Logging. As developers we all need to do this, especially for deployments where it’s entirely unreasonable to ask our end clients to use / maintain / notify us of crash events, and even if they did it gives us a needle’s-eye view of the problem. So while interim development usually starts off with good ole trace it’s potential for releasing sensitive data into the stream is greatly amplified by either the type of application you are writing and it’s complexity. Just having a debugger running, we’ve all seen every one else’s trace statements.

Security:

So AIR helps take the LogLogger from the SDK and gives us the potential to write log files, typically in the application storage area. While this can and does help the alpha and beta stages of release to end-clients it significantly increases their exposure of sensitive data because now you are writing it in area where only the user needs privilege – not admin.

The Better Approach:

Because we want to use the ILogger interface along with standard LogEvents (because Targets use these), the LogLogger is easy enough to re-write. With each message being ultimately broadcast in the event – this is the most appropriate domain to intercept, scramble, and resend. I started with each defined interface method and before each dispatch, a new method is used to build/construct the message. With the help of an implementation of a secure logger (composition goes a long way to help swap parts out instead of inheritance), this class is tasked with doing the actual work of rewriting or removing selected properties from objects we need to keep secure.

The Implementation:

The key with the build method is not only to introspect top level objects as part the …rest parameters, but also to see if anything is an array and to iterate that list to make sure the class type is or isn’t the type that needs to be secured. Any object which needs to be secured is then sent through the secure logger, otherwise a straight obj.toString() is used.

 

LogLogger

public class LogLogger extends EventDispatcher implements ILogger
{

private var _secureLogger:SecureLogger;
private var _category:String;
private var resourceManager:IResourceManager = ResourceManager.getInstance();

public function LogLogger(category:String)
{
super();
_category = category;
}

public function get category():String { return _category; }

public function log(level:int, msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, level));
}

public function debug(msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, LogEventLevel.DEBUG));
}

public function error(msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, LogEventLevel.ERROR));
}

public function fatal(msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, LogEventLevel.FATAL));
}

public function info(msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, LogEventLevel.INFO));
}

public function warn(msg:String, … rest):void
{
if (!hasEventListener(LogEvent.LOG)) return;
msg = build(msg, rest);
dispatchEvent(new LogEvent(msg, LogEventLevel.WARN));
}

private function get secureLogger():SecureLogger { return _secureLogger ||= new SecureLogger(); }

private function build(msg:String, params:Array):String
{
var repl:String =”;
for (var i:int = 0; i < params.length; i++)
{
var obj:Object = params[i];
if (!obj) repl = ‘null’;
else if (secureLogger.hasReplacementRule(obj))
{
if (obj is Array)
{
var n:int = (obj as Array).length;
for (var j:int = 0; j < n; j++)
{
var child:Object = obj[j];
repl += secureLogger.getReplacement(child);
}
}
else repl = secureLogger.getReplacement(obj);
}
else repl = obj.toString();

msg = msg.replace(new RegExp(“\\{“+i+”\\}”, “g”), repl);
}
return msg;
}
}

SecureLogger

public class SecureLogger
{
public function SecureLogger()  { super(); }

public function hasReplacementRule(obj:Object):Boolean
{
if (validateReplacementRule(obj)) return true;
if (obj is Array)
{
for each (var child:Object in obj)
{
if (validateReplacementRule(child)) return true;
}
}
return false;
}

private function validateReplacementRule(obj:Object):Boolean
{
if (obj is Account) return true;
if (obj is LoginCredentials) return true;
return false;
}

public function getReplacement(obj:Object):String
{
var str:String;
if (obj is Account) str = secureAccount(Account(obj));
else if (obj is LoginCredentials) str = ‘[LoginCredentials]‘;
else str = obj.toString(); return str;
}

//An example of using regex to re-write sensitive data
//Since it’s working with direct string manipulation, it must know about it’s format
private function secureAccount(obj:Account):String
{
var str:String = obj.toString();
str = str.replace(/number=\d+/, ‘number=’+ obj.id +’ ‘);
str = str.replace(/name=.*?(?=type)/, ‘name=Account ‘+ obj.id +’ ‘);
str = str.replace(/institutionName=.*?(?=institutionWebsite)/, ‘institutionName= Institution ‘+ obj.id +’ ‘);
str = str.replace(/institutionWebsite=.*?(?=status)/, ‘institutionWebsite= ‘); return str;
}

}