Skip to content

Instantly share code, notes, and snippets.

@issackelly
Created September 17, 2011 14:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save issackelly/1223987 to your computer and use it in GitHub Desktop.
Save issackelly/1223987 to your computer and use it in GitHub Desktop.
Experimental implementation of Django Signing in JavaScript
base64 = require 'base64'
crypto = require 'crypto'
gzip = require 'gzip'
class BadSignature extends Error
constructor: (msg="Signature does not match") ->
super msg
class SignatureExpired extends BadSignature
constructor: (msg="Signature timestamp is older than required max_age") ->
super msg
ALPHABETS =
_2: '01'
_16: '0123456789ABCDEF'
_56: '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz'
_36: '0123456789abcdefghijklmnopqrstuvwxyz'
_62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
_64: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'
settings =
SECRET_KEY: 'SHOULD HAVE A REAL KEY'
zip = (seq1, seq2) ->
new_seq = []
for i in seq1
new_seq.push([seq1[_i], seq2[_i]])
new_seq
constant_time_compare = (val1, val2) ->
#
# Returns True if the two strings are equal, False otherwise.
#
# The time taken is independent of the number of characters that match.
#
if val1.length != val2.length
return false
result = 0
for x, y in zip(val1, val2)
result |= x.charCodeAt(0) ^ y.charCodeAt(0)
result == 0
class BaseChanger
constructor: (@base=62) ->
@alphabet = ALPHABETS['_'+ @base]
encode: (i) ->
digits = []
while(i > 0)
@digits.push(@alphabet[i % @base])
i = Math.floor(i / @base)
@digits.reverse().join('')
decode: (s) ->
digits = s.split('')
sum = 0
for i in digits
sum += @alphabet.indexOf(i) * Math.pow(@base, (digits.length - (_i+1)))
sum
salted_hmac = (key_salt, value, secret=settings.SECRET_KEY) ->
key = (new crypto.Hash).init("sha1").update(key_salt + secret).digest("hex");
(new crypto.Hmac).init('sha1', key).update(value)
b64_encode = (s) ->
base64.encode(s).replace('+', '-').replace('_', '/').replace('=', '')
b64_decode = (s) ->
pad = Array((s.length % 4) + 1).join('=')
base64.decode(s.replace('_', '/').replace('-', '+') + pad)
base64_hmac = (salt, value, key) ->
b64_encode(salted_hmac(salt, value, key).digest('hex'))
dumps = (obj, key=None, salt='django.core.signing', serializer=JSON, compress=false) ->
block_for_gzip = false
data = serializer.dumps(obj)
if compress
block_for_gzip = true
gzip data, (err, zipped_data) ->
data = zipped_data
block_for_gzip = false
while block_for_gzip
i = null
base64d = b64_encode(data)
if is_compressed
base64d = '.' + base64d
data
class Signer
constructor: (@key=settings.SECRET_KEY, @sep=":", @salt=null) ->
signature: (value) ->
base64_hmac(@salt+'signer', value, @key)
sign: (value) ->
parseString(value) + @sep + @signature(value)
unsign: (signed_value) ->
if not @sep in signed_value
throw new BadSignature()
values = signed_value.split(@sep)
sig = values[..-1][0]
value = values[0..-2]
if constant_time_compare(sig, self.signature(value))
return value
throw new BadSignature()
class TimeStampSigner extends Signer
unix_timestamp: ->
Math.round((new Date()).getTime() / 1000)
timestamp: ->
b = new BaseChanger()
b.encode(@unix_timestamp)
sign: (value) ->
value = value + @sep + @timestamp
value + @sep + @signature(value)
unsign: (value, max_age=null) ->
result = super value
values = signed_value.split(@sep)
timestamp = values[..-1][0]
value = values[0..-2]
b = new BaseChanger()
timestamp = b.decode(ts)
if max_age?
age = @unix_timestamp - timestamp
if age > max_age
throw new SignatureExpired()
return value
var ALPHABETS, BadSignature, BaseChanger, SignatureExpired, Signer, TimeStampSigner, b64_decode, b64_encode, base64, base64_hmac, constant_time_compare, crypto, dumps, gzip, salted_hmac, settings, zip;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
}, __indexOf = Array.prototype.indexOf || function(item) {
for (var i = 0, l = this.length; i < l; i++) {
if (this[i] === item) return i;
}
return -1;
};
base64 = require('base64');
crypto = require('crypto');
gzip = require('gzip');
BadSignature = (function() {
__extends(BadSignature, Error);
function BadSignature(msg) {
if (msg == null) {
msg = "Signature does not match";
}
BadSignature.__super__.constructor.call(this, msg);
}
return BadSignature;
})();
SignatureExpired = (function() {
__extends(SignatureExpired, BadSignature);
function SignatureExpired(msg) {
if (msg == null) {
msg = "Signature timestamp is older than required max_age";
}
SignatureExpired.__super__.constructor.call(this, msg);
}
return SignatureExpired;
})();
ALPHABETS = {
_2: '01',
_16: '0123456789ABCDEF',
_56: '23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz',
_36: '0123456789abcdefghijklmnopqrstuvwxyz',
_62: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
_64: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'
};
settings = {
SECRET_KEY: 'SHOULD HAVE A REAL KEY'
};
zip = function(seq1, seq2) {
var i, new_seq, _i, _len;
new_seq = [];
for (_i = 0, _len = seq1.length; _i < _len; _i++) {
i = seq1[_i];
new_seq.push([seq1[_i], seq2[_i]]);
}
return new_seq;
};
constant_time_compare = function(val1, val2) {
var result, x, y, _len, _ref;
if (val1.length !== val2.length) {
return false;
}
result = 0;
_ref = zip(val1, val2);
for (y = 0, _len = _ref.length; y < _len; y++) {
x = _ref[y];
result |= x.charCodeAt(0) ^ y.charCodeAt(0);
}
return result === 0;
};
BaseChanger = (function() {
function BaseChanger(base) {
this.base = base != null ? base : 62;
this.alphabet = ALPHABETS['_' + this.base];
}
BaseChanger.prototype.encode = function(i) {
var digits;
digits = [];
while (i > 0) {
this.digits.push(this.alphabet[i % this.base]);
i = Math.floor(i / this.base);
}
return this.digits.reverse().join('');
};
BaseChanger.prototype.decode = function(s) {
var digits, i, sum, _i, _len;
digits = s.split('');
sum = 0;
for (_i = 0, _len = digits.length; _i < _len; _i++) {
i = digits[_i];
sum += this.alphabet.indexOf(i) * Math.pow(this.base, digits.length - (_i + 1));
}
return sum;
};
return BaseChanger;
})();
salted_hmac = function(key_salt, value, secret) {
var key;
if (secret == null) {
secret = settings.SECRET_KEY;
}
key = (new crypto.Hash).init("sha1").update(key_salt + secret).digest("hex");
return (new crypto.Hmac).init('sha1', key).update(value);
};
b64_encode = function(s) {
return base64.encode(s).replace('+', '-').replace('_', '/').replace('=', '');
};
b64_decode = function(s) {
var pad;
pad = Array((s.length % 4) + 1).join('=');
return base64.decode(s.replace('_', '/').replace('-', '+') + pad);
};
base64_hmac = function(salt, value, key) {
return b64_encode(salted_hmac(salt, value, key).digest('hex'));
};
dumps = function(obj, key, salt, serializer, compress) {
var base64d, block_for_gzip, data, i;
if (key == null) {
key = None;
}
if (salt == null) {
salt = 'django.core.signing';
}
if (serializer == null) {
serializer = JSON;
}
if (compress == null) {
compress = false;
}
block_for_gzip = false;
data = serializer.dumps(obj);
if (compress) {
block_for_gzip = true;
gzip(data, function(err, zipped_data) {
data = zipped_data;
return block_for_gzip = false;
});
}
while (block_for_gzip) {
i = null;
}
base64d = b64_encode(data);
if (is_compressed) {
base64d = '.' + base64d;
}
return data;
};
Signer = (function() {
function Signer(key, sep, salt) {
this.key = key != null ? key : settings.SECRET_KEY;
this.sep = sep != null ? sep : ":";
this.salt = salt != null ? salt : null;
}
Signer.prototype.signature = function(value) {
return base64_hmac(this.salt + 'signer', value, this.key);
};
Signer.prototype.sign = function(value) {
return parseString(value) + this.sep + this.signature(value);
};
Signer.prototype.unsign = function(signed_value) {
var sig, value, values, _ref;
if (_ref = !this.sep, __indexOf.call(signed_value, _ref) >= 0) {
throw new BadSignature();
}
values = signed_value.split(this.sep);
sig = values.slice(0)[0];
value = values.slice(0, -1);
if (constant_time_compare(sig, self.signature(value))) {
return value;
}
throw new BadSignature();
};
return Signer;
})();
TimeStampSigner = (function() {
__extends(TimeStampSigner, Signer);
function TimeStampSigner() {
TimeStampSigner.__super__.constructor.apply(this, arguments);
}
TimeStampSigner.prototype.unix_timestamp = function() {
return Math.round((new Date()).getTime() / 1000);
};
TimeStampSigner.prototype.timestamp = function() {
var b;
b = new BaseChanger();
return b.encode(this.unix_timestamp);
};
TimeStampSigner.prototype.sign = function(value) {
value = value + this.sep + this.timestamp;
return value + this.sep + this.signature(value);
};
TimeStampSigner.prototype.unsign = function(value, max_age) {
var age, b, result, timestamp, values;
if (max_age == null) {
max_age = null;
}
result = TimeStampSigner.__super__.unsign.call(this, value);
values = signed_value.split(this.sep);
timestamp = values.slice(0)[0];
value = values.slice(0, -1);
b = new BaseChanger();
timestamp = b.decode(ts);
if (max_age != null) {
age = this.unix_timestamp - timestamp;
if (age > max_age) {
throw new SignatureExpired();
}
}
return value;
};
return TimeStampSigner;
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment