Skip to content

Commit bc809f1

Browse files
Merge pull request #1905 from zawata/static-openssl
Statically compile OpenSSL on linux for electron
2 parents f8be27a + 38d64f2 commit bc809f1

7 files changed

Lines changed: 215 additions & 73 deletions

File tree

generate/templates/templates/binding.gyp

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33
"is_electron%": "<!(node ./utils/isBuildingForElectron.js <(node_root_dir))",
44
"is_IBMi%": "<!(node -p \"os.platform() == 'aix' && os.type() == 'OS400' ? 1 : 0\")",
55
"electron_openssl_root%": "<!(node ./utils/getElectronOpenSSLRoot.js <(module_root_dir))",
6+
"electron_openssl_static%": "<!(node -p \"process.platform !== 'linux' || process.env.NODEGIT_OPENSSL_STATIC_LINK === '1' ? 1 : 0\")",
67
"macOS_deployment_target": "10.11"
78
},
89

910
"targets": [
1011
{
1112
"target_name": "acquireOpenSSL",
13+
"type": "none",
1214
"conditions": [
13-
["<(is_electron) == 1 and OS != 'linux' and <!(node -p \"process.env.npm_config_openssl_dir ? 0 : 1\")", {
15+
["<(is_electron) == 1 and <!(node -p \"process.env.npm_config_openssl_dir ? 0 : 1\")", {
1416
"actions": [{
1517
"action_name": "acquire",
1618
"action": ["node", "utils/acquireOpenSSL.js", "<(macOS_deployment_target)"],
@@ -23,6 +25,7 @@
2325
},
2426
{
2527
"target_name": "configureLibssh2",
28+
"type": "none",
2629
"actions": [{
2730
"action_name": "configure",
2831
"action": ["node", "utils/configureLibssh2.js"],
@@ -110,8 +113,8 @@
110113
"<(electron_openssl_root)/include"
111114
],
112115
"libraries": [
113-
"<(electron_openssl_root)/lib/libcrypto.a",
114-
"<(electron_openssl_root)/lib/libssl.a"
116+
"<(electron_openssl_root)/lib/libssl.a",
117+
"<(electron_openssl_root)/lib/libcrypto.a"
115118
]
116119
}]
117120
],
@@ -168,21 +171,29 @@
168171
"<!(krb5-config gssapi --libs)"
169172
]
170173
}],
171-
[
172-
"OS=='linux' or OS.endswith('bsd') or <(is_IBMi) == 1", {
173-
"cflags": [
174-
"-std=c++14"
175-
]
176-
}
177-
],
178-
[
179-
"OS.endswith('bsd') or (<(is_electron) == 1 and OS=='linux') or <(is_IBMi) == 1", {
180-
"libraries": [
181-
"-lcrypto",
182-
"-lssl"
183-
],
184-
}
185-
],
174+
["OS=='linux' or OS.endswith('bsd') or <(is_IBMi) == 1", {
175+
"cflags": [
176+
"-std=c++14"
177+
],
178+
"conditions": [
179+
["<(is_electron) == 1 and <(electron_openssl_static) == 1", {
180+
"include_dirs": [
181+
"<(electron_openssl_root)/include"
182+
],
183+
"libraries": [
184+
# this order is significant on centos7 apparently...
185+
"<(electron_openssl_root)/lib/libssl.a",
186+
"<(electron_openssl_root)/lib/libcrypto.a"
187+
]
188+
}],
189+
["<(is_electron) == 1 and <(electron_openssl_static) != 1", {
190+
"libraries": [
191+
"-lcrypto",
192+
"-lssl"
193+
]
194+
}]
195+
],
196+
}],
186197
[
187198
"<(is_IBMi) == 1", {
188199
"include_dirs": [

guides/install/from-source/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ npm install nodegit --msvs_version=2013
6565
```
6666

6767
### Electron and OpenSSL ###
68-
A local version of OpenSSL is required when building for Electron on Windows and macOS. This is due to Electron using BoringSSL, as we are not able to link to it like we are OpenSSL in Node.
68+
A local version of OpenSSL is required when building for Electron on Windows and macOS. This is due to Electron using BoringSSL, as we are not able to link to it like we are OpenSSL in Node. Additionally, OpenSSL can be statically linked on Linux by setting the `NODEGIT_OPENSSL_STATIC_LINK` environment variable to `1`.
6969

7070
`acquireOpenSSL.js` will attempt to download and build OpenSSL locally. On macOS, this should Just Work(tm). On Windows, things are a little trickier.
7171

utils/acquireOpenSSL.js

Lines changed: 98 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const pipeline = promisify(stream.pipeline);
1616

1717
const win32BatPath = path.join(__dirname, "build-openssl.bat");
1818
const vendorPath = path.resolve(__dirname, "..", "vendor");
19+
const opensslPatchPath = path.join(vendorPath, "patches", "openssl");
1920
const extractPath = path.join(vendorPath, "openssl");
2021

2122
const getOpenSSLSourceUrl = (version) => `https://www.openssl.org/source/openssl-${version}.tar.gz`;
@@ -27,7 +28,7 @@ class HashVerify extends stream.Transform {
2728
this.expected = expected;
2829
this.hash = crypto.createHash(algorithm);
2930
}
30-
31+
3132
_transform(chunk, encoding, callback) {
3233
this.hash.update(chunk, encoding);
3334
callback(null, chunk);
@@ -40,28 +41,95 @@ class HashVerify extends stream.Transform {
4041
}
4142
}
4243

44+
// currently this only needs to be done on linux
45+
const applyOpenSSLPatches = async (buildCwd) => {
46+
try {
47+
for (const patchFilename of await fse.readdir(opensslPatchPath)) {
48+
if (patchFilename.split(".").pop() === "patch") {
49+
console.log(`applying ${patchFilename}`);
50+
await execPromise(`patch -up0 -i ${path.join(opensslPatchPath, patchFilename)}`, {
51+
cwd: buildCwd
52+
}, { pipeOutput: true });
53+
}
54+
}
55+
} catch(e) {
56+
console.log("Patch application failed: ", e);
57+
throw e;
58+
}
59+
}
60+
4361
const buildDarwin = async (buildCwd, macOsDeploymentTarget) => {
44-
const triplet = process.arch === 'x64'
45-
? 'darwin64-x86_64-cc'
46-
: 'darwin64-arm64-cc';
47-
48-
await execPromise(`./Configure ${
49-
triplet
50-
} no-shared enable-ec_nistp_64_gcc_128 no-ssl2 no-ssl3 no-comp --prefix="${
51-
extractPath
52-
}" --openssldir="${extractPath}" -mmacosx-version-min=${macOsDeploymentTarget}`, {
62+
const arguments = [
63+
process.arch === "x64" ? "darwin64-x86_64-cc" : "darwin64-arm64-cc",
64+
// speed up ecdh on little-endian platforms with 128bit int support
65+
"enable-ec_nistp_64_gcc_128",
66+
// compile static libraries
67+
"no-shared",
68+
// disable ssl2, ssl3, and compression
69+
"no-ssl2",
70+
"no-ssl3",
71+
"no-comp",
72+
// set install directory
73+
`--prefix="${extractPath}"`,
74+
`--openssldir="${extractPath}"`,
75+
// set macos version requirement
76+
`-mmacosx-version-min=${macOsDeploymentTarget}`
77+
];
78+
79+
await execPromise(`./Configure ${arguments.join(" ")}`, {
5380
cwd: buildCwd
5481
}, { pipeOutput: true });
5582

56-
await execPromise("make", {
83+
// only build the libraries, not the tests/fuzzer or apps
84+
await execPromise("make build_libs", {
5785
cwd: buildCwd
5886
}, { pipeOutput: true });
5987

6088
await execPromise("make test", {
6189
cwd: buildCwd
6290
}, { pipeOutput: true });
6391

64-
await execPromise("make install", {
92+
await execPromise("make install_sw", {
93+
cwd: buildCwd,
94+
maxBuffer: 10 * 1024 * 1024 // we should really just use spawn
95+
}, { pipeOutput: true });
96+
};
97+
98+
const buildLinux = async (buildCwd) => {
99+
const arguments = [
100+
"linux-x86_64",
101+
// Electron(at least on centos7) imports the libcups library at runtime, which has a
102+
// dependency on the system libssl/libcrypto which causes symbol conflicts and segfaults.
103+
// To fix this we need to hide all the openssl symbols to prevent them from being overridden
104+
// by the runtime linker.
105+
"-fvisibility=hidden",
106+
// compile static libraries
107+
"no-shared",
108+
// disable ssl2, ssl3, and compression
109+
"no-ssl2",
110+
"no-ssl3",
111+
"no-comp",
112+
// set install directory
113+
`--prefix="${extractPath}"`,
114+
`--openssldir="${extractPath}"`
115+
];
116+
await execPromise(`./Configure ${arguments.join(" ")}`, {
117+
cwd: buildCwd
118+
}, { pipeOutput: true });
119+
120+
await applyOpenSSLPatches(buildCwd);
121+
122+
// only build the libraries, not the tests/fuzzer or apps
123+
await execPromise("make build_libs", {
124+
cwd: buildCwd
125+
}, { pipeOutput: true });
126+
127+
await execPromise("make test", {
128+
cwd: buildCwd
129+
}, { pipeOutput: true });
130+
131+
// only install software, not the docs
132+
await execPromise("make install_sw", {
65133
cwd: buildCwd,
66134
maxBuffer: 10 * 1024 * 1024 // we should really just use spawn
67135
}, { pipeOutput: true });
@@ -80,7 +148,7 @@ const buildWin32 = async (buildCwd) => {
80148
} catch {
81149
throw new Error(`vcvarsall.bat not found at ${vcvarsallPath}`);
82150
}
83-
151+
84152
const vcTarget = vcvarsallArch === "x64" ? "VC-WIN64A" : "VC-WIN32";
85153
await execPromise(`"${win32BatPath}" "${vcvarsallPath}" ${vcvarsallArch} ${vcTarget}`, {
86154
cwd: buildCwd,
@@ -128,13 +196,18 @@ const makeOnStreamDownloadProgress = () => {
128196
};
129197

130198
const buildOpenSSLIfNecessary = async (openSSLVersion, macOsDeploymentTarget) => {
131-
if (process.platform !== "darwin" && process.platform !== "win32") {
199+
if (process.platform !== "darwin" && process.platform !== "win32" && process.platform !== 'linux') {
132200
console.log(`Skipping OpenSSL build, not required on ${process.platform}`);
133201
return;
134202
}
135203

204+
if (process.platform === 'linux' && process.env.NODEGIT_OPENSSL_STATIC_LINK !== '1') {
205+
console.log(`Skipping OpenSSL build, NODEGIT_OPENSSL_STATIC_LINK !== 1`);
206+
return;
207+
}
208+
136209
await removeOpenSSLIfOudated(openSSLVersion);
137-
210+
138211
try {
139212
await fs.stat(extractPath);
140213
console.log("Skipping OpenSSL build, dir exists");
@@ -148,7 +221,7 @@ const buildOpenSSLIfNecessary = async (openSSLVersion, macOsDeploymentTarget) =>
148221

149222
const downloadStream = got.stream(openSSLUrl);
150223
downloadStream.on("downloadProgress", makeOnStreamDownloadProgress());
151-
224+
152225
await pipeline(
153226
downloadStream,
154227
new HashVerify("sha256", openSSLSha256),
@@ -162,6 +235,8 @@ const buildOpenSSLIfNecessary = async (openSSLVersion, macOsDeploymentTarget) =>
162235

163236
if (process.platform === "darwin") {
164237
await buildDarwin(buildCwd, macOsDeploymentTarget);
238+
} else if (process.platform === "linux" && process.env.NODEGIT_OPENSSL_STATIC_LINK === '1') {
239+
await buildLinux(buildCwd);
165240
} else if (process.platform === "win32") {
166241
await buildWin32(buildCwd);
167242
} else {
@@ -172,11 +247,16 @@ const buildOpenSSLIfNecessary = async (openSSLVersion, macOsDeploymentTarget) =>
172247
}
173248

174249
const downloadOpenSSLIfNecessary = async (downloadBinUrl, maybeDownloadSha256) => {
175-
if (process.platform !== "darwin" && process.platform !== "win32") {
250+
if (process.platform !== "darwin" && process.platform !== "win32" && process.platform !== 'linux') {
176251
console.log(`Skipping OpenSSL download, not required on ${process.platform}`);
177252
return;
178253
}
179254

255+
if (process.platform === 'linux' && process.env.NODEGIT_OPENSSL_STATIC_LINK !== '1') {
256+
console.log(`Skipping OpenSSL download, NODEGIT_OPENSSL_STATIC_LINK !== 1`);
257+
return;
258+
}
259+
180260
try {
181261
await fs.stat(extractPath);
182262
console.log("Skipping OpenSSL download, dir exists");
@@ -185,7 +265,7 @@ const downloadOpenSSLIfNecessary = async (downloadBinUrl, maybeDownloadSha256) =
185265

186266
const downloadStream = got.stream(downloadBinUrl);
187267
downloadStream.on("downloadProgress", makeOnStreamDownloadProgress());
188-
268+
189269
const pipelineSteps = [
190270
downloadStream,
191271
maybeDownloadSha256 ? new HashVerify("sha256", maybeDownloadSha256) : null,

utils/configureLibssh2.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ var cp = require("child_process");
22
var fse = require("fs-extra");
33
var path = require("path");
44

5+
const opensslVendorDirectory = path.resolve(__dirname, "..", "vendor", "openssl");
56
const libssh2VendorDirectory = path.resolve(__dirname, "..", "vendor", "libssh2");
67
const libssh2ConfigureScript = path.join(libssh2VendorDirectory, "configure");
78
const libssh2StaticConfigDirectory = path.resolve(__dirname, "..", "vendor", "static_config", "libssh2");
@@ -24,8 +25,11 @@ module.exports = function retrieveExternalDependencies() {
2425
newEnv[key] = process.env[key];
2526
});
2627

28+
let cpArgs = process.env.NODEGIT_OPENSSL_STATIC_LINK === '1'
29+
? ` --with-libssl-prefix=${opensslVendorDirectory}`
30+
: '';
2731
cp.exec(
28-
libssh2ConfigureScript,
32+
`${libssh2ConfigureScript}${cpArgs}`,
2933
{
3034
cwd: libssh2VendorDirectory,
3135
env: newEnv

0 commit comments

Comments
 (0)