Nabto String Shortener Service
This service takes a long string and maps it to a short code, similar to URL shorteners like bit.ly. This simplifies the Nabto Edge pairing process in some scenarios where a lot of information must be passed from the Nabto Edge device to the client who wants to pair.
Motivation
When doing remote pairing, you need to specify quite a few bits of input to perform pairing: The target device’s product + device ids, a pairing username + password and a remote connect token.
When running the TCP tunnel device application, it outputs the necessary data to pass to the client on a side channel (e.g., show on display, transfer using bluetooth, transfer in a voice call) - all represented in the compact pairing string at the bottom:
$ ./tcp_tunnel_device
######## Nabto TCP Tunnel Device ########
# Product ID: pr-aaaaaaaa
# Device ID: de-bbbbbbbb
# Fingerprint: 08c955a5f7505f16f03bc3e3e0db89ff56ce571e0dd6be153c5bae9174d62ac6
# Version: 5.2.0
# Local UDP Port: 5592
#
# The device is not yet paired with the initial user. You can use Local Initial Pairing to get access.
#
# The initial user has not been paired yet. You can pair with the device using Password Invite Pairing.
# Initial Pairing Usermame: admin
# Initial Pairing Password: sCUhpbiRpjEc
# Initial Pairing SCT: FUmot9E9XCKW
# Initial Pairing String: p=pr-aaaaaaaa,d=de-bbbbbbbb,u=admin,pwd=sCUhpbiRpjEc,sct=FUmot9E9XCKW
#
If you have a display and can show a QR code or you can otherwise somehow pass it on from an administrator to an invited user, you can just use the pairing string as-is.
However, if the pairing information e.g. must be read aloud on a phone, it becomes very cumbersome and errorprone due to the large amount of data.
The Nabto String Shortener Service
To simplify this process, Nabto offers a shortener service: It takes as input an arbitrary string (such as a pairing string) and returns a short string formatted as the caller decides and with a lifetime decided by the caller. In this way the developer can decide on the tradeoff between shorter (easier to remember - and to guess/bruteforce) and longer (more difficult to remember and guess/bruteforce) strings.
When passing the short string to the service, the initial long string is returned. Access to the service can be restricted through an IP whitelist and password authentication.
Since Nabto does not want to be a trusted 3rd party and since the shortener service needs to maintain the pairing password, Nabto does not host this as a standard offering but provides the software for customers to run on their own. Nabto can optionally host the service - in such case it is hosted completely isolated from the Nabto basestation services in a separate deployment.
REST API
The Nabto String Shortener Service is used through a set of HTTP REST endpoints. The intention is to invoke one of these through the Nabto Edge Service Invocation API on the device side to shorten the pairing strings.
The client application expands the resulting short code back into the full string by directly invoking the HTTP REST service through a general HTTP client such as provided by NSURLRequest
and NSURLConnection
on Apple systems.
Short codes are created by a user, identified by a username and authenticated with a password, both specified through HTTP basic auth.
Users belong to an organization. Short codes can only be resolved by users belonging to the same organization as the user who created the short code (if user authentication is required - if not, short codes can be resolved by anyone).
Each organization is born with a user in the administrator role. This user can create other users (including other administrators), also through the REST API.
Store string and create short code: POST /shortener/codes
Input is specified as a JSON document:
{
long_string: string,
expiration_time?: number,
requires_auth?: boolean,
use_whitelist?: boolean,
format?: string
}
Parameters:
long_string
: The input string to shorten (mandatory)expiration_time
: The number of seconds the short code should be valid (optional, default is 7 days)requires_auth
: Indicate if only password authenticated clients can retrieve the short code or if anybody can retrieve it (default false)use_whitelist
: Indicate if only clients with an IP address in the organization’s configured whitelist are allowed (default false)format
: The short code format, see below (default is 6 random characters)
The shortcode format can contain alphanumeric characters (a-zA-Z0-9
), dashes (-
) and placeholders for randomly generated characters. 3 such different placeholders exists to generate random characters in 3 different alphabets:
Placeholder .
: A random alphanumeric char (upper+lower case) with some omissions to reduce ambiguity
Periods (.
) are replaced by the service with random characters in the following 47-character alphabet:
abcdefghijkmnopqrstuvwxyzACEFHJKLMNPRTUVWXY3479
That is, the following ambigious characters are not used in short codes:
B8G6I1l0OQDS5Z2
Placeholder :
: A random lowercase alphanumeric char
Colons (:
) are replaced by the service with random characters in the following 36-character alphabet:
abcdefghijklmnopqrstuvwxyz0123456789
Placeholder #
: A random uppercase alphanumeric char
Hash signs (#
) are replaced by the service with random characters in the following 36-character alphabet:
ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
HTTP status codes:
204
: On success400
: Bad request (invalid input)401
: Access denied (invalid or missing credentials)
Example:
$ curl -s -X POST --user some_username:some_password -H "Content-Type: application/json" \
--data '{"format": "pair-...-...", "long_string": "p=pr-aaaaaaaa,d=de-bbbbbbbb,u=admin,pwd=4oDisw2gJ3SZ,sct=wG9tjzWoYGfN"}' \
https://europe-west3-nabto-dev.cloudfunctions.net/shortener/codes
pair-tK3-Atr
$
Store string and create short code using base64 encoding: POST /shortener/codes/base64
This is a version of the POST /shortener/codes
endpoint listed above that is suitable for integration with the Nabto basestation: The Nabto basestation encodes the input data passed on to the Embedded SDK to nabto_device_service_invocation_set_message
as base64 when passing on to the configured service.
This endpoint should not be invoked directly, but only configured as a service endpoint in the Nabto Cloud Console. The device application then specifies a JSON input document as outlined for POST /shortener/codes
above and gets a response back as documented - the basestation and shortener service handles all the base64 encoding and decoding behind the scenes:
const char* json = "{\"format\": \"pair-...-...\", \"long_string\": \"p=pr-aaaaaaaa,d=de-bbbbbbbb,u=admin,pwd=4oDisw2gJ3SZ,sct=wG9tjzWoYGfN\"}";
nabto_device_service_invocation_set_message(service, json, strlen(json));
See the Nabto integration section below.
Retrieve string based on short code: GET /shortener/codes/:short_code
HTTP status codes:
200
: On success400
: Bad request (invalid / missing input)401
: Access denied (invalid or missing credentials)403
: IP address is forbidden404
: Unknown short code
Example:
$ curl -s https://europe-west3-nabto-dev.cloudfunctions.net/shortener/codes/pair-tK3-Atr
{"long_string": "p=pr-aaaaaaaa,d=de-bbbbbbbb,u=admin,pwd=4oDisw2gJ3SZ,sct=wG9tjzWoYGfN"}
Add user (admin only): POST /organizations/:org/users
Adds a user to the specified organization.
Input data for the new user is specified as a JSON document:
{
name: string,
password: string,
role?: number
}
Parameters:
name
: The username of the new userpassword
: The password for the new userrole
: The optional custom role of the new user, current allowed values are 1 (admin) and 2 (regular user), default is 2 (regular user).
HTTP status codes:
204
: On success400
: Bad request (invalid / missing input)401
: Access denied (invalid or missing credentials)403
: Unauthorized (user is not admin)
Example:
$ curl -X POST --user some_admin_user:some_password -H "Content-Type: application/json" \
--data '{"name": "new_user_1", "password": "secret"}' \
https://europe-west3-nabto-dev.cloudfunctions.net/shortener/organizations/some_org/users
Add IP whitelist entry (admin only): POST /organizations/:org/ip_whitelist
Add one or more IP addresses to the IP whitelist for the specified organization. The IP whitelist controls which clients are allowed to resolve shortcodes.
Input data for the entries is specified as a JSON document:
{
ip: string[]
}
IPs are given in CIDR notation. You can whitelist single IPs or networks of IPs at a time.
HTTP status codes:
204
: On success400
: Bad request (invalid / missing input)401
: Access denied (invalid or missing credentials)403
: Unauthorized (user is not admin)
Example:
$ curl -X POST --user some_admin_user:some_password -H "Content-Type: application/json" \
--data '{"ip": ["178.157.253.152", "194.192.21.0/24"] }' \
https://europe-west3-nabto-dev.cloudfunctions.net/shortener/organizations/some_org/ip_whitelist
Get IP whitelist entries (admin only): GET /organizations/:org/ip_whitelist
Get the IP whitelist for the specified organization.
HTTP status codes:
200
: On success400
: Bad request (invalid / missing input)401
: Access denied (invalid or missing credentials)403
: Unauthorized (user is not admin)
Example:
$ curl -s --user some_admin_user:some_password -H "Content-Type: application/json"
https://europe-west3-nabto-dev.cloudfunctions.net/shortener/organizations/some_org/ip_whitelist
{
"whitelist": [
{
"value": "1.2.3.4/32",
"from_ip": "1.2.3.4",
"to_ip": "1.2.3.4"
},
{
"value": "123.123.10.0/24",
"from_ip": "123.123.10.0",
"to_ip": "123.123.10.255"
}
]
}
Nabto Integration
In the Nabto Cloud Console, add a new service:
The URL is the Nabto Shortener service URL to create a short code using base64. For test and development, you can specify https://europe-west3-nabto-dev.cloudfunctions.net/shortener/codes/base64
.
The username and password is for a user created using POST /organizations/:org/users
.
Integration on the device is demonstrated in a full, simple example. Key excerpts:
void service_invocation(NabtoDevice* device)
{
printf("Invoking the service %s\n", serviceId_);
NabtoDeviceServiceInvocation* invocation = nabto_device_service_invocation_new(device);
nabto_device_service_invocation_set_service_id(invocation, serviceId_);
nabto_device_service_invocation_set_message(invocation, (const uint8_t*)message_, strlen(message_));
NabtoDeviceFuture* future = nabto_device_future_new(device);
nabto_device_service_invocation_execute(invocation, future);
NabtoDeviceError ec = nabto_device_future_wait(future);
if (ec != NABTO_DEVICE_EC_OK) {
printf("Service invocation failed %s\n", nabto_device_error_get_message(ec));
} else {
uint16_t statusCode = nabto_device_service_invocation_get_response_status_code(invocation);
const uint8_t* message = nabto_device_service_invocation_get_response_message_data(invocation);
size_t messageLength = nabto_device_service_invocation_get_response_message_size(invocation);
printf("Service invocation ok. StatusCode: %d, MessageLength: %d, Message %.*s\n", statusCode, (int)messageLength, (int)messageLength, (const char*)message);
}
}