Merge "Add the token issuer web service" into sc-dev
diff --git a/.gitignore b/.gitignore
index 35d81b6..e57a8b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,14 @@
 *.iml
+.classpath
+.project
+.settings
 .gradle
 /local.properties
 /.idea
 .DS_Store
 build/
-app/google-services.json
-app/*.pem
+certs/
+google-services.json
+package-lock.json
+*.pem
+*.der
diff --git a/server/HOW_TO.md b/server/HOW_TO.md
new file mode 100644
index 0000000..188be00
--- /dev/null
+++ b/server/HOW_TO.md
@@ -0,0 +1,65 @@
+# Debugging Access Token Issuer
+
+This sample demonstrates a Firebase Cloud Function that issues debugging access
+tokens to authenticated and authorized users.
+
+The directory structure looks like this:
+
+```shell
+server/
+ |
+ +- firebase.json  # Describes properties of the project
+ |
+ +- genkey.sh      # Optional script to generate token signing
+                   # key and certificates with a self-signed CA
+ |
+ + package.json    # npm package file
+ |
+ +- functions/     # Directory containing the function code
+      |
+      +- api_config.SAMPLE.json # A sample configuration
+      |
+      +- .eslintrc.json  # Rules for JavaScript linting
+      |
+      +- package.json  # npm package file
+      |
+      +- index.js      # main source file
+      |
+      +- node_modules/ # directory where the dependencies (declared in
+                       # package.json) are installed
+```
+
+## Running the Samples
+
+*   Setup Firebase Cloud Functions. See Step 1-3 of the guide at
+    [https://firebase.google.com/docs/functions/get-started](https://firebase.google.com/docs/functions/get-started).
+*   Prepare the token signing key and certificate. The key MUST be a RSA key
+    with at least 2048 bits. The certificate MUST contain at least one Subject
+    Alternative Name
+    ([SAN](https://en.wikipedia.org/wiki/Subject_Alternative_Name)). Use
+    `genkey.sh` if signing the certificate with a self-signed CA.
+*   Configure the token issuer in a config file. See `api_config.SAMPLE.json`
+    for an example. The config file contains secret information; do NOT commit
+    it in git.
+*   Deploy the config file by running `firebase functions:config:set
+    api_config="$(cat YOUR_CONFIG.json)"`.
+*   Deploy the Cloud Function. See the instructions at
+    [https://firebase.google.com/docs/functions/manage-functions](hhttps://firebase.google.com/docs/functions/manage-functions).
+
+## License
+
+Copyright 2021 Google LLC
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
diff --git a/server/firebase.json b/server/firebase.json
new file mode 100644
index 0000000..a68a195
--- /dev/null
+++ b/server/firebase.json
@@ -0,0 +1,7 @@
+{
+  "functions": {
+    "predeploy": [
+      "npm --prefix \"$RESOURCE_DIR\" run lint"
+    ]
+  }
+}
diff --git a/server/functions/.eslintrc.json b/server/functions/.eslintrc.json
new file mode 100644
index 0000000..3a614dd
--- /dev/null
+++ b/server/functions/.eslintrc.json
@@ -0,0 +1,123 @@
+{
+  "parserOptions": {
+    // Required for certain syntax usages
+    "ecmaVersion": 2017
+  },
+  "plugins": [
+    "promise"
+  ],
+  "extends": "eslint:recommended",
+  "rules": {
+    // Removed rule "disallow the use of console" from recommended eslint rules
+    "no-console": "off",
+
+    // Removed rule "disallow multiple spaces in regular expressions" from recommended eslint rules
+    "no-regex-spaces": "off",
+
+    // Removed rule "disallow the use of debugger" from recommended eslint rules
+    "no-debugger": "off",
+
+    // Removed rule "disallow unused variables" from recommended eslint rules
+    "no-unused-vars": "off",
+
+    // Removed rule "disallow mixed spaces and tabs for indentation" from recommended eslint rules
+    "no-mixed-spaces-and-tabs": "off",
+
+    // Removed rule "disallow the use of undeclared variables unless mentioned in /*global */ comments" from recommended eslint rules
+    "no-undef": "off",
+
+    // Warn against template literal placeholder syntax in regular strings
+    "no-template-curly-in-string": 1,
+
+    // Warn if return statements do not either always or never specify values
+    "consistent-return": 1,
+
+    // Warn if no return statements in callbacks of array methods
+    "array-callback-return": 1,
+
+    // Require the use of === and !==
+    "eqeqeq": 2,
+
+    // Disallow the use of alert, confirm, and prompt
+    "no-alert": 2,
+
+    // Disallow the use of arguments.caller or arguments.callee
+    "no-caller": 2,
+
+    // Disallow null comparisons without type-checking operators
+    "no-eq-null": 2,
+
+    // Disallow the use of eval()
+    "no-eval": 2,
+
+    // Warn against extending native types
+    "no-extend-native": 1,
+
+    // Warn against unnecessary calls to .bind()
+    "no-extra-bind": 1,
+
+    // Warn against unnecessary labels
+    "no-extra-label": 1,
+
+    // Disallow leading or trailing decimal points in numeric literals
+    "no-floating-decimal": 2,
+
+    // Warn against shorthand type conversions
+    "no-implicit-coercion": 1,
+
+    // Warn against function declarations and expressions inside loop statements
+    "no-loop-func": 1,
+
+    // Disallow new operators with the Function object
+    "no-new-func": 2,
+
+    // Warn against new operators with the String, Number, and Boolean objects
+    "no-new-wrappers": 1,
+
+    // Disallow throwing literals as exceptions
+    "no-throw-literal": 2,
+
+    // Require using Error objects as Promise rejection reasons
+    "prefer-promise-reject-errors": 2,
+
+    // Enforce “for” loop update clause moving the counter in the right direction
+    "for-direction": 2,
+
+    // Enforce return statements in getters
+    "getter-return": 2,
+
+    // Disallow await inside of loops
+    "no-await-in-loop": 2,
+
+    // Disallow comparing against -0
+    "no-compare-neg-zero": 2,
+
+    // Warn against catch clause parameters from shadowing variables in the outer scope
+    "no-catch-shadow": 1,
+
+    // Disallow identifiers from shadowing restricted names
+    "no-shadow-restricted-names": 2,
+
+    // Enforce return statements in callbacks of array methods
+    "callback-return": 2,
+
+    // Require error handling in callbacks
+    "handle-callback-err": 2,
+
+    // Warn against string concatenation with __dirname and __filename
+    "no-path-concat": 1,
+
+    // Prefer using arrow functions for callbacks
+    "prefer-arrow-callback": 1,
+
+    // Return inside each then() to create readable and reusable Promise chains.
+    // Forces developers to return console logs and http calls in promises.
+    "promise/always-return": 2,
+
+    //Enforces the use of catch() on un-returned promises
+    "promise/catch-or-return": 2,
+
+    // Warn against nested then() or catch() statements
+    "promise/no-nesting": 1
+  }
+}
diff --git a/server/functions/.gitignore b/server/functions/.gitignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/server/functions/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/server/functions/api_config.SAMPLE.json b/server/functions/api_config.SAMPLE.json
new file mode 100644
index 0000000..af9ea43
--- /dev/null
+++ b/server/functions/api_config.SAMPLE.json
@@ -0,0 +1,9 @@
+{
+    "key": "*** Put PEM-encoded private key here ***",
+    "certificates.0": "-----BEGIN CERTIFICATE-----\nTOKEN_SIGNING_CERT\n-----END CERTIFICATE-----\n",
+    "certificates.1": "-----BEGIN CERTIFICATE-----\nINTERMEDIATE_CA_CERT\n-----END CERTIFICATE-----\n",
+    "certificates.2": "-----BEGIN CERTIFICATE-----\nROOT_CA_CERT\n-----END CERTIFICATE-----\n",
+    "expiration": "30m",
+    "issuer": "Debugging Access Token Issuer",
+    "audience": "IHU"
+}
diff --git a/server/functions/index.js b/server/functions/index.js
new file mode 100644
index 0000000..fe15a1c
--- /dev/null
+++ b/server/functions/index.js
@@ -0,0 +1,32 @@
+'use strict';
+
+const functions = require('firebase-functions');
+const jws = require('jsonwebtoken');
+
+function cert_to_x5c(cert) {
+  return cert.replace(/-----[^\n]+\n?/gm, '').replace(/\n/g, '');
+}
+
+exports.requestAccessToken = functions.https.onCall((data, context) => {
+  if (!context.auth) {
+    throw new functions.https.HttpsError('failed-precondition', 'Unauthorized user');
+  }
+
+  const payload = {
+    nonce: data.nonce,
+    deviceId: data.deviceId,
+    restrictions: { 'no_debugging_features': false }
+  };
+  functions.logger.log("Payload: ", payload);
+  const config = functions.config().api_config;
+  const options = {
+    algorithm: 'RS256',
+    expiresIn: config.expiration,
+    issuer: config.issuer,
+    audience: config.audience,
+    header: { x5c: config.certificates.map(cert_to_x5c) }
+  };
+  const token = jws.sign(payload, config.key, options);
+  functions.logger.log("Signed Token: ", token);
+  return { token: token };
+});
diff --git a/server/functions/package.json b/server/functions/package.json
new file mode 100644
index 0000000..85ba565
--- /dev/null
+++ b/server/functions/package.json
@@ -0,0 +1,27 @@
+{
+  "name": "aaos_debugging_access_token_issuer",
+  "description": "AAOS Debugging Restriction API Endpoint",
+  "scripts": {
+    "lint": "eslint .",
+    "serve": "firebase emulators:start --only functions",
+    "shell": "firebase functions:shell",
+    "start": "npm run shell",
+    "deploy": "firebase deploy --only functions",
+    "logs": "firebase functions:log"
+  },
+  "engines": {
+    "node": "12"
+  },
+  "main": "index.js",
+  "dependencies": {
+    "firebase-admin": "^9.3.0",
+    "firebase-functions": "^3.11.0",
+    "jsonwebtoken": "^8.5.1"
+  },
+  "devDependencies": {
+    "eslint": "^5.12.0",
+    "eslint-plugin-promise": "^4.0.1",
+    "firebase-functions-test": "^0.2.0"
+  },
+  "private": true
+}
diff --git a/server/genkey.sh b/server/genkey.sh
new file mode 100755
index 0000000..b278df0
--- /dev/null
+++ b/server/genkey.sh
@@ -0,0 +1,75 @@
+#/bin/bash
+
+echo "This script generates the key and certificate chain for deploying"
+echo "the AAOS Debugging Restriction Controller client and service"
+echo
+echo "WARNING: Only use this script if you are using a self-signed CA."
+echo
+echo "Continue (y/N)?"
+read c
+if [[ "$c" != "y" ]]
+then
+    exit -1
+fi
+
+echo "Enter the path of the CA certificate:"
+read ca_cert
+
+echo "Enter path of the CA private key:"
+read ca_key
+
+echo
+echo "Enter the number of days the token signing key should be valid for:"
+echo "  (press return for 365 days)"
+read validity
+
+if [[ -z "$validity" ]] ; then
+  validity=365
+fi
+echo "Using '$validity' days"
+
+echo
+echo "Enter the hostname that identifies the token signer:"
+read hostname
+
+echo
+echo "Generating the token signing key and certificate signing request ..."
+echo "Please fill in the fields when requested."
+date=$(date +%Y-%m-%d)
+folder=$(mktemp -d)
+req="$folder/token_signing-${date}.req"
+key="$folder/token_signing-${date}.key"
+signed="$folder/token_signing-${date}.pem"
+
+config="
+[ server ]
+basicConstraints = critical,CA:false
+keyUsage = nonRepudiation, digitalSignature
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid:always,issuer:always
+subjectAltName = @alt_names
+
+[ alt_names ]
+DNS.1 = $hostname
+"
+
+openssl req -nodes -newkey rsa:2048 -sha256 -keyout "${key}" -out "${req}"
+echo "Signing the certificate ..."
+
+openssl x509 -req \
+    -in "$req" -out "$signed" -CA "$ca_cert" -CAkey "$ca_key" \
+    -sha256 -days "$validity" -set_serial 666 \
+    -extensions server -extfile <(echo "$config")
+
+key_out="token_signing_key-$date.pem"
+cert_chain_out="token_signing_certs-$date.pem"
+cat "$key" > "$key_out"
+cat "$signed" "$ca_cert" > "$cert_chain_out"
+
+
+echo "The token signing key and certificate chain have been created."
+echo "See $key_out and $cert_chain_out."
+echo
+echo "Verifying the certificate chain ..."
+openssl verify -CAfile "$ca_cert" "$cert_chain_out"
+rm -rf "$folder"
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..269e852
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,6 @@
+{
+  "dependencies": {
+    "firebase-admin": "^9.5.0",
+    "firebase-functions": "^3.13.2"
+  }
+}