Compare commits

...
5 changed files with 4445 additions and 2 deletions

No files matched your search

+1 -1
View File
@@ -19,7 +19,6 @@
"@expo/multipart-body-parser": "1.1.0",
"@expo/osascript": "2.0.33",
"@expo/package-manager": "1.1.2",
"@expo/pkcs12": "0.0.8",
"@expo/plist": "0.0.20",
"@expo/plugin-help": "5.1.23",
"@expo/plugin-warn-if-update-available": "2.5.1",
@@ -95,6 +94,7 @@
"@types/fs-extra": "9.0.13",
"@types/getenv": "^1.0.0",
"@types/mime": "3.0.1",
"@types/node": "^18.6.0",
"@types/node-fetch": "2.6.2",
"@types/node-forge": "1.3.1",
"@types/pngjs": "6.0.1",
@@ -0,0 +1,210 @@
import {
getAsn1Hash,
getCertificateFingerprint,
getFormattedSerialNumber,
getX509Asn1ByFriendlyName,
getX509Certificate,
getX509CertificateByFriendlyName,
parsePKCS12,
} from '../pkcs12';
// https://pkijs.org/examples/PKCS12SimpleExample.html
// PKCS#12 With Password-Based Integrity Protection And No Privacy Protection
// A PKCS#12 file with one key and one certificate (most common case)
const conventionalP12 = {
base64EncodedP12:
'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',
password: 'test',
serialNumber: '1',
md5Fingerprint: '2557d902372f89802767249fc6d65113',
sha1Fingerprint: '02ec75a7181c575757baa931fe3105b7125ff10a',
sha256Fingerprint: 'fba434e1877fbbf48cd041389ef9e1c0b511d1426c69d6d110e7d2a7db6b5890',
};
// Generated by: keytool -genkey -alias test-alias -keystore keystore.p12 -storetype PKCS12 -keyalg RSA -storepass password -validity 730 -keysize 2048
// A Keystore in a PKCS#12 file, where all entries are under aliases
// This particular PKCS#12 has one key and one certificate under 'test-alias'
const keystoreP12 = {
base64EncodedP12:
'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',
password: 'password',
alias: 'test-alias',
// Expected fingerprints derived from: keytool -list -v -keystore [keystore-path]
md5Fingerprint: 'ccd9d0fd20a862b1e67ea838c99dd0c5',
sha1Fingerprint: '2ef9743a1bf12189af5503787b54bfd3254b46a8',
sha256Fingerprint: 'eecdd35e90bb272d52ec261b4063c5d586980795452340525f9c8cc56db53e2b',
};
// keytool -genkey -alias UPPERCASE -keystore keystore.p12 -storetype PKCS12 -keyalg RSA -storepass password -validity 730 -keysize 2048
const uppercaseAliasKeystoreP12 = {
base64EncodedP12:
'MIITawIBAzCCEyQGCSqGSIb3DQEHAaCCExUEghMRMIITDTCCCskGCSqGSIb3DQEHAaCCCroEggq2MIIKsjCCBVYGCyqGSIb3DQEMCgECoIIE+zCCBPcwKQYKKoZIhvcNAQwBAzAbBBT5CPXUtTFIhZO3Cpdw39WK9cZenQIDAMNQBIIEyPG/T570+fv3FyUiwm7Nwk2p6jMyCmlyO8iYsFuCaTW6vbA2KMHa/A7A6CtpRZdnOB4rOJDx8kCTi+vf3KxHM9GWitFkKhsnYIxf9t6tGEQUPpHetSbe2dr+/F/C3GhmLLh7lWP+dt3V4RqC8otu8IBA+nfHN7AYx7Sg/VCh7wi7gxdZ1q7A7pnvEM/bxZic3W9OOgspgCfEkyu09uZVmxix4Ri3lZpVXPcOazsSFa5mr1wTfsXvrveW/p5aSZ4FGwPyVL8yGaCLL4ggpmHGAcq81SEoZSf6r5sldtZnMYc+rVM8Jjt+E0NDa9lxum4QOodniPUy4iQXm2r8DA0h8aKD5ndjcyyM2ppvJwSiK2CX+X8bqY/yBUTNeFjzIxaEVk4p/BD5SiO/YOWTFTPftPOWDWltue63pmKQXUHPCGr5WAAE7tOCf2kofvJ7EbBmeqPSSj3o2nPOcBkiWem0yLXLDVZUi/G5qBDeogGN8hVWx14MGp6Pe4x+6DZUlSaDrqlPKJ8O0JhyUZ2WrnMzrtQ8W+5MWhVtxU7YLtfq3/eAzXTQJ0vhA/ZjqGQqyA3H+WndQ5hQWJUBewRsjX9qthfrrIHM5TnI4eWy6OAkXCq0fNcx0eJoDlViUgfRy7yvb7D+sokXkyN3di+DNHNvqplK8P2UH0wzfaDBows5/nQqWnRBuR07ujAge+5tEDEjDUul1BFTce2yMkP1jM1jf2JOxkmGCqXGlfK/rbBWiH7ToV1pDHUKf3s4D0Xybs3aOp2xD1jpja6z88GYZvC/8xwjCM8RyirOtYG5ULnaqqLKf5Yo4QyUJVJiDG5EEixQ7Kvn61lwYAg1Jbk5jh5IHn8xnkLaGVfE5nocnCUdy5rn2QQ3GsljIqudqlsVPQx9TdQofw9spoC+STDPUCZeQ0zu9WuYZpEJIMq6ZyDZwZq5S672Xb0emUFI6xvQDg7tLc5Xxo6keYJpxP+kSxlNq6YMg68xRoLhWLQaZhUCV9lSGmoaweDaYNFbc4dr8IVin3QGjjDgxcLuAPhdN0KR/yT1POxAMw6Pit9QIUCUnHOYxucFvwA/INRzN7xGITxrTSRNwtXUbGguxDBtHeAn19LDB860mGauKHZROc9nlFS8iKOhB9eZoEqccuyir+asf4uzuChMEaXBl2A74a/w1opi4q+6sKZkkpV9jCo2A606lXxZ9AUe2moyNjbxDusxrL6X/Fyvpdro7CE+jqw7YE4Uqcnu/muV7juJWDjUKJTi3H91NTE4Ud1IKUEsmmWKCj/tL1aae6r4ZbsbiBn0MP5WWB4sgiXy4ZvXpeCkBeszUx+zbZC11/ag7Jkc7rlIPxMTopQgRS9KIQ/9tIzYHBad8ro45DgFig37KyX2GMZ0pr7KbuZ8RWDTqEHmNnto4aAwGoI+ypKfDMHrqNhAUtvGZzFCjwjEvJFXhFLScvTjyiVzbeU8cIPU2sXIoYQRfavp2vUp+jNKX2SIkQ4Nh3n8YBfOoCW/YNjVJFeDVObwSmiv65XEd+HwXl2cLe8jJXDMXN9ZNdnxWUcgpTF9dp3Zxhr0TmRVJCngL9HjgWrFL9hReZTX3C/Qwnk4rCG7aH2uuDNP5g9Stt1ZDJ0TlUY+hjShVVDSXzFIMCMGCSqGSIb3DQEJFDEWHhQAdABlAHMAdAAtAGEAbABpAGEAczAhBgkqhkiG9w0BCRUxFAQSVGltZSAxNjAzNjA2OTEwMTg0MIIFVAYLKoZIhvcNAQwKAQKgggT7MIIE9zApBgoqhkiG9w0BDAEDMBsEFMdpD/HFDCPc6Fw7bT/BarWYBo2BAgMAw1AEggTI0iM7OfSWxze0x6rLReXZqRxei+0GdHAvPHGYCYq1Y3TQVFy6IOwBqDJD3Ou0dHbeZYoAGmXHfY3MWqKZMqoO7aEJAhrK5BO+GKjoFnxFxA/OTfiApCqdGpOQyqF1cQSmb9Dsq93Fk1bUGB8ovrKkunGH+8r7VfDQa8oGMxuyqd4EvwbrEKJ/kV+Z0ASXYjY0ub0wV9ECqNwdakAPYlg8q+F63oQsxYsls69uGE6TCH68BbSWKgMUXTvtwpEfxiru3OFBCWQZ2YRAG3uqEF0uYse8syXbbosEZ6nWlr4kBFGgr4xGIZ1FQ4/o3JgAbXoRHv3OLjVKbU2QYmxnEYkQ8iz+u37EUUKjenLQtgA3/sWYR9bGOa8q0LddyY+QQg1eRS6ZkOvJTbHnWHxC/2Iw/hwFOLzARSXjto+feWObWxGmjL7/5/PQKfg0+WK+ZWqOBe1rBuKgtOKLU7pNBtZcyD/gS1LZ1kdv7UgR3CUOoEDTy+vUjX5d2tmkInqM5woqBSbi2voP3EbhzCeWB6LvZbiFK/OyTSZ2ihgK1NS97BlULcMXuLcVSHKJCpl6Sw24pSkwqIMihUamO/XJbDLrnOfp99aiJeOVfMdWXpBCeztFs9yvF8/w2+w/quJQjs53pTiIXtxC/Ozs9rtPWfva5+C7MaYn822zaWFKkIcahqHDuqMucG/UZtjyPIhiCIPTc9az2fxHjS4Epzj6Exp/h8BmB5qcfQuuhjC+vqq4qqNVrUUEBEsp7voQL80IfMxuiOOIcrsy4D5gIhq2ZYOQeQZ9DGBUGmL6JFpmJZq0O9hd+DMsxEp7uXcYrUP6z/BrZgv22M0UAUSaszmd6WtgRXiCNliMEDee2MOgEEgNRqEcv2/L7VlLztPNZfitb4Ebpt8nNRqgpW5rAVKWBx53s4pW9XbdJT1T/H39o5JG8H0K/7Ra8AWt8byH+vp9MSuGsFojx1YSMrFyYqasaOvteebTxd6b068AKNIZTEgcE1MDZVmVWFTefiWW5D20jdc1CT/z1MDoyux25/kp/vWDa5fy3akXwNWmS5kAe4HY9Ms23Xm9NPwKqIXIqn25bjcZbWW4fqXUE1LUa0fNsMUj3AYnE5Nkwzx6Tnnx+qdOgJ3RySpmvpeK7xtzAnqIjkOuBj8hW4CXn0SjRS8jWuqSpGqn7wPoobwQLEDoIzIxXXrHkjkjxFcwynzkvwc1CpSagGnNIzaoFCdO1BJeyqPu/0DFlbfn6+NKu+C0YhRmS9bo9G56B1BF6gob3PyyA2o3S3B5L/0FPuseW0PF4lb+JRBV3WCW/BPiwXLMiuuZJaA1PvsTzMVEYciFYVw9cw+sNWu5D+7mkd5j/MO5las+5/jVAlQXR5qNDZynU+K8LCct56uS3ewPHoL5ZIommJo2s23aalmTUO5yuagA6IC/Elfcsc+rE79sp6WCmf+eWoTmKCO/AEEx3bMe+0socN9hHRw14Qn2rXv4w1gSR/tbAYThY22tstTsfzDcAJH0szZVhOGnIxjVfRILoUcQ8QhCagltQI6aQICWyKuJHvm5zBU8ifcrPksYhjLlt5simoEwIEUGfUdDDmQ1XfTVJkedTd3FR3LTgk0JedTnWd5kJmxh096yZNnQMUYwIQYJKoZIhvcNAQkUMRQeEgB1AHAAcABlAHIAYwBhAHMAZTAhBgkqhkiG9w0BCRUxFAQSVGltZSAxNjAzNjA4NjkxNzY4MIIIPAYJKoZIhvcNAQcGoIIILTCCCCkCAQAwgggiBgkqhkiG9w0BBwEwKQYKKoZIhvcNAQwBBjAbBBSwPJdg3O5zbJ1XueAQj2uQB/JMAwIDAMNQgIIH6O4I9P0S2ThumqriXDBWrfUyOc7GIdQRaagFG8+39Hye0a5G7lpkaDFNHrgv6XLAOYHrBNpEK9MSTsrSlfBYUX1jNAaQgsCJPOH1h11PBp7Fm2193cgpwxDIlkjLfnOZCBsTMs21mLRei5h6/0ks9wnBBpPb+w6nNs9qsFObSTq0LyuJb+yL7uH2Cxm4e43S1ZwTANs3dSsLJk8a73LsBj9H7w5I300bbHuhhtM6efvkKwGbOQHoceLmCnu8Qm6YX9hqUbg6hbEnXfAY2Mp+X3qGoCI8xNukocFbwBLiz72TIf2kJKmahvtVI17qZS44bQ9K0UMd9G8oBba7F0liJdvf9lXV0WDfR+L+IfO+EJqf8dx3ZsundJ2z9CBpK/7ZXhXKUaNeLMtG2NK/rfkQxoeIOaFbI8we+YnEba+92HV7O3+yvx1naq4pOxP4Iy2c9K/O3Xx+Sb8zOaYgYDiQ41wZPRccGYzCPtLKCGhDQrMpB40O/YMF7eeUfYJiae5fuq6hwy65jKlGcH7hZEPitp8eDIfJFwMJ/dSwOP6Bta51H+sXrYilpPnFtjTAzVM7KcIqAO/aaMCm2N5Nv32V3mCL4A3XjuPMyuxevrsBIq7B3n6uKWcFzsLHLMcp2IoqEmzRftgJvCS22SaPyvkeRVWO32nw8eUE5eKKxPFkub5yEsuIGx9FAezvC7rltwABqMVQXw92osRHcfmFi6LIOFysyPRU9chFEaLItm7gpwnfZ8MDftu5+rwOnbpE4rKZhIL9oySDu99VSa4QjzdAOe5YdSIKHEBhTC6t+FAVvIlM6knv4GpxoNIb8O4b4vVYgmTxcgX+bk321oxAl+tzuEYe1UOtdc6RREHTXn/DjfYfwGPZbQD/7D2eYA5vVrLPSQKzZBix/KVaqkoNrnOsqFb4zSVyEuUjG1ejwzMl9ItoVMKxDNPslnLWO8It18nAGUve8O2pXhcTRAYzWaLCiTv69UEqZ7EtBLaViSsWHOHjrjaZ6ZVDP6Okbvt86VIG9GpE81USq1I9i9yvTxek+52sq/XxZsevhkJuBJTs7TqLcUNVVknfq4JA8omMjW/tAPrkIWrb88WVTcucDtAr2BU70G1rx+8WelBG4phYugYPwukXx3GJQRkkZALine truncated
password: 'password',
alias: 'UPPERCASE',
};
/**
* Generate DSA certificate:
* openssl dsaparam -out params.pem 3072
* openssl gendsa -out key.pem params.pem
* openssl req -new -key key.pem -out req.pem
* openssl x509 -req -in req.pem -signkey key.pem -out certificate.cer
*
* Package into a PKCS#12 keystore:
* keytool -import -alias test -file certificate.cer -keystore dsapkcs.keystore -storetype pkcs12
*
* node-forge only supports RSA certificates, so this certificate will be 'unknown'
*/
const dsaKeystoreP12 = {
base64EncodedP12:
'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',
password: 'password',
alias: 'test',
// Expected fingerprints derived from: keytool -list -v -keystore [keystore-path]
md5Fingerprint: 'f1371f2a5cd89d813882aebce8f609a4',
sha1Fingerprint: '10c530286a74e0c2e6e6ecf6845edbf686da2aee',
sha256Fingerprint: '822007287d1c8759e5900c34337339bc88a0112feecb4e01e6c74adc830ce3c5',
};
describe('computing fingerprints of X.509 certificates', () => {
it('computes fingerprints of certificates in conventional p12 files', async () => {
const {
base64EncodedP12,
password,
md5Fingerprint: expectedMd5Fingerprint,
sha1Fingerprint: expectedSha1Fingerprint,
sha256Fingerprint: expectedSha256Fingerprint,
} = conventionalP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509Certificate(p12);
const md5Fingerprint = getCertificateFingerprint(certificate, {
hashAlgorithm: 'md5',
});
const sha1Fingerprint = getCertificateFingerprint(certificate, {
hashAlgorithm: 'sha1',
});
const sha256Fingerprint = getCertificateFingerprint(certificate, {
hashAlgorithm: 'sha256',
});
expect(md5Fingerprint).toEqual(expectedMd5Fingerprint);
expect(sha1Fingerprint).toEqual(expectedSha1Fingerprint);
expect(sha256Fingerprint).toEqual(expectedSha256Fingerprint);
});
it('computes fingerprints of certificates in p12 keystores', async () => {
const {
base64EncodedP12,
password,
alias,
md5Fingerprint: expectedMd5Fingerprint,
sha1Fingerprint: expectedSha1Fingerprint,
sha256Fingerprint: expectedSha256Fingerprint,
} = keystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509CertificateByFriendlyName(p12, alias);
expect(certificate).toBeDefined();
const md5Fingerprint = getCertificateFingerprint(certificate!, {
hashAlgorithm: 'md5',
});
const sha1Fingerprint = getCertificateFingerprint(certificate!, {
hashAlgorithm: 'sha1',
});
const sha256Fingerprint = getCertificateFingerprint(certificate!, {
hashAlgorithm: 'sha256',
});
expect(md5Fingerprint).toEqual(expectedMd5Fingerprint);
expect(sha1Fingerprint).toEqual(expectedSha1Fingerprint);
expect(sha256Fingerprint).toEqual(expectedSha256Fingerprint);
});
});
describe('computing fingerprints from the ASN.1 representation of an X.509 certificate', () => {
it.each([
['supported RSA certificates', keystoreP12],
['unsupported DSA certificates', dsaKeystoreP12],
])(
'computes fingerprints of %s using their ASN.1 values in PKCS #12 keystores',
async (_description, keystoreP12) => {
const {
base64EncodedP12,
password,
alias,
md5Fingerprint: expectedMd5Fingerprint,
sha1Fingerprint: expectedSha1Fingerprint,
sha256Fingerprint: expectedSha256Fingerprint,
} = keystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const asn1 = getX509Asn1ByFriendlyName(p12, alias);
expect(asn1).toBeDefined();
const md5Fingerprint = getAsn1Hash(asn1!, {
hashAlgorithm: 'md5',
});
const sha1Fingerprint = getAsn1Hash(asn1!, {
hashAlgorithm: 'sha1',
});
const sha256Fingerprint = getAsn1Hash(asn1!, {
hashAlgorithm: 'sha256',
});
expect(md5Fingerprint).toEqual(expectedMd5Fingerprint);
expect(sha1Fingerprint).toEqual(expectedSha1Fingerprint);
expect(sha256Fingerprint).toEqual(expectedSha256Fingerprint);
}
);
});
describe('getting the ASN.1 representation of a X.509 certificate from PKCS#12 files', () => {
it.each([
['supported RSA certificates', keystoreP12],
['unsupported DSA certificates', dsaKeystoreP12],
])('gets the ASN.1 value of %s from PKCS #12 keystores', async (_description, keystoreP12) => {
const { base64EncodedP12, password, alias } = keystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const asn1 = getX509Asn1ByFriendlyName(p12, alias);
expect(asn1).toMatchSnapshot();
});
});
describe('reading X.509 certificates from PKCS#12 files', () => {
it('is unable to parse a DSA X.509 certificate (limitation)', async () => {
const { base64EncodedP12, password, alias } = dsaKeystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
expect(() => {
getX509CertificateByFriendlyName(p12, alias);
}).toThrowError();
});
it('reads X.509 certificate serial numbers from conventional p12 files', async () => {
const { base64EncodedP12, password, serialNumber: expectedSerialNumber } = conventionalP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509Certificate(p12);
const serialNumber = getFormattedSerialNumber(certificate);
expect(serialNumber).toEqual(expectedSerialNumber);
});
it('reads X.509 certificates from conventional p12 files using #getX509Certificate', async () => {
const { base64EncodedP12, password } = conventionalP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509Certificate(p12);
expect(certificate).toMatchSnapshot();
});
it('reads X.509 certificates from p12 keystores using #getX509Certificate', async () => {
const { base64EncodedP12, password } = keystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
// gets an arbitrary certificate from the keystore
const certificate = getX509Certificate(p12);
expect(certificate).toMatchSnapshot();
});
it('reads X.509 certificates from p12 keystores using #getX509CertificateByFriendlyName', async () => {
const { base64EncodedP12, password, alias } = keystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509CertificateByFriendlyName(p12, alias);
expect(certificate).toMatchSnapshot();
});
it('returns null if there are no X.509 certificates under friendly name for p12 keystores using #getX509CertificateByFriendlyName', async () => {
const { base64EncodedP12, password } = conventionalP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509CertificateByFriendlyName(p12, 'no keystore here :(');
expect(certificate).toBe(null);
});
it('reads X.509 certificates from p12 keystores with uppercase alias using #getX509CertificateByFriendlyName', async () => {
const { base64EncodedP12, password, alias } = uppercaseAliasKeystoreP12;
const p12 = parsePKCS12(base64EncodedP12, password);
const certificate = getX509CertificateByFriendlyName(p12, alias);
expect(certificate).toMatchSnapshot();
});
});
@@ -1,10 +1,10 @@
import { getX509Asn1ByFriendlyName, parsePKCS12 } from '@expo/pkcs12';
import jks from 'jks-js';
import { asn1 } from 'node-forge';
import { AndroidKeystoreType } from '../../../graphql/generated';
import Log from '../../../log';
import { Keystore, KeystoreWithType } from '../credentials';
import { getX509Asn1ByFriendlyName, parsePKCS12 } from './pkcs12';
export function getKeystoreWithType(keystore: Keystore): KeystoreWithType {
const type = getKeystoreType(keystore);
@@ -0,0 +1,144 @@
import crypto, { HexBase64Latin1Encoding, Utf8AsciiLatin1Encoding } from 'crypto';
import forge from 'node-forge';
/**
* Returns the serial number of the given X.509 certificate as an uppercased hexadecimal string
*/
export function getFormattedSerialNumber(certificate: forge.pki.Certificate): string | null {
const { serialNumber } = certificate;
return serialNumber ? serialNumber.replace(/^0+/, '').toUpperCase() : null;
}
/**
* Extracts a certificate from PKCS#12
* This is assumed to be a conventional PKCS#12 where there is exactly one certificate and one key
*/
export function getX509Certificate(p12: forge.pkcs12.Pkcs12Pfx): forge.pki.Certificate {
const certBagType = forge.pki.oids.certBag;
const bags = p12.getBags({ bagType: certBagType })[certBagType];
if (!bags || bags.length === 0) {
throw new Error(`PKCS12: No certificates found`);
}
return getX509CertificateFromBag(bags[0]);
}
/**
* Extracts a certificate from PKCS#12
* This is assumed to be a PKCS#12 containing a keystore where the friendlyName (alias) contains a PrivateKeyEntry
* A PrivateKeyEntry contains exactly one certificate and one key
* https://docs.oracle.com/javase/7/docs/api/java/security/KeyStore.PrivateKeyEntry.html
*/
export function getX509CertificateByFriendlyName(
p12: forge.pkcs12.Pkcs12Pfx,
friendlyName: string
): forge.pki.Certificate | null {
const certBagType = forge.pki.oids.certBag;
// node-forge converts friendly names to lowercase, so we search by lowercase
const bags = p12.getBags({
friendlyName: friendlyName.toLowerCase(),
bagType: certBagType,
}).friendlyName;
if (!bags || bags.length === 0) {
return null;
}
return getX509CertificateFromBag(bags[0]);
}
function getX509CertificateFromBag(bag: forge.pkcs12.Bag): forge.pki.Certificate {
const { cert, asn1 } = bag;
if (!cert && asn1) {
// if asn1 is present but certificate isnt, the certificate type was unknown
// github.com/digitalbazaar/forge/blob/1887cfce43a8f5ca9cb5c256168cf12ce1715ecf/lib/pkcs12.js#L703
throw new Error('PKCS12: unknown X.509 certificate type');
}
if (!cert) {
throw new Error('PKCS12: bag is not a certificate');
}
return cert;
}
export function getX509Asn1ByFriendlyName(
p12: forge.pkcs12.Pkcs12Pfx,
friendlyName: string
): forge.asn1.Asn1 | null {
const certBagType = forge.pki.oids.certBag;
// node-forge converts friendly names to lowercase, so we search by lowercase
const bags = p12.getBags({
friendlyName: friendlyName.toLowerCase(),
bagType: certBagType,
}).friendlyName;
if (!bags || bags.length === 0) {
return null;
}
const { cert, asn1 } = bags[0];
if (cert) {
return forge.pki.certificateToAsn1(cert);
}
// if asn1 is present but certificate isnt, the certificate type was unknown
// github.com/digitalbazaar/forge/blob/1887cfce43a8f5ca9cb5c256168cf12ce1715ecf/lib/pkcs12.js#L703
return asn1 ?? null;
}
export function parsePKCS12(
p12BufferOrBase64String: Buffer | string,
maybePassword: string | null
): forge.pkcs12.Pkcs12Pfx {
const base64EncodedP12 = Buffer.isBuffer(p12BufferOrBase64String)
? p12BufferOrBase64String.toString('base64')
: p12BufferOrBase64String;
const password = String(maybePassword ?? '');
const p12Der = forge.util.decode64(base64EncodedP12);
const p12Asn1 = forge.asn1.fromDer(p12Der);
return forge.pkcs12.pkcs12FromAsn1(p12Asn1, password);
}
function getHash(
data: string,
{
hashAlgorithm,
hashEncoding,
inputEncoding,
}: {
hashAlgorithm?: string;
hashEncoding?: HexBase64Latin1Encoding;
inputEncoding?: Utf8AsciiLatin1Encoding;
}
): string {
const hash = crypto.createHash(hashAlgorithm ?? 'sha1');
if (inputEncoding) {
hash.update(data, inputEncoding);
} else {
hash.update(data); // use Node's default inputEncoding
}
return hash.digest(hashEncoding ?? 'hex');
}
export function getAsn1Hash(
asn1: forge.asn1.Asn1,
{
hashAlgorithm,
}: {
hashAlgorithm?: string;
}
): string {
const certDer = forge.asn1.toDer(asn1).getBytes(); // binary encoded string
return getHash(certDer, {
hashAlgorithm,
hashEncoding: 'hex',
inputEncoding: 'latin1', // latin1 is an alias for binary
});
}
export function getCertificateFingerprint(
certificate: forge.pki.Certificate,
{
hashAlgorithm,
}: {
hashAlgorithm?: string;
}
): string {
const certAsn1 = forge.pki.certificateToAsn1(certificate);
return getAsn1Hash(certAsn1, {
hashAlgorithm,
});
}