1.0.15 • Published 9 months ago

@govtechsg/ndi-app-check v1.0.15

Weekly downloads
-
License
ISC
Repository
-
Last release
9 months ago

NDI App Check

API Specs

1) NPM install @govtechsg/ndi-app-check

Import the ndi-app-check npm library

npm install ndi-app-check

2) Initialize the NdiAppCheck class

Import the ndi-app-check library into your codebase. ndi-app-check is am ESModule so it can only be used via import. However, CommonJS can still import the library via dynamic import syntax shown below.

Typescript (ESM)

import { 
    NdiAppCheck, // NdiAppCheck class houses the functions to do app check
    NdiAppCheckParams,  // NdiAppCheck.validateNdiAppCheckHeader function param interface
    Options // NdiAppCheck class constructor init param interface
} from "ndi-app-check"

import path from "path"
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// The file path to the Google service account json file
let serviceConfigFilePath = path.join(__dirname, "your-service-config.json")

// Alternatively use a json object instead of Google service account json config file
let serviceConfigJson = {
    "type": "service_account",
    "project_id": "your project_id",
    "private_key_id": "your private_key_id",
    "private_key": "<REDACTED>",
    "client_email": "play-integrity-verifier@your_project.iam.gserviceaccount.com",
    "client_id": "your client_id",
    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
    "token_uri": "https://oauth2.googleapis.com/token",
    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
    "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/play-integrity-verifier%40your_project.iam.gserviceaccount.com",
    "universe_domain": "googleapis.com"
}

let options: Options = {
    gcpServiceConfig: serviceConfigFilePath, // the file path string to your Google service account json file
    gcpServiceConfigJson: serviceConfigJson, // your service config json object, if this is supplied, gcpServiceConfig can be omitted
    iosDeviceCheckKey: "-----BEGIN PRIVATE KEY----- your key -----END PRIVATE KEY-----", // iOS device check private key to call apple deviec ehcek backend api
    iosDeviceCheckKeyId: "you device check key id", // iOS device check key id
    expiryLimitSeconds: 120, // optional expiry config, default is 120 seconds
    inProd: true,  // dictates the validation criteria between production and staging environment, allows package name sg.ndi.dev, and also disallow development environment for apple iOS attestation and deviceChecks
    debugMode: false  // // if true, more console logs will be printed to aid debugging
}

// Initialize NdiAppCheck class
let ndiAppCheck = new NdiAppCheck(options)

Javascript (CommonJS)

(async function() { // CommonJs does not allow top level await

    // The file path to the Google service account json file
    let serviceConfigFilePath = path.join(__dirname, "your-service-config.json")
    
    let options = {
        gcpServiceConfig: serviceConfigFilePath, // the file path string to your Google service account json file
        gcpServiceConfigJson: serviceConfigJson, // your service config json object, if this is supplied, gcpServiceConfig can be omitted
        iosDeviceCheckKey: "-----BEGIN PRIVATE KEY----- your key -----END PRIVATE KEY-----", // iOs device check private key to call apple deviec ehcek backend api
        iosDeviceCheckKeyId: "you device check key id", // iOS device check key id
        expiryLimitSeconds: 120, // optional expiry config, default is 120 seconds
        inProd: true,  // dictates the validation criteria between production and staging environment, allows package name sg.ndi.dev, and also disallow development environment for apple iOS attestation and deviceChecks
        debugMode: false  // if true, more console logs will be printed to aid debugging
    }

    let ndiAppCheck;
    await import("@govtechsg/ndi-app-check").then( ({ default: defaultImport }) => {
        // Initialize NdiAppCheck class
        ndiAppCheck = new defaultImport(options)
    } );
}) ();

2) Call the validateNdiAppCheckHeader function in NdiAppCheck class

NdiAppCheck class exposes 2 functions.

Typescript (ESM) validateNdiAppCheckHeader api call

// Determine if this header value requires a public key to validate
let headerNeedsPublicKey = ndiAppCheck.headerNeedsPublicKey(header)

