Spring Boot Audit Logging

Jotain backendimpää taas vaihteeksi: Projekteissa tulee melkolailla tiheään vaadetta saada aikaan audit loggausta. Vaikkei tulisikaan, se antaa pitkän elinkaaren projekteissa itsellekin mielenrauhaa, että kykenee vastaamaan kysymykseen kuka teki mitä teki milloin teki (miksi teki ei vielä onnistu mutta ehkä IoT avulla sekin ratkaistavissa).

Audit loggausta voi tehdä villistikin eri tavoin ja eri vaatimuksilla. Joissain projekteissa on tultu nähtyä yksinkertainen audit service jota kutsutaan aina tarvittaessa, halutuista paikoista. Tässä on huonoa se, että pitää muistaa kutsua sitä, eli ei ole taattua että suuremmassa projektissa joka koodaaja on laittanut auditit paikalleen, lisäksi se rikkoo DRY periaatetta aika rumasti. Toisaalta on mahdollista tehdä monellakin tapaa filter/interceptor, joka tulee aina väliin ja loggaa vaikka kaiken. Mutta tässä mallissa voi olla ongelmana suuri hälyn määrä, eli voi olla että logi täyttyy tapahtumista jotka eivät ole oikeasti kiinnostavia mutta joita on paljon.

Kirjoittelen tätä blogia koska löysin mielestäni fiksun ratkaisun Spring Frameworkin puolelta, vieläpä Spring Boot yhteensopivana, eli ei xml:ää vaativana. Ratkaisu on fiksu koska se on mukava kompromissi kahdesta mainitusta ääripään tavasta – sisältäen tavallaan molempien huonoja ja hyviä puolia. Mutta ennenkaikkea se on melko kaunis, esteettinen, eikä riko yhtälailla ikävästi DRY periaatetta. Kirjaan näitä ylös myös ennenkaikkea itselleni muistiin, vähentää kivasti tarvittavaa aikaa soveltaa uudelleen, kun on tiedossa testattua luotettavaa ja (tällä hetkellä) ajantasaista tietoa.

Se mitä halusin on oikeastaan mahdollisuus auditoida metoditasolla on-demand, missä haluan. Ei täysautomaattisesti kaikkea, mutta ei myöskään samaa koodia copy-pasteillen joka paikkaan. Lisäksi halusin että voin halutessani määrittää audit eventille nimen, ja/tai kategorian, ja/tai koodin, pelkän metodi/luokannimen sijasta.

Homma lähtee liikkeelle ihan perinteisistä Spring AOP annotaatioista. Eli tarvitaan ensin Spring Boot projekti. Niistä olen kirjaillut jo aiemmin eli en lähde ihan sillä tasolla asiaa avaamaan tällä kertaa. Mutta sen päälle tarvitaan AOP dependency, näin:

 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
 <version>${spring.boot.version}</version>
 </dependency>

Ja nyt ollaan jo aika pitkällä 😉 Hyvä huomata että Spring Boot on aika herkkä sille mitä kaikkea automatiikkaa olet kytkenyt päälle, itse olen saanut AOP featuret vahingossa joskus pois päältä esim. väärillä annotaatiolla Application/Configuration-luokassa. Mutta yleisin syy silti AOP toimimattomuuteen on rikkinäiset pointcutit. Joten testataanpa ensin iisisti mahdollisimman lavealla interceptorilla:

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuditAOP {
@After("execution(* *.*(..))")
 public void logServiceAccess(JoinPoint joinPoint) {
 System.out.println("AuditAOP: Completed : " + joinPoint);
 }
}

Jep, tuossa on AspectJ joinpoint joka tarraa kiinni ihan kaikkeen, niin kauan kuin mennään Springin läpi eli kohteena on Spring-manageroitu komponentti.Tässä kohtaa vain logataan joinpoint. Hyvä katsoa toimiiko, loggaako. Jos loggaa, erinomaista. Tarvittaessa Joinpointilta voidaan louhia lisääkin tietoja:

