Support for triggering Global Auto-Type

This commit is contained in:
varjolintu 2021-03-13 10:40:56 +02:00
parent f19044e21b
commit ea84842c81
9 changed files with 108 additions and 2 deletions

View file

@ -31,6 +31,10 @@
"message": "Fill attribute…",
"description": "Context menu parent item for filling a custom attribute."
},
"contextMenuRequestGlobalAutoType": {
"message": "Request Global Auto-Type",
"description": "Global Auto-Type shortcut text."
},
"multipleCredentialsDetected": {
"message": "HTTP authentication with multiple credentials detected. Click on the extension icon to choose the correct one.",
"description": "Notification when HTTP Authentication has multiple credentials detected."

View file

@ -236,6 +236,7 @@ kpxcEvent.messageHandlers = {
'popup_login': kpxcEvent.onLoginPopup,
'reconnect': kpxcEvent.onReconnect,
'remove_credentials_from_tab_information': kpxcEvent.onRemoveCredentialsFromTabInformation,
'request_autotype': keepass.requestAutotype,
'retrieve_credentials': page.retrieveCredentials,
'show_default_browseraction': browserAction.showDefault,
'update_credentials': keepass.updateCredentials,

View file

@ -114,7 +114,8 @@ const contextMenuItems = [
{ title: tr('contextMenuFillTOTP'), action: 'fill_totp' },
{ title: tr('contextMenuFillAttribute'), id: 'fill_attribute', visible: false },
{ title: tr('contextMenuShowPasswordGenerator'), action: 'show_password_generator' },
{ title: tr('contextMenuSaveCredentials'), action: 'save_credentials' }
{ title: tr('contextMenuSaveCredentials'), action: 'save_credentials' },
{ title: tr('contextMenuRequestGlobalAutoType'), action: 'request_autotype' }
];
const menuContexts = [ 'editable' ];

View file

@ -37,7 +37,8 @@ const kpActions = {
DATABASE_UNLOCKED: 'database-unlocked',
GET_DATABASE_GROUPS: 'get-database-groups',
CREATE_NEW_GROUP: 'create-new-group',
GET_TOTP: 'get-totp'
GET_TOTP: 'get-totp',
REQUEST_AUTOTYPE: 'request-autotype'
};
const kpErrors = {
@ -852,6 +853,50 @@ keepass.getTotp = async function(tab, args = []) {
}
};
keepass.requestAutotype = async function(tab, args = []) {
if (!keepass.isConnected) {
keepass.handleError(tab, kpErrors.TIMEOUT_OR_NOT_CONNECTED);
return false;
}
const kpAction = kpActions.REQUEST_AUTOTYPE;
const [ nonce, incrementedNonce ] = keepass.getNonces();
const search = getTopLevelDomainFromUrl(args[0]);
const messageData = {
action: kpAction,
search: search
};
const request = keepass.buildRequest(kpAction, keepass.encrypt(messageData, nonce), nonce, keepass.clientID);
try {
const response = await keepass.sendNativeMessage(request);
if (response.message && response.nonce) {
const res = keepass.decrypt(response.message, response.nonce);
if (!res) {
keepass.handleError(tab, kpErrors.CANNOT_DECRYPT_MESSAGE);
return false;
}
const message = nacl.util.encodeUTF8(res);
const parsed = JSON.parse(message);
if (keepass.verifyResponse(parsed, incrementedNonce)) {
return true;
}
} else if (response.error && response.errorCode) {
keepass.handleError(tab, response.errorCode, response.error);
}
return false;
} catch (err) {
console.log('requestAutotype failed: ', err);
return false;
}
};
keepass.generateNewKeyPair = function() {
keepass.keyPair = nacl.box.keyPair();
//console.log(nacl.util.encodeBase64(keepass.keyPair.publicKey) + ' ' + nacl.util.encodeBase64(keepass.keyPair.secretKey));

View file

@ -140,6 +140,24 @@ const slashNeededForUrl = function(pattern) {
return matchPattern.exec(pattern);
};
// Returns the top level domain, e.g. https://another.example.co.uk -> example.co.uk
const getTopLevelDomainFromUrl = function(hostname) {
const ipRegex = new RegExp(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/g);
const domainRegex = new RegExp(/(\w+).(com|net|org|edu|co)*(.\w+)$/g);
// If the hostname is an IP address, return it directly
if (hostname.match(ipRegex)) {
return hostname;
}
const domainMatch = domainRegex.exec(hostname);
if (domainMatch) {
return domainMatch[0];
}
return hostname;
};
function tr(key, params) {
return browser.i18n.getMessage(key, params);
}

View file

@ -2114,6 +2114,8 @@ browser.runtime.onMessage.addListener(async function(req, sender) {
await kpxc.retrieveCredentials(true);
} else if (req.action === 'show_password_generator') {
kpxcPasswordDialog.trigger();
} else if (req.action === 'request_autotype') {
sendMessage('request_autotype', [ window.location.hostname ]);
}
}
});

View file

@ -108,6 +108,9 @@
},
"retrive_credentials_forced": {
"description": "__MSG_popupReopenButton__"
},
"request_autotype": {
"description": "__MSG_contextMenuRequestGlobalAutoType__"
}
},
"web_accessible_resources": [

View file

@ -404,3 +404,21 @@ Response message data (success, decrypted):
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
}
```
### request-autotype (KeePassXC 2.7.0 and newer)
Request (no unencrypted message is needed):
```json
{
"action": "request-autotype",
"search": "<base domain of URL>"
}
```
Response message data (success, decrypted):
```json
{
"version": "2.7.0",
"success": "true",
"nonce": "tZvLrBzkQ9GxXq9PvKJj4iAnfPT0VZ3Q"
}
```

View file

@ -43,6 +43,20 @@ async function testGeneral() {
for (const m of matches) {
assertRegex(siteMatch(m[0], m[1]), m[2], testCard, `siteMatch() for ${m[1]}`);
}
// Base domain parsing (window.location.hostname)
const domains = [
[ 'another.example.co.uk', 'example.co.uk' ],
[ 'www.example.com', 'example.com' ],
[ 'test.net', 'test.net' ],
[ 'so.many.subdomains.co.jp', 'subdomains.co.jp' ],
[ 'test.site.example.com.au', 'example.com.au' ],
[ '192.168.0.1', '192.168.0.1' ]
];
for (const d of domains) {
kpxcAssert(getTopLevelDomainFromUrl(d[0]), d[1], testCard, 'getBaseDomainFromUrl() for ' + d[0]);
}
}
// Input field matching (keepassxc-browser.js)