BlackBerry Spark Communications Services Guide

Firebase for Cloud Key Storage

Spark Communications Services uses cryptographic keys to protect communications. You can chose to store and distribute keys using the SDK's Cloud Key Storage option.

The SDK can use any cloud storage service that meets some basic requirements, including Firebase. This page explains how to use Firebase with the SDK.

Using Firebase

Firebase offers a cloud-hosted NoSQL real-time database.

Firebase also offers a variety of authentication system integration options that allow you to be flexible in your design. For more information, visit Firebase Authentication.

The mechanism for storing private and public security keys must adhere to the following rules.

Private Key Data Rules

Public Key Data Rules

For more information on these rules, please refer to the Firebase Realtime Database Rules.

Because the Firebase Realtime Database employs JSON as a means of data storage and transfer, it is recommended that all encryption, signing, and symmetric keys stored in this database be base64url encoded, without padding.

It is also recommended that any keys retrieved from the public key store be monitored for changes, to ensure that the most up-to-date copy of a user's public keys are available to the client, and are cached locally. For more information, visit the Firebase documentation for Android or iOS.

Security and Data Integrity Rules

The following rules may be installed into a project's Realtime Database via the project's Firebase console.

{
  "rules": {
    "bbmsdk" : {
      "identity": {
        //User records can be written by the record creator and read by all
        "users": {
          ".read": "auth != null",
          "$uid": {
            ".write": "auth != null && auth.token.firebase.identities['google.com'][0] == $uid",
          }
        }
      }
    },

    // The Private Key Store is used to store a user's private keys (profile) for both
    // encryption and signing purposes as well as the symmetric key for each
    // mailbox to which the user is subscribed.
    //
    // Also stored is the encrypted management key (manage) used to encrypt all other
    // entries in the private key store.  The management key is encrypted
    // with a key derived from the users password.
    // All encrypted values must contain a payload, mac and nonce.
    // ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
    "keyStore": {
      // Indexed by the OAuth user ID.

      "$uid" : {
        //Reading and writing records is restricted to the record creator. You may
        //only write records that are indexed by your OAuth UID
        ".read": "auth != null && auth.token.firebase.identities['google.com'][0] == $uid",
        //You may only write records that are indexed by your OAuth UID
        ".write": "auth != null && auth.token.firebase.identities['google.com'][0] == $uid",
        //Any new key store entry must include both private and public keys
        ".validate": "newData.hasChildren(['private', 'public'])",
        "private" : {
          //Any new private keys entry must include both profile and management keys
          ".validate": "newData.hasChildren(['profile', 'manage'])",

          //The profiles keys must conform to the encrypted key schema
          //All values must be present
          "profile": {
            //Identity (profile) keys must include signing and encryption values
            ".validate": "newData.hasChildren(['sign', 'encrypt'])",
            "encrypt" : {
              ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
              "payload": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "nonce": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "mac": {
                ".validate": "newData.isString() && newData.val().length > 0"
              }
            },
            "sign" : {
              ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
              "payload": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "nonce": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "mac": {
                ".validate": "newData.isString() && newData.val().length > 0"
              }
            }
          },

          //The management keys must conform to the encrypted key schema
          //All values must be present
          "manage": {
            //Management keys must include signing and encryption values
            ".validate": "newData.hasChildren(['sign', 'encrypt'])",
            "encrypt" : {
              ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
              "payload": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "nonce": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "mac": {
                ".validate": "newData.isString() && newData.val().length > 0"
              }
            },
            "sign" : {
              ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
              "payload": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "nonce": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "mac": {
                ".validate": "newData.isString() && newData.val().length > 0"
              }
            }
          },

          // Each mailbox key entry must include payload, mac and nonce values
          "mailboxes": {
            "$mailboxId": {
              ".validate": "newData.hasChildren(['payload', 'nonce', 'mac'])",
              "payload": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "nonce": {
                ".validate": "newData.isString() && newData.val().length > 0"
              },
              "mac": {
                ".validate": "newData.isString() && newData.val().length > 0"
              }
            }
          }
        },

        // The Public Key Store is used to store a user's public keys for
        // both encryption and signing purposes.
        // Public key data may be deleted or modified by the owner without restriction.
        // They may be read by any authenticated user without restriction
        "public": {
          // Relax the read rule for public keys, any authenticated user can read the public key data.
          ".read": "auth != null",
          // The encryption key must have a value for "key" that is not empty
          "encrypt": {
            ".validate": "newData.hasChildren(['key'])",
            "key": {
              ".validate": "newData.isString() && newData.val().length > 0"
            }
          },

          // The encryption key must have a value of for "key" that is not empty
          "sign": {
            ".validate": "newData.hasChildren(['key'])",
            "key" : {
              ".validate": "newData.isString() && newData.val().length > 0"
            }
          }
        }
      }
    }
  }
}

Sample Data

{
  "keyStore": {
    "uid1" : {
       //Private keys readable only by the record creator
       "private":{
          "profile":{
             "sign":{
                "payload":"eBRU2lqSa...",
                "nonce":"YLe66ttSezN1QsDpV0gP6g",
                "mac":"9wj-FisK2_Ngj_cEal2rtPLq8zk1128jrFQyo9Swcjk"
             },
             "encrypt":{
                "payload":"Y_YCyX04pvMEQ5LKbHA8...",
                "nonce":"XX8onyiJAArL8LJs5pykwA",
                "mac":"iWzRxcSNE9hc0ZUR7qpW7L3EE0bLYNRgxUNJjklfOnY"
             }
          },

          //Management Keys
          "manage":{
             "sign":{
                "payload":"Rq9c-dCyPCCykK2wwNS3l18G3-VuxYSaTebMHuRzgCw",
                "nonce":"Vsk0dD0gf6T4jk4smvxoTw",
                "mac":"_TiZvhnF5_-ZdLYfJefFKGW5IFmx8tVqS3YXT-3VXWw"
             },
             "encrypt":{
                "payload":"zIddo9WcfjjGb0r-GYmSAnGUWb1jtNmbJpVWWgebNy8",
                "nonce":"rC1zFOH4HLgu6qPgzx67WA",
                "mac":"B_yJQoHdOEnfDwm2IZ7Iip4O1ByPO6Qj8m7gp3DG6kU"
             }
          },

          //Private Chat Keys
          "mailboxes" : {
            //Base64 encoded mailbox identifier
            "NjQzODkwMjhlMTJiMDdlMTMyYzAyYTdjYWFlN2YwNzY1OTQ4NmMyMy4x":{
              "sign":{
                 "payload":"eBRU2lqSa-ZRCah4rJrRu3CRL-K...",
                 "nonce":"YLe66ttSezN1QsDpV0gP6g",
                 "mac":"9wj-FisK2_Ngj_cEal2rtPLq8zk1128jrFQyo9Swcjk"
               },
               "encrypt":{
                 "payload":"Y_YCyX04pvMEQ5LKbHA8JrxmJ72ZDC-1gicw...",
                 "nonce":"XX8onyiJAArL8LJs5pykwA",
                 "mac":"iWzRxcSNE9hc0ZUR7qpW7L3EE0bLYNRgxUNJjklfOnY"
             }
          }
       },

       //Public User Keys.  Readable by all, writable only by the record creator
       "public":{
          "sign":{
             "key":"BAACgSYxcOQExQ8elUZA6lWCg6I_uOI_k0oIFHmCS..."
          },
          "encrypt":{
             "key":"BABxm_YOiu6dJuk1OEPJFzqPUVf_IEgKCr5yc1XRg...."
          }
       }
     }
  },
}