// Fetch the public key from a database or other persistent storage
let pubkey = undefined
if (headerNeedPublicKey) {
    /**
     * fetch public keys from database
     */
    pubkey = <databased fetched publicKey>
}

let appcheck_params: NdiAppCheckParams = {
    header: header, //string - header value
    nonce: nonce, // string - nonce, hash, challenge (session-token, reg_ref, swk_qr_ref etc)
    publicKey: pubKey, // any - publickey only used for Apple key attestation, this value should be obtained from the result of validateNdiAppCheckHeader when a publickey is returned
}

let ndiAppCheckResult = await ndiAppCheck.validateNdiAppCheckHeader(appcheck_params)

Javascript (CommonJS) validateNdiAppCheckHeader api call

// Determine if this header value requires a public key to validate
let headerNeedsPublicKey = ndiAppCheck.headerNeedsPublicKey(header)

// Fetch the public key from a database or other persistent storage
let pubkey = undefined
if (headerNeedPublicKey) {
    /**
     * fetch public keys from database
     */
    pubkey = <databased fetched publicKey>
}

let appcheck_params = {
    header: header, //string - header value
    nonce: nonce, // string - nonce, hash, challenge (session-token, reg_ref, swk_qr_ref etc)
    publicKey: pubKey, // any - publickey only used for Apple key attestation, this value should be obtained from the result of validateNdiAppCheckHeader when a publickey is returned
}

let ndiAppCheckResult = await ndiAppCheck.validateNdiAppCheckHeader(appcheck_params)

Typescript (ESM) validateNdiAppCheckHeader response format

// result returned from ndiAppCheck.validateNdiAppCheckHeader conforms to the below interface
declare interface NdiAppCheckResponse {
    state: Boolean; // state will be true when validation was successful
    reason?: string; // reason will NOT be undefined/null when state == false and validation failed due to security criteria
    error?: string; // error will NOT be undefined/null when state == false and validation failed with an exception/error
    pubicKey?: any; // pubkey will NOT be undefined/null when state === true and validation succeeded with apple iOS attestation header
}

The NdiAppCheck.validateNdiAppCheckHeader function will return a result adhering to the interface NdiAppCheckResponse.

if ndiAppCheckResult contains a publicKey property, it means the validateNdiAppCheckHeader call was made using a Apple iOS key attestation header. This publicKey is to be saved into a database so that when a validateNdiAppCheckHeader call made with Apple iOS app assertion header, the assertion header can be validated successfully using the aforementioned publicKey

Accepted Header formats

Android (Google Play)

Header for Google Play Integrity

X-ndi-appcheck: gpi <token>

Android (Huawei)

Header for Huawei SysIntegrity

X-ndi-appcheck: hwsi <token>

Apple iOS

Header for Apple iOS DeviceCheck

X-ndi-appcheck: adc <token>

Header for Apple iOS App Key Attestation

X-ndi-appcheck: aka <token>

Header for Apple iOS App Assertion

X-ndi-appcheck: aaa <token>

Contributing

1) Git clone the project from private repository (do contact the author for permissions) 2) cd SpaAppAttestationModule 3) Run npm i 4) As an optional step (if the node-app-attest library has not been transpiled i.e. node-app-attest folder does not exist on the root of this project directory), run

npm run babel:node-app-attest
  • Afterwhich, search for the following usages of the asn1js and pkijs library in node-app-attest/src/verifyAttestation.js
    ```
    const asn1 = _asn1js.default.fromBER(clientCertificate.raw); const certificate = new _pkijs.default.Certificate({ schema: asn1.result });
    and remove the `default` property from `_asn1js` and `_pkijs`. This will result in the following.
    const asn1 = _asn1js.fromBER(clientCertificate.raw); const certificate = new _pkijs.Certificate({ schema: asn1.result });
    5) Build the `src` folder by running `npm run build`
    6) `npm publish` for publishing the npm package
1.0.15

9 months ago

1.0.14

10 months ago

1.0.13

10 months ago

1.0.12

10 months ago

1.0.9

1 year ago

1.0.11

1 year ago

1.0.10

1 year ago

1.0.7

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.1

1 year ago

1.0.0

1 year ago