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 somehow pass it on from an administrator to an invited user, you can just pass on 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 (available on Silver tiers and up). 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 periods (.). 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

HTTP status codes:

  • 204: On success
  • 400: 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 success
  • 400: Bad request (invalid / missing input)
  • 401: Access denied (invalid or missing credentials)
  • 403: IP address is forbidden
  • 404: 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 user
  • password: The password for the new user
  • role: 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 success
  • 400: 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 success
  • 400: 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 success
  • 400: 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);
    }
}