fix encoding where signature contains a zero

This commit is contained in:
Rick Watson 2019-05-25 17:41:27 +01:00
parent 6935b63706
commit 4fdc3eee66
5 changed files with 110 additions and 88 deletions

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { ValidatorForm } from 'react-material-ui-form-validator'; import { ValidatorForm } from 'react-material-ui-form-validator';
@ -13,6 +13,8 @@ import TableCell from '@material-ui/core/TableCell';
import TableFooter from '@material-ui/core/TableFooter'; import TableFooter from '@material-ui/core/TableFooter';
import TableHead from '@material-ui/core/TableHead'; import TableHead from '@material-ui/core/TableHead';
import TableRow from '@material-ui/core/TableRow'; import TableRow from '@material-ui/core/TableRow';
import Box from '@material-ui/core/Box';
import EditIcon from '@material-ui/icons/Edit'; import EditIcon from '@material-ui/icons/Edit';
import DeleteIcon from '@material-ui/icons/Delete'; import DeleteIcon from '@material-ui/icons/Delete';
@ -80,6 +82,12 @@ class ManageUsersForm extends React.Component {
return !this.props.userData.users.find(u => u.admin); return !this.props.userData.users.find(u => u.admin);
} }
removeUser = user => {
const { userData } = this.props;
const users = userData.users.filter(u => u.username !== user.username);
this.props.setData({ ...userData, users });
}
startEditingUser = user => { startEditingUser = user => {
this.setState({ this.setState({
creating: false, creating: false,
@ -96,8 +104,7 @@ class ManageUsersForm extends React.Component {
doneEditingUser = () => { doneEditingUser = () => {
const { user } = this.state; const { user } = this.state;
const { userData } = this.props; const { userData } = this.props;
let { users } = userData; const users = userData.users.filter(u => u.username !== user.username);
users = users.filter(u => u.username !== user.username);
users.push(user); users.push(user);
this.props.setData({ ...userData, users }); this.props.setData({ ...userData, users });
this.setState({ this.setState({
@ -125,7 +132,7 @@ class ManageUsersForm extends React.Component {
onSubmit = () => { onSubmit = () => {
this.props.onSubmit(); this.props.onSubmit();
this.props.authenticationContex.refresh(); this.props.authenticationContext.refresh();
} }
render() { render() {
@ -143,17 +150,7 @@ class ManageUsersForm extends React.Component {
</div> </div>
: :
userData ? userData ?
user ? <Fragment>
<UserForm
user={user}
creating={creating}
onDoneEditing={this.doneEditingUser}
onCancelEditing={this.cancelEditingUser}
handleValueChange={this.handleUserValueChange}
handleCheckboxChange={this.handleUserCheckboxChange}
uniqueUsername={this.uniqueUsername}
/>
:
<ValidatorForm onSubmit={this.onSubmit}> <ValidatorForm onSubmit={this.onSubmit}>
<Table className={classes.table}> <Table className={classes.table}>
<TableHead> <TableHead>
@ -175,7 +172,7 @@ class ManageUsersForm extends React.Component {
} }
</TableCell> </TableCell>
<TableCell align="center"> <TableCell align="center">
<IconButton aria-label="Delete"> <IconButton aria-label="Delete" onClick={() => this.removeUser(user)}>
<DeleteIcon /> <DeleteIcon />
</IconButton> </IconButton>
<IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}> <IconButton aria-label="Edit" onClick={() => this.startEditingUser(user)}>
@ -187,22 +184,23 @@ class ManageUsersForm extends React.Component {
</TableBody> </TableBody>
<TableFooter> <TableFooter>
<TableRow> <TableRow>
<TableCell colSpan={2}> <TableCell colSpan={2} />
{
this.noAdminConfigured() &&
<Typography variant="body1" color="error">
You must have at least one admin user configured.
</Typography>
}
</TableCell>
<TableCell align="center"> <TableCell align="center">
<Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}> <Button variant="contained" color="secondary" className={classes.button} onClick={this.createUser}>
Add User Add User
</Button> </Button>
</TableCell> </TableCell>
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
{
this.noAdminConfigured() &&
<Typography component="div" variant="body1">
<Box bgcolor="error.main" color="error.contrastText" p={2} m={1}>
You must have at least one admin user configured.
</Box>
</Typography>
}
<Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}> <Button variant="contained" color="primary" className={classes.button} type="submit" disabled={this.noAdminConfigured()}>
Save Save
</Button> </Button>
@ -210,6 +208,21 @@ class ManageUsersForm extends React.Component {
Reset Reset
</Button> </Button>
</ValidatorForm> </ValidatorForm>
{
user &&
<UserForm
user={user}
creating={creating}
onDoneEditing={this.doneEditingUser}
onCancelEditing={this.cancelEditingUser}
handleValueChange={this.handleUserValueChange}
handleCheckboxChange={this.handleUserCheckboxChange}
uniqueUsername={this.uniqueUsername}
/>
}
</Fragment>
: :
<SectionContent title="Manage Users"> <SectionContent title="Manage Users">
<Typography variant="h4" className={classes.loadingSettingsDetails}> <Typography variant="h4" className={classes.loadingSettingsDetails}>

View File

@ -1,79 +1,88 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import Button from '@material-ui/core/Button'; import Button from '@material-ui/core/Button';
import { TextValidator, ValidatorForm } from 'react-material-ui-form-validator';
import PasswordValidator from '../components/PasswordValidator';
import FormControlLabel from '@material-ui/core/FormControlLabel'; import FormControlLabel from '@material-ui/core/FormControlLabel';
import Switch from '@material-ui/core/Switch'; import Switch from '@material-ui/core/Switch';
import FormGroup from '@material-ui/core/FormGroup'; import FormGroup from '@material-ui/core/FormGroup';
import DialogTitle from '@material-ui/core/DialogTitle';
import Dialog from '@material-ui/core/Dialog';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import PasswordValidator from '../components/PasswordValidator';
const styles = theme => ({ const styles = theme => ({
textField: { textField: {
width: "100%" width: "100%"
}, },
checkboxControl: {
width: "100%"
},
chips: {
display: 'flex',
flexWrap: 'wrap',
},
chip: {
marginRight: theme.spacing.unit,
},
button: { button: {
marginRight: theme.spacing.unit * 2, margin: theme.spacing.unit
marginTop: theme.spacing.unit * 2,
} }
}); });
class UserForm extends React.Component { class UserForm extends React.Component {
constructor(props) {
super(props);
this.formRef = React.createRef();
}
componentWillMount() { componentWillMount() {
ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername); ValidatorForm.addValidationRule('uniqueUsername', this.props.uniqueUsername);
} }
submit = () => {
this.formRef.current.submit();
}
render() { render() {
const { classes, user, creating, handleValueChange, handleCheckboxChange, onDoneEditing, onCancelEditing } = this.props; const { classes, user, creating, handleValueChange, handleCheckboxChange, onDoneEditing, onCancelEditing } = this.props;
return ( return (
<ValidatorForm onSubmit={onDoneEditing}> <ValidatorForm onSubmit={onDoneEditing} ref={this.formRef}>
<TextValidator <Dialog onClose={onCancelEditing} aria-labelledby="modify-user-dialog-title" open={true} scroll="paper">
validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []} <DialogTitle id="modify-user-dialog-title">Modify User</DialogTitle>
errorMessages={creating ? ['Username is required', "That username already exists", "Must be 1-24 characters: alpha numberic, '_' or '.'"] : []} <DialogContent>
name="username" <TextValidator
label="Username" validators={creating ? ['required', 'uniqueUsername', 'matchRegexp:^[a-zA-Z0-9_\\.]{1,24}$'] : []}
className={classes.textField} errorMessages={creating ? ['Username is required', "That username already exists", "Must be 1-24 characters: alpha numberic, '_' or '.'"] : []}
value={user.username} name="username"
disabled={!creating} label="Username"
onChange={handleValueChange('username')} className={classes.textField}
margin="normal" value={user.username}
/> disabled={!creating}
<PasswordValidator onChange={handleValueChange('username')}
validators={['required', 'matchRegexp:^.{0,64}$']} margin="normal"
errorMessages={['Password is required', 'Password must be 64 characters or less']} />
name="password" <PasswordValidator
label="Password" validators={['required', 'matchRegexp:^.{0,64}$']}
className={classes.textField} errorMessages={['Password is required', 'Password must be 64 characters or less']}
value={user.password} name="password"
onChange={handleCheckboxChange('password')} label="Password"
margin="normal" className={classes.textField}
/> value={user.password}
<FormGroup> onChange={handleValueChange('password')}
<FormControlLabel margin="normal"
control={<Switch checked={user.admin} onChange={handleCheckboxChange('admin')} id="admin" />} />
label="Admin?" <FormGroup>
/> <FormControlLabel
</FormGroup> control={<Switch checked={user.admin} onChange={handleCheckboxChange('admin')} id="admin" />}
<Button variant="contained" color="primary" className={classes.button} type="submit"> label="Admin?"
Save />
</Button> </FormGroup>
<Button variant="contained" color="secondary" className={classes.button} onClick={onCancelEditing}> </DialogContent>
Back <DialogActions >
</Button> <Button variant="contained" color="primary" className={classes.button} type="submit" onClick={this.submit}>
Done
</Button>
<Button variant="contained" color="secondary" className={classes.button} type="submit" onClick={onCancelEditing}>
Cancel
</Button>
</DialogActions>
</Dialog>
</ValidatorForm> </ValidatorForm>
); );
} }

View File

@ -14,7 +14,7 @@ void ArduinoJsonJWT::setSecret(String secret){
* No need to pull in additional crypto libraries - lets use what we already have. * No need to pull in additional crypto libraries - lets use what we already have.
*/ */
String ArduinoJsonJWT::sign(String &payload) { String ArduinoJsonJWT::sign(String &payload) {
unsigned char hmacResult[33]; unsigned char hmacResult[32];
{ {
#if defined(ESP_PLATFORM) #if defined(ESP_PLATFORM)
mbedtls_md_context_t ctx; mbedtls_md_context_t ctx;
@ -34,15 +34,14 @@ String ArduinoJsonJWT::sign(String &payload) {
br_hmac_out(&hmacCtx, hmacResult); br_hmac_out(&hmacCtx, hmacResult);
#endif #endif
} }
hmacResult[32] = 0; return encode((char *) hmacResult, 32);
return encode(String((char *) hmacResult));
} }
String ArduinoJsonJWT::buildJWT(JsonObject &payload) { String ArduinoJsonJWT::buildJWT(JsonObject &payload) {
// serialize, then encode payload // serialize, then encode payload
String jwt; String jwt;
serializeJson(payload, jwt); serializeJson(payload, jwt);
jwt = encode(jwt); jwt = encode(jwt.c_str(), jwt.length());
// add the header to payload // add the header to payload
jwt = JWT_HEADER + '.' + jwt; jwt = JWT_HEADER + '.' + jwt;
@ -89,27 +88,27 @@ void ArduinoJsonJWT::parseJWT(String jwt, JsonDocument &jsonDocument) {
} }
} }
String ArduinoJsonJWT::encode(String value) { String ArduinoJsonJWT::encode(const char *cstr, int inputLen) {
// prepare encoder // prepare encoder
base64_encodestate _state; base64_encodestate _state;
#if defined(ESP8266) #if defined(ESP8266)
base64_init_encodestate_nonewlines(&_state); base64_init_encodestate_nonewlines(&_state);
size_t encodedLength = base64_encode_expected_len_nonewlines(value.length()) + 1; size_t encodedLength = base64_encode_expected_len_nonewlines(inputLen) + 1;
#elif defined(ESP_PLATFORM) #elif defined(ESP_PLATFORM)
base64_init_encodestate(&_state); base64_init_encodestate(&_state);
size_t encodedLength = base64_encode_expected_len(value.length()) + 1; size_t encodedLength = base64_encode_expected_len(inputLen) + 1;
#endif #endif
// prepare buffer of correct length // prepare buffer of correct length
char buffer[encodedLength]; char buffer[encodedLength];
// encode to buffer // encode to buffer
int len = base64_encode_block(value.c_str(), value.length(), &buffer[0], &_state); int len = base64_encode_block(cstr, inputLen, &buffer[0], &_state);
len += base64_encode_blockend(&buffer[len], &_state); len += base64_encode_blockend(&buffer[len], &_state);
buffer[len] = 0; buffer[len] = 0;
// convert to arduino string // convert to arduino string
value = String(buffer); String value = String(buffer);
// remove padding and convert to URL safe form // remove padding and convert to URL safe form
while (value.charAt(value.length() - 1) == '='){ while (value.charAt(value.length() - 1) == '='){

View File

@ -24,7 +24,7 @@ private:
String sign(String &value); String sign(String &value);
static String encode(String value); static String encode(const char *cstr, int len);
static String decode(String value); static String decode(String value);
public: public:

View File

@ -43,9 +43,10 @@ Authentication SecurityManager::authenticateRequest(AsyncWebServerRequest *reque
AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER); AsyncWebHeader* authorizationHeader = request->getHeader(AUTHORIZATION_HEADER);
if (authorizationHeader) { if (authorizationHeader) {
String value = authorizationHeader->value(); String value = authorizationHeader->value();
value.startsWith(AUTHORIZATION_HEADER_PREFIX); if (value.startsWith(AUTHORIZATION_HEADER_PREFIX)){
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN); value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
return authenticateJWT(value); return authenticateJWT(value);
}
} }
return Authentication(); return Authentication();
} }