diff --git a/doc/plugin-test-authorize.js b/doc/plugin-test-authorize.js index 3da49345..9f3683f1 100644 --- a/doc/plugin-test-authorize.js +++ b/doc/plugin-test-authorize.js @@ -60,88 +60,42 @@ define(['angular', 'sjcl'], function(angular, sjcl) { }; $window.testCreateSuseridServer = function() { - - var url = mediaStream.url.api("users"); - console.log("URL", url); - var data = { - id: mediaStream.api.id, - sid: mediaStream.api.sid - } - console.log("Data", data); - $.ajax({ - type: "POST", - url: url, - contentType: "application/json", - dataType: "json", - data: JSON.stringify(data), - success: function(data) { - if (data.success) { - lastNonce = data.nonce; - lastUserid = data.userid; - lastUseridCombo = data.useridcombo; - lastSecret = data.secret; - console.log("Retrieved user", data); - } - }, - error: function() { - console.log("error", arguments) - } + mediaStream.users.register(function(data) { + lastNonce = data.nonce; + lastUserid = data.userid; + lastUseridCombo = data.useridcombo; + lastSecret = data.secret; + console.log("Retrieved user", data); + }, function() { + console.log("Register error", arguments); }); - }; $window.testAuthorize = function(useridCombo, secret) { - console.log("Testing authorize with userid", useridCombo, secret); - var url = mediaStream.url.api("sessions") + "/" + mediaStream.api.id + "/"; - console.log("URL", url); - var data = { - id: mediaStream.api.id, - sid: mediaStream.api.sid, - useridcombo: useridCombo, - secret: secret - } - console.log("Data", data); - $.ajax({ - type: "PATCH", - url: url, - contentType: "application/json", - dataType: "json", - data: JSON.stringify(data), - success: function(data) { - if (data.success) { - lastNonce = data.nonce; - lastUserid = data.userid; - console.log("Retrieved nonce", data); - } - }, - error: function() { - console.log("error", arguments) - } + mediaStream.users.authorize(useridCombo, secret, function(data) { + lastNonce = data.nonce; + lastUserid = data.userid; + console.log("Retrieved nonce", data); + }, function() { + console.log("Authorize error", arguments); }); - }; $window.testLastAuthenticate = function() { - if (!lastNonce || !lastUserid) { console.log("Run testAuthorize first."); return } - mediaStream.api.requestAuthentication(lastUserid, lastNonce); - }; $window.testLastAuthorize = function() { - if (!lastUseridCombo || !lastSecret) { console.log("Run testCreateSuseridServer fist."); return } - $window.testAuthorize(lastUseridCombo, lastSecret); - }; }]); diff --git a/server.conf.in b/server.conf.in index b7007509..ec1cd06d 100644 --- a/server.conf.in +++ b/server.conf.in @@ -71,6 +71,10 @@ sessionSecret = the-default-secret-do-not-keep-me ; all users will join this room if enabled. If it is disabled then a room join ; form will be shown instead. ;defaultRoomEnabled = true +; Server token is a public random string which is used to enhance security of +; server generated security tokens. When the serverToken is changed all existing +; nonces become invalid. Use 32 or 64 byte random data. +;serverToken = i-did-not-change-the-public-token-boo ; Full path to an extra templates directory. Templates in this directory ending ; with .html will be parsed on startup and can be used to fill the supported ; extra-* template slots. If the extra folder has a sub folder "static", the @@ -89,7 +93,7 @@ sessionSecret = the-default-secret-do-not-keep-me [users] ; Set to true to enable user functionality. -;enabled = false +enabled = false ; Set authorization mode for users. Currently implemented is the "sharedsecret" ; mode which does validate the userid with a HMAC authentication secret. ; The format goes like this: diff --git a/src/app/spreed-speakfreely-server/config.go b/src/app/spreed-speakfreely-server/config.go index b5516a55..fe3fec09 100644 --- a/src/app/spreed-speakfreely-server/config.go +++ b/src/app/spreed-speakfreely-server/config.go @@ -26,20 +26,38 @@ import ( ) type Config struct { - Title string // Title - ver string // Version (not exported to Javascript) - S string // Static URL prefix with version - B string // Base URL - StunURIs []string // STUN server URIs - TurnURIs []string // TURN server URIs - Tokens bool // True when we got a tokens file - Version string // Server version number - globalRoomid string // Id of the global room (not exported to Javascript) - defaultRoomEnabled bool // Flag to enable default room ("") - Plugin string // Plugin to load + Title string // Title + ver string // Version (not exported to Javascript) + S string // Static URL prefix with version + B string // Base URL + Token string // Server token + StunURIs []string // STUN server URIs + TurnURIs []string // TURN server URIs + Tokens bool // True when we got a tokens file + Version string // Server version number + UsersEnabled bool // Flag if users are enabled + UsersAllowRegistration bool // Flag if users can register + Plugin string // Plugin to load + globalRoomid string // Id of the global room (not exported to Javascript) + defaultRoomEnabled bool // Flag if default room ("") is enabled } -func NewConfig(title, ver, runtimeVersion, basePath string, stunURIs, turnURIs []string, tokens bool, globalRoomid string, defaultRoomEnabled bool, plugin string) *Config { +func NewConfig(title, ver, runtimeVersion, basePath, serverToken string, stunURIs, turnURIs []string, tokens bool, globalRoomid string, defaultRoomEnabled, usersEnabled, usersAllowRegistration bool, plugin string) *Config { sv := fmt.Sprintf("static/ver=%s", ver) - return &Config{Title: title, ver: ver, S: sv, B: basePath, StunURIs: stunURIs, TurnURIs: turnURIs, Tokens: tokens, Version: runtimeVersion, globalRoomid: globalRoomid, defaultRoomEnabled: defaultRoomEnabled, Plugin: plugin} + return &Config{ + Title: title, + ver: ver, + S: sv, + B: basePath, + Token: serverToken, + StunURIs: stunURIs, + TurnURIs: turnURIs, + Tokens: tokens, + Version: runtimeVersion, + UsersEnabled: usersEnabled, + UsersAllowRegistration: usersAllowRegistration, + Plugin: plugin, + globalRoomid: globalRoomid, + defaultRoomEnabled: defaultRoomEnabled, + } } diff --git a/src/app/spreed-speakfreely-server/hub.go b/src/app/spreed-speakfreely-server/hub.go index 09063cf2..4833f6b4 100644 --- a/src/app/spreed-speakfreely-server/hub.go +++ b/src/app/spreed-speakfreely-server/hub.go @@ -174,7 +174,11 @@ func (h *Hub) CreateSession(st *SessionToken) *Session { session = NewSession(id, sid, "") log.Println("Created new session id", len(id), id, sid) } else { - session = NewSession(st.Id, st.Sid, st.Userid) + userid := st.Userid + if !h.config.UsersEnabled { + userid = "" + } + session = NewSession(st.Id, st.Sid, userid) } return session diff --git a/src/app/spreed-speakfreely-server/main.go b/src/app/spreed-speakfreely-server/main.go index 4d175543..9e8ed7c0 100644 --- a/src/app/spreed-speakfreely-server/main.go +++ b/src/app/spreed-speakfreely-server/main.go @@ -263,6 +263,24 @@ func runner(runtime phoenix.Runtime) error { defaultRoomEnabled = defaultRoomEnabledString == "true" } + serverToken, err := runtime.GetString("app", "serverToken") + if err == nil { + //TODO(longsleep): When we have a database, generate this once from random source and store it. + serverToken = "i-did-not-change-the-public-token-boo" + } + + usersEnabled := false + usersEnabledString, err := runtime.GetString("users", "enabled") + if err == nil { + usersEnabled = usersEnabledString == "true" + } + + usersAllowRegistration := false + usersAllowRegistrationString, err := runtime.GetString("users", "allowRegistration") + if err == nil { + usersAllowRegistration = usersAllowRegistrationString == "true" + } + // Create token provider. var tokenProvider TokenProvider if tokenFile != "" { @@ -271,7 +289,7 @@ func runner(runtime phoenix.Runtime) error { } // Create configuration data structure. - config = NewConfig(title, ver, runtimeVersion, basePath, stunURIs, turnURIs, tokenProvider != nil, globalRoomid, defaultRoomEnabled, plugin) + config = NewConfig(title, ver, runtimeVersion, basePath, serverToken, stunURIs, turnURIs, tokenProvider != nil, globalRoomid, defaultRoomEnabled, usersEnabled, usersAllowRegistration, plugin) // Load templates. tt := template.New("") @@ -298,9 +316,6 @@ func runner(runtime phoenix.Runtime) error { // Create our hub instance. hub := NewHub(runtimeVersion, config, sessionSecret, turnSecret) - // Create Users handler. - users := NewUsers(hub, runtime) - // Set number of go routines if it is 1 if goruntime.GOMAXPROCS(0) == 1 { nCPU := goruntime.NumCPU() @@ -346,9 +361,13 @@ func runner(runtime phoenix.Runtime) error { api.SetMux(r.PathPrefix("/api/v1/").Subrouter()) api.AddResource(&Rooms{}, "/rooms") api.AddResourceWithWrapper(&Tokens{tokenProvider}, httputils.MakeGzipHandler, "/tokens") - if users.Enabled { + if usersEnabled { + // Create Users handler. + users := NewUsers(hub, runtime) api.AddResource(&Sessions{hub: hub, users: users}, "/sessions/{id}/") - api.AddResource(users, "/users") + if usersAllowRegistration { + api.AddResource(users, "/users") + } } if statsEnabled { api.AddResourceWithWrapper(&Stats{hub: hub}, httputils.MakeGzipHandler, "/stats") diff --git a/src/app/spreed-speakfreely-server/users.go b/src/app/spreed-speakfreely-server/users.go index b6aa18a0..e71b8cd0 100644 --- a/src/app/spreed-speakfreely-server/users.go +++ b/src/app/spreed-speakfreely-server/users.go @@ -99,55 +99,32 @@ type UserNonce struct { type Users struct { hub *Hub - Enabled bool - Create bool Handler UsersHandler } func NewUsers(hub *Hub, runtime phoenix.Runtime) *Users { - enabled := false - enabledString, err := runtime.GetString("users", "enabled") - if err == nil { - enabled = enabledString == "true" - } - - create := false - createString, err := runtime.GetString("users", "allowRegistration") - if err == nil { - create = createString == "true" - } - var handler UsersHandler - if enabled { - - mode, _ := runtime.GetString("users", "mode") - switch mode { - case "sharedsecret": - secret, _ := runtime.GetString("users", "sharedsecret_secret") - if secret != "" { - handler = &UsersSharedsecretHandler{secret: []byte(secret)} - } - default: - mode = "" - } - - if handler == nil { - enabled = false - } else { - log.Printf("Enabled users handler '%s'\n", mode) - if create { - log.Println("Enabled users registration") - } + mode, _ := runtime.GetString("users", "mode") + switch mode { + case "sharedsecret": + secret, _ := runtime.GetString("users", "sharedsecret_secret") + if secret != "" { + handler = &UsersSharedsecretHandler{secret: []byte(secret)} } + default: + mode = "" + } + if handler == nil { + handler = &UsersSharedsecretHandler{secret: []byte("")} } + log.Printf("Enabled users handler '%s'\n", mode) + return &Users{ hub: hub, - Enabled: enabled, - Create: create, Handler: handler, } @@ -156,10 +133,6 @@ func NewUsers(hub *Hub, runtime phoenix.Runtime) *Users { // Post is used to create new userids for this server. func (users *Users) Post(request *http.Request) (int, interface{}, http.Header) { - if !users.Create { - return 404, "404 page not found", http.Header{"Content-Type": {"text/plain"}} - } - decoder := json.NewDecoder(request.Body) var snr SessionNonceRequest err := decoder.Decode(&snr) diff --git a/static/js/controllers/mediastreamcontroller.js b/static/js/controllers/mediastreamcontroller.js index b97e5193..3a055c7a 100644 --- a/static/js/controllers/mediastreamcontroller.js +++ b/static/js/controllers/mediastreamcontroller.js @@ -18,7 +18,7 @@ * along with this program. If not, see . * */ -define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigScreen, moment) { +define(['underscore', 'bigscreen', 'moment', 'sjcl', 'webrtc.adapter'], function(_, BigScreen, moment, sjcl) { return ["$scope", "$rootScope", "$element", "$window", "$timeout", "safeDisplayName", "safeApply", "mediaStream", "appData", "playSound", "desktopNotify", "alertify", "toastr", "translation", "fileDownload", function($scope, $rootScope, $element, $window, $timeout, safeDisplayName, safeApply, mediaStream, appData, playSound, desktopNotify, alertify, toastr, translation, fileDownload) { @@ -130,6 +130,7 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS // Default scope data. $scope.status = "initializing"; $scope.id = null; + $scope.userid = null; $scope.peer = null; $scope.dialing = null; $scope.conference = null; @@ -153,6 +154,7 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS language: "" } }; + $scope.withStoredLogin = false; // Data voids. var cache = {}; @@ -365,9 +367,11 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS var reloadDialog = false; mediaStream.api.e.on("received.self", function(event, data) { + $timeout.cancel(ttlTimeout); safeApply($scope, function(scope) { scope.id = scope.myid = data.Id; + scope.userid = data.Userid; scope.turn = data.Turn; scope.stun = data.Stun; scope.refreshWebrtcSettings(); @@ -385,6 +389,66 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS }, 300); } } + + // Support authentication. + if (!data.Userid && mediaStream.config.UsersEnabled) { + + var key = mediaStream.config.Token; + + // Check if we have something in store. + var login = localStorage.getItem("mediastream-login"); + if (login) { + safeApply($scope, function(scope) { + scope.withStoredLogin = true; + }); + try { + login = sjcl.decrypt(key, login); + login = JSON.parse(login) + } catch(err) { + console.error("Failed to parse login data", err); + login = {}; + } + console.log("Trying to authorize with stored credentials ..."); + switch (login.v) { + case 1: + var useridCombo = login.a; + var secret = login.b; + var expiry = login.t; + if (useridCombo && secret) { + mediaStream.users.authorize(useridCombo, secret, function(data) { + console.info("Retrieved nonce - authenticating as user:", data.userid); + mediaStream.api.requestAuthentication(data.userid, data.nonce); + delete data.nonce; + }, function(data, status) { + console.error("Failed to authorize session", status, data); + }); + } + break; + default: + console.warn("Unknown stored credentials", login.v); + break + } + } + if (!login && mediaStream.config.UsersAllowRegistration) { + console.log("No userid - creating one ..."); + mediaStream.users.register(function(data) { + var login = sjcl.encrypt(key, JSON.stringify({ + v: 1, + t: data.timestamp || "", + a: data.useridcombo, + b: data.secret, + })); + localStorage.setItem("mediastream-login", login); + console.info("Created new userid:", data.userid); + mediaStream.api.requestAuthentication(data.userid, data.nonce); + delete data.nonce; + }, function(data, status) { + console.error("Failed to create userid", status, data); + }); + } + + } + // Support to upgrade stuff when ttl was reached. if (data.Turn.ttl) { ttlTimeout = $timeout(function() { @@ -519,6 +583,7 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS if (opts.soft) { return; } + $scope.userid = null; break; case "error": if (reconnecting || connected) { @@ -593,6 +658,12 @@ define(['underscore', 'bigscreen', 'moment', 'webrtc.adapter'], function(_, BigS } }); + $scope.$watch("userid", function(userid) { + if (userid) { + console.info("Session is now authenticated:", userid); + } + }); + // Apply all layout stuff as classes to our element. $scope.$watch("layout", (function() { var makeName = function(prefix, n) { diff --git a/static/js/libs/sjcl.js b/static/js/libs/sjcl.js index c4c5e39d..1d5110d1 100644 --- a/static/js/libs/sjcl.js +++ b/static/js/libs/sjcl.js @@ -1,26 +1,2419 @@ // http://bitwiseshiftleft.github.io/sjcl/ -// ./configure --without-all --with-sha256 --with-sha512 --with-sha1 --with-hmac --with-codecBase64 --with-codecString +// ./configure --without-all --with-sha256 --with-sha512 --with-sha1 --with-hmac --with-codecBase64 --with-codecString --with-aes --with-ccm --with-convenience --compress=none // Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh, Stanford University. // SJCL is dual-licensed under the GNU GPL version 2.0 or higher, and a 2-clause BSD license. -"use strict";var sjcl={cipher:{},hash:{},keyexchange:{},mode:{},misc:{},codec:{},exception:{corrupt:function(a){this.toString=function(){return"CORRUPT: "+this.message};this.message=a},invalid:function(a){this.toString=function(){return"INVALID: "+this.message};this.message=a},bug:function(a){this.toString=function(){return"BUG: "+this.message};this.message=a},notReady:function(a){this.toString=function(){return"NOT READY: "+this.message};this.message=a}}}; -"undefined"!==typeof module&&module.exports&&(module.exports=sjcl); -sjcl.bitArray={bitSlice:function(a,b,c){a=sjcl.bitArray.l(a.slice(b/32),32-(b&31)).slice(1);return void 0===c?a:sjcl.bitArray.clamp(a,c-b)},extract:function(a,b,c){var e=Math.floor(-b-c&31);return((b+c-1^b)&-32?a[b/32|0]<<32-e^a[b/32+1|0]>>>e:a[b/32|0]>>>e)&(1<>b-1,1));return a},partial:function(a,b,c){return 32===a?b:(c?b|0:b<<32-a)+0x10000000000*a},getPartial:function(a){return Math.round(a/0x10000000000)||32},equal:function(a,b){if(sjcl.bitArray.bitLength(a)!==sjcl.bitArray.bitLength(b))return!1;var c=0,e;for(e=0;e>>b),c=a[d]<<32-b;d=a.length?a[a.length-1]:0;a=sjcl.bitArray.getPartial(d);e.push(sjcl.bitArray.partial(b+a&31,32>>24),d<<=8;return decodeURIComponent(escape(b))},toBits:function(a){a=unescape(encodeURIComponent(a));var b=[],c,e=0;for(c=0;c>>d)>>>26),6>d?(k=a[c]<<6-d,d+=26,c++):(k<<=6,d-=6);for(;e.length&3&&!b;)e+="=";return e},toBits:function(a,b){a=a.replace(/\s|=/g,"");var c=[],e,d=0,h=sjcl.codec.base64.j,k=0,f;b&&(h=h.substr(0,62)+"-_");for(e=0;ef)throw new sjcl.exception.invalid("this isn't base64!");26>>d),k=f<<32-d):(d+=6,k^=f<<32-d)}d&56&&c.push(sjcl.bitArray.partial(d&56,k,1));return c}};sjcl.codec.base64url={fromBits:function(a){return sjcl.codec.base64.fromBits(a,1,1)},toBits:function(a){return sjcl.codec.base64.toBits(a,1)}};sjcl.hash.sha256=function(a){this.d[0]||this.h();a?(this.c=a.c.slice(0),this.b=a.b.slice(0),this.a=a.a):this.reset()};sjcl.hash.sha256.hash=function(a){return(new sjcl.hash.sha256).update(a).finalize()}; -sjcl.hash.sha256.prototype={blockSize:512,reset:function(){this.c=this.f.slice(0);this.b=[];this.a=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.b=sjcl.bitArray.concat(this.b,a);b=this.a;a=this.a=b+sjcl.bitArray.bitLength(a);for(b=512+b&-512;b<=a;b+=512)this.e(c.splice(0,16));return this},finalize:function(){var a,b=this.b,c=this.c,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0);b.push(Math.floor(this.a/ -4294967296));for(b.push(this.a|0);b.length;)this.e(b.splice(0,16));this.reset();return c},f:[],d:[],h:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}var b=0,c=2,e;a:for(;64>b;c++){for(e=2;e*e<=c;e++)if(0===c%e)continue a;8>b&&(this.f[b]=a(Math.pow(c,0.5)));this.d[b]=a(Math.pow(c,1/3));b++}},e:function(a){var b,c,e=a.slice(0),d=this.c,h=this.d,k=d[0],f=d[1],g=d[2],u=d[3],m=d[4],v=d[5],w=d[6],x=d[7];for(a=0;64>a;a++)16>a?b=e[a]:(b=e[a+1&15],c=e[a+14&15],b=e[a&15]=(b>>>7^b>>>18^b>>>3^ -b<<25^b<<14)+(c>>>17^c>>>19^c>>>10^c<<15^c<<13)+e[a&15]+e[a+9&15]|0),b=b+x+(m>>>6^m>>>11^m>>>25^m<<26^m<<21^m<<7)+(w^m&(v^w))+h[a],x=w,w=v,v=m,m=u+b|0,u=g,g=f,f=k,k=b+(f&g^u&(f^g))+(f>>>2^f>>>13^f>>>22^f<<30^f<<19^f<<10)|0;d[0]=d[0]+k|0;d[1]=d[1]+f|0;d[2]=d[2]+g|0;d[3]=d[3]+u|0;d[4]=d[4]+m|0;d[5]=d[5]+v|0;d[6]=d[6]+w|0;d[7]=d[7]+x|0}};sjcl.hash.sha512=function(a){this.d[0]||this.h();a?(this.c=a.c.slice(0),this.b=a.b.slice(0),this.a=a.a):this.reset()};sjcl.hash.sha512.hash=function(a){return(new sjcl.hash.sha512).update(a).finalize()}; -sjcl.hash.sha512.prototype={blockSize:1024,reset:function(){this.c=this.f.slice(0);this.b=[];this.a=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.b=sjcl.bitArray.concat(this.b,a);b=this.a;a=this.a=b+sjcl.bitArray.bitLength(a);for(b=1024+b&-1024;b<=a;b+=1024)this.e(c.splice(0,32));return this},finalize:function(){var a,b=this.b,c=this.c,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+4;a&31;a++)b.push(0);b.push(0);b.push(0); -b.push(Math.floor(this.a/0x100000000));for(b.push(this.a|0);b.length;)this.e(b.splice(0,32));this.reset();return c},f:[],n:[12372232,13281083,9762859,1914609,15106769,4090911,4308331,8266105],d:[],o:[2666018,15689165,5061423,9034684,4764984,380953,1658779,7176472,197186,7368638,14987916,16757986,8096111,1480369,13046325,6891156,15813330,5187043,9229749,11312229,2818677,10937475,4324308,1135541,6741931,11809296,16458047,15666916,11046850,698149,229999,945776,13774844,2541862,12856045,9810911,11494366, -7844520,15576806,8533307,15795044,4337665,16291729,5553712,15684120,6662416,7413802,12308920,13816008,4303699,9366425,10176680,13195875,4295371,6546291,11712675,15708924,1519456,15772530,6568428,6495784,8568297,13007125,7492395,2515356,12632583,14740254,7262584,1535930,13146278,16321966,1853211,294276,13051027,13221564,1051980,4080310,6651434,14088940,4675607],h:function(){function a(a){return 0x100000000*(a-Math.floor(a))|0}function b(a){return 0x10000000000*(a-Math.floor(a))&255}var c=0,e=2,d;a:for(;80> -c;e++){for(d=2;d*d<=e;d++)if(0===e%d)continue a;8>c&&(this.f[2*c]=a(Math.pow(e,0.5)),this.f[2*c+1]=b(Math.pow(e,0.5))<<24|this.n[c]);this.d[2*c]=a(Math.pow(e,1/3));this.d[2*c+1]=b(Math.pow(e,1/3))<<24|this.o[c];c++}},e:function(a){var b,c,e=a.slice(0),d=this.c,h=this.d,k=d[0],f=d[1],g=d[2],u=d[3],m=d[4],v=d[5],w=d[6],x=d[7],R=d[8],H=d[9],S=d[10],I=d[11],T=d[12],J=d[13],U=d[14],K=d[15],q=k,n=f,A=g,y=u,B=m,z=v,N=w,C=x,r=R,p=H,L=S,D=I,M=T,E=J,O=U,F=K;for(a=0;80>a;a++){if(16>a)b=e[2*a],c=e[2*a+1];else{c= -e[2*(a-15)];var l=e[2*(a-15)+1];b=(l<<31|c>>>1)^(l<<24|c>>>8)^c>>>7;var s=(c<<31|l>>>1)^(c<<24|l>>>8)^(c<<25|l>>>7);c=e[2*(a-2)];var t=e[2*(a-2)+1],l=(t<<13|c>>>19)^(c<<3|t>>>29)^c>>>6,t=(c<<13|t>>>19)^(t<<3|c>>>29)^(c<<26|t>>>6),P=e[2*(a-7)],Q=e[2*(a-16)],G=e[2*(a-16)+1];c=s+e[2*(a-7)+1];b=b+P+(c>>>0>>0?1:0);c+=t;b+=l+(c>>>0>>0?1:0);c+=G;b+=Q+(c>>>0>>0?1:0)}e[2*a]=b|=0;e[2*a+1]=c|=0;var P=r&L^~r&M,V=p&D^~p&E,t=q&A^q&B^A&B,X=n&y^n&z^y&z,Q=(n<<4|q>>>28)^(q<<30|n>>>2)^(q<<25|n>>>7),G=(q<<4| -n>>>28)^(n<<30|q>>>2)^(n<<25|q>>>7),Y=h[2*a],W=h[2*a+1],l=F+((r<<18|p>>>14)^(r<<14|p>>>18)^(p<<23|r>>>9)),s=O+((p<<18|r>>>14)^(p<<14|r>>>18)^(r<<23|p>>>9))+(l>>>0>>0?1:0),l=l+V,s=s+(P+(l>>>0>>0?1:0)),l=l+W,s=s+(Y+(l>>>0>>0?1:0)),l=l+c,s=s+(b+(l>>>0>>0?1:0));c=G+X;b=Q+t+(c>>>0>>0?1:0);O=M;F=E;M=L;E=D;L=r;D=p;p=C+l|0;r=N+s+(p>>>0>>0?1:0)|0;N=B;C=z;B=A;z=y;A=q;y=n;n=l+c|0;q=s+b+(n>>>0>>0?1:0)|0}f=d[1]=f+n|0;d[0]=k+q+(f>>>0>>0?1:0)|0;u=d[3]=u+y|0;d[2]=g+A+(u>>>0>>0?1:0)|0;v= -d[5]=v+z|0;d[4]=m+B+(v>>>0>>0?1:0)|0;x=d[7]=x+C|0;d[6]=w+N+(x>>>0>>0?1:0)|0;H=d[9]=H+p|0;d[8]=R+r+(H>>>0

>>0?1:0)|0;I=d[11]=I+D|0;d[10]=S+L+(I>>>0>>0?1:0)|0;J=d[13]=J+E|0;d[12]=T+M+(J>>>0>>0?1:0)|0;K=d[15]=K+F|0;d[14]=U+O+(K>>>0>>0?1:0)|0}};sjcl.hash.sha1=function(a){a?(this.c=a.c.slice(0),this.b=a.b.slice(0),this.a=a.a):this.reset()};sjcl.hash.sha1.hash=function(a){return(new sjcl.hash.sha1).update(a).finalize()}; -sjcl.hash.sha1.prototype={blockSize:512,reset:function(){this.c=this.f.slice(0);this.b=[];this.a=0;return this},update:function(a){"string"===typeof a&&(a=sjcl.codec.utf8String.toBits(a));var b,c=this.b=sjcl.bitArray.concat(this.b,a);b=this.a;a=this.a=b+sjcl.bitArray.bitLength(a);for(b=this.blockSize+b&-this.blockSize;b<=a;b+=this.blockSize)this.e(c.splice(0,16));return this},finalize:function(){var a,b=this.b,c=this.c,b=sjcl.bitArray.concat(b,[sjcl.bitArray.partial(1,1)]);for(a=b.length+2;a&15;a++)b.push(0); -b.push(Math.floor(this.a/0x100000000));for(b.push(this.a|0);b.length;)this.e(b.splice(0,16));this.reset();return c},f:[1732584193,4023233417,2562383102,271733878,3285377520],d:[1518500249,1859775393,2400959708,3395469782],e:function(a){var b,c,e,d,h,k,f=a.slice(0),g=this.c;c=g[0];e=g[1];d=g[2];h=g[3];k=g[4];for(a=0;79>=a;a++)16<=a&&(f[a]=(f[a-3]^f[a-8]^f[a-14]^f[a-16])<<1|(f[a-3]^f[a-8]^f[a-14]^f[a-16])>>>31),b=19>=a?e&d|~e&h:39>=a?e^d^h:59>=a?e&d|e&h|d&h:79>=a?e^d^h:void 0,b=(c<<5|c>>>27)+b+k+f[a]+ -this.d[Math.floor(a/20)]|0,k=h,h=d,d=e<<30|e>>>2,e=c,c=b;g[0]=g[0]+c|0;g[1]=g[1]+e|0;g[2]=g[2]+d|0;g[3]=g[3]+h|0;g[4]=g[4]+k|0}};sjcl.misc.hmac=function(a,b){this.k=b=b||sjcl.hash.sha256;var c=[[],[]],e,d=b.prototype.blockSize/32;this.g=[new b,new b];a.length>d&&(a=b.hash(a));for(e=0;e>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255]; + + // shift rows and add rcon + if (i%keyLen === 0) { + tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24; + rcon = rcon<<1 ^ (rcon>>7)*283; + } + } + + encKey[i] = encKey[i-keyLen] ^ tmp; + } + + // schedule decryption keys + for (j = 0; i; j++, i--) { + tmp = encKey[j&3 ? i : i - 4]; + if (i<=4 || j<4) { + decKey[j] = tmp; + } else { + decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^ + decTable[1][sbox[tmp>>16 & 255]] ^ + decTable[2][sbox[tmp>>8 & 255]] ^ + decTable[3][sbox[tmp & 255]]; + } + } +}; + +sjcl.cipher.aes.prototype = { + // public + /* Something like this might appear here eventually + name: "AES", + blockSize: 4, + keySizes: [4,6,8], + */ + + /** + * Encrypt an array of 4 big-endian words. + * @param {Array} data The plaintext. + * @return {Array} The ciphertext. + */ + encrypt:function (data) { return this._crypt(data,0); }, + + /** + * Decrypt an array of 4 big-endian words. + * @param {Array} data The ciphertext. + * @return {Array} The plaintext. + */ + decrypt:function (data) { return this._crypt(data,1); }, + + /** + * The expanded S-box and inverse S-box tables. These will be computed + * on the client so that we don't have to send them down the wire. + * + * There are two tables, _tables[0] is for encryption and + * _tables[1] is for decryption. + * + * The first 4 sub-tables are the expanded S-box with MixColumns. The + * last (_tables[01][4]) is the S-box itself. + * + * @private + */ + _tables: [[[],[],[],[],[]],[[],[],[],[],[]]], + + /** + * Expand the S-box tables. + * + * @private + */ + _precompute: function () { + var encTable = this._tables[0], decTable = this._tables[1], + sbox = encTable[4], sboxInv = decTable[4], + i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec; + + // Compute double and third tables + for (i = 0; i < 256; i++) { + th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i; + } + + for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) { + // Compute sbox + s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4; + s = s>>8 ^ s&255 ^ 99; + sbox[x] = s; + sboxInv[s] = x; + + // Compute MixColumns + x8 = d[x4 = d[x2 = d[x]]]; + tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100; + tEnc = d[s]*0x101 ^ s*0x1010100; + + for (i = 0; i < 4; i++) { + encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8; + decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8; + } + } + + // Compactify. Considerable speedup on Firefox. + for (i = 0; i < 5; i++) { + encTable[i] = encTable[i].slice(0); + decTable[i] = decTable[i].slice(0); + } + }, + + /** + * Encryption and decryption core. + * @param {Array} input Four words to be encrypted or decrypted. + * @param dir The direction, 0 for encrypt and 1 for decrypt. + * @return {Array} The four encrypted or decrypted words. + * @private + */ + _crypt:function (input, dir) { + if (input.length !== 4) { + throw new sjcl.exception.invalid("invalid aes block size"); + } + + var key = this._key[dir], + // state variables a,b,c,d are loaded with pre-whitened data + a = input[0] ^ key[0], + b = input[dir ? 3 : 1] ^ key[1], + c = input[2] ^ key[2], + d = input[dir ? 1 : 3] ^ key[3], + a2, b2, c2, + + nInnerRounds = key.length/4 - 2, + i, + kIndex = 4, + out = [0,0,0,0], + table = this._tables[dir], + + // load up the tables + t0 = table[0], + t1 = table[1], + t2 = table[2], + t3 = table[3], + sbox = table[4]; + + // Inner rounds. Cribbed from OpenSSL. + for (i = 0; i < nInnerRounds; i++) { + a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex]; + b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1]; + c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2]; + d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3]; + kIndex += 4; + a=a2; b=b2; c=c2; + } + + // Last round. + for (i = 0; i < 4; i++) { + out[dir ? 3&-i : i] = + sbox[a>>>24 ]<<24 ^ + sbox[b>>16 & 255]<<16 ^ + sbox[c>>8 & 255]<<8 ^ + sbox[d & 255] ^ + key[kIndex++]; + a2=a; a=b; b=c; c=d; d=a2; + } + + return out; + } +}; + +/** @fileOverview Arrays of bits, encoded as arrays of Numbers. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +/** @namespace Arrays of bits, encoded as arrays of Numbers. + * + * @description + *

+ * These objects are the currency accepted by SJCL's crypto functions. + *

+ * + *

+ * Most of our crypto primitives operate on arrays of 4-byte words internally, + * but many of them can take arguments that are not a multiple of 4 bytes. + * This library encodes arrays of bits (whose size need not be a multiple of 8 + * bits) as arrays of 32-bit words. The bits are packed, big-endian, into an + * array of words, 32 bits at a time. Since the words are double-precision + * floating point numbers, they fit some extra data. We use this (in a private, + * possibly-changing manner) to encode the number of bits actually present + * in the last word of the array. + *

+ * + *

+ * Because bitwise ops clear this out-of-band data, these arrays can be passed + * to ciphers like AES which want arrays of words. + *

+ */ +sjcl.bitArray = { + /** + * Array slices in units of bits. + * @param {bitArray} a The array to slice. + * @param {Number} bstart The offset to the start of the slice, in bits. + * @param {Number} bend The offset to the end of the slice, in bits. If this is undefined, + * slice until the end of the array. + * @return {bitArray} The requested slice. + */ + bitSlice: function (a, bstart, bend) { + a = sjcl.bitArray._shiftRight(a.slice(bstart/32), 32 - (bstart & 31)).slice(1); + return (bend === undefined) ? a : sjcl.bitArray.clamp(a, bend-bstart); + }, + + /** + * Extract a number packed into a bit array. + * @param {bitArray} a The array to slice. + * @param {Number} bstart The offset to the start of the slice, in bits. + * @param {Number} length The length of the number to extract. + * @return {Number} The requested slice. + */ + extract: function(a, bstart, blength) { + // FIXME: this Math.floor is not necessary at all, but for some reason + // seems to suppress a bug in the Chromium JIT. + var x, sh = Math.floor((-bstart-blength) & 31); + if ((bstart + blength - 1 ^ bstart) & -32) { + // it crosses a boundary + x = (a[bstart/32|0] << (32 - sh)) ^ (a[bstart/32+1|0] >>> sh); + } else { + // within a single word + x = a[bstart/32|0] >>> sh; + } + return x & ((1< 0 && len) { + a[l-1] = sjcl.bitArray.partial(len, a[l-1] & 0x80000000 >> (len-1), 1); + } + return a; + }, + + /** + * Make a partial word for a bit array. + * @param {Number} len The number of bits in the word. + * @param {Number} x The bits. + * @param {Number} [0] _end Pass 1 if x has already been shifted to the high side. + * @return {Number} The partial word. + */ + partial: function (len, x, _end) { + if (len === 32) { return x; } + return (_end ? x|0 : x << (32-len)) + len * 0x10000000000; + }, + + /** + * Get the number of bits used by a partial word. + * @param {Number} x The partial word. + * @return {Number} The number of bits used by the partial word. + */ + getPartial: function (x) { + return Math.round(x/0x10000000000) || 32; + }, + + /** + * Compare two arrays for equality in a predictable amount of time. + * @param {bitArray} a The first array. + * @param {bitArray} b The second array. + * @return {boolean} true if a == b; false otherwise. + */ + equal: function (a, b) { + if (sjcl.bitArray.bitLength(a) !== sjcl.bitArray.bitLength(b)) { + return false; + } + var x = 0, i; + for (i=0; i= 32; shift -= 32) { + out.push(carry); + carry = 0; + } + if (shift === 0) { + return out.concat(a); + } + + for (i=0; i>>shift); + carry = a[i] << (32-shift); + } + last2 = a.length ? a[a.length-1] : 0; + shift2 = sjcl.bitArray.getPartial(last2); + out.push(sjcl.bitArray.partial(shift+shift2 & 31, (shift + shift2 > 32) ? carry : out.pop(),1)); + return out; + }, + + /** xor a block of 4 words together. + * @private + */ + _xor4: function(x,y) { + return [x[0]^y[0],x[1]^y[1],x[2]^y[2],x[3]^y[3]]; + } +}; +/** @fileOverview Bit array codec implementations. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +/** @namespace UTF-8 strings */ +sjcl.codec.utf8String = { + /** Convert from a bitArray to a UTF-8 string. */ + fromBits: function (arr) { + var out = "", bl = sjcl.bitArray.bitLength(arr), i, tmp; + for (i=0; i>> 24); + tmp <<= 8; + } + return decodeURIComponent(escape(out)); + }, + + /** Convert from a UTF-8 string to a bitArray. */ + toBits: function (str) { + str = unescape(encodeURIComponent(str)); + var out = [], i, tmp=0; + for (i=0; i>>bits) >>> 26); + if (bits < 6) { + ta = arr[i] << (6-bits); + bits += 26; + i++; + } else { + ta <<= 6; + bits -= 6; + } + } + while ((out.length & 3) && !_noEquals) { out += "="; } + return out; + }, + + /** Convert from a base64 string to a bitArray */ + toBits: function(str, _url) { + str = str.replace(/\s|=/g,''); + var out = [], i, bits=0, c = sjcl.codec.base64._chars, ta=0, x; + if (_url) { + c = c.substr(0,62) + '-_'; + } + for (i=0; i 26) { + bits -= 26; + out.push(ta ^ x>>>bits); + ta = x << (32-bits); + } else { + bits += 6; + ta ^= x << (32-bits); + } + } + if (bits&56) { + out.push(sjcl.bitArray.partial(bits&56, ta, 1)); + } + return out; + } +}; + +sjcl.codec.base64url = { + fromBits: function (arr) { return sjcl.codec.base64.fromBits(arr,1,1); }, + toBits: function (str) { return sjcl.codec.base64.toBits(str,1); } +}; +/** @fileOverview Javascript SHA-256 implementation. + * + * An older version of this implementation is available in the public + * domain, but this one is (c) Emily Stark, Mike Hamburg, Dan Boneh, + * Stanford University 2008-2010 and BSD-licensed for liability + * reasons. + * + * Special thanks to Aldo Cortesi for pointing out several bugs in + * this code. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +/** + * Context for a SHA-256 operation in progress. + * @constructor + * @class Secure Hash Algorithm, 256 bits. + */ +sjcl.hash.sha256 = function (hash) { + if (!this._key[0]) { this._precompute(); } + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 16 big-endian words. + */ +sjcl.hash.sha256.hash = function (data) { + return (new sjcl.hash.sha256()).update(data).finalize(); +}; + +sjcl.hash.sha256.prototype = { + /** + * The hash's block size, in bits. + * @constant + */ + blockSize: 512, + + /** + * Reset the hash state. + * @return this + */ + reset:function () { + this._h = this._init.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Input several words to the hash. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if (typeof data === "string") { + data = sjcl.codec.utf8String.toBits(data); + } + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 512+ol & -512; i <= nl; i+= 512) { + this._block(b.splice(0,16)); + } + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 8 big-endian words. + */ + finalize:function () { + var i, b = this._buffer, h = this._h; + + // Round out and push the buffer + b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); + + // Round out the buffer to a multiple of 16 words, less the 2 length words. + for (i = b.length + 2; i & 15; i++) { + b.push(0); + } + + // append the length + b.push(Math.floor(this._length / 0x100000000)); + b.push(this._length | 0); + + while (b.length) { + this._block(b.splice(0,16)); + } + + this.reset(); + return h; + }, + + /** + * The SHA-256 initialization vector, to be precomputed. + * @private + */ + _init:[], + /* + _init:[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19], + */ + + /** + * The SHA-256 hash key, to be precomputed. + * @private + */ + _key:[], + /* + _key: + [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2], + */ + + + /** + * Function to precompute _init and _key. + * @private + */ + _precompute: function () { + var i = 0, prime = 2, factor; + + function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; } + + outer: for (; i<64; prime++) { + for (factor=2; factor*factor <= prime; factor++) { + if (prime % factor === 0) { + // not a prime + continue outer; + } + } + + if (i<8) { + this._init[i] = frac(Math.pow(prime, 1/2)); + } + this._key[i] = frac(Math.pow(prime, 1/3)); + i++; + } + }, + + /** + * Perform one cycle of SHA-256. + * @param {bitArray} words one block of words. + * @private + */ + _block:function (words) { + var i, tmp, a, b, + w = words.slice(0), + h = this._h, + k = this._key, + h0 = h[0], h1 = h[1], h2 = h[2], h3 = h[3], + h4 = h[4], h5 = h[5], h6 = h[6], h7 = h[7]; + + /* Rationale for placement of |0 : + * If a value can overflow is original 32 bits by a factor of more than a few + * million (2^23 ish), there is a possibility that it might overflow the + * 53-bit mantissa and lose precision. + * + * To avoid this, we clamp back to 32 bits by |'ing with 0 on any value that + * propagates around the loop, and on the hash state h[]. I don't believe + * that the clamps on h4 and on h0 are strictly necessary, but it's close + * (for h4 anyway), and better safe than sorry. + * + * The clamps on h[] are necessary for the output to be correct even in the + * common case and for short inputs. + */ + for (i=0; i<64; i++) { + // load up the input word for this round + if (i<16) { + tmp = w[i]; + } else { + a = w[(i+1 ) & 15]; + b = w[(i+14) & 15]; + tmp = w[i&15] = ((a>>>7 ^ a>>>18 ^ a>>>3 ^ a<<25 ^ a<<14) + + (b>>>17 ^ b>>>19 ^ b>>>10 ^ b<<15 ^ b<<13) + + w[i&15] + w[(i+9) & 15]) | 0; + } + + tmp = (tmp + h7 + (h4>>>6 ^ h4>>>11 ^ h4>>>25 ^ h4<<26 ^ h4<<21 ^ h4<<7) + (h6 ^ h4&(h5^h6)) + k[i]); // | 0; + + // shift register + h7 = h6; h6 = h5; h5 = h4; + h4 = h3 + tmp | 0; + h3 = h2; h2 = h1; h1 = h0; + + h0 = (tmp + ((h1&h2) ^ (h3&(h1^h2))) + (h1>>>2 ^ h1>>>13 ^ h1>>>22 ^ h1<<30 ^ h1<<19 ^ h1<<10)) | 0; + } + + h[0] = h[0]+h0 | 0; + h[1] = h[1]+h1 | 0; + h[2] = h[2]+h2 | 0; + h[3] = h[3]+h3 | 0; + h[4] = h[4]+h4 | 0; + h[5] = h[5]+h5 | 0; + h[6] = h[6]+h6 | 0; + h[7] = h[7]+h7 | 0; + } +}; + + +/** @fileOverview Javascript SHA-512 implementation. + * + * This implementation was written for CryptoJS by Jeff Mott and adapted for + * SJCL by Stefan Thomas. + * + * CryptoJS (c) 2009–2012 by Jeff Mott. All rights reserved. + * Released with New BSD License + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + * @author Jeff Mott + * @author Stefan Thomas + */ + +/** + * Context for a SHA-512 operation in progress. + * @constructor + * @class Secure Hash Algorithm, 512 bits. + */ +sjcl.hash.sha512 = function (hash) { + if (!this._key[0]) { this._precompute(); } + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 16 big-endian words. + */ +sjcl.hash.sha512.hash = function (data) { + return (new sjcl.hash.sha512()).update(data).finalize(); +}; + +sjcl.hash.sha512.prototype = { + /** + * The hash's block size, in bits. + * @constant + */ + blockSize: 1024, + + /** + * Reset the hash state. + * @return this + */ + reset:function () { + this._h = this._init.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Input several words to the hash. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if (typeof data === "string") { + data = sjcl.codec.utf8String.toBits(data); + } + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 1024+ol & -1024; i <= nl; i+= 1024) { + this._block(b.splice(0,32)); + } + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 16 big-endian words. + */ + finalize:function () { + var i, b = this._buffer, h = this._h; + + // Round out and push the buffer + b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); + + // Round out the buffer to a multiple of 32 words, less the 4 length words. + for (i = b.length + 4; i & 31; i++) { + b.push(0); + } + + // append the length + b.push(0); + b.push(0); + b.push(Math.floor(this._length / 0x100000000)); + b.push(this._length | 0); + + while (b.length) { + this._block(b.splice(0,32)); + } + + this.reset(); + return h; + }, + + /** + * The SHA-512 initialization vector, to be precomputed. + * @private + */ + _init:[], + + /** + * Least significant 24 bits of SHA512 initialization values. + * + * Javascript only has 53 bits of precision, so we compute the 40 most + * significant bits and add the remaining 24 bits as constants. + * + * @private + */ + _initr: [ 0xbcc908, 0xcaa73b, 0x94f82b, 0x1d36f1, 0xe682d1, 0x3e6c1f, 0x41bd6b, 0x7e2179 ], + + /* + _init: + [0x6a09e667, 0xf3bcc908, 0xbb67ae85, 0x84caa73b, 0x3c6ef372, 0xfe94f82b, 0xa54ff53a, 0x5f1d36f1, + 0x510e527f, 0xade682d1, 0x9b05688c, 0x2b3e6c1f, 0x1f83d9ab, 0xfb41bd6b, 0x5be0cd19, 0x137e2179], + */ + + /** + * The SHA-512 hash key, to be precomputed. + * @private + */ + _key:[], + + /** + * Least significant 24 bits of SHA512 key values. + * @private + */ + _keyr: + [0x28ae22, 0xef65cd, 0x4d3b2f, 0x89dbbc, 0x48b538, 0x05d019, 0x194f9b, 0x6d8118, + 0x030242, 0x706fbe, 0xe4b28c, 0xffb4e2, 0x7b896f, 0x1696b1, 0xc71235, 0x692694, + 0xf14ad2, 0x4f25e3, 0x8cd5b5, 0xac9c65, 0x2b0275, 0xa6e483, 0x41fbd4, 0x1153b5, + 0x66dfab, 0xb43210, 0xfb213f, 0xef0ee4, 0xa88fc2, 0x0aa725, 0x03826f, 0x0e6e70, + 0xd22ffc, 0x26c926, 0xc42aed, 0x95b3df, 0xaf63de, 0x77b2a8, 0xedaee6, 0x82353b, + 0xf10364, 0x423001, 0xf89791, 0x54be30, 0xef5218, 0x65a910, 0x71202a, 0xbbd1b8, + 0xd2d0c8, 0x41ab53, 0x8eeb99, 0x9b48a8, 0xc95a63, 0x418acb, 0x63e373, 0xb2b8a3, + 0xefb2fc, 0x172f60, 0xf0ab72, 0x6439ec, 0x631e28, 0x82bde9, 0xc67915, 0x72532b, + 0x26619c, 0xc0c207, 0xe0eb1e, 0x6ed178, 0x176fba, 0xc898a6, 0xf90dae, 0x1c471b, + 0x047d84, 0xc72493, 0xc9bebc, 0x100d4c, 0x3e42b6, 0x657e2a, 0xd6faec, 0x475817], + + /* + _key: + [0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817], + */ + + /** + * Function to precompute _init and _key. + * @private + */ + _precompute: function () { + // XXX: This code is for precomputing the SHA256 constants, change for + // SHA512 and re-enable. + var i = 0, prime = 2, factor; + + function frac(x) { return (x-Math.floor(x)) * 0x100000000 | 0; } + function frac2(x) { return (x-Math.floor(x)) * 0x10000000000 & 0xff; } + + outer: for (; i<80; prime++) { + for (factor=2; factor*factor <= prime; factor++) { + if (prime % factor === 0) { + // not a prime + continue outer; + } + } + + if (i<8) { + this._init[i*2] = frac(Math.pow(prime, 1/2)); + this._init[i*2+1] = (frac2(Math.pow(prime, 1/2)) << 24) | this._initr[i]; + } + this._key[i*2] = frac(Math.pow(prime, 1/3)); + this._key[i*2+1] = (frac2(Math.pow(prime, 1/3)) << 24) | this._keyr[i]; + i++; + } + }, + + /** + * Perform one cycle of SHA-512. + * @param {bitArray} words one block of words. + * @private + */ + _block:function (words) { + var i, wrh, wrl, + w = words.slice(0), + h = this._h, + k = this._key, + h0h = h[ 0], h0l = h[ 1], h1h = h[ 2], h1l = h[ 3], + h2h = h[ 4], h2l = h[ 5], h3h = h[ 6], h3l = h[ 7], + h4h = h[ 8], h4l = h[ 9], h5h = h[10], h5l = h[11], + h6h = h[12], h6l = h[13], h7h = h[14], h7l = h[15]; + + // Working variables + var ah = h0h, al = h0l, bh = h1h, bl = h1l, + ch = h2h, cl = h2l, dh = h3h, dl = h3l, + eh = h4h, el = h4l, fh = h5h, fl = h5l, + gh = h6h, gl = h6l, hh = h7h, hl = h7l; + + for (i=0; i<80; i++) { + // load up the input word for this round + if (i<16) { + wrh = w[i * 2]; + wrl = w[i * 2 + 1]; + } else { + // Gamma0 + var gamma0xh = w[(i-15) * 2]; + var gamma0xl = w[(i-15) * 2 + 1]; + var gamma0h = + ((gamma0xl << 31) | (gamma0xh >>> 1)) ^ + ((gamma0xl << 24) | (gamma0xh >>> 8)) ^ + (gamma0xh >>> 7); + var gamma0l = + ((gamma0xh << 31) | (gamma0xl >>> 1)) ^ + ((gamma0xh << 24) | (gamma0xl >>> 8)) ^ + ((gamma0xh << 25) | (gamma0xl >>> 7)); + + // Gamma1 + var gamma1xh = w[(i-2) * 2]; + var gamma1xl = w[(i-2) * 2 + 1]; + var gamma1h = + ((gamma1xl << 13) | (gamma1xh >>> 19)) ^ + ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ + (gamma1xh >>> 6); + var gamma1l = + ((gamma1xh << 13) | (gamma1xl >>> 19)) ^ + ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ + ((gamma1xh << 26) | (gamma1xl >>> 6)); + + // Shortcuts + var wr7h = w[(i-7) * 2]; + var wr7l = w[(i-7) * 2 + 1]; + + var wr16h = w[(i-16) * 2]; + var wr16l = w[(i-16) * 2 + 1]; + + // W(round) = gamma0 + W(round - 7) + gamma1 + W(round - 16) + wrl = gamma0l + wr7l; + wrh = gamma0h + wr7h + ((wrl >>> 0) < (gamma0l >>> 0) ? 1 : 0); + wrl += gamma1l; + wrh += gamma1h + ((wrl >>> 0) < (gamma1l >>> 0) ? 1 : 0); + wrl += wr16l; + wrh += wr16h + ((wrl >>> 0) < (wr16l >>> 0) ? 1 : 0); + } + + w[i*2] = wrh |= 0; + w[i*2 + 1] = wrl |= 0; + + // Ch + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + + // Maj + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + // Sigma0 + var sigma0h = ((al << 4) | (ah >>> 28)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((ah << 4) | (al >>> 28)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + + // Sigma1 + var sigma1h = ((el << 18) | (eh >>> 14)) ^ ((el << 14) | (eh >>> 18)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((eh << 18) | (el >>> 14)) ^ ((eh << 14) | (el >>> 18)) ^ ((el << 23) | (eh >>> 9)); + + // K(round) + var krh = k[i*2]; + var krl = k[i*2+1]; + + // t1 = h + sigma1 + ch + K(round) + W(round) + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + t1l += chl; + t1h += chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + t1l += krl; + t1h += krh + ((t1l >>> 0) < (krl >>> 0) ? 1 : 0); + t1l += wrl; + t1h += wrh + ((t1l >>> 0) < (wrl >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash + h0l = h[1] = (h0l + al) | 0; + h[0] = (h0h + ah + ((h0l >>> 0) < (al >>> 0) ? 1 : 0)) | 0; + h1l = h[3] = (h1l + bl) | 0; + h[2] = (h1h + bh + ((h1l >>> 0) < (bl >>> 0) ? 1 : 0)) | 0; + h2l = h[5] = (h2l + cl) | 0; + h[4] = (h2h + ch + ((h2l >>> 0) < (cl >>> 0) ? 1 : 0)) | 0; + h3l = h[7] = (h3l + dl) | 0; + h[6] = (h3h + dh + ((h3l >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + h4l = h[9] = (h4l + el) | 0; + h[8] = (h4h + eh + ((h4l >>> 0) < (el >>> 0) ? 1 : 0)) | 0; + h5l = h[11] = (h5l + fl) | 0; + h[10] = (h5h + fh + ((h5l >>> 0) < (fl >>> 0) ? 1 : 0)) | 0; + h6l = h[13] = (h6l + gl) | 0; + h[12] = (h6h + gh + ((h6l >>> 0) < (gl >>> 0) ? 1 : 0)) | 0; + h7l = h[15] = (h7l + hl) | 0; + h[14] = (h7h + hh + ((h7l >>> 0) < (hl >>> 0) ? 1 : 0)) | 0; + } +}; + + +/** @fileOverview Javascript SHA-1 implementation. + * + * Based on the implementation in RFC 3174, method 1, and on the SJCL + * SHA-256 implementation. + * + * @author Quinn Slack + */ + +/** + * Context for a SHA-1 operation in progress. + * @constructor + * @class Secure Hash Algorithm, 160 bits. + */ +sjcl.hash.sha1 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.sha1.hash = function (data) { + return (new sjcl.hash.sha1()).update(data).finalize(); +}; + +sjcl.hash.sha1.prototype = { + /** + * The hash's block size, in bits. + * @constant + */ + blockSize: 512, + + /** + * Reset the hash state. + * @return this + */ + reset:function () { + this._h = this._init.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Input several words to the hash. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if (typeof data === "string") { + data = sjcl.codec.utf8String.toBits(data); + } + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = this.blockSize+ol & -this.blockSize; i <= nl; + i+= this.blockSize) { + this._block(b.splice(0,16)); + } + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. TODO + */ + finalize:function () { + var i, b = this._buffer, h = this._h; + + // Round out and push the buffer + b = sjcl.bitArray.concat(b, [sjcl.bitArray.partial(1,1)]); + // Round out the buffer to a multiple of 16 words, less the 2 length words. + for (i = b.length + 2; i & 15; i++) { + b.push(0); + } + + // append the length + b.push(Math.floor(this._length / 0x100000000)); + b.push(this._length | 0); + + while (b.length) { + this._block(b.splice(0,16)); + } + + this.reset(); + return h; + }, + + /** + * The SHA-1 initialization vector. + * @private + */ + _init:[0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0], + + /** + * The SHA-1 hash key. + * @private + */ + _key:[0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6], + + /** + * The SHA-1 logical functions f(0), f(1), ..., f(79). + * @private + */ + _f:function(t, b, c, d) { + if (t <= 19) { + return (b & c) | (~b & d); + } else if (t <= 39) { + return b ^ c ^ d; + } else if (t <= 59) { + return (b & c) | (b & d) | (c & d); + } else if (t <= 79) { + return b ^ c ^ d; + } + }, + + /** + * Circular left-shift operator. + * @private + */ + _S:function(n, x) { + return (x << n) | (x >>> 32-n); + }, + + /** + * Perform one cycle of SHA-1. + * @param {bitArray} words one block of words. + * @private + */ + _block:function (words) { + var t, tmp, a, b, c, d, e, + w = words.slice(0), + h = this._h, + k = this._key; + + a = h[0]; b = h[1]; c = h[2]; d = h[3]; e = h[4]; + + for (t=0; t<=79; t++) { + if (t >= 16) { + w[t] = this._S(1, w[t-3] ^ w[t-8] ^ w[t-14] ^ w[t-16]); + } + tmp = (this._S(5, a) + this._f(t, b, c, d) + e + w[t] + + this._key[Math.floor(t/20)]) | 0; + e = d; + d = c; + c = this._S(30, b); + b = a; + a = tmp; + } + + h[0] = (h[0]+a) |0; + h[1] = (h[1]+b) |0; + h[2] = (h[2]+c) |0; + h[3] = (h[3]+d) |0; + h[4] = (h[4]+e) |0; + } +}; +/** @fileOverview CCM mode implementation. + * + * Special thanks to Roy Nicholson for pointing out a bug in our + * implementation. + * + * @author Emily Stark + * @author Mike Hamburg + * @author Dan Boneh + */ + +/** @namespace CTR mode with CBC MAC. */ +sjcl.mode.ccm = { + /** The name of the mode. + * @constant + */ + name: "ccm", + + /** Encrypt in CCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} plaintext The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [adata=[]] The authenticated data. + * @param {Number} [tlen=64] the desired tag length, in bits. + * @return {bitArray} The encrypted data, an array of bytes. + */ + encrypt: function(prf, plaintext, iv, adata, tlen) { + var L, i, out = plaintext.slice(0), tag, w=sjcl.bitArray, ivl = w.bitLength(iv) / 8, ol = w.bitLength(out) / 8; + tlen = tlen || 64; + adata = adata || []; + + if (ivl < 7) { + throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); + } + + // compute the length of the length + for (L=2; L<4 && ol >>> 8*L; L++) {} + if (L < 15 - ivl) { L = 15-ivl; } + iv = w.clamp(iv,8*(15-L)); + + // compute the tag + tag = sjcl.mode.ccm._computeTag(prf, plaintext, iv, adata, tlen, L); + + // encrypt + out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); + + return w.concat(out.data, out.tag); + }, + + /** Decrypt in CCM mode. + * @static + * @param {Object} prf The pseudorandom function. It must have a block size of 16 bytes. + * @param {bitArray} ciphertext The ciphertext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} [[]] adata The authenticated data. + * @param {Number} [64] tlen the desired tag length, in bits. + * @return {bitArray} The decrypted data. + */ + decrypt: function(prf, ciphertext, iv, adata, tlen) { + tlen = tlen || 64; + adata = adata || []; + var L, i, + w=sjcl.bitArray, + ivl = w.bitLength(iv) / 8, + ol = w.bitLength(ciphertext), + out = w.clamp(ciphertext, ol - tlen), + tag = w.bitSlice(ciphertext, ol - tlen), tag2; + + + ol = (ol - tlen) / 8; + + if (ivl < 7) { + throw new sjcl.exception.invalid("ccm: iv must be at least 7 bytes"); + } + + // compute the length of the length + for (L=2; L<4 && ol >>> 8*L; L++) {} + if (L < 15 - ivl) { L = 15-ivl; } + iv = w.clamp(iv,8*(15-L)); + + // decrypt + out = sjcl.mode.ccm._ctrMode(prf, out, iv, tag, tlen, L); + + // check the tag + tag2 = sjcl.mode.ccm._computeTag(prf, out.data, iv, adata, tlen, L); + if (!w.equal(out.tag, tag2)) { + throw new sjcl.exception.corrupt("ccm: tag doesn't match"); + } + + return out.data; + }, + + /* Compute the (unencrypted) authentication tag, according to the CCM specification + * @param {Object} prf The pseudorandom function. + * @param {bitArray} plaintext The plaintext data. + * @param {bitArray} iv The initialization value. + * @param {bitArray} adata The authenticated data. + * @param {Number} tlen the desired tag length, in bits. + * @return {bitArray} The tag, but not yet encrypted. + * @private + */ + _computeTag: function(prf, plaintext, iv, adata, tlen, L) { + // compute B[0] + var q, mac, field = 0, offset = 24, tmp, i, macData = [], w=sjcl.bitArray, xor = w._xor4; + + tlen /= 8; + + // check tag length and message length + if (tlen % 2 || tlen < 4 || tlen > 16) { + throw new sjcl.exception.invalid("ccm: invalid tag length"); + } + + if (adata.length > 0xFFFFFFFF || plaintext.length > 0xFFFFFFFF) { + // I don't want to deal with extracting high words from doubles. + throw new sjcl.exception.bug("ccm: can't deal with 4GiB or more data"); + } + + // mac the flags + mac = [w.partial(8, (adata.length ? 1<<6 : 0) | (tlen-2) << 2 | L-1)]; + + // mac the iv and length + mac = w.concat(mac, iv); + mac[3] |= w.bitLength(plaintext)/8; + mac = prf.encrypt(mac); + + + if (adata.length) { + // mac the associated data. start with its length... + tmp = w.bitLength(adata)/8; + if (tmp <= 0xFEFF) { + macData = [w.partial(16, tmp)]; + } else if (tmp <= 0xFFFFFFFF) { + macData = w.concat([w.partial(16,0xFFFE)], [tmp]); + } // else ... + + // mac the data itself + macData = w.concat(macData, adata); + for (i=0; i bs) { + key = Hash.hash(key); + } + + for (i=0; iUse sjcl.random as a singleton for this class! + *

+ * This random number generator is a derivative of Ferguson and Schneier's + * generator Fortuna. It collects entropy from various events into several + * pools, implemented by streaming SHA-256 instances. It differs from + * ordinary Fortuna in a few ways, though. + *

+ * + *

+ * Most importantly, it has an entropy estimator. This is present because + * there is a strong conflict here between making the generator available + * as soon as possible, and making sure that it doesn't "run on empty". + * In Fortuna, there is a saved state file, and the system is likely to have + * time to warm up. + *

+ * + *

+ * Second, because users are unlikely to stay on the page for very long, + * and to speed startup time, the number of pools increases logarithmically: + * a new pool is created when the previous one is actually used for a reseed. + * This gives the same asymptotic guarantees as Fortuna, but gives more + * entropy to early reseeds. + *

+ * + *

+ * The entire mechanism here feels pretty klunky. Furthermore, there are + * several improvements that should be made, including support for + * dedicated cryptographic functions that may be present in some browsers; + * state files in local storage; cookies containing randomness; etc. So + * look for improvements in future versions. + *

+ */ +sjcl.prng = function(defaultParanoia) { + + /* private */ + this._pools = [new sjcl.hash.sha256()]; + this._poolEntropy = [0]; + this._reseedCount = 0; + this._robins = {}; + this._eventId = 0; + + this._collectorIds = {}; + this._collectorIdNext = 0; + + this._strength = 0; + this._poolStrength = 0; + this._nextReseed = 0; + this._key = [0,0,0,0,0,0,0,0]; + this._counter = [0,0,0,0]; + this._cipher = undefined; + this._defaultParanoia = defaultParanoia; + + /* event listener stuff */ + this._collectorsStarted = false; + this._callbacks = {progress: {}, seeded: {}}; + this._callbackI = 0; + + /* constants */ + this._NOT_READY = 0; + this._READY = 1; + this._REQUIRES_RESEED = 2; + + this._MAX_WORDS_PER_BURST = 65536; + this._PARANOIA_LEVELS = [0,48,64,96,128,192,256,384,512,768,1024]; + this._MILLISECONDS_PER_RESEED = 30000; + this._BITS_PER_RESEED = 80; +}; + +sjcl.prng.prototype = { + /** Generate several random words, and return them in an array. + * A word consists of 32 bits (4 bytes) + * @param {Number} nwords The number of words to generate. + */ + randomWords: function (nwords, paranoia) { + var out = [], i, readiness = this.isReady(paranoia), g; + + if (readiness === this._NOT_READY) { + throw new sjcl.exception.notReady("generator isn't seeded"); + } else if (readiness & this._REQUIRES_RESEED) { + this._reseedFromPools(!(readiness & this._READY)); + } + + for (i=0; i0) { + estimatedEntropy++; + tmp = tmp >>> 1; + } + } + } + this._pools[robin].update([id,this._eventId++,2,estimatedEntropy,t,data.length].concat(data)); + } + break; + + case "string": + if (estimatedEntropy === undefined) { + /* English text has just over 1 bit per character of entropy. + * But this might be HTML or something, and have far less + * entropy than English... Oh well, let's just say one bit. + */ + estimatedEntropy = data.length; + } + this._pools[robin].update([id,this._eventId++,3,estimatedEntropy,t,data.length]); + this._pools[robin].update(data); + break; + + default: + err=1; + } + if (err) { + throw new sjcl.exception.bug("random: addEntropy only supports number, array of numbers or string"); + } + + /* record the new strength */ + this._poolEntropy[robin] += estimatedEntropy; + this._poolStrength += estimatedEntropy; + + /* fire off events */ + if (oldReady === this._NOT_READY) { + if (this.isReady() !== this._NOT_READY) { + this._fireEvent("seeded", Math.max(this._strength, this._poolStrength)); + } + this._fireEvent("progress", this.getProgress()); + } + }, + + /** Is the generator ready? */ + isReady: function (paranoia) { + var entropyRequired = this._PARANOIA_LEVELS[ (paranoia !== undefined) ? paranoia : this._defaultParanoia ]; + + if (this._strength && this._strength >= entropyRequired) { + return (this._poolEntropy[0] > this._BITS_PER_RESEED && (new Date()).valueOf() > this._nextReseed) ? + this._REQUIRES_RESEED | this._READY : + this._READY; + } else { + return (this._poolStrength >= entropyRequired) ? + this._REQUIRES_RESEED | this._NOT_READY : + this._NOT_READY; + } + }, + + /** Get the generator's progress toward readiness, as a fraction */ + getProgress: function (paranoia) { + var entropyRequired = this._PARANOIA_LEVELS[ paranoia ? paranoia : this._defaultParanoia ]; + + if (this._strength >= entropyRequired) { + return 1.0; + } else { + return (this._poolStrength > entropyRequired) ? + 1.0 : + this._poolStrength / entropyRequired; + } + }, + + /** start the built-in entropy collectors */ + startCollectors: function () { + if (this._collectorsStarted) { return; } + + this._eventListener = { + loadTimeCollector: this._bind(this._loadTimeCollector), + mouseCollector: this._bind(this._mouseCollector), + keyboardCollector: this._bind(this._keyboardCollector), + accelerometerCollector: this._bind(this._accelerometerCollector) + } + + if (window.addEventListener) { + window.addEventListener("load", this._eventListener.loadTimeCollector, false); + window.addEventListener("mousemove", this._eventListener.mouseCollector, false); + window.addEventListener("keypress", this._eventListener.keyboardCollector, false); + window.addEventListener("devicemotion", this._eventListener.accelerometerCollector, false); + } else if (document.attachEvent) { + document.attachEvent("onload", this._eventListener.loadTimeCollector); + document.attachEvent("onmousemove", this._eventListener.mouseCollector); + document.attachEvent("keypress", this._eventListener.keyboardCollector); + } else { + throw new sjcl.exception.bug("can't attach event"); + } + + this._collectorsStarted = true; + }, + + /** stop the built-in entropy collectors */ + stopCollectors: function () { + if (!this._collectorsStarted) { return; } + + if (window.removeEventListener) { + window.removeEventListener("load", this._eventListener.loadTimeCollector, false); + window.removeEventListener("mousemove", this._eventListener.mouseCollector, false); + window.removeEventListener("keypress", this._eventListener.keyboardCollector, false); + window.removeEventListener("devicemotion", this._eventListener.accelerometerCollector, false); + } else if (document.detachEvent) { + document.detachEvent("onload", this._eventListener.loadTimeCollector); + document.detachEvent("onmousemove", this._eventListener.mouseCollector); + document.detachEvent("keypress", this._eventListener.keyboardCollector); + } + + this._collectorsStarted = false; + }, + + /* use a cookie to store entropy. + useCookie: function (all_cookies) { + throw new sjcl.exception.bug("random: useCookie is unimplemented"); + },*/ + + /** add an event listener for progress or seeded-ness. */ + addEventListener: function (name, callback) { + this._callbacks[name][this._callbackI++] = callback; + }, + + /** remove an event listener for progress or seeded-ness */ + removeEventListener: function (name, cb) { + var i, j, cbs=this._callbacks[name], jsTemp=[]; + + /* I'm not sure if this is necessary; in C++, iterating over a + * collection and modifying it at the same time is a no-no. + */ + + for (j in cbs) { + if (cbs.hasOwnProperty(j) && cbs[j] === cb) { + jsTemp.push(j); + } + } + + for (i=0; i= 1 << this._pools.length) { + this._pools.push(new sjcl.hash.sha256()); + this._poolEntropy.push(0); + } + + /* how strong was this reseed? */ + this._poolStrength -= strength; + if (strength > this._strength) { + this._strength = strength; + } + + this._reseedCount ++; + this._reseed(reseedData); + }, + + _keyboardCollector: function () { + this._addCurrentTimeToEntropy(1); + }, + + _mouseCollector: function (ev) { + var x = ev.x || ev.clientX || ev.offsetX || 0, y = ev.y || ev.clientY || ev.offsetY || 0; + sjcl.random.addEntropy([x,y], 2, "mouse"); + this._addCurrentTimeToEntropy(0); + }, + + _loadTimeCollector: function () { + this._addCurrentTimeToEntropy(2); + }, + + _addCurrentTimeToEntropy: function (estimatedEntropy) { + if (window && window.performance && typeof window.performance.now === "function") { + //how much entropy do we want to add here? + sjcl.random.addEntropy(window.performance.now(), estimatedEntropy, "loadtime"); + } else { + sjcl.random.addEntropy((new Date()).valueOf(), estimatedEntropy, "loadtime"); + } + }, + _accelerometerCollector: function (ev) { + var ac = ev.accelerationIncludingGravity.x||ev.accelerationIncludingGravity.y||ev.accelerationIncludingGravity.z; + if(window.orientation){ + var or = window.orientation; + if (typeof or === "number") { + sjcl.random.addEntropy(or, 1, "accelerometer"); + } + } + if (ac) { + sjcl.random.addEntropy(ac, 2, "accelerometer"); + } + this._addCurrentTimeToEntropy(0); + }, + + _fireEvent: function (name, arg) { + var j, cbs=sjcl.random._callbacks[name], cbsTemp=[]; + /* TODO: there is a race condition between removing collectors and firing them */ + + /* I'm not sure if this is necessary; in C++, iterating over a + * collection and modifying it at the same time is a no-no. + */ + + for (j in cbs) { + if (cbs.hasOwnProperty(j)) { + cbsTemp.push(cbs[j]); + } + } + + for (j=0; j 4)) { + throw new sjcl.exception.invalid("json encrypt: invalid parameters"); + } + + if (typeof password === "string") { + tmp = sjcl.misc.cachedPbkdf2(password, p); + password = tmp.key.slice(0,p.ks/32); + p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.publicKey) { + tmp = password.kem(); + p.kemtag = tmp.tag; + password = tmp.key.slice(0,p.ks/32); + } + if (typeof plaintext === "string") { + plaintext = sjcl.codec.utf8String.toBits(plaintext); + } + if (typeof adata === "string") { + adata = sjcl.codec.utf8String.toBits(adata); + } + prp = new sjcl.cipher[p.cipher](password); + + /* return the json data */ + j._add(rp, p); + rp.key = password; + + /* do the encryption */ + p.ct = sjcl.mode[p.mode].encrypt(prp, plaintext, p.iv, adata, p.ts); + + //return j.encode(j._subtract(p, j.defaults)); + return p; + }, + + /** Simple encryption function. + * @param {String|bitArray} password The password or key. + * @param {String} plaintext The data to encrypt. + * @param {Object} [params] The parameters including tag, iv and salt. + * @param {Object} [rp] A returned version with filled-in parameters. + * @return {String} The ciphertext serialized data. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + */ + encrypt: function (password, plaintext, params, rp) { + var j = sjcl.json, p = j._encrypt.apply(j, arguments); + return j.encode(p); + }, + + /** Simple decryption function. + * @param {String|bitArray} password The password or key. + * @param {Object} ciphertext The cipher raw data to decrypt. + * @param {Object} [params] Additional non-default parameters. + * @param {Object} [rp] A returned object with filled parameters. + * @return {String} The plaintext. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. + */ + _decrypt: function (password, ciphertext, params, rp) { + params = params || {}; + rp = rp || {}; + + var j = sjcl.json, p = j._add(j._add(j._add({},j.defaults),ciphertext), params, true), ct, tmp, prp, adata=p.adata; + if (typeof p.salt === "string") { + p.salt = sjcl.codec.base64.toBits(p.salt); + } + if (typeof p.iv === "string") { + p.iv = sjcl.codec.base64.toBits(p.iv); + } + + if (!sjcl.mode[p.mode] || + !sjcl.cipher[p.cipher] || + (typeof password === "string" && p.iter <= 100) || + (p.ts !== 64 && p.ts !== 96 && p.ts !== 128) || + (p.ks !== 128 && p.ks !== 192 && p.ks !== 256) || + (!p.iv) || + (p.iv.length < 2 || p.iv.length > 4)) { + throw new sjcl.exception.invalid("json decrypt: invalid parameters"); + } + + if (typeof password === "string") { + tmp = sjcl.misc.cachedPbkdf2(password, p); + password = tmp.key.slice(0,p.ks/32); + p.salt = tmp.salt; + } else if (sjcl.ecc && password instanceof sjcl.ecc.elGamal.secretKey) { + password = password.unkem(sjcl.codec.base64.toBits(p.kemtag)).slice(0,p.ks/32); + } + if (typeof adata === "string") { + adata = sjcl.codec.utf8String.toBits(adata); + } + prp = new sjcl.cipher[p.cipher](password); + + /* do the decryption */ + ct = sjcl.mode[p.mode].decrypt(prp, p.ct, p.iv, adata, p.ts); + + /* return the json data */ + j._add(rp, p); + rp.key = password; + + return sjcl.codec.utf8String.fromBits(ct); + }, + + /** Simple decryption function. + * @param {String|bitArray} password The password or key. + * @param {String} ciphertext The ciphertext to decrypt. + * @param {Object} [params] Additional non-default parameters. + * @param {Object} [rp] A returned object with filled parameters. + * @return {String} The plaintext. + * @throws {sjcl.exception.invalid} if a parameter is invalid. + * @throws {sjcl.exception.corrupt} if the ciphertext is corrupt. + */ + decrypt: function (password, ciphertext, params, rp) { + var j = sjcl.json; + return j._decrypt(password, j.decode(ciphertext), params, rp); + }, + + /** Encode a flat structure into a JSON string. + * @param {Object} obj The structure to encode. + * @return {String} A JSON string. + * @throws {sjcl.exception.invalid} if obj has a non-alphanumeric property. + * @throws {sjcl.exception.bug} if a parameter has an unsupported type. + */ + encode: function (obj) { + var i, out='{', comma=''; + for (i in obj) { + if (obj.hasOwnProperty(i)) { + if (!i.match(/^[a-z0-9]+$/i)) { + throw new sjcl.exception.invalid("json encode: invalid property name"); + } + out += comma + '"' + i + '":'; + comma = ','; + + switch (typeof obj[i]) { + case 'number': + case 'boolean': + out += obj[i]; + break; + + case 'string': + out += '"' + escape(obj[i]) + '"'; + break; + + case 'object': + out += '"' + sjcl.codec.base64.fromBits(obj[i],0) + '"'; + break; + + default: + throw new sjcl.exception.bug("json encode: unsupported type"); + } + } + } + return out+'}'; + }, + + /** Decode a simple (flat) JSON string into a structure. The ciphertext, + * adata, salt and iv will be base64-decoded. + * @param {String} str The string. + * @return {Object} The decoded structure. + * @throws {sjcl.exception.invalid} if str isn't (simple) JSON. + */ + decode: function (str) { + str = str.replace(/\s/g,''); + if (!str.match(/^\{.*\}$/)) { + throw new sjcl.exception.invalid("json decode: this isn't json!"); + } + var a = str.replace(/^\{|\}$/g, '').split(/,/), out={}, i, m; + for (i=0; i