Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Secure HIP-2 address resolution #524

Merged
merged 1 commit into from
Jun 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions app/background/hip2/hip2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// Based on:
// - https://github.com/Falci/well-known-wallets-hns/blob/master/lib.js
// - https://github.com/lukeburns/hip2-dane/blob/main/index.js

import isValidAddress from '../../utils/verifyAddress';

const hdns = require('hdns');
const https = require('https');

const MAX_LENGTH = 90;

const verifyTLSA = async (cert, host) => {
try {
const tlsa = await hdns.resolveTLSA(host, 'tcp', 443);
const valid = hdns.verifyTLSA(tlsa[0], cert.raw);

return valid;
} catch (e) {
console.error(e);
return false;
}
};

export async function getAddress(host, network) {
let certificate = undefined;

return new Promise(async (resolve, reject) => {
const options = {
rejectUnauthorized: false,
lookup: hdns.legacy,
};

const req = https.get(`https://${host}/.well-known/wallets/HNS`, options, res => {
res.setEncoding('utf8');

let data = '';

res.on('data', chunk => {
// undefined = not yet stored
// null = socket destroyed
// object = may contain certificate
if (certificate === undefined) {
certificate = res.socket.getPeerCertificate(false);
}

const newLine = chunk.indexOf('\n');
if (newLine >= 0) {
req.destroy();
chunk = chunk.slice(0, newLine);
}

if (data.length + chunk.length > MAX_LENGTH) {
if (!req.destroyed) {
req.destroy();
}
const error = new Error('response too large');
error.code = 'ELARGE';
return reject(error);
}

data += chunk;
})

res.on('end', async () => {
const dane = await verifyTLSA(certificate, host);
if (!dane) {
const error = new Error('invalid DANE');
error.code = 'EINSECURE';
return reject(error);
}

if (res.statusCode >= 400) {
const error = new Error(res.statusMessage);
error.code = res.statusCode;
return reject(error);
}

const addr = data.trim();

if (!isValidAddress(addr, network)) {
const error = new Error('invalid address');
error.code = 'EINVALID';
return reject(error);
}

return resolve(data.trim());
});
});

req.on('error', reject);
req.end();
});
}

export const { setServers } = hdns;
39 changes: 20 additions & 19 deletions app/background/hip2/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { SET_HIP2_PORT } from '../../ducks/hip2Reducer';
import { get, put } from '../db/service';
import { dispatchToMainWindow } from '../../mainWindow';
import { service } from '../node/service';
const { fetchAddress, setServers } = require('hip2-dane');
import { setServers, getAddress } from './hip2';
import isValidAddress from '../../utils/verifyAddress';

const HIP2_PORT = 'hip2/port';

async function getPort () {
async function getPort() {
const hip2Port = await get(HIP2_PORT);
if (hip2Port !== null) {
return hip2Port;
Expand All @@ -15,36 +16,36 @@ async function getPort () {
return 9892;
}

async function setPort (port) {
async function setPort(port) {
await put(HIP2_PORT, port);
dispatchToMainWindow({
type: SET_HIP2_PORT,
payload: port
});
}

const sName = 'Hip2';
async function fetchAddress(host) {
const network = service.network.type;

const networkPrefix = {
simnet: 'ss1',
testnet: 'ts1',
main: 'hs1',
regtest: 'rs1'
};
// Host should not be a Handshake address
if (isValidAddress(host, network)) {
const error = new Error('alias cannot be a valid address')
error.code = 'ECOLLISION'
throw error;
}

return await getAddress(host, network);
}

const sName = 'Hip2';

const methods = {
getPort,
setPort,
fetchAddress: async address => await fetchAddress(address, {
token: 'HNS',
maxLength: 90,
validate: addr => {
return typeof addr === 'string' && addr.slice(0, 3) === networkPrefix[service.network.type];
}
}),
setServers
fetchAddress,
setServers,
};

export async function start (server) {
export async function start(server) {
server.withService(sName, methods);
}
20 changes: 10 additions & 10 deletions app/components/SendModal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ class SendModal extends Component {
if (input) {
this.setState({ hip2Loading: true })

// delay lookup by 120ms
// delay lookup by 120ms
setTimeout(() => {
// abort lookup if input has changed
if (!(this.state.hip2Input && this.state.hip2To === input)) return
Expand All @@ -124,7 +124,7 @@ class SendModal extends Component {
hip2Error = this.context.t('hip2InvalidAddress')
} else if (err.code === 'ECOLLISION') {
hip2Error = this.context.t('hip2InvalidAlias')
} else if (err.code === -1) {
} else if (err.code === 'EINSECURE') {
hip2Error = this.context.t('hip2InvalidTLSA')
} else {
hip2Error = this.context.t('hip2AddressNotFound')
Expand Down Expand Up @@ -310,20 +310,20 @@ class SendModal extends Component {
<div className="send__to">
<div className="send__label">{t('sendToLabel')}</div>
<div className="send__input" key="send-input">
{hip2Input &&
{hip2Input &&
<span className="send__prefix">{to ? (
<img src={LockSVG} />
) : ( hip2Loading ?
<img className="send__hip2-loading" src={RingsSVG} />
) : ( hip2Loading ?
<img className="send__hip2-loading" src={RingsSVG} />
: '@')}
</span>}
<input
type="text"
placeholder={hip2Input ?
t('recipientHip2Address') :
(hip2Enabled ?
t('recipientAddressHip2Enabled') :
(!this.props.noDns ?
placeholder={hip2Input ?
t('recipientHip2Address') :
(hip2Enabled ?
t('recipientAddressHip2Enabled') :
(!this.props.noDns ?
t('recipientAddressHip2Syncing') :
t('recipientAddress')
)
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,6 @@
"copy-to-clipboard": "3.3.1",
"deep-equal": "2.0.1",
"electron-debug": "3.2.0",
"hip2-dane": "0.4.2",
"history": "4.7.2",
"hs-airdrop": "^0.10.0",
"hs-client": "=0.0.10",
Expand Down