diff options
author | Yiming Jing <yimingjing@google.com> | 2021-03-05 00:06:22 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2021-03-05 00:06:22 +0000 |
commit | df660d593495d41b167e50b570de00e320e68437 (patch) | |
tree | 5d392e1612d176b2f5cb26ddf7728fc5b1a41b6b | |
parent | 1eaeb817a8be1827cfccd9374861af6b83e2e0d7 (diff) | |
parent | 5bee5c45ef1a014c87c9b3b14404e2669e205e8d (diff) | |
download | DebuggingRestrictionController-df660d593495d41b167e50b570de00e320e68437.tar.gz |
Merge "Add the token issuer web service" into sc-dev
-rw-r--r-- | .gitignore | 10 | ||||
-rw-r--r-- | server/HOW_TO.md | 65 | ||||
-rw-r--r-- | server/firebase.json | 7 | ||||
-rw-r--r-- | server/functions/.eslintrc.json | 123 | ||||
-rw-r--r-- | server/functions/.gitignore | 1 | ||||
-rw-r--r-- | server/functions/api_config.SAMPLE.json | 9 | ||||
-rw-r--r-- | server/functions/index.js | 32 | ||||
-rw-r--r-- | server/functions/package.json | 27 | ||||
-rwxr-xr-x | server/genkey.sh | 75 | ||||
-rw-r--r-- | server/package.json | 6 |
10 files changed, 353 insertions, 2 deletions
@@ -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" + } +} |