UNPKG

@salesforce/plugin-release-management

Version:
106 lines 4.36 kB
/* * Copyright (c) 2020, salesforce.com, inc. * All rights reserved. * Licensed under the BSD 3-Clause license. * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ /** * Signed npm packages have two fields that point plugin-trust to the URLs for the public key and signature file * see src/package.ts/#PackageJsonSfdxProperty * * This code generates a keypair using node's crypto library, * signs the tarball, * verifies the signature * and uploads the .sig and .crt (public key) to their AWS bucket * * The private key is never persisted to disk and ephemeral (exists only during this signing process) * There are no security issues returning the sig/pubKey contents because those will be public * * This verification uses the sig/crt in memory--it verifies the match before uploading. Other code verifies it from S3. * * For security reasons, the url paths and bucket are hardcoded. */ import { generateKeyPair, createSign, createVerify } from 'node:crypto'; import { createReadStream } from 'node:fs'; import { putObject } from './upload.js'; const CRYPTO_LEVEL = 'RSA-SHA256'; const BUCKET = 'dfc-data-production'; export const BASE_URL = 'https://developer.salesforce.com'; export const SECURITY_PATH = 'media/salesforce-cli/security'; export const getSfdxProperty = (packageName, packageVersion) => { const fullPathNoExtension = `${BASE_URL}/${SECURITY_PATH}/${packageName}/${packageVersion}`; return { publicKeyUrl: `${fullPathNoExtension}.crt`, signatureUrl: `${fullPathNoExtension}.sig`, }; }; export const signVerifyUpload = async (signingRequest) => { const { publicKey, privateKey } = await getOneTimeUseKeys(); const { packageName, packageVersion } = signingRequest; const signatureContents = await getSignature(privateKey, signingRequest.targetFileToSign); // verify that signature/key/data worked properly await verify(signingRequest.targetFileToSign, publicKey, signatureContents); const output = { publicKeyContents: publicKey, signatureContents, packageJsonSfdxProperty: getSfdxProperty(packageName, packageVersion), fileTarPath: signingRequest.targetFileToSign, packageName, packageVersion, }; if (signingRequest.upload) { await upload(output); } return output; }; /** * Save the security items (publicKey and .sig file) to AWS based on the generates filenames */ const upload = async (input) => Promise.all([ // signature file putObject(BUCKET, input.packageJsonSfdxProperty.signatureUrl.replace(`${BASE_URL}/`, ''), input.signatureContents), // publicKey putObject(BUCKET, input.packageJsonSfdxProperty.publicKeyUrl.replace(`${BASE_URL}/`, ''), input.publicKeyContents), ]); const getOneTimeUseKeys = () => new Promise((resolve, reject) => { generateKeyPair('rsa', { modulusLength: 4096, publicKeyEncoding: { type: 'spki', format: 'pem', }, privateKeyEncoding: { type: 'pkcs8', format: 'pem', }, }, (err, publicKey, privateKey) => { if (err) reject(err); resolve({ publicKey, privateKey, }); }); }); const getSignature = async (privateKey, dataToSignFilePath) => { const signApi = createSign(CRYPTO_LEVEL); return new Promise((resolve, reject) => { const dataToSignStream = createReadStream(dataToSignFilePath, { encoding: 'binary' }); dataToSignStream.pipe(signApi); dataToSignStream.on('end', () => resolve(signApi.sign(privateKey, 'base64'))); dataToSignStream.on('error', (err) => reject(err)); }); }; const verify = async (dataToSignFilePath, publicKey, signature) => new Promise((resolve, reject) => { const verifier = createVerify(CRYPTO_LEVEL); const dataToVerifyStream = createReadStream(dataToSignFilePath, { encoding: 'binary' }); dataToVerifyStream.pipe(verifier); dataToVerifyStream.on('end', () => { if (verifier.verify(publicKey, signature, 'base64')) { return resolve(true); } return reject('The signature did not verify'); }); dataToVerifyStream.on('error', (err) => reject(err)); }); //# sourceMappingURL=SimplifiedSigning.js.map