Skip to content

Commit

Permalink
Add message type check before message parsing
Browse files Browse the repository at this point in the history
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
  • Loading branch information
dborovcanin committed Mar 29, 2019
1 parent 72e6286 commit 2e8865b
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 63 deletions.
13 changes: 9 additions & 4 deletions docker/nginx/nginx-key.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
###
# Mainflux NGINX Configuration
###
##
## Copyright (c) 2018
## Mainflux
##
## SPDX-License-Identifier: Apache-2.0
##

# This is the default Mainflux NGINX configuration.

user nginx;
worker_processes auto;
Expand Down Expand Up @@ -188,8 +193,8 @@ http {
}
}

# MQTT
stream {
# MQTT
server {
listen 8883 ssl;
listen [::]:8883 ssl;
Expand Down
19 changes: 15 additions & 4 deletions docker/nginx/nginx-x509.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
###
# Mainflux NGINX Configuration
###
##
## Copyright (c) 2018
## Mainflux
##
## SPDX-License-Identifier: Apache-2.0
##

# This is the Mainflux NGINX configuration for mututal authentication based on X.509 certifiactes.

user nginx;
worker_processes auto;
Expand All @@ -13,7 +18,6 @@ events {
worker_connections 768;
}

# HTTP
http {
sendfile on;
tcp_nopush on;
Expand Down Expand Up @@ -170,6 +174,13 @@ http {

# Proxy pass to mainflux-mqtt-adapter over WS
location /mqtt {
if ($ssl_client_verify != SUCCESS) {
return 403;
}
if ($auth_key = '') {
return 403;
}

proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
Expand Down
112 changes: 58 additions & 54 deletions docker/ssl/authorization.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var clientKey = '';

// Check certificate MQTTS.
function authenticate(s) {
if (!s.variables.ssl_client_s_dn || !s.variables.ssl_client_s_dn.length) {
s.deny();
Expand All @@ -11,6 +12,16 @@ function authenticate(s) {
return s.AGAIN
}

var packet_type_flags_byte = data.codePointAt(0);
// First MQTT packet contain message type and flags. CONNECTON message type
// is encoded as 0001, and we're not interested in flags, so only values
// 0001xxxx (which is between 16 and 32) should be checked.
if (packet_type_flags_byte < 16 || packet_type_flags_byte >= 32) {
s.off('upload');
s.allow();
return;
}

if (clientKey === '') {
clientKey = parseCert(s.variables.ssl_client_s_dn, 'CN');
}
Expand All @@ -21,6 +32,7 @@ function authenticate(s) {
s.error('Cert CN (' + clientKey + ') does not match client ID');
s.off('upload')
s.deny();
return;
}

s.off('upload');
Expand All @@ -37,25 +49,25 @@ function parsePackage(s, data) {

/*
0 1 2 3
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TYPE | RSRVD | REMAINING LEN | PROTOCOL NAME LEN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| TYPE | RSRVD | REMAINING LEN | PROTOCOL NAME LEN |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| PROTOCOL NAME |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
| VERSION | FLAGS | KEEP ALIVE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
| Payload (if any) ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| VERSION | FLAGS | KEEP ALIVE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-|
| Payload (if any) ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
First byte with remaining length represents fixed header.
Remaining Length is the length of the variable header (10 bytes) plus the length of the Payload.
It is encoded in the manner described here:
http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180836.
Connect flags byte looks like this:
| 7 | 6 | 5 | 4 3 | 2 | 1 | 0 |
| Username Flag | Password Flag | Will Retain | Will QoS | Will Flag | Clean Session | Reserved |
| 7 | 6 | 5 | 4 3 | 2 | 1 | 0 |
| Username Flag | Password Flag | Will Retain | Will QoS | Will Flag | Clean Session | Reserved |
The payload is determined by the flags and comes in this order:
1. Client ID (2 bytes length + ID value)
Expand All @@ -67,56 +79,48 @@ function parsePackage(s, data) {
This method extracts Password field.
*/

var packet_type_flags_byte = data.codePointAt(0);
// First MQTT packet contain message type and flags. CONN message type
// is encoded as 0001, and we're not interested in flags, so all values
// 0001xxxx are valid for us, which is between 16 and 32.
if (packet_type_flags_byte >= 16 && packet_type_flags_byte < 32) {
// Extract variable length header. It's 1-4 bytes. As long as continuation byte is
// 1, there are more bytes in this header. This algorithm is explained here:
// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180836
var len_size = 1;
for (var remaining_len = 1; remaining_len < 5; remaining_len++) {
if (data.codePointAt(remaining_len) > 128) {
len_size += 1;
continue;
}
break;
}
// CONTROL(1) + MSG_LEN(1-4) + PROTO_NAME_LEN(2) + PROTO_NAME(4) + PROTO_VERSION(1)
var flags_pos = 1 + len_size + 2 + 4 + 1;
var flags = data.codePointAt(flags_pos);
// If there are no username and password flags (11xxxxxx), return.
if (flags < 192) {
s.error('MQTT username or password not provided');
return '';
// Extract variable length header. It's 1-4 bytes. As long as continuation byte is
// 1, there are more bytes in this header. This algorithm is explained here:
// http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180836
var len_size = 1;
for (var remaining_len = 1; remaining_len < 5; remaining_len++) {
if (data.codePointAt(remaining_len) > 128) {
len_size += 1;
continue;
}
// FLAGS(1) + KEEP_ALIVE(2)
var shift = flags_pos + 1 + 2;

// Number of bytes to encode length.
var len_bytes_num = 2;
// If Wil Flag is present, Will Topic and Will Message need to be skipped as well.
var shift_flags = 196 <= flags ? 5 : 3;
var len_msb, len_lsb, len;
for (var i = 0; i < shift_flags; i++) {
len_msb = data.codePointAt(shift).toString(16);
len_lsb = data.codePointAt(shift + 1).toString(16);
len = calcLen(len_msb, len_lsb);
shift += len_bytes_num;
if (i != shift_flags - 1) {
shift += len;
}
break;
}
// CONTROL(1) + MSG_LEN(1-4) + PROTO_NAME_LEN(2) + PROTO_NAME(4) + PROTO_VERSION(1)
var flags_pos = 1 + len_size + 2 + 4 + 1;
var flags = data.codePointAt(flags_pos);
// If there are no username and password flags (11xxxxxx), return.
if (flags < 192) {
s.error('MQTT username or password not provided');
return '';
}
// FLAGS(1) + KEEP_ALIVE(2)
var shift = flags_pos + 1 + 2;

// Number of bytes to encode length.
var len_bytes_num = 2;
// If Wil Flag is present, Will Topic and Will Message need to be skipped as well.
var shift_flags = 196 <= flags ? 5 : 3;
var len_msb, len_lsb, len;
for (var i = 0; i < shift_flags; i++) {
len_msb = data.codePointAt(shift).toString(16);
len_lsb = data.codePointAt(shift + 1).toString(16);
len = calcLen(len_msb, len_lsb);
shift += len_bytes_num;
if (i != shift_flags - 1) {
shift += len;
}

var password = data.substring(shift, shift + len);
return password;
}

return '';

var password = data.substring(shift, shift + len);
return password;
}

// Check certificate HTTPS and WSS.
function setKey(r) {
if (clientKey === '') {
clientKey = parseCert(r.variables.ssl_client_s_dn, 'CN');
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ pages:
- Messaging: messaging.md
- Storage: storage.md
- LoRa: lora.md
- Securing communication: secure-communication.md
- Security: security.md
- CLI: cli.md
- Bootstrap: bootstrap.md
- Developer's Guide: dev-guide.md
Expand Down

0 comments on commit 2e8865b

Please sign in to comment.