@After("execution(* *.*(..))")
public void logServiceAccess(JoinPoint joinPoint) {
  System.out.println("AuditAOP: Completed : " + joinPoint);
  Signature signature = joinPoint.getSignature();
  String methodName = signature.getName();
  String arguments = Arrays.toString(joinPoint.getArgs());
  System.out.println("Method: " + methodName + " with arguments "
    + arguments +  " has just been called");
}

Toimiiko tämäkin? Loistavaa. Nyt on sitten aika siirtyä itse pihviin. Voit nimittäin tehdä tästä annotaatiovetoista, annotaatiota voi käyttää halusi mukaan joko kääntämään auditin pois päältä, tai päälle. Itse tykkäisin että on annotaatio audit, jolla voin valita auditoitavan eventin nimen. Sen käyttö tapahtuisi näin:

@Component
class JokuRandomiSpringService {
  @Audit("ACCOUNT_DELETE")
  public void poistaPirunTarkeeTili() {
    // Jotain ihan järkyn fiksua koodia tähän kohtaan
  }
}

Jeah, aika mukava? Joten tehdään tämmöinen:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Audit {
  String value() default "";
}

Sitten siihen todelliseen taikuuteen. Eli miten aop interceptor aktivoituu vain annotaation havaitessaan? Näin:

@Before("execution(* *.*(..)) && @annotation(audit)")
public void logServiceAccess(JoinPoint joinPoint, Audit audit) {
  String event = audit.value();
  if ("".equals(event)) {
    event = joinPoint.getSignature().getName();
  }
  Principal user = (Principal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
  String remoteAddress = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
    .getRequest().getRemoteAddr();
  auditEventService.createEvent(new AuditEventEntity(user.getName(), event, remoteAddress));
}

Huomaa myös että annotaation mäpätään joinpointtiin muuttujanimellä, ja tulee parametriksi interceptorille. Annotaation sisältä voidaan kaivaa halutut parametrit, tässä tapauksessa value, joka olisi audit eventin nimi.

Tämän esimerkin koodi menee vähän pidemmälle. Jos nimeä ei ole annettu, oletusnimi on kutsuttavan metodin nimi, eli value on valinnainen. Lisäksi kaivellaan käyttäjän identiteetti security contextista, ja ip-osoite request contextista. Huom! Esitetty malli ei ole yksikkötestiystävällisintä, voi olla että on elegantimpiakin tapoja injektoida nämä contextit.

Mitäs vielä? Tuossa koodissa oleva auditEventService on ihan tavallinen Spring komponentti/service, jossa on yksi rivi koodia jolla talletetaan audit eventti kantaan, sopivaan tauluun, jossa on halutut sarakkeet. Samoin auditevententity on yksinkertaisesti Entity Object, jossa on kentät username, event, remoteaddress – id ja aikaleima ovat autogeneroituja. Lisätään tietoa sen mukaan mikä on paranoian taso.

Joskus tuli tehtyä sellaistakin järjestelmää jossa haluttiin mahdollisimman iisi tietoturva – yleinen tietoturvan sääntö kun on, että mitä tiukemmin kiristää käyttäjille näkyvää tietoturvaa, ja vaikeuttaa arkea, sitä luovemmin opitaan kiertämään se tietoturva, luoden usein jopa turvattomampi ratkaisu kuin alunperin (salasanoja muistilapuilla, sama salasana kaikkialla, kulunvalvottujen ovien availu kohteliaisuudesta, jne). Hyviä tietoturvaratkaisuja ovat eritoten ne systeemit joissa tietoturva ei hankaloita käyttäjän arkea. (Tämän takia salasanat ovat helvetistä)

Esim. tarkka auditointi tarkkojen roolilokeroiden sijasta, kaikki saavat tehdä lähes kaikkea mutta kaikesta jää jäljet. Tai jos haluaa niin molemmat päälle. Riippuu ympäristöstä mikä on fiksua, tarpeellista tai lainsäädännön sanelemaa.

Hyvä huomata että tämän tason auditointi ei loggaa virheitä jotka johtivat keskeytymiseen jo aiemmin ketjussa, eli jos haluat vielä laveammalla siveltimellä, voit täydentää esim. servlet tason filttereillä ja virhekäsittelijöillä.

 

Tiivistettä Javalla

Kauan aikaa sitten kouluttelimme kurssia Java Security. Jostain syystä se ei ollut koskaan suuri hitti, niihin aikoihin tietoturva kuten testauskin oli harvojen herkkua, koska molemmat tehtiin projektien lopussa vähäisellä prioriteetilla, pienikin viivästys aiheutti sen että karsitaan tietoturvasta ja testauksesta. Ja siksi harvoja kiinnosti miten Java-kielessä voi kryptata sisältöä raskaillakin algoritmeilla tai miten dataan voi tehokkaimmin laskea tiivisteitä tai enkoodata tai dekoodata sitä esim. base64 muotoon.

Rajapinnat olivat kuitenkin suorastaan hauskoja ja helppoja, joten on vähän sääli että niitä ei ole päässyt sen enempiä käyttelemään itsekään. Pitkästä aikaa törmäsin artikkeliin jossa käsiteltiin näitä, ja ajattelin verrytellä koodiesimerkin toimivaan kuntoon. Nykyään tietoturva alkaa taas pakostakin olemaan enemmän tapetilla, ja luottamuksellisuus ja kiistämättömyys voivat olla taas tavoiteltavia asioita.

Eli tässä koodipätkä itselleni muistiin, miten Java-kielen valmiilla piirteillä voi laskea datasta sha-1 -tiivisteen, ja samaan syssyyn Base64-koodata sen siirtoa tai talletusta varten:

public class MessageDigestTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        String sourceString = "HelloWorldTextToDigest";
        System.out.println("Source text:" + sourceString);
        String returnString = "";
        MessageDigest messageDigester = MessageDigest.getInstance("SHA-1");
        messageDigester.update(sourceString.getBytes("utf8"));
        returnString = new String(Base64.encode(messageDigester.digest()));
        System.out.println("Base64 encoded SHA-1 digest of source string:" + returnString);
    }
}

Pari huomioitavaa asiaa koodiesimerkistä:

  • Base64 on nykyisellään Javassa ei-standardi HotSpot myllyn sisäinen toteutusluokka, niinkin mehukkaassa paketissa kuin com.sun.org.apache.xerces.internal.impl.dv.util.Base64 – eli sen läsnäoloon tai rajapinnan muuttumattomuuteen ei voi luottaa
  • Java 8 tuonee mukanaan tästä standardipaketti-version, jossa rajapinta pysyy samana ja joka näin ollen löytyisi kaikista virtuaalikoneista aikanaan, Base64 muuttuu siis helpommaksi. Java 8:ssa Base64-luokka löytyy paketista java.util. Aiemmille toteutuksille voi olla hyvä käyttää jotain 3rd party kirjastoa jota voi itse hallita
  • MessageDigest taas on sisäänrakennettu hyvinkin vanha luokka. Sen kautta voi käyttää mitä hyvänsä Javalle saatavilla olevia alghoritmeja, nykyisiä ja tulevia, tiivisteen laskemiseen. Algoritmeja löytyy esim. SHA-1, SHA-256, SHA-384, SHA-512, MD2 ja MD5.
  • Samaan tapaan Java-kielessä toimii myös luokka nimeltä Cipher, jolla voi kryptata sisältöä. Sekin luodaan ensin, sitten update toiminnolla dataa sisään, ja lopuksi kryptattu versio ulos. Tähän tulee mukaan myöa avaintenhallinnan rajapinnat. Nykyisellään Cipher tukee algoritmeja kuten AES,AESWrap,ARCFOUR, DES,DESede, RSA, mutta myös esim. Blowfish, ECIES (Elliptic Curve Integrated Encryption Scheme), ja lukuisia muita. Jos ne eivät riitä, lisäalgoritmeja pystyy asentamaan. Noista mainituistahan saa nykypäivänä unohtaa heti osan algoritmeista, koska niitä ei pidetä enää turvallisena.
  • Pienenä haasteena jenkkilässä rinnastetaan kryptografiaa aseisiin ja ammuksiin, ja järeiden avainvahvuuksien algoritmeilla on rajoituksia maastaviennin suhteen. Tämän vuoksi oletus-Java asennuksessa avainten maksimivahvuus on rajoitettu ja avatakseen täyden vahvuuden avaimet täytyy asentaa virtuaalikoneeseen lisäpalikkaa. Tai kirjoittaa pieni koodihäkkäys joka poistaa eston.
  • Lisätietoa algoritmeista esim. http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#MessageDigest

