@salesforce/plugin-release-management
Version:
A plugin for preparing and publishing npm packages
106 lines • 4.36 kB
JavaScript
/*
* 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