office-addin-dev-certs
Version:
For managing certificates when developing Office Add-ins.
187 lines • 8.92 kB
JavaScript
;
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyCertificates = exports.getCaCertificateStatus = exports.isCaCertificateInstalled = exports.outputMarker = exports.CertificateStatus = void 0;
const child_process_1 = require("child_process");
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const defaults = __importStar(require("./defaults"));
const defaults_1 = require("./defaults");
const office_addin_usage_data_1 = require("office-addin-usage-data");
var CertificateStatus;
(function (CertificateStatus) {
CertificateStatus["Installed"] = "installed";
CertificateStatus["NotInstalled"] = "not-installed";
CertificateStatus["Error"] = "error";
})(CertificateStatus = exports.CertificateStatus || (exports.CertificateStatus = {}));
/* global Buffer process __dirname */
// On win32 this is a unique hash used with PowerShell command to reliably delineate command output
exports.outputMarker = process.platform === "win32"
? `[${crypto_1.default.createHash("md5").update(`${defaults.certificateName}${defaults.caCertificatePath}`).digest("hex")}]`
: "";
function getVerifyCommand(returnInvalidCertificate) {
switch (process.platform) {
case "win32": {
const script = path_1.default.resolve(__dirname, "..\\scripts\\verify.ps1");
const defaultCommand = `powershell -ExecutionPolicy Bypass -File "${script}" -CaCertificateName "${defaults.certificateName}" -CaCertificatePath "${defaults.caCertificatePath}" -LocalhostCertificatePath "${defaults.localhostCertificatePath}" -OutputMarker "${exports.outputMarker}"`;
if (returnInvalidCertificate) {
return defaultCommand + ` -ReturnInvalidCertificate`;
}
return defaultCommand;
}
case "darwin": {
// macOS
const script = path_1.default.resolve(__dirname, "../scripts/verify.sh");
return `sh '${script}' '${defaults.certificateName}'`;
}
case "linux": {
const script = path_1.default.resolve(__dirname, "../scripts/verify_linux.sh");
return `sh '${script}' '${defaults.caCertificateFileName}'`;
}
default:
throw new office_addin_usage_data_1.ExpectedError(`Platform not supported: ${process.platform}`);
}
}
/**
* Checks if the CA certificate is installed.
* @param returnInvalidCertificate - If true, returns true even if the certificate is expired.
* @returns true if the certificate is installed, false otherwise.
* @throws Error if the verification script fails to execute (e.g., PowerShell not found, permission issues).
*/
function isCaCertificateInstalled(returnInvalidCertificate = false) {
const result = getCaCertificateStatus(returnInvalidCertificate);
if (result.status === CertificateStatus.Error) {
throw result.error;
}
return result.status === CertificateStatus.Installed;
}
exports.isCaCertificateInstalled = isCaCertificateInstalled;
/**
* Gets the CA certificate installation status with detailed error information.
* @param returnInvalidCertificate - If true, returns Installed even if the certificate is expired.
* @returns A CertificateVerificationResult object with status and optional error information.
*/
function getCaCertificateStatus(returnInvalidCertificate = false) {
const command = getVerifyCommand(returnInvalidCertificate);
try {
const output = (0, child_process_1.execSync)(command, { stdio: "pipe" }).toString();
if (process.platform === "win32") {
// Remove any PowerShell output that preceeds invoking the actual certificate check command
const hasOutput = output.slice(output.lastIndexOf(exports.outputMarker) + exports.outputMarker.length).trim().length !== 0;
return { status: hasOutput ? CertificateStatus.Installed : CertificateStatus.NotInstalled };
}
// script files return empty string if the certificate not found or expired
if (output.length !== 0) {
return { status: CertificateStatus.Installed };
}
return { status: CertificateStatus.NotInstalled };
}
catch (err) {
// Distinguish between "certificate not found" (expected) and script execution errors (unexpected).
// On Windows, PowerShell with $ErrorActionPreference = 'Stop' throws when the cert is not found.
// On macOS/Linux, scripts return empty output rather than throwing for missing certs.
if (process.platform === "win32" && err instanceof Error) {
const errorMessage = err.message.toLowerCase();
// PowerShell script errors that indicate the script ran but cert wasn't found
if (errorMessage.includes("cannot find") ||
errorMessage.includes("not found") ||
errorMessage.includes("no certificate")) {
return { status: CertificateStatus.NotInstalled };
}
}
// For other platforms or unexpected errors, check if it's a script execution failure
const error = err instanceof Error ? err : new Error(String(err));
const errorMessage = error.message.toLowerCase();
// Common script execution failures that should be reported as errors
if (errorMessage.includes("command not found") ||
errorMessage.includes("not recognized") ||
errorMessage.includes("cannot be loaded") ||
errorMessage.includes("permission denied") ||
errorMessage.includes("enoent") ||
errorMessage.includes("access denied") ||
errorMessage.includes("spawn")) {
return { status: CertificateStatus.Error, error };
}
// For other errors, assume the certificate is not installed (backwards compatibility)
// but log the error for debugging purposes
return { status: CertificateStatus.NotInstalled };
}
}
exports.getCaCertificateStatus = getCaCertificateStatus;
function validateCertificateAndKey(certificatePath, keyPath) {
let certificate = "";
let key = "";
try {
certificate = fs_1.default.readFileSync(certificatePath).toString();
}
catch (err) {
throw new Error(`Unable to read the certificate.\n${err}`);
}
try {
key = fs_1.default.readFileSync(keyPath).toString();
}
catch (err) {
throw new Error(`Unable to read the certificate key.\n${err}`);
}
let encrypted;
try {
encrypted = crypto_1.default.publicEncrypt(certificate, Buffer.from("test"));
}
catch (err) {
throw new Error(`The certificate is not valid.\n${err}`);
}
try {
crypto_1.default.privateDecrypt(key, encrypted);
}
catch (err) {
throw new Error(`The certificate key is not valid.\n${err}`);
}
}
function verifyCertificates(certificatePath = defaults.localhostCertificatePath, keyPath = defaults.localhostKeyPath) {
try {
let isCertificateValid = true;
try {
validateCertificateAndKey(certificatePath, keyPath);
}
catch (_a) {
isCertificateValid = false;
}
let output = isCertificateValid && isCaCertificateInstalled();
defaults_1.usageDataObject.reportSuccess("verifyCertificates()");
return output;
}
catch (err) {
defaults_1.usageDataObject.reportException("verifyCertificates()", err);
throw err;
}
}
exports.verifyCertificates = verifyCertificates;
//# sourceMappingURL=verify.js.map