forked from j62/ctbrec
1
0
Fork 0

Add HMAC support to the webinterface

The webinterface didn't work, if HMAC authentication was enabled.
To make this work, the webinterface downloads the HMAC from the
server and stores it in the local storage of the browser. The
download URL is secured by Basic Auth. The credentials are configured
in the server.json
This commit is contained in:
0xboobface 2019-08-04 12:51:13 +02:00
parent 8450ddd98c
commit bb02b5fd9f
8 changed files with 170 additions and 24 deletions

View File

@ -1,9 +1,14 @@
2.2.0
========================
* Added HMAC authentication support to the webinterface
2.1.0 2.1.0
======================== ========================
This release is mainly for the server. This release is mainly for the server.
* Added webinterface for the server. Has to be activated in the server config * Added webinterface for the server. Has to be activated in the server config
(webinterface). You can access it via http://host:4711/static/index.html (webinterface). You can access it via http://host:port/static/index.html
* Disabled MyFreeCams for the time being. (If you are brave, you can still * Disabled MyFreeCams for the time being. (If you are brave, you can still
use an older version, but don't blame me, if your account or IP is getting use an older version, but don't blame me, if your account or IP is getting
blocked) blocked)

View File

@ -4,6 +4,7 @@ import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
@ -16,11 +17,11 @@ public class Hmac {
private static final transient Logger LOG = LoggerFactory.getLogger(Hmac.class); private static final transient Logger LOG = LoggerFactory.getLogger(Hmac.class);
public static byte[] generateKey() { public static byte[] generateKey() {
LOG.debug("Generating key"); LOG.debug("Generating HMAC key");
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
byte[] key = new byte[32]; byte[] key = new byte[32];
random.nextBytes(key); random.nextBytes(key);
return key; return Base64.getEncoder().encode(key);
} }
public static String calculate(String msg, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException { public static String calculate(String msg, byte[] key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {
@ -42,7 +43,10 @@ public class Hmac {
* @param hash * @param hash
* @return string * @return string
*/ */
static String bytesToHex(byte[] hash) { public static String bytesToHex(byte[] hash) {
if (hash == null) {
return "";
}
StringBuffer hexString = new StringBuffer(); StringBuffer hexString = new StringBuffer();
for (int i = 0; i < hash.length; i++) { for (int i = 0; i < hash.length; i++) {
String hex = Integer.toHexString(0xff & hash[i]); String hex = Integer.toHexString(0xff & hash[i]);

View File

@ -115,4 +115,6 @@ public class Settings {
public int windowX; public int windowX;
public int windowY; public int windowY;
public boolean webinterface = false; public boolean webinterface = false;
public String webinterfaceUsername = "ctbrec";
public String webinterfacePassword = "sucks";
} }

View File

@ -31,7 +31,7 @@ public class StaticFileServlet extends HttpServlet {
private void serveFile(String resource, HttpServletResponse resp) throws IOException { private void serveFile(String resource, HttpServletResponse resp) throws IOException {
InputStream resourceAsStream = getClass().getResourceAsStream(classPathRoot + resource); InputStream resourceAsStream = getClass().getResourceAsStream(classPathRoot + resource);
if(resourceAsStream == null) { if (resourceAsStream == null) {
throw new FileNotFoundException(); throw new FileNotFoundException();
} }
resp.setContentType(URLConnection.guessContentTypeFromName(resource)); resp.setContentType(URLConnection.guessContentTypeFromName(resource));
@ -39,7 +39,7 @@ public class StaticFileServlet extends HttpServlet {
OutputStream out = resp.getOutputStream(); OutputStream out = resp.getOutputStream();
int length = 0; int length = 0;
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
while( (length = resourceAsStream.read(buffer)) >= 0 ) { while ((length = resourceAsStream.read(buffer)) >= 0) {
out.write(buffer, 0, length); out.write(buffer, 0, length);
} }
} }

View File

@ -8,17 +8,34 @@ import java.net.BindException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.SecurityHandler;
import org.eclipse.jetty.security.UserStore;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.json.JSONObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
import ctbrec.Config; import ctbrec.Config;
import ctbrec.Version; import ctbrec.Version;
import ctbrec.event.EventBusHolder; import ctbrec.event.EventBusHolder;
@ -128,10 +145,14 @@ public class HttpServer {
http.setIdleTimeout(this.config.getSettings().httpTimeout); http.setIdleTimeout(this.config.getSettings().httpTimeout);
server.addConnector(http); server.addConnector(http);
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/secured/*");
server.setHandler(context);
ServletHandler handler = new ServletHandler(); ServletHandler handler = new ServletHandler();
server.setHandler(handler); //server.setHandler(handler);
HandlerList handlers = new HandlerList(); HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[] { handler }); handlers.setHandlers(new Handler[] { context, handler });
server.setHandler(handlers); server.setHandler(handlers);
RecorderServlet recorderServlet = new RecorderServlet(recorder, sites); RecorderServlet recorderServlet = new RecorderServlet(recorder, sites);
@ -142,12 +163,31 @@ public class HttpServer {
holder = new ServletHolder(hlsServlet); holder = new ServletHolder(hlsServlet);
handler.addServletWithMapping(holder, "/hls/*"); handler.addServletWithMapping(holder, "/hls/*");
if (this.config.getSettings().webinterface) { if (this.config.getSettings().webinterface) {
String staticContext = "/static/*"; LOG.info("Register static file servlet under {}", context.getContextPath());
LOG.info("Register static file servlet under {}", staticContext);
StaticFileServlet staticFileServlet = new StaticFileServlet("/html"); StaticFileServlet staticFileServlet = new StaticFileServlet("/html");
holder = new ServletHolder(staticFileServlet); holder = new ServletHolder(staticFileServlet);
handler.addServletWithMapping(holder, staticContext); handler.addServletWithMapping(holder, "/static/*");
//context.addServlet(holder, "/");
// servlet to retrieve the HMAC secured by basic auth
String username = this.config.getSettings().webinterfaceUsername;
String password = this.config.getSettings().webinterfacePassword;
context.setSecurityHandler(basicAuth(username, password, "CTB Recorder"));
context.addServlet(new ServletHolder(new HttpServlet() {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
if(Objects.equal(username, req.getRemoteUser())) {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("application/json");
byte[] hmac = HttpServer.this.config.getSettings().key;
JSONObject response = new JSONObject();
response.put("hmac", new String(hmac, "utf-8"));
resp.getOutputStream().println(response.toString());
}
}
}), "/hmac");
} }
try { try {
@ -159,6 +199,31 @@ public class HttpServer {
} }
} }
private static final SecurityHandler basicAuth(String username, String password, String realm) {
UserStore userStore = new UserStore();
userStore.addUser(username, Credential.getCredential(password), new String[] { "user" });
HashLoginService l = new HashLoginService();
l.setUserStore(userStore);
l.setName(realm);
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);
constraint.setRoles(new String[] { "user" });
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
ConstraintSecurityHandler csh = new ConstraintSecurityHandler();
csh.setAuthenticator(new BasicAuthenticator());
csh.setRealmName("myrealm");
csh.addConstraintMapping(cm);
csh.setLoginService(l);
return csh;
}
private void registerAlertSystem() { private void registerAlertSystem() {
for (EventHandlerConfiguration config : Config.getInstance().getSettings().eventHandlers) { for (EventHandlerConfiguration config : Config.getInstance().getSettings().eventHandlers) {
EventHandler handler = new EventHandler(config); EventHandler handler = new EventHandler(config);

View File

@ -207,11 +207,12 @@
</video> </video>
</div> </div>
</div> </div>
<!-- HLS MediaSource support --> <!-- HLS MediaSource support -->
<script src="/static/vendor/hls.js/hls.js"></script> <script src="/static/vendor/hls.js/hls.js"></script>
<script type="text/javascript">
<!-- CryptoJS for HMAc authentication -->
</script> <script src="/static/vendor/CryptoJS/hmac-sha256.js"></script>
<script> <script>
let onlineModels = []; let onlineModels = [];
@ -223,6 +224,7 @@
percent: ko.observable(0), percent: ko.observable(0),
text: ko.observable('') text: ko.observable('')
}; };
let hmac;
function addModelKeyPressed(e) { function addModelKeyPressed(e) {
let charCode = (typeof e.which === "number") ? e.which : e.keyCode; let charCode = (typeof e.which === "number") ? e.which : e.keyCode;
@ -243,13 +245,15 @@
url: modelUrl url: modelUrl
}; };
console.log(model); console.log(model);
let action = '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "startByUrl", "model": ' + JSON.stringify(model) + '}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -270,13 +274,15 @@
resume: function(model) { resume: function(model) {
try { try {
let action = '{"action": "resume", "model": ' + JSON.stringify(model) + '}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "resume", "model": ' + JSON.stringify(model) + '}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -296,13 +302,15 @@
suspend: function(model) { suspend: function(model) {
try { try {
let action = '{"action": "suspend", "model": ' + JSON.stringify(model) + '}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "suspend", "model": ' + JSON.stringify(model) + '}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -322,13 +330,15 @@
stop: function(model) { stop: function(model) {
try { try {
let action = '{"action": "stop", "model": ' + JSON.stringify(model) + '}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "stop", "model": ' + JSON.stringify(model) + '}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -350,13 +360,15 @@
deleteRecording: function(recording) { deleteRecording: function(recording) {
let name = recording.model.name + ' ' + recording.ko_date(); let name = recording.model.name + ' ' + recording.ko_date();
try { try {
let action = '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "delete", "recording": ' + JSON.stringify(recording) + '}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -382,7 +394,11 @@
var hls = new Hls(); var hls = new Hls();
hls.attachMedia(video); hls.attachMedia(video);
hls.on(Hls.Events.MEDIA_ATTACHED, function () { hls.on(Hls.Events.MEDIA_ATTACHED, function () {
hls.loadSource(recording.playlist); let src = recording.playlist;
if (hmac.length > 0) {
src += "?hmac=" + CryptoJS.HmacSHA256(src, hmac);
}
hls.loadSource(src);
hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) { hls.on(Hls.Events.MANIFEST_PARSED, function (event, data) {
console.log("manifest loaded, found " + data.levels.length + " quality level"); console.log("manifest loaded, found " + data.levels.length + " quality level");
$('#player-window').css('display', 'block'); $('#player-window').css('display', 'block');
@ -415,6 +431,33 @@
} }
$(document).ready(function() { $(document).ready(function() {
if (localStorage !== undefined && localStorage.hmac !== undefined) {
console.log('using hmac from cookie');
hmac = localStorage.hmac;
} else {
console.log('hmac not found in local storage. requesting hmac from server');
$.ajax({
type : 'GET',
url : '/secured/hmac',
dataType : 'json',
async : true,
timeout : 60000
})
.done(function(data) {
console.log(data);
hmac = data.hmac;
if (localStorage !== undefined) {
console.log('saving hmac to local storage');
localStorage.setItem("hmac", hmac);
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.log(textStatus, errorThrown);
$.notify('Couldn\'t get HMAC', 'error');
hmac = '';
});
}
function tab(id) { function tab(id) {
$(id).css('display: block'); $(id).css('display: block');
} }
@ -433,13 +476,15 @@
function updateOnlineModels() { function updateOnlineModels() {
try { try {
let action = '{"action": "listOnline"}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "listOnline"}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data, textStatus, jqXHR) { .done(function(data, textStatus, jqXHR) {
if (data.status === 'success') { if (data.status === 'success') {
@ -542,13 +587,15 @@
function updateModels() { function updateModels() {
try { try {
let action = '{"action": "list"}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "list"}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -632,13 +679,15 @@
function updateRecordings() { function updateRecordings() {
try { try {
let action = '{"action": "recordings"}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "recordings"}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {
@ -658,13 +707,15 @@
} }
function updateDiskSpace() { function updateDiskSpace() {
let action = '{"action": "space"}';
$.ajax({ $.ajax({
type : 'POST', type : 'POST',
url : '/rec', url : '/rec',
dataType : 'json', dataType : 'json',
async : true, async : true,
timeout : 60000, timeout : 60000,
data : '{"action": "space"}' headers : {'CTBREC-HMAC': CryptoJS.HmacSHA256(action, hmac)},
data : action
}) })
.done(function(data) { .done(function(data) {
if (data.status === 'success') { if (data.status === 'success') {

View File

@ -0,0 +1 @@
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?module.exports=factory(global):typeof define==="function"&&define.amd?define(factory):factory(global)})(typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:this,function(global){"use strict";global=global||{};var _Base64=global.Base64;var version="2.5.1";var buffer;if(typeof module!=="undefined"&&module.exports){try{buffer=eval("require('buffer').Buffer")}catch(err){buffer=undefined}}var b64chars="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var b64tab=function(bin){var t={};for(var i=0,l=bin.length;i<l;i++)t[bin.charAt(i)]=i;return t}(b64chars);var fromCharCode=String.fromCharCode;var cb_utob=function(c){if(c.length<2){var cc=c.charCodeAt(0);return cc<128?c:cc<2048?fromCharCode(192|cc>>>6)+fromCharCode(128|cc&63):fromCharCode(224|cc>>>12&15)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}else{var cc=65536+(c.charCodeAt(0)-55296)*1024+(c.charCodeAt(1)-56320);return fromCharCode(240|cc>>>18&7)+fromCharCode(128|cc>>>12&63)+fromCharCode(128|cc>>>6&63)+fromCharCode(128|cc&63)}};var re_utob=/[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g;var utob=function(u){return u.replace(re_utob,cb_utob)};var cb_encode=function(ccc){var padlen=[0,2,1][ccc.length%3],ord=ccc.charCodeAt(0)<<16|(ccc.length>1?ccc.charCodeAt(1):0)<<8|(ccc.length>2?ccc.charCodeAt(2):0),chars=[b64chars.charAt(ord>>>18),b64chars.charAt(ord>>>12&63),padlen>=2?"=":b64chars.charAt(ord>>>6&63),padlen>=1?"=":b64chars.charAt(ord&63)];return chars.join("")};var btoa=global.btoa?function(b){return global.btoa(b)}:function(b){return b.replace(/[\s\S]{1,3}/g,cb_encode)};var _encode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(u){return(u.constructor===buffer.constructor?u:buffer.from(u)).toString("base64")}:function(u){return(u.constructor===buffer.constructor?u:new buffer(u)).toString("base64")}:function(u){return btoa(utob(u))};var encode=function(u,urisafe){return!urisafe?_encode(String(u)):_encode(String(u)).replace(/[+\/]/g,function(m0){return m0=="+"?"-":"_"}).replace(/=/g,"")};var encodeURI=function(u){return encode(u,true)};var re_btou=new RegExp(["[À-ß][€-¿]","[à-ï][€-¿]{2}","[ð-÷][€-¿]{3}"].join("|"),"g");var cb_btou=function(cccc){switch(cccc.length){case 4:var cp=(7&cccc.charCodeAt(0))<<18|(63&cccc.charCodeAt(1))<<12|(63&cccc.charCodeAt(2))<<6|63&cccc.charCodeAt(3),offset=cp-65536;return fromCharCode((offset>>>10)+55296)+fromCharCode((offset&1023)+56320);case 3:return fromCharCode((15&cccc.charCodeAt(0))<<12|(63&cccc.charCodeAt(1))<<6|63&cccc.charCodeAt(2));default:return fromCharCode((31&cccc.charCodeAt(0))<<6|63&cccc.charCodeAt(1))}};var btou=function(b){return b.replace(re_btou,cb_btou)};var cb_decode=function(cccc){var len=cccc.length,padlen=len%4,n=(len>0?b64tab[cccc.charAt(0)]<<18:0)|(len>1?b64tab[cccc.charAt(1)]<<12:0)|(len>2?b64tab[cccc.charAt(2)]<<6:0)|(len>3?b64tab[cccc.charAt(3)]:0),chars=[fromCharCode(n>>>16),fromCharCode(n>>>8&255),fromCharCode(n&255)];chars.length-=[0,0,2,1][padlen];return chars.join("")};var _atob=global.atob?function(a){return global.atob(a)}:function(a){return a.replace(/\S{1,4}/g,cb_decode)};var atob=function(a){return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g,""))};var _decode=buffer?buffer.from&&Uint8Array&&buffer.from!==Uint8Array.from?function(a){return(a.constructor===buffer.constructor?a:buffer.from(a,"base64")).toString()}:function(a){return(a.constructor===buffer.constructor?a:new buffer(a,"base64")).toString()}:function(a){return btou(_atob(a))};var decode=function(a){return _decode(String(a).replace(/[-_]/g,function(m0){return m0=="-"?"+":"/"}).replace(/[^A-Za-z0-9\+\/]/g,""))};var noConflict=function(){var Base64=global.Base64;global.Base64=_Base64;return Base64};global.Base64={VERSION:version,atob:atob,btoa:btoa,fromBase64:decode,toBase64:encode,utob:utob,encode:encode,encodeURI:encodeURI,btou:btou,decode:decode,noConflict:noConflict,__buffer__:buffer};if(typeof Object.defineProperty==="function"){var noEnum=function(v){return{value:v,enumerable:false,writable:true,configurable:true}};global.Base64.extendString=function(){Object.defineProperty(String.prototype,"fromBase64",noEnum(function(){return decode(this)}));Object.defineProperty(String.prototype,"toBase64",noEnum(function(urisafe){return encode(this,urisafe)}));Object.defineProperty(String.prototype,"toBase64URI",noEnum(function(){return encode(this,true)}))}}if(global["Meteor"]){Base64=global.Base64}if(typeof module!=="undefined"&&module.exports){module.exports.Base64=global.Base64}else if(typeof define==="function"&&define.amd){define([],function(){return global.Base64})}return{Base64:global.Base64}});

View File

@ -0,0 +1,18 @@
/*
CryptoJS v3.1.2
code.google.com/p/crypto-js
(c) 2009-2013 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
32-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new r.init(c,a)}}),l=f.enc={},k=l.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,
2),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}},
u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);c.sigBytes-=b}return new r.init(g,b)},clone:function(){var a=m.clone.call(this);
a._data=this._data.clone();return a},_minBufferSize:0});g.Hasher=u.extend({cfg:m.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){u.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new t.HMAC.init(a,
d)).finalize(c)}}});var t=f.algo={};return f}(Math);
(function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]=
c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;
d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math);
(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j<h;j++)k[j]^=1549556828,n[j]^=909522486;r.sigBytes=l.sigBytes=m;this.reset()},reset:function(){var f=this._hasher;f.reset();f.update(this._iKey)},update:function(f){this._hasher.update(f);return this},finalize:function(f){var g=
this._hasher;f=g.finalize(f);g.reset();return g.finalize(this._oKey.clone().concat(f))}})})();