Created
September 17, 2011 14:31
-
-
Save issackelly/1223987 to your computer and use it in GitHub Desktop.
Experimental implementation of Django Signing in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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