Sampo luopuu Javasta

Client pään Java on kokenut kovia iskuja viime aikoina, melkein voisi puhua viikon tietoturvareiästä. Jatkuvalla syötöllä on löytynyt heikkouksia ja reikiä joita jopa kaupataan kelle hyvänsä joka haluaa niitä käyttää. Oracle on pelaillut catch-up peliä mutta paikkoja tulee myöhässä, ja näemmä vasta kun haavoittuvuus päätyy lööppeihin, ja samalla kun tulee paikka löytyy uusi reikä.

Tämä ei tietysti eroa siitä mitä Flashille tapahtui aikanaan, eikä siitä mitä HTML5:lle tulee tapahtumaan jahka se yleistyy. Eikä tietysti vaikuta niinkään palvelin-Javaan joka on edelleen Javan suurin käyttöalue. Mutta on se aika ikävää työasemien osalta. Itsekin olen päivitellyt updateja tiuhemmin kuin yleensä, ja yrittänyt sulkea Javan pois selaimista joita en käytä. Sampo pankin asiakkaana on jopa ollut ikävää käytellä verkkopankkia joka vaatii Java Applet tukea toimiakseen ollenkaan. Muun hauskan lisäksi kotona 64-bittisellä win8:lla ainoa selain jolla sampo pankin Java applet toimii oli Microsoft Internet Explorer – joka perinteisesti on ollut kävelevä tietoturvareikäjuusto jota en juuri edes käynnistä vuosiin jos voin sen vältellä. Luotettu Chrome ja Firefox kieltäytyvät eri tavoin yhteistyöstä – Chrome valittaa että Javaa ei ole asennettu vaikka sen on moneen kertaan eri variaatioina asennettukin, ja Firefox vain mystisesti jää pyörimään, lataan Javaa-looppiin.

Mutta tämä ei lupaa kovin hyvää JavaFX-tekniikalle, joka muutenkin on kyseenalainen HTML5:sen kanssa kilpailuasetelmassa oleva teknologia. Se vaatii vakaan ja turvallisen Java-asennuksen toimiakseen, ja onko sellaista luvassa enää? Asia on sikäli murheellinen että JavaFX on kaunista, esteettistä, tehokasta. Mutta ellei tässä tapahdu muutoksia se on tuhoon tuomittu jo alkumetreillään. Aika näyttää, tämä on Javan vuosi, sillä tulossa on massiiviset ja mullistavat Java SE 8 sekä Java EE 7 päivitykset – paljon tulee muuttumaan Java maailmassa.

 

Lähde:

http://yle.fi/uutiset/danske_bank_luopuu_javasta_-_verkkopankkiin_uudistuksia/6470709

 

Java EE tietoturva

Törmäsin hyvään esitykseen turvallisesta Java EE rakentamisesta.  Ei mitään uutta eikä mullistavaa, mutta samoja hyviä käytäntöjä joita itsekin allekirjoitan:

http://www.java-tv.com/2012/12/04/java-ee-security-in-practice-with-java-ee-6-and-glassfish/

Kyllähän tiedetään mikä tekee ratkaisusta turvallisen. Silti häkellyttävää miten paljon turvatonta koodia on liikenteessä. Tietoturva on usein se asia joka jää hakkuupölkylle yhdessä testauksen kanssa kun tulee kiirettä.

Tähän voi tulla muutos. Briteissä on jo ollut pari vuotta voimassa säädös jonka nojalla yritystä voidaan sakottaa tietomurroista jotka ovat johtuneet huonosta tietoturvasta järjestelmissä – sakko voi nousta 500,000 puntaan asti. Tietomurtoja ovat esim. tilanteet jossa asiakkaiden salasanoja tai henkilö tai maksutietoja joutuu vääriin käsiin huolimattomuuden tai haavoittuvuuden vuoksi. EU puuhastelee lainsääsäntöä (General Data Protection Regulation) jossa rikkeestä voi päästä maksamaan sakkoa 2% yrityksen vuotuisesta liikevaihdosta – tai litteän miljoonan. Auts!

