nebulab-dropbox
Version:
Client library for the Dropbox API
307 lines (276 loc) • 9.8 kB
text/coffeescript
# HMAC-SHA1 implementation heavily inspired from
# http://pajhome.org.uk/crypt/md5/sha1.js
do ->
# Base64-encoded HMAC-SHA1.
#
# @private
# This should only be used by {Dropbox.Util.Oauth}.
#
# @param {String} string the ASCII string to be signed
# @param {String} key the HMAC key
# @return {String} a base64-encoded HMAC of the given string and key
Dropbox.Util.hmac = (string, key) ->
arrayToBase64 hmacSha1(stringToArray(string), stringToArray(key),
string.length, key.length)
# Base64-encoded SHA1.
#
# @private
# This should only be used by {Dropbox.Util.Oauth}.
#
# @param {String} string the ASCII string to be hashed
# @return {String} a base64-encoded SHA1 hash of the given string
Dropbox.Util.sha1 = (string) ->
arrayToBase64 sha1(stringToArray(string), string.length)
Dropbox.Util.sha256 = (string) ->
arrayToBase64 sha256(stringToArray(string), string.length)
# SHA1 and HMAC-SHA1 versions that use the node.js builtin crypto.
if Dropbox.Env.require
try
crypto = Dropbox.Env.require 'crypto'
if crypto.createHmac and crypto.createHash
Dropbox.Util.hmac = (string, key) ->
hmac = crypto.createHmac 'sha1', key
hmac.update string
hmac.digest 'base64'
Dropbox.Util.sha1 = (string) ->
hash = crypto.createHash 'sha1'
hash.update string
hash.digest 'base64'
Dropbox.Util.sha256 = (string) ->
hash = crypto.createHash 'sha256'
hash.update string
hash.digest 'base64'
catch requireError
# The slow versions defined at the top of the file work everywhere.
# HMAC-SHA1 implementation.
#
# @private
# This method is not exported.
#
# @param {Array} string the HMAC input, as an array of 32-bit numbers
# @param {Array} key the HMAC input, as an array of 32-bit numbers
# @param {Number} length the length of the HMAC input, in bytes
# @return {Array} the HMAC output, as an array of 32-bit numbers
hmacSha1 = (string, key, length, keyLength) ->
if key.length > 16
key = sha1 key, keyLength
ipad = (key[i] ^ 0x36363636 for i in [0...16])
opad = (key[i] ^ 0x5C5C5C5C for i in [0...16])
hash1 = sha1 ipad.concat(string), 64 + length
sha1 opad.concat(hash1), 64 + 20
# SHA1 implementation.
#
# @private
# This method is not exported.
#
# @param {Array<Number>} string the SHA1 input, as an array of 32-bit
# numbers; the computation trashes the array
# @param {Number} length the number of bytes in the SHA1 input; used in the
# SHA1 padding algorithm
# @return {Array<Number>} the SHA1 output, as an array of 32-bit numbers
sha1 = (string, length) ->
string[length >> 2] |= 1 << (31 - ((length & 0x03) << 3))
string[(((length + 8) >> 6) << 4) + 15] = length << 3
state = Array 80
a = 0x67452301
b = 0xefcdab89
c = 0x98badcfe
d = 0x10325476
e = 0xc3d2e1f0
i = 0
limit = string.length
# Uncomment the line below to debug packing.
# console.log string.map(xxx)
while i < limit
a0 = a
b0 = b
c0 = c
d0 = d
e0 = e
for j in [0...80]
if j < 16
state[j] = string[(i + j) << 2 >> 2] | 0
else
n = (state[(j - 3) << 2 >> 2] | 0) ^ (state[(j - 8) << 2 >> 2] | 0) ^
(state[(j - 14) << 2 >> 2] | 0) ^ (state[(j - 16) << 2 >> 2] | 0)
state[j] = (n << 1) | (n >>> 31)
t = (((((a << 5) | (a >>> 27)) + e) | 0) + state[j << 2 >> 2]) | 0
if j < 20
t = (t + ((((b & c) | (~b & d)) + 0x5a827999) | 0)) | 0
else if j < 40
t = (t + (((b ^ c ^ d) + 0x6ed9eba1) | 0)) | 0
else if j < 60
t = (t + (((b & c) | (b & d) | (c & d)) - 0x70e44324) | 0) | 0
else
t = (t + (((b ^ c ^ d) - 0x359d3e2a) | 0)) | 0
e = d
d = c
c = (b << 30) | (b >>> 2)
b = a
a = t
# Uncomment the line below to debug block computation.
# console.log(xxx(v) for v in [a, b, c, d, e])
a = (a0 + a) | 0
b = (b0 + b) | 0
c = (c0 + c) | 0
d = (d0 + d) | 0
e = (e0 + e) | 0
i = (i + 16) | 0
# Uncomment the line below to see the input to the base64 encoder.
# console.log(xxx(v) for v in [a, b, c, d, e])
[a, b, c, d, e]
# SHA256 implementation.
#
# @private
# This method is not exported.
#
# @param {Array<Number>} string the SHA256 input, as an array of 32-bit
# numbers; the computation trashes the array
# @param {Number} length the number of bytes in the SHA256 input; used in the
# SHA256 padding algorithm
# @return {Array<Number>} the SHA256 output, as an array of 32-bit numbers
sha256 = (string, length) ->
string[length >> 2] |= 1 << (31 - ((length & 0x03) << 3))
string[(((length + 8) >> 6) << 4) + 15] = length << 3
state = Array 80
[a, b, c, d, e, f, g, h] = sha256Init
i = 0
limit = string.length
# Uncomment the line below to debug packing.
# console.log string.map(xxx)
while i < limit
a0 = a
b0 = b
c0 = c
d0 = d
e0 = e
f0 = f
g0 = g
h0 = h
for j in [0...64]
if j < 16
sj = state[j] = string[(i + j) << 2 >> 2] | 0
else
gamma0x = state[(j - 15) << 2 >> 2] | 0
gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^
((gamma0x << 14) | (gamma0x >>> 18)) ^
(gamma0x >>> 3)
gamma1x = state[(j - 2) << 2 >> 2] | 0
gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^
((gamma1x << 13) | (gamma1x >>> 19)) ^
(gamma1x >>> 10)
sj = state[j] = (((gamma0 + (state[(j - 7) << 2 >> 2] | 0)) | 0) +
((gamma1 + (state[(j - 16) << 2 >> 2] | 0)) | 0)) | 0
ch = (e & f) ^ (~e & g)
maj = (a & b) ^ (a & c) ^ (b & c)
sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^
((a << 10) | (a >>> 22))
sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^
((e << 7) | (e >>> 25))
t1 = (((((h + sigma1) | 0) + ((ch + sj) | 0)) | 0) +
(sha256Key[j << 2 >> 2] | 0)) | 0
t2 = (sigma0 + maj) | 0
h = g
g = f
f = e
e = (d + t1) | 0
d = c
c = b
b = a
a = (t1 + t2) | 0
# Uncomment the line below to debug block computation.
# console.log(['round', j])
# console.log(xxx(v) for v in [a, b, c, d, e, f, g, h])
a = (a0 + a) | 0
b = (b0 + b) | 0
c = (c0 + c) | 0
d = (d0 + d) | 0
e = (e0 + e) | 0
f = (f0 + f) | 0
g = (g0 + g) | 0
h = (h0 + h) | 0
i += 16
# Uncomment the line below to see the input to the base64 encoder.
# console.log(xxx(v) for v in [a, b, c, d, e, f, g, h])
[a, b, c, d, e, f, g, h]
# Uncomment the definition below for debugging.
#
# Returns the hexadecimal representation of a 32-bit number.
xxx = (n) ->
n = (1 << 30) * 4 + n if n < 0
n.toString 16
# The SHA256 initial vector.
#
# @private
# This constant is not exported.
sha256Init = []
# The SHA256 round constants.
#
# @private
# This constant is not exported.
sha256Key = []
# Generating code for sha256Init and sha256Key.
do ->
# @return {Number} the fractional part of a number times 2^32, rounded down
fractional = (x) ->
((x - Math.floor(x)) * 0x100000000) | 0
prime = 2
for i in [0...64]
loop
isPrime = true
factor = 2
while factor * factor <= prime
if prime % factor is 0
isPrime = false
break
factor += 1
break if isPrime
prime += 1
continue
if i < 8
sha256Init[i] = fractional Math.pow(prime, 1/2)
sha256Key[i] = fractional Math.pow(prime, 1/3)
prime += 1
# Uncomment the line below to debug the constant-generating code.
# console.log(xxx(v) for v in sha256Init)
# console.log(xxx(v) for v in sha256Key)
# Converts a 32-bit number array into a base64-encoded string.
#
# @private
# This method is not exported.
#
# @param {Array} an array of big-endian 32-bit numbers
# @return {String} base64 encoding of the given array of numbers
arrayToBase64 = (array) ->
string = ""
i = 0
limit = array.length * 4
while i < limit
i2 = i
trit = ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 16
i2 += 1
trit |= ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 8
i2 += 1
trit |= (array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF
string += _base64Digits[(trit >> 18) & 0x3F]
string += _base64Digits[(trit >> 12) & 0x3F]
i += 1
if i >= limit
string += '='
else
string += _base64Digits[(trit >> 6) & 0x3F]
i += 1
if i >= limit
string += '='
else
string += _base64Digits[trit & 0x3F]
i += 1
string
_base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
# Converts an ASCII string into array of 32-bit numbers.
stringToArray = (string) ->
array = []
mask = 0xFF
for i in [0...string.length]
array[i >> 2] |= (string.charCodeAt(i) & mask) << ((3 - (i & 3)) << 3)
array