summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYiming Jing <yimingjing@google.com>2021-02-26 20:05:38 +0000
committerYiming Jing <yimingjing@google.com>2021-02-26 20:24:42 +0000
commit5bee5c45ef1a014c87c9b3b14404e2669e205e8d (patch)
treee8026acb3819649c03d057a75e025f29aa5f67dd
parentbbc5d5ea496b76a5e075a8b729e90633576625da (diff)
downloadDebuggingRestrictionController-5bee5c45ef1a014c87c9b3b14404e2669e205e8d.tar.gz
Add the token issuer web service
The token issuer is implemented as a Firebase Cloud Function. The function issues JsonWebSignatures using RS256. Only authenticated users can get a token. This is done by checking the `context.auth` property of each incoming request. The API takes a `nonce` and a `deviceId` as inputs for now; but it can be easily extended in case more fine-grained control is needed. See more details in `server/HOW_TO.md`. Bug: 173734542 Bug: 173734533 Bug: 173734974 Bug: 180963653 Test: npm install -g firebase-tools Test: firebase emulators:start --only functions Change-Id: Ibfc1978ef0cb42bad8867b2e202afe608a55c4ad
-rw-r--r--.gitignore10
-rw-r--r--server/HOW_TO.md65
-rw-r--r--server/firebase.json7
-rw-r--r--server/functions/.eslintrc.json123
-rw-r--r--server/functions/.gitignore1
-rw-r--r--server/functions/api_config.SAMPLE.json9
-rw-r--r--server/functions/index.js32
-rw-r--r--server/functions/package.json27
-rwxr-xr-xserver/genkey.sh75
-rw-r--r--server/package.json6
10 files changed, 353 insertions, 2 deletions
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"
+ }
+}