Kun tietoturva nostaa päätään myös Open Web Application Security Project OWASP on nousussa, antaen vähän kättä pidempää tietoturvahaavoittuvuuksien tunnistamiseen ja koulutuksen formalisointiin. OWASP on hyvin sovellettavissa esim. Java web hankkeisiin tai Scala tai Grails projekteihin.

Lähde:

http://ec.europa.eu/justice/data-protection/document/review2012/com_2012_11_en.pdf

http://en.wikipedia.org/wiki/OWASP

Java vs HTML 5 vs Flash

Olen pannut huvittuneisuudella merkille miten maailmalla kirjoitetaan artikkeleita Javan tuhosta. Alustalla on tietoturvahaavoittuvuuksia, se suositellaan siis poistettavan. Näin ollen Javan aika on ohi ja se vuotaa rahaa Oraclelta. Tässä lauseessahan on niin paljon väärin ettei tiedä mistä aloittaa. Javan pääkäyttöhän ei ole työasemissa vaan servereissä, jonne mm. IBM, Oracle ja RedHat ovat tehneet ja tulevat tekemään rahallisia panostuksia, ja joissa Java-pohjaiset web serverit ovat tyypillisimpiä alustoja web-sovelluksille, etenkin yrityspuolella. Ja jossa noilla mainituilla haavottuvuuksilla ei ole (juuri) mitään merkitystä. Java siis elää tai kuolee sen mukaan miten hyvin se suoriutuu palvelinohjelmoinnissa vastaan muita teknologioita, ja miten hyvin sitä puolta tuetaan ja kehitetään. Jos tänä päivänä java se tapettaisiin kokonaan, merkitys Javan kannalta ei olisi suuren suuri. Javan merkitys on servereissä, ei työasemissa. Tosin työasemissakin sekä mobiililaitteissa on Javaa asenneltuna miljardeja yksi

Väite: Pitäisikö sitten Java työasemista poistaa? Sehän on täynnä tietoturva-aukkoja ja joka kuukausi eletään pelossa löytyykö uusi. Lisäksi, kuka enää tekee rich client ohjelmointia, kun kaiken voi tehdä HTML5:lla? Adobekin luopuu flashistä. Java on ihan tarpeeton tekele?

Vai onko? Katsotaanpa tarkemmin näitä argumentteja:

Voiko kaiken todella tehdä HTML5:lla? Kenties voi. Perinteisesti rikasta käyttöliittymää lähdettiin tekemään Javalla tai Flashillä siksi että perinteinen HTML pohjainen web-sovellus on staattinen ja jäykkä, se reagoi vain napinpainallukseen ja pystyy ainoastaan näyttämään web sivuja ja ottamaan syötettä, ei esim. minkäänlaista pääsyä työaseman/mobiililaitteen rautaan kuten älykortinlukijoihin, (web) kameraan, paikkatietopalveluihin, yhteystietoihin, kalenteriin, värinärajapintaan, kiihtyvyysanturiin, kallistusanturiin, verkkokorttiin, äänikorttiin, piirtämiseen, videon soittoon, matalan tason tehokkaisiin grafiikkarajapintoihin, jne. JavaScript laajensi palettia antamalla mm. rikkautta tapahtumankäsittelyyn. AJAX lisäsi vielä lisää rikkautta kun dataa voi hakea dynaamisesti verkon yli ja sillä päivittää sivua ilman että täytyy edes navigoida. Ja HTML 5 lisää vielä lisää kuten Canvas piirtoalusta, ääni ja videorajapintoja, paikallinen tietovarasto. Sillä voi soitella videota ja ääntä, ja tehdä kaunista käyttöliittymää.

Mitä sitten uupuu? No pari asiaa. HTML 5 ei pärjää suorituskyvyssä lähempänä käyttöjärjestelmää ajetulle koodille, eli kun halutaan ottaa koneesta kaikki irti, se jää jälkeen. HTML5 pääsee vain niihin resursseihin käsiksi jotka on siihen avattu, eli aina kun joku uusi vimpain keksitään työasemiin tai mobiililaitteisiin, HTML5 sovellukset ovat jälkijunassa sen soveltamisessa. Samoin tietysti Java, mutta vähemmän kuin HTML. Esim. JavaFX teknologian suorituskyky on täysin eri planeetalta ja visuaalista ilmaisukykyä riittää eri sarjassa.

Pahimmat niitit tulevat kuitenkin tässä: HTML5 ei ole vielä edes valmis, se on tällä hetkellä vaporwarea kunnes toisin todistetaan. Sen spesifikaatiokaan ei ole valmis, se _ehkä_ tulee 2014. Selainvalmistajat _ehkä_ toteuttavat sen. Siihen mennessä _ehkä_ maailma ei ole muuttunut yhtään. Muistan myös 90-luvun suuret selainsodat, kun Dynamic HTML ja JavaScript olivat uusi ja kuuma juttu, ja valmistajat kilpailivat siitä kuka tekee hienoimmat toteutukset ja tulkitsee standardeja nerokkaimmin ja mahdollisimman epästandardein tavoin. 2014 web sivustot tulevat räjähtämään käsiin ellei ihmiskunta ja eritoten selainvalmistajat ja kehittäjät ole jotenkin viisastuneet siitä. Todennäköisesti joudutaan taas tekemään eri versiot sivustoista eri selainversioille, tätä on jo nyt näkyvissä vaikka speksi ei ole edes valmis.

Entäpä sitten tietoturvaongelmat? Javassahan on niitä, haavoittuvuuksia, jotka pitää paikata tai niitä hyödynnetään. Samoin on Windowsissa. Linuxissa. JavaScriptissä. Ja niin myös Ajaxissa ja eritoten HTML 5:ssa. Jos haluaa alustan jossa ei ole tietoturvaongelmia niin se onnistuu kunhan alustassa ei voi tehdä mitään. Mitä enemmän alustalla voi tehdä sitä rajummin siellä on potentiaalia tietoturva-aukoille, ja aukotonta järjestelmää ei olekaan. Miten nopeasti tulevat HTML5 aukot paikataan ja kenen toimesta?

No, siinä pohdittavaa. HTML5 on mielestäni mielettömän kaunis ja lupaava teknologia, mutta se ei ole mullistava eikä sovelluskehityksen pelastaja eikä missään nimessä ongelmaton. Siinä tulee olemaan tietoturvaongelmia ja vakavia sellaisia kun sen käyttö lisääntyy, samalla kun sen ominaisuudet muuttuvat paremmiksi. Suuren vallan mukana tulee suuri vastuu.

Monet artikkelit ja mielipiteet julistavat sokeasti HTML5:den uutta tulemista ja kaikkien rikkaiden käyttöliittymäteknologioiden kuten Flash, Applet, JavaFX kuolemaa, ja tämä on ehkä yksipuolinen näkemys. En halua myöskään väittää että JavaFX jyräisi HTML5:sen, tuskinpa vain. Hyvä jos sille tilaa riittää markkinoilla. Mutta HTML5 ei ole Suuri Pelastaja joka mullistaa kaiken. Se on yksi teknologia muiden joukossa, ja omaa samoja ongelmia kuin muutkin. Suorituskyky, pääsy uusiin rautaratkaisuihin ja rajapintoihin client päässä, todellinen siirrettävyys eri alustoilla kuten eri selaimet, käyttöjärjestelmät, ja eri valmistajien mobiililaitteet, sekä tietoturva, ovat kaikki haasteita, jotka tulee ratkaista. Yhtäkaikki ei ole huono juttu että 2015 vuonna työkalupakissa on useita hyviä uusia mahdollisuuksia.

Lähdelinkkejä ja lisäluettavaa:

Facebook liian aikaisessa HTML5:sen kanssa:
http://css.dzone.com/articles/facebook%E2%80%99s-html5-mistake

HTML5 suorituskykyä voidaan kiihdyttää:
http://www.html5rocks.com/en/mobile/nativedebate/

Green is good 😉
http://en.wikipedia.org/wiki/Comparison_of_layout_engines_(HTML5)

Java vs HTML5 Canvas:
http://www.zynaps.com/site/experiments/mandelbrot.html