abdera-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From James Snell <jasn...@gmail.com>
Subject [abdera2] experimental security stuff
Date Tue, 27 Sep 2011 06:39:47 GMT
Earlier today I checked in a few useful utility classes to the Abdera2
trunk, mostly focused on providing a number of basic utilities for
adding a variety of security measures to an application. They are not
used directly by any existing part of Abdera2, for the most part, but
are provided mainly to make it easier for people building on top of
Abdera2 to do Interesting Security Things.

For example, it is very common for REST api's these days to use API
Keys. These are special tokens used within the API (usually the
querystring unfortunately) that the service provider uses to monitor
and meter a client's use of the api. API Keys are generated by the
service provider, given to the client, and hopefully kept secret. It's
a very good idea if the API Key is generated randomly using
cryptographically strong methods to prevent a potential attacker from
being able to guess or compromise the API Key. To make things easier,
I added a new ApiKey class to the Abdera2 common library. It uses a
user supplied Key to generate an hmac over randomly generated bytes,
then base64 encodes the hmac replacing all non-alphanumeric characters
with periods ('.'). For instance,

    byte[] key = new byte[] {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0};

    ApiKey apikey = new ApiKey(key,256);

    System.out.println(apikey.generateNext());

Would print something like...

Cj.IRc16YqN.EjBgn.Zq0Ai0gFbIoYQ2diYbKJ6oKE8

Obviously the key we used is fake, but you get the idea... the key can
really be any set of bytes but it's a good idea to use something that
cryptographically strong. Every call to ApiKey.generateNext() will
yield a new random string usable as an API Key value.

If you're a user of Google's Two Factor Authentication, then you're
familiar already with the use of One-Time Passwords. Basically, OTP is
a protocol allowing multiple parties to independently derive a common
"password" using a shared secret and something called a "moving
factor". Assuming both parties synchronize their "moving factor", both
are able to independently generate identical passwords.

Google's Two Factor Authentication uses Time-based One Time Passwords,
as described in RFC 6238, essentially using the system clock as the
moving factor and generating a OTP based on an hmac of the number of
specific time intervals (or steps) that have passed since the Unix
Epoch. Other moving factors may be used as an alternative, such as a
simple counter.

The use of One Time Passwords within REST API's currently is
essentially non-existent currently. The inclusion of an OTP
implementation within Abdera2, then, is largely speculative and
experimental. Using the new Otp class, it is possible to generate
One-Time-Passwords. A Time-Based One Time Password implementation is
included, and the Otp class can be subclassed to implement more.

    byte[] key = new byte[] {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0};

    Otp.Totp totp = new Otp.Totp(30, key);

    totp.generateNext();

This creates an object capable of generating a unique
One-time-password for every 30-second interval since the Unix Epoch.
Again, the key used is just for example, a cryptographically-strong
key should be used in practice.

The output of this would look something like: 83223665

The Otp class allows you to adjust the number of digits in the
generated password. For instance, to generate an OTP with 4 digits,
you would simply use the constructor new Otp.Totp(30,key,4). The
maximum number of digits is 9. The minimum is 1... although a Otp with
only a single digit would be downright silly.

Just for fun, I just want to point out that the Otp and ApiKey classes
can be used together. For instance, suppose you wanted to allow your
clients to keep their API Keys secret and pass them around in URI
Query String parameters. You could, as an alternative, use One-Time
Passwords and a Client ID. The One-time password would be based on the
API Key... for instance..

// first, generate the api key... give that to the client
Key key = KeyGenerator.getInstance("DES").generateKey();
ApiKey apikey = new ApiKey(key);
String hexkey = apikey.generateNextHex();

// the client then uses the api key to generate the time-based one
time password...
Otp.Totp totp = new Otp.Totp(30, hexkey, 9);
String otp = totp.generateNext();

Combine that with a client id, include it in the client request, and
you're off an running with all the benefits of an api key without
compromising the security of your api key, and it's all implemented
with just a few simple lines of code.

If you want to get really fancy, just for pure fun, you can use the
Diffie-Hellman Key Exchange to independently negotiate the secret key
that's used to independently negotiate the one-time-password.

    DHBase dh1 = new DHBase();
    DHBase dh2 = new DHBase(dh1.getRequestString());
    dh1.setPublicKey(dh2.getResponseString());

    Otp.Totp totp1 = new Otp.Totp(30, dh1.generateSecret(), 9);
    Otp.Totp totp2 = new Otp.Totp(30, dh2.generateSecret(), 9);

    System.out.println(totp1.generateNext());
    System.out.println(totp2.generateNext());

The DHBase class is another utility class evolved from the DHContext
class originally in Abdera 1.x. The DHContext class allows for
streamlined negotiation of encryption keys for XML Encrypted Atom
documents. DHBase pulls out the core of the DH support so it's not
tied to the Atom classes. The DHBase.getRequestString() and
DHBase.getResponseString() methods return values that are compatible
for use with HTTP's WWW-Authenticate and Authorization headers, making
it very simple to negotiate a DH session.

Moving on... I also checked in a OAuth2Helper class that makes it a
bit easier to implement client-side OAuth2 support. It is, to be
certain, no where near a full OAuth2 implementation and doesn't
pretend to be. Rather, it provides a few methods designed to make
implementing the client side request flow of acquiring OAuth2 tokens a
bit easier. So far it's only been tested against Google's OAuth 2
endpoint.

Finally, I checked in a simple implementation of the JSON Web Token
specification (http://tools.ietf.org/html/draft-jones-json-web-token-05).
A JSON Web Token (or JWT) is essentially Digital Signatures for JSON
objects. A base64 encoded JSON object is used as a header and contains
information about the signing key, algorithm etc. This is combined
with a second part that can either be a base64 encoded JSON object or
any other arbitrary base64 encoded binary data and digitally signed.
The resulting signature is also base64 encoded and concatenated into a
single string with the header and the payload. When all is said and
done, it looks something like:
"eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"

Using the Jwt class that is included in the
org.apache.abdera2.activities.extras package, generating a Jwt for an
object is pretty simple...

    KeyHelper.prepareDefaultJceProvider();
    KeyPair pair = KeyHelper.generateKeyPair("DSA", 1024);

    Activity activity = new Activity();
    activity.setVerb(Verb.POST);
    // set other properties

    String jwt = Jwt.generate(pair.getPrivate(), activity);

The resulting JWT string can then validated using a simple call to
Jwt.validate(pair.getPublic(), jwt) which returns either true or
false.

The utility method Jwt.getClaimIfValid() can be used to extract the
JSON payload from the JWT if and only if the signature is valid, e.g.:

activity = (Activity) Jwt.getClaimIfValid(pair.getPrivate(), jwt);

The method will return null if the JWT signature is invalid.

So that's it.. there were a few other changes made today as well, such
as an update to the URI Templates implementation due to changes made
in the Draft 7 of that spec. All of the security related stuff should
be treated as highly experimental. It's still not clear yet exactly
how useful all of this will be and the implementation details could
change dramatically before everything settles out, but I wanted to at
least make y'all aware of what was there.

Oh, and the build should be working now for everyone. I'll be getting
all the test cases back up and running tomorrow.

Mime
View raw message