Common stuff for all my devices' web server pages: css, login, log, ipv4, ipv6, firmware update, clock, reset info etc.

Dependents:   oldheating gps motorhome heating

Security

A password has to be set whenever there has been a software reset. Resets following faults or power on do not require a new password as the hash is restored from the RTC GPREG register.

The password is not saved on the device; instead a 32 bit hash of the password is saved. It would take 2^31 attempts to brute force the password: this could be done in under a month if an attempt were possible every millisecond. To prevent this a 200 ms delay is introduced in the reply to the login form, that gives a more reasonable 13 years to brute force the password.

Once the password is accepted a random session id is created. This is 36 bit to give six base 64 characters but without an extra delay. If an attempt could be made every ms then this would still take over a year to brute force.

The most likely attack would to use a dictionary with, say, 10 million entries against the password which would still take 20 days to do.

Committer:
andrewboyson
Date:
Tue May 11 11:00:00 2021 +0000
Revision:
160:daa94b75b94c
Parent:
156:005785e4740e
CSS modified to not change the text colour when hovering over a disabled button: it still does for buttons which are enabled.

Who changed what in which revision?

UserRevisionLine numberNew contents of line
andrewboyson 96:eb2eb75bad0f 1 //Ajax class
andrewboyson 95:8c9dda8a0caf 2 'use strict';
andrewboyson 95:8c9dda8a0caf 3
andrewboyson 95:8c9dda8a0caf 4 //Exposed properties
andrewboyson 95:8c9dda8a0caf 5 let ajaxResponse_ = '';
andrewboyson 95:8c9dda8a0caf 6 let ajaxHeaders_ = '';
andrewboyson 96:eb2eb75bad0f 7 let ajaxDate_ = null;
andrewboyson 95:8c9dda8a0caf 8 let ajaxMs_ = 0;
andrewboyson 95:8c9dda8a0caf 9 let ajaxOnResponse_ = null;
andrewboyson 95:8c9dda8a0caf 10 let ajaxOnTick_ = null;
andrewboyson 95:8c9dda8a0caf 11 let ajaxServer_ = '';
andrewboyson 95:8c9dda8a0caf 12
andrewboyson 95:8c9dda8a0caf 13 //Private variables
andrewboyson 95:8c9dda8a0caf 14 let ajaxOverrideBlockUpdateOnFocus_ = false;
andrewboyson 95:8c9dda8a0caf 15 let ajaxXhr_ = null;
andrewboyson 95:8c9dda8a0caf 16 let ajaxMsCountAtAjaxSend_ = 0;
andrewboyson 95:8c9dda8a0caf 17 const ajaxTickMs_ = 100;
andrewboyson 156:005785e4740e 18 const ajaxQuickUpdateMs_ = 1000;
andrewboyson 95:8c9dda8a0caf 19 const ajaxUpdateMs_ = 10000;
andrewboyson 156:005785e4740e 20 let ajaxDoQuickUpdate_ = false;
andrewboyson 95:8c9dda8a0caf 21
andrewboyson 95:8c9dda8a0caf 22 //Private utilities
andrewboyson 95:8c9dda8a0caf 23 function ajaxGetElementOrNull_(elementName) //Returns the element if it: exists; block is overidden; does not have focus
andrewboyson 95:8c9dda8a0caf 24 {
andrewboyson 95:8c9dda8a0caf 25 let elem = document.getElementById(elementName);
andrewboyson 95:8c9dda8a0caf 26 if (!elem) return null;
andrewboyson 95:8c9dda8a0caf 27 if (ajaxOverrideBlockUpdateOnFocus_) return elem;
andrewboyson 95:8c9dda8a0caf 28 if (elem !== document.activeElement) return elem;
andrewboyson 95:8c9dda8a0caf 29 return null;
andrewboyson 95:8c9dda8a0caf 30 }
andrewboyson 95:8c9dda8a0caf 31 function ajaxHexToBit_(text, iBit)
andrewboyson 95:8c9dda8a0caf 32 {
andrewboyson 95:8c9dda8a0caf 33 let value = parseInt(text, 16);
andrewboyson 95:8c9dda8a0caf 34 value >>= iBit;
andrewboyson 95:8c9dda8a0caf 35 return value & 1;
andrewboyson 95:8c9dda8a0caf 36 }
andrewboyson 98:4e099563d5b9 37 function ajaxHexToSignedInt8_(text)
andrewboyson 98:4e099563d5b9 38 {
andrewboyson 98:4e099563d5b9 39 let value = parseInt(text, 16);
andrewboyson 98:4e099563d5b9 40 if (value < 0x80) return value;
andrewboyson 98:4e099563d5b9 41 return value - 0x100;
andrewboyson 98:4e099563d5b9 42 }
andrewboyson 98:4e099563d5b9 43 function ajaxHexToSignedInt16_(text)
andrewboyson 98:4e099563d5b9 44 {
andrewboyson 98:4e099563d5b9 45 let value = parseInt(text, 16);
andrewboyson 98:4e099563d5b9 46 if (value < 0x8000) return value;
andrewboyson 98:4e099563d5b9 47 return value - 0x10000;
andrewboyson 98:4e099563d5b9 48 }
andrewboyson 98:4e099563d5b9 49 function ajaxHexToSignedInt32_(text)
andrewboyson 98:4e099563d5b9 50 {
andrewboyson 98:4e099563d5b9 51 let value = parseInt(text, 16);
andrewboyson 98:4e099563d5b9 52 if (value < 0x80000000) return value;
andrewboyson 98:4e099563d5b9 53 return value - 0x100000000;
andrewboyson 98:4e099563d5b9 54 }
andrewboyson 98:4e099563d5b9 55
andrewboyson 95:8c9dda8a0caf 56
andrewboyson 95:8c9dda8a0caf 57 //Private ajax functions
andrewboyson 95:8c9dda8a0caf 58 function ajaxHandleAjaxResponse_()
andrewboyson 95:8c9dda8a0caf 59 {
andrewboyson 95:8c9dda8a0caf 60 if (ajaxXhr_.readyState == 4 && ajaxXhr_.status == 200)
andrewboyson 95:8c9dda8a0caf 61 {
andrewboyson 95:8c9dda8a0caf 62 ajaxResponse_ = ajaxXhr_.responseText;
andrewboyson 95:8c9dda8a0caf 63 ajaxHeaders_ = ajaxXhr_.getAllResponseHeaders();
andrewboyson 96:eb2eb75bad0f 64 let iDateStart = Ajax.headers.toLowerCase().indexOf('date:');
andrewboyson 96:eb2eb75bad0f 65 let iDateEnd = Ajax.headers.indexOf('\r', iDateStart);
andrewboyson 96:eb2eb75bad0f 66 ajaxDate_ = new Date(Ajax.headers.slice(iDateStart + 5, iDateEnd));
andrewboyson 96:eb2eb75bad0f 67
andrewboyson 95:8c9dda8a0caf 68 let elem;
andrewboyson 98:4e099563d5b9 69 elem = ajaxGetElementOrNull_('ajax-response' ); if (elem) elem.textContent = ajaxResponse_;
andrewboyson 98:4e099563d5b9 70 elem = ajaxGetElementOrNull_('ajax-headers' ); if (elem) elem.textContent = ajaxHeaders_;
andrewboyson 98:4e099563d5b9 71 elem = ajaxGetElementOrNull_('ajax-date-local' );
andrewboyson 98:4e099563d5b9 72 if (elem)
andrewboyson 98:4e099563d5b9 73 {
andrewboyson 98:4e099563d5b9 74 elem.textContent = ajaxDate_.toLocaleString( undefined, { weekday : 'short' ,
andrewboyson 98:4e099563d5b9 75 day : '2-digit',
andrewboyson 98:4e099563d5b9 76 month : 'short' ,
andrewboyson 98:4e099563d5b9 77 year : 'numeric',
andrewboyson 98:4e099563d5b9 78 hour : '2-digit',
andrewboyson 98:4e099563d5b9 79 minute : '2-digit',
andrewboyson 98:4e099563d5b9 80 timeZoneName: 'short'
andrewboyson 98:4e099563d5b9 81 }
andrewboyson 98:4e099563d5b9 82 );
andrewboyson 98:4e099563d5b9 83 }
andrewboyson 95:8c9dda8a0caf 84 if (ajaxOnResponse_) ajaxOnResponse_();
andrewboyson 95:8c9dda8a0caf 85 ajaxOverrideBlockUpdateOnFocus_ = false; //Received response so reset override after display
andrewboyson 95:8c9dda8a0caf 86 }
andrewboyson 95:8c9dda8a0caf 87 }
andrewboyson 138:44d84506b2f6 88 function ajaxSendNameValue_(name, value) //Used by this script and from HTML page
andrewboyson 95:8c9dda8a0caf 89 {
andrewboyson 95:8c9dda8a0caf 90 ajaxXhr_ = new XMLHttpRequest();
andrewboyson 95:8c9dda8a0caf 91 ajaxXhr_.onreadystatechange = ajaxHandleAjaxResponse_;
andrewboyson 138:44d84506b2f6 92 if (name)
andrewboyson 98:4e099563d5b9 93 {
andrewboyson 138:44d84506b2f6 94 if (value)
andrewboyson 138:44d84506b2f6 95 {
andrewboyson 140:8951a8b45289 96 ajaxXhr_.open('GET', ajaxServer_ + '?' + name + '=' + encodeURIComponent(value), true);
andrewboyson 138:44d84506b2f6 97 }
andrewboyson 138:44d84506b2f6 98 else
andrewboyson 138:44d84506b2f6 99 {
andrewboyson 138:44d84506b2f6 100 ajaxXhr_.open('GET', ajaxServer_ + '?' + name, true);
andrewboyson 138:44d84506b2f6 101 }
andrewboyson 98:4e099563d5b9 102 }
andrewboyson 98:4e099563d5b9 103 else
andrewboyson 98:4e099563d5b9 104 {
andrewboyson 138:44d84506b2f6 105 ajaxXhr_.open('GET', ajaxServer_, true);
andrewboyson 98:4e099563d5b9 106 }
andrewboyson 95:8c9dda8a0caf 107 ajaxXhr_.send();
andrewboyson 156:005785e4740e 108 ajaxDoQuickUpdate_ = false;
andrewboyson 95:8c9dda8a0caf 109 ajaxMsCountAtAjaxSend_ = ajaxMs_;
andrewboyson 95:8c9dda8a0caf 110 }
andrewboyson 138:44d84506b2f6 111 function AjaxSendNameValue(name, value) //From html
andrewboyson 95:8c9dda8a0caf 112 {
andrewboyson 95:8c9dda8a0caf 113 ajaxOverrideBlockUpdateOnFocus_ = true; //Request has come from an update
andrewboyson 138:44d84506b2f6 114 ajaxSendNameValue_(name, value);
andrewboyson 156:005785e4740e 115 ajaxDoQuickUpdate_ = true; //Make the next request a quick one
andrewboyson 95:8c9dda8a0caf 116 }
andrewboyson 95:8c9dda8a0caf 117
andrewboyson 95:8c9dda8a0caf 118 //Private functions
andrewboyson 95:8c9dda8a0caf 119 function ajaxTick_() //Called about every 100ms
andrewboyson 95:8c9dda8a0caf 120 {
andrewboyson 95:8c9dda8a0caf 121 ajaxMs_ += ajaxTickMs_; //Don't use Date.now() as we don't know when the PC's clock will be updated around a leap second
andrewboyson 139:e189c6669983 122 if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxUpdateMs_) ajaxSendNameValue_('', '');
andrewboyson 156:005785e4740e 123 if (ajaxDoQuickUpdate_)
andrewboyson 156:005785e4740e 124 {
andrewboyson 156:005785e4740e 125 if (ajaxMs_ >= ajaxMsCountAtAjaxSend_ + ajaxQuickUpdateMs_) ajaxSendNameValue_('', '');
andrewboyson 156:005785e4740e 126 }
andrewboyson 95:8c9dda8a0caf 127 if (ajaxOnTick_) ajaxOnTick_();
andrewboyson 95:8c9dda8a0caf 128 }
andrewboyson 95:8c9dda8a0caf 129 function ajaxInit_()
andrewboyson 95:8c9dda8a0caf 130 {
andrewboyson 95:8c9dda8a0caf 131 setInterval(ajaxTick_, ajaxTickMs_);
andrewboyson 139:e189c6669983 132 ajaxSendNameValue_('', '');
andrewboyson 95:8c9dda8a0caf 133 }
andrewboyson 95:8c9dda8a0caf 134
andrewboyson 95:8c9dda8a0caf 135 //Exposed public
andrewboyson 95:8c9dda8a0caf 136 class Ajax
andrewboyson 95:8c9dda8a0caf 137 {
andrewboyson 98:4e099563d5b9 138 static get ms () { return ajaxMs_ ; }
andrewboyson 98:4e099563d5b9 139 static get response () { return ajaxResponse_ ; }
andrewboyson 98:4e099563d5b9 140 static get headers () { return ajaxHeaders_ ; }
andrewboyson 98:4e099563d5b9 141 static get date () { return ajaxDate_ ; }
andrewboyson 98:4e099563d5b9 142
andrewboyson 98:4e099563d5b9 143 static set tickMs (v) { ajaxTickMs_ = v; }
andrewboyson 98:4e099563d5b9 144 static set updateMs (v) { ajaxUpdateMs_ = v; }
andrewboyson 98:4e099563d5b9 145 static set server (v) { ajaxServer_ = v; }
andrewboyson 98:4e099563d5b9 146 static set onResponse(v) { ajaxOnResponse_ = v; }
andrewboyson 98:4e099563d5b9 147 static set onTick (v) { ajaxOnTick_ = v; }
andrewboyson 95:8c9dda8a0caf 148
andrewboyson 95:8c9dda8a0caf 149 static getElementOrNull(elementName) { return ajaxGetElementOrNull_(elementName) ; }
andrewboyson 95:8c9dda8a0caf 150 static hexToBit (text, iBit ) { return ajaxHexToBit_ (text, iBit ) ; }
andrewboyson 98:4e099563d5b9 151 static hexToSignedInt8 (text ) { return ajaxHexToSignedInt8_ (text ) ; }
andrewboyson 98:4e099563d5b9 152 static hexToSignedInt16(text ) { return ajaxHexToSignedInt16_(text ) ; }
andrewboyson 98:4e099563d5b9 153 static hexToSignedInt32(text ) { return ajaxHexToSignedInt32_(text ) ; }
andrewboyson 95:8c9dda8a0caf 154
andrewboyson 95:8c9dda8a0caf 155 static init()
andrewboyson 95:8c9dda8a0caf 156 {
andrewboyson 95:8c9dda8a0caf 157 if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ajaxInit_ ); // Loading hasn't finished yet
andrewboyson 95:8c9dda8a0caf 158 else ajaxInit_(); //`DOMContentLoaded` has already fired
andrewboyson 95:8c9dda8a0caf 159 }
andrewboyson 95:8c9dda8a0caf 160 }