Skip to content

Commit 6006a2f

Browse files
author
Zhen Li
authored
Merge pull request #110 from legraphista/fix/known_hosts-fingerprint-duplication-error/1.0
Fix/known hosts fingerprint duplication error/upstream 1.0
2 parents 1e27eb5 + 6e38ec1 commit 6006a2f

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

src/v1/internal/ch-node.js

+20-2
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ function loadFingerprint( serverId, knownHostsPath, cb ) {
4545
require('readline').createInterface({
4646
input: fs.createReadStream(knownHostsPath)
4747
}).on('line', (line) => {
48-
if( line.startsWith( serverId )) {
48+
if( !found && line.startsWith( serverId )) {
4949
found = true;
5050
cb( line.split(" ")[1] );
5151
}
@@ -56,12 +56,30 @@ function loadFingerprint( serverId, knownHostsPath, cb ) {
5656
});
5757
}
5858

59-
function storeFingerprint(serverId, knownHostsPath, fingerprint) {
59+
const _lockFingerprintFromAppending = {};
60+
function storeFingerprint( serverId, knownHostsPath, fingerprint ) {
61+
// we check if the serverId has been appended
62+
if(!!_lockFingerprintFromAppending[serverId]){
63+
// if it has, we ignore it
64+
return;
65+
}
66+
67+
// we make the line as appended
68+
// ( 1 is more efficient to store than true because true is an oddball )
69+
_lockFingerprintFromAppending[serverId] = 1;
70+
71+
// we append to file
6072
fs.appendFile(knownHostsPath, serverId + " " + fingerprint + EOL, "utf8", (err) => {
6173
if (err) {
6274
console.log(err);
6375
}
6476
});
77+
78+
// since the error occurs in the span of one tick
79+
// after one tick we clean up to not interfere with anything else
80+
setImmediate(() => {
81+
delete _lockFingerprintFromAppending[serverId];
82+
});
6583
}
6684

6785
const TrustStrategy = {

test/internal/tls.test.js

+91
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,44 @@ describe('trust-on-first-use', function() {
7676

7777
var driver;
7878

79+
it('should not throw an error if the host file contains two host duplicates', function(done) {
80+
'use strict';
81+
// Assuming we only run this test on NodeJS with TOFU support
82+
if( !hasFeature("trust_on_first_use") ) {
83+
done();
84+
return;
85+
}
86+
87+
// Given
88+
var knownHostsPath = "build/known_hosts";
89+
if( fs.existsSync(knownHostsPath) ) {
90+
fs.unlinkSync(knownHostsPath);
91+
}
92+
93+
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
94+
encrypted: true,
95+
trust: "TRUST_ON_FIRST_USE",
96+
knownHosts: knownHostsPath
97+
});
98+
99+
driver.session(); // write into the knownHost file
100+
101+
// duplicate the same serverId twice
102+
setTimeout(function() {
103+
var text = fs.readFileSync(knownHostsPath, 'utf8');
104+
fs.writeFileSync(knownHostsPath, text + text);
105+
}, 1000);
106+
107+
// When
108+
setTimeout(function() {
109+
driver.session().run("RETURN true AS a").then( function(data) {
110+
// Then we get to here.
111+
expect( data.records[0].get('a') ).toBe( true );
112+
done();
113+
});
114+
}, 2000);
115+
});
116+
79117
it('should accept previously un-seen hosts', function(done) {
80118
// Assuming we only run this test on NodeJS with TOFU support
81119
if( !hasFeature("trust_on_first_use") ) {
@@ -104,6 +142,59 @@ describe('trust-on-first-use', function() {
104142
});
105143
});
106144

145+
it('should not duplicate fingerprint entries', function(done) {
146+
// Assuming we only run this test on NodeJS with TOFU support
147+
if( !hasFeature("trust_on_first_use") ) {
148+
done();
149+
return;
150+
}
151+
152+
// Given
153+
var knownHostsPath = "build/known_hosts";
154+
if( fs.existsSync(knownHostsPath) ) {
155+
fs.unlinkSync(knownHostsPath);
156+
}
157+
fs.writeFileSync(knownHostsPath, '');
158+
159+
driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
160+
encrypted: true,
161+
trust: "TRUST_ON_FIRST_USE",
162+
knownHosts: knownHostsPath
163+
});
164+
165+
// When
166+
driver.session();
167+
driver.session();
168+
169+
setTimeout(function() {
170+
var lines = {};
171+
fs.readFileSync(knownHostsPath, 'utf8')
172+
.split('\n')
173+
.filter(function(line) {
174+
return !! (line.trim());
175+
})
176+
.forEach(function(line) {
177+
if (!lines[line]) {
178+
lines[line] = 0;
179+
}
180+
lines[line]++;
181+
});
182+
183+
var duplicatedLines = Object
184+
.keys(lines)
185+
.map(function(line) {
186+
return lines[line];
187+
})
188+
.filter(function(count) {
189+
return count > 1;
190+
})
191+
.length;
192+
193+
expect( duplicatedLines ).toBe( 0 );
194+
done();
195+
}, 1000);
196+
});
197+
107198
it('should should give helpful error if database cert does not match stored certificate', function(done) {
108199
// Assuming we only run this test on NodeJS with TOFU support
109200
if( !hasFeature("trust_on_first_use") ) {

0 commit comments

Comments
 (0)