@expo/code-signing-certificates
Advanced tools
Comparing version
@@ -74,3 +74,3 @@ "use strict"; | ||
// check self-signed | ||
expect(certificate.issuer.hash).toEqual(certificate.subject.hash); | ||
expect(certificate.verify(certificate)).toBe(true); | ||
// check extensions | ||
@@ -223,2 +223,47 @@ expect(certificate.getExtension('keyUsage')).toMatchObject({ | ||
}); | ||
describe('CSR generation and certificate generation from CA + CSR', () => { | ||
it('generates a development certificate', async () => { | ||
const [issuerPrivateKeyPEM, issuerCertificatePEM] = await Promise.all([ | ||
fs_1.promises.readFile(path_1.default.join(__dirname, './fixtures/test-private-key.pem'), 'utf8'), | ||
fs_1.promises.readFile(path_1.default.join(__dirname, './fixtures/test-certificate.pem'), 'utf8'), | ||
]); | ||
const issuerPrivateKey = (0, main_1.convertPrivateKeyPEMToPrivateKey)(issuerPrivateKeyPEM); | ||
const issuerCertificate = (0, main_1.convertCertificatePEMToCertificate)(issuerCertificatePEM); | ||
const keyPair = (0, main_1.generateKeyPair)(); | ||
const csr1 = (0, main_1.generateCSR)(keyPair, 'Test common name'); | ||
const csrPEM = (0, main_1.convertCSRToCSRPEM)(csr1); | ||
const csr = (0, main_1.convertCSRPEMToCSR)(csrPEM); | ||
const certificate = (0, main_1.generateDevelopmentCertificateFromCSR)(issuerPrivateKey, issuerCertificate, csr, 'testApp', 'testScopeKey'); | ||
// check signed by issuer | ||
expect(issuerCertificate.verify(certificate)).toBe(true); | ||
// check subject attributes are transferred | ||
expect(certificate.subject.getField('CN').value).toEqual('Test common name'); | ||
// check extensions | ||
expect(certificate.getExtension('keyUsage')).toMatchObject({ | ||
critical: true, | ||
dataEncipherment: false, | ||
digitalSignature: true, | ||
id: '2.5.29.15', | ||
keyCertSign: false, | ||
keyEncipherment: false, | ||
name: 'keyUsage', | ||
nonRepudiation: false, | ||
}); | ||
expect(certificate.getExtension('extKeyUsage')).toMatchObject({ | ||
clientAuth: false, | ||
codeSigning: true, | ||
critical: true, | ||
emailProtection: false, | ||
id: '2.5.29.37', | ||
name: 'extKeyUsage', | ||
serverAuth: false, | ||
timeStamping: false, | ||
}); | ||
expect(certificate.getExtension('expoProjectInformation')).toMatchObject({ | ||
name: 'expoProjectInformation', | ||
id: main_1.expoProjectInformationOID, | ||
value: 'testApp,testScopeKey', | ||
}); | ||
}); | ||
}); | ||
//# sourceMappingURL=main-test.js.map |
import { pki as PKI } from 'node-forge'; | ||
export declare const expoProjectInformationOID = "1.2.840.113556.1.8000.2554.43437.254.128.102.157.7894389.20439.2.1"; | ||
/** | ||
@@ -50,2 +51,14 @@ * Generate a public and private RSA key pair. | ||
export declare function convertCertificatePEMToCertificate(certificatePEM: string): PKI.Certificate; | ||
/** | ||
* Convert a CSR to PEM-formatted X.509 CSR | ||
* @param csr CSR | ||
* @returns X.509 CSR | ||
*/ | ||
export declare function convertCSRToCSRPEM(csr: PKI.CertificateRequest): string; | ||
/** | ||
* Convert a PEM-formatted X.509 CSR to a CSR | ||
* @param CSRPEM PEM-formatted X.509 CSR | ||
* @returns CSR | ||
*/ | ||
export declare function convertCSRPEMToCSR(CSRPEM: string): PKI.CertificateRequest; | ||
declare type GenerateParameters = { | ||
@@ -93,2 +106,25 @@ /** | ||
export declare function signStringRSASHA256AndVerify(privateKey: PKI.rsa.PrivateKey, certificate: PKI.Certificate, stringToSign: string): string; | ||
/** | ||
* Generate a self-signed CSR for a given key pair. Most commonly used with {@link generateDevelopmentCertificateFromCSR}. | ||
* @param keyPair RSA key pair | ||
* @param commonName commonName attribute of the subject of the resulting certificate (human readable name of the certificate) | ||
* @returns CSR | ||
*/ | ||
export declare function generateCSR(keyPair: PKI.rsa.KeyPair, commonName: string): PKI.CertificateRequest; | ||
/** | ||
* For use by a server to generate a development certificate (good for 30 days) for a particular | ||
* appId and scopeKey (fields verified by the client during certificate validation). | ||
* | ||
* Note that this function assumes the issuer is trusted, and that the user that created the CSR and issued | ||
* the request has permission to sign manifests for the appId and scopeKey. This constraint must be | ||
* verified on the server before calling this method. | ||
* | ||
* @param issuerPrivateKey private key to sign the resulting certificate with | ||
* @param issuerCertificate parent certificate (should be a CA) of the resulting certificate | ||
* @param csr certificate signing request containing the user's public key | ||
* @param appId app ID (UUID) of the app that the resulting certificate will sign the development manifest for | ||
* @param scopeKey scope key of the app that the resuting certificate will sign the development manifest for | ||
* @returns certificate to use to sign development manifests | ||
*/ | ||
export declare function generateDevelopmentCertificateFromCSR(issuerPrivateKey: PKI.rsa.PrivateKey, issuerCertificate: PKI.Certificate, csr: PKI.CertificateRequest, appId: string, scopeKey: string): PKI.Certificate; | ||
export {}; |
@@ -6,6 +6,8 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.signStringRSASHA256AndVerify = exports.validateSelfSignedCertificate = exports.generateSelfSignedCodeSigningCertificate = exports.convertCertificatePEMToCertificate = exports.convertPrivateKeyPEMToPrivateKey = exports.convertPublicKeyPEMToPublicKey = exports.convertKeyPairPEMToKeyPair = exports.convertCertificateToCertificatePEM = exports.convertKeyPairToPEM = exports.generateKeyPair = void 0; | ||
exports.generateDevelopmentCertificateFromCSR = exports.generateCSR = exports.signStringRSASHA256AndVerify = exports.validateSelfSignedCertificate = exports.generateSelfSignedCodeSigningCertificate = exports.convertCSRPEMToCSR = exports.convertCSRToCSRPEM = exports.convertCertificatePEMToCertificate = exports.convertPrivateKeyPEMToPrivateKey = exports.convertPublicKeyPEMToPublicKey = exports.convertKeyPairPEMToKeyPair = exports.convertCertificateToCertificatePEM = exports.convertKeyPairToPEM = exports.generateKeyPair = exports.expoProjectInformationOID = void 0; | ||
const assert_1 = __importDefault(require("assert")); | ||
const node_forge_1 = require("node-forge"); | ||
const utils_1 = require("./utils"); | ||
// generated with oidgen script. in the microsoft OID space. could apply for Expo space but would take time: https://pen.iana.org/pen/PenApplication.page | ||
exports.expoProjectInformationOID = '1.2.840.113556.1.8000.2554.43437.254.128.102.157.7894389.20439.2.1'; | ||
/** | ||
@@ -77,6 +79,24 @@ * Generate a public and private RSA key pair. | ||
function convertCertificatePEMToCertificate(certificatePEM) { | ||
return node_forge_1.pki.certificateFromPem(certificatePEM); | ||
return node_forge_1.pki.certificateFromPem(certificatePEM, true); | ||
} | ||
exports.convertCertificatePEMToCertificate = convertCertificatePEMToCertificate; | ||
/** | ||
* Convert a CSR to PEM-formatted X.509 CSR | ||
* @param csr CSR | ||
* @returns X.509 CSR | ||
*/ | ||
function convertCSRToCSRPEM(csr) { | ||
return node_forge_1.pki.certificationRequestToPem(csr); | ||
} | ||
exports.convertCSRToCSRPEM = convertCSRToCSRPEM; | ||
/** | ||
* Convert a PEM-formatted X.509 CSR to a CSR | ||
* @param CSRPEM PEM-formatted X.509 CSR | ||
* @returns CSR | ||
*/ | ||
function convertCSRPEMToCSR(CSRPEM) { | ||
return node_forge_1.pki.certificationRequestFromPem(CSRPEM, true); | ||
} | ||
exports.convertCSRPEMToCSR = convertCSRPEMToCSR; | ||
/** | ||
* Generate a self-signed code-signing certificate for use with expo-updates. | ||
@@ -187,2 +207,79 @@ * Note that while certificate chains may be supported at some point in expo-updates, for now | ||
exports.signStringRSASHA256AndVerify = signStringRSASHA256AndVerify; | ||
/** | ||
* Generate a self-signed CSR for a given key pair. Most commonly used with {@link generateDevelopmentCertificateFromCSR}. | ||
* @param keyPair RSA key pair | ||
* @param commonName commonName attribute of the subject of the resulting certificate (human readable name of the certificate) | ||
* @returns CSR | ||
*/ | ||
function generateCSR(keyPair, commonName) { | ||
const csr = node_forge_1.pki.createCertificationRequest(); | ||
csr.publicKey = keyPair.publicKey; | ||
const attrs = [ | ||
{ | ||
name: 'commonName', | ||
value: commonName, | ||
}, | ||
]; | ||
csr.setSubject(attrs); | ||
csr.sign(keyPair.privateKey, node_forge_1.md.sha256.create()); | ||
return csr; | ||
} | ||
exports.generateCSR = generateCSR; | ||
/** | ||
* For use by a server to generate a development certificate (good for 30 days) for a particular | ||
* appId and scopeKey (fields verified by the client during certificate validation). | ||
* | ||
* Note that this function assumes the issuer is trusted, and that the user that created the CSR and issued | ||
* the request has permission to sign manifests for the appId and scopeKey. This constraint must be | ||
* verified on the server before calling this method. | ||
* | ||
* @param issuerPrivateKey private key to sign the resulting certificate with | ||
* @param issuerCertificate parent certificate (should be a CA) of the resulting certificate | ||
* @param csr certificate signing request containing the user's public key | ||
* @param appId app ID (UUID) of the app that the resulting certificate will sign the development manifest for | ||
* @param scopeKey scope key of the app that the resuting certificate will sign the development manifest for | ||
* @returns certificate to use to sign development manifests | ||
*/ | ||
function generateDevelopmentCertificateFromCSR(issuerPrivateKey, issuerCertificate, csr, appId, scopeKey) { | ||
(0, assert_1.default)(csr.verify(csr), 'CSR not self-signed'); | ||
const certificate = node_forge_1.pki.createCertificate(); | ||
certificate.publicKey = csr.publicKey; | ||
certificate.serialNumber = (0, utils_1.toPositiveHex)(node_forge_1.util.bytesToHex(node_forge_1.random.getBytesSync(9))); | ||
// set certificate subject attrs from CSR | ||
certificate.setSubject(csr.subject.attributes); | ||
// 30 day validity | ||
certificate.validity.notBefore = new Date(); | ||
certificate.validity.notAfter = new Date(); | ||
certificate.validity.notAfter.setDate(certificate.validity.notBefore.getDate() + 30); | ||
certificate.setIssuer(issuerCertificate.subject.attributes); | ||
certificate.setExtensions([ | ||
{ | ||
name: 'keyUsage', | ||
critical: true, | ||
keyCertSign: false, | ||
digitalSignature: true, | ||
nonRepudiation: false, | ||
keyEncipherment: false, | ||
dataEncipherment: false, | ||
}, | ||
{ | ||
name: 'extKeyUsage', | ||
critical: true, | ||
serverAuth: false, | ||
clientAuth: false, | ||
codeSigning: true, | ||
emailProtection: false, | ||
timeStamping: false, | ||
}, | ||
{ | ||
name: 'expoProjectInformation', | ||
id: exports.expoProjectInformationOID, | ||
// critical: true, // can't be critical since openssl verify doesn't know about this extension | ||
value: `${appId},${scopeKey}`, | ||
}, | ||
]); | ||
certificate.sign(issuerPrivateKey, node_forge_1.md.sha256.create()); | ||
return certificate; | ||
} | ||
exports.generateDevelopmentCertificateFromCSR = generateDevelopmentCertificateFromCSR; | ||
//# sourceMappingURL=main.js.map |
{ | ||
"name": "@expo/code-signing-certificates", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "A library for working with expo-updates code signing certificates", | ||
@@ -12,3 +12,7 @@ "main": "build/main.js", | ||
"lint-fix": "eslint src --fix", | ||
"test": "jest --rootDir . --config jest.config.js" | ||
"test": "jest --rootDir . --config jest.config.js", | ||
"generate-example-certificates": "ts-node -r tsconfig-paths/register --project tsconfig.scripts.json ./scripts/generateExampleCertificates.ts", | ||
"generate-example-self-signed": "ts-node -r tsconfig-paths/register --project tsconfig.scripts.json ./scripts/generateExampleSelfSignedCertificate.ts", | ||
"oidgen": "ts-node -r tsconfig-paths/register --project tsconfig.scripts.json ./scripts/oidgen/oidgen.ts", | ||
"sign-manifest-for-testing": "ts-node -r tsconfig-paths/register --project tsconfig.scripts.json ./scripts/signManifestForTesting.ts" | ||
}, | ||
@@ -36,5 +40,7 @@ "repository": { | ||
"devDependencies": { | ||
"@tsconfig/node14": "^1.0.1", | ||
"@types/jest": "^27.4.0", | ||
"@types/jsbn": "^1.2.30", | ||
"@types/node-forge": "^1.0.0", | ||
"@types/uuid": "^8.3.4", | ||
"@typescript-eslint/eslint-plugin": "^5.11.0", | ||
@@ -47,4 +53,6 @@ "@typescript-eslint/parser": "^5.11.0", | ||
"ts-jest": "^27.1.3", | ||
"typescript": "^4.5.5" | ||
"ts-node-dev": "^1.1.8", | ||
"typescript": "^4.5.5", | ||
"uuid": "^8.3.2" | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
51786
33.81%714
33.46%15
36.36%