doipjs/src/keys.js

264 lines
7 KiB
JavaScript
Raw Normal View History

2020-11-16 18:13:56 -07:00
/*
2021-01-13 05:20:33 -07:00
Copyright 2021 Yarmo Mackenbach
2020-11-16 18:13:56 -07:00
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
2022-02-08 11:10:23 -07:00
const axios = require('axios')
2020-11-16 18:13:56 -07:00
const validUrl = require('valid-url')
2020-12-09 17:56:34 -07:00
const openpgp = require('openpgp')
2021-11-17 07:54:48 -07:00
const HKP = require('@openpgp/hkp-client')
const WKD = require('@openpgp/wkd-client')
2021-04-19 03:06:29 -06:00
const Claim = require('./claim')
2020-11-16 18:13:56 -07:00
2021-04-22 07:14:21 -06:00
/**
* Functions related to the fetching and handling of keys
* @module keys
*/
/**
* Fetch a public key using keyservers
* @function
* @param {string} identifier - Fingerprint or email address
* @param {string} [keyserverDomain=keys.openpgp.org] - Domain of the keyserver
2021-11-17 07:54:48 -07:00
* @returns {openpgp.PublicKey}
2021-04-22 07:14:21 -06:00
* @example
* const key1 = doip.keys.fetchHKP('alice@domain.tld');
* const key2 = doip.keys.fetchHKP('123abc123abc');
*/
2021-07-09 15:44:52 -06:00
exports.fetchHKP = async (identifier, keyserverDomain) => {
const keyserverBaseUrl = keyserverDomain
? `https://${keyserverDomain}`
: 'https://keys.openpgp.org'
2021-11-17 07:54:48 -07:00
const hkp = new HKP(keyserverBaseUrl)
2021-07-09 15:44:52 -06:00
const lookupOpts = {
query: identifier
}
2021-11-17 07:54:48 -07:00
const publicKey = await hkp
.lookup(lookupOpts)
.catch((error) => {
throw new Error(`Key does not exist or could not be fetched (${error})`)
})
2020-11-16 18:13:56 -07:00
2021-07-09 15:44:52 -06:00
if (!publicKey) {
throw new Error('Key does not exist or could not be fetched')
}
2020-11-16 18:13:56 -07:00
2021-11-17 07:54:48 -07:00
return await openpgp.readKey({
armoredKey: publicKey
})
2021-07-09 15:44:52 -06:00
.catch((error) => {
2021-11-17 07:54:48 -07:00
throw new Error(`Key could not be read (${error})`)
})
2020-11-16 18:13:56 -07:00
}
2021-04-22 07:14:21 -06:00
/**
* Fetch a public key using Web Key Directory
* @function
* @param {string} identifier - Identifier of format 'username@domain.tld`
2021-11-17 07:54:48 -07:00
* @returns {openpgp.PublicKey}
2021-04-22 07:14:21 -06:00
* @example
* const key = doip.keys.fetchWKD('alice@domain.tld');
*/
2021-07-09 15:44:52 -06:00
exports.fetchWKD = async (identifier) => {
2021-11-17 07:54:48 -07:00
const wkd = new WKD()
2021-07-09 15:44:52 -06:00
const lookupOpts = {
email: identifier
}
2021-11-17 07:54:48 -07:00
const publicKey = await wkd
2021-07-09 15:44:52 -06:00
.lookup(lookupOpts)
.catch((error) => {
throw new Error(`Key does not exist or could not be fetched (${error})`)
})
2021-11-17 07:54:48 -07:00
if (!publicKey) {
throw new Error('Key does not exist or could not be fetched')
}
return await openpgp.readKey({
binaryKey: publicKey
})
.catch((error) => {
throw new Error(`Key could not be read (${error})`)
})
2020-11-16 18:13:56 -07:00
}
2021-04-22 07:14:21 -06:00
/**
* Fetch a public key from Keybase
* @function
* @param {string} username - Keybase username
* @param {string} fingerprint - Fingerprint of key
2021-11-17 07:54:48 -07:00
* @returns {openpgp.PublicKey}
2021-04-22 07:14:21 -06:00
* @example
* const key = doip.keys.fetchKeybase('alice', '123abc123abc');
*/
2021-07-09 15:44:52 -06:00
exports.fetchKeybase = async (username, fingerprint) => {
const keyLink = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
let rawKeyContent
try {
2022-02-08 11:10:23 -07:00
rawKeyContent = await axios.get(
keyLink,
{
responseType: 'text'
}
)
2021-07-09 15:44:52 -06:00
.then((response) => {
if (response.status === 200) {
return response
}
2021-03-01 10:27:29 -07:00
})
2022-02-08 11:10:23 -07:00
.then((response) => response.data)
2021-07-09 15:44:52 -06:00
} catch (e) {
throw new Error(`Error fetching Keybase key: ${e.message}`)
}
2021-11-17 07:54:48 -07:00
return await openpgp.readKey({
armoredKey: rawKeyContent
})
2021-07-09 15:44:52 -06:00
.catch((error) => {
throw new Error(`Key does not exist or could not be fetched (${error})`)
})
2020-11-16 18:13:56 -07:00
}
2021-04-22 07:14:21 -06:00
/**
* Get a public key from plaintext data
* @function
* @param {string} rawKeyContent - Plaintext ASCII-formatted public key data
2021-11-17 07:54:48 -07:00
* @returns {openpgp.PublicKey}
2021-04-22 07:14:21 -06:00
* @example
* const plainkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
2021-04-22 08:00:37 -06:00
*
2021-04-22 07:14:21 -06:00
* mQINBF0mIsIBEADacleiyiV+z6FIunvLWrO6ZETxGNVpqM+WbBQKdW1BVrJBBolg
* [...]
* =6lib
* -----END PGP PUBLIC KEY BLOCK-----`
* const key = doip.keys.fetchPlaintext(plainkey);
*/
2021-07-09 15:44:52 -06:00
exports.fetchPlaintext = async (rawKeyContent) => {
2021-11-17 07:54:48 -07:00
const publicKey = await openpgp.readKey({
armoredKey: rawKeyContent
})
.catch((error) => {
throw new Error(`Key could not be read (${error})`)
})
2021-07-09 15:44:52 -06:00
return publicKey
2020-11-16 18:13:56 -07:00
}
2021-04-22 07:14:21 -06:00
/**
* Fetch a public key using an URI
* @function
* @param {string} uri - URI that defines the location of the key
2021-11-17 07:54:48 -07:00
* @returns {openpgp.PublicKey}
2021-04-22 07:14:21 -06:00
* @example
* const key1 = doip.keys.fetchURI('hkp:alice@domain.tld');
* const key2 = doip.keys.fetchURI('hkp:123abc123abc');
* const key3 = doip.keys.fetchURI('wkd:alice@domain.tld');
*/
2021-07-09 15:44:52 -06:00
exports.fetchURI = async (uri) => {
if (!validUrl.isUri(uri)) {
throw new Error('Invalid URI')
}
const re = /([a-zA-Z0-9]*):([a-zA-Z0-9@._=+-]*)(?::([a-zA-Z0-9@._=+-]*))?/
const match = uri.match(re)
if (!match[1]) {
throw new Error('Invalid URI')
}
switch (match[1]) {
case 'hkp':
return exports.fetchHKP(
match[3] ? match[3] : match[2],
match[3] ? match[2] : null
)
case 'wkd':
return exports.fetchWKD(match[2])
case 'kb':
return exports.fetchKeybase(match[2], match.length >= 4 ? match[3] : null)
default:
throw new Error('Invalid URI protocol')
}
2020-11-16 18:13:56 -07:00
}
2021-04-22 07:14:21 -06:00
/**
* Process a public key to get user data and claims
* @function
2021-11-17 07:54:48 -07:00
* @param {openpgp.PublicKey} publicKey - The public key to process
2021-04-22 07:14:21 -06:00
* @returns {object}
* @example
* const key = doip.keys.fetchURI('hkp:alice@domain.tld');
* const data = doip.keys.process(key);
* data.users[0].claims.forEach(claim => {
* console.log(claim.uri);
* });
*/
2021-07-09 15:44:52 -06:00
exports.process = async (publicKey) => {
2021-11-17 07:54:48 -07:00
if (!publicKey || !(publicKey instanceof openpgp.PublicKey)) {
2021-07-09 15:44:52 -06:00
throw new Error('Invalid public key')
}
2021-11-17 07:54:48 -07:00
const fingerprint = publicKey.getFingerprint()
2021-07-09 15:44:52 -06:00
const primaryUser = await publicKey.getPrimaryUser()
const users = publicKey.users
const usersOutput = []
users.forEach((user, i) => {
usersOutput[i] = {
userData: {
2022-02-25 16:07:37 -07:00
id: user.userID ? user.userID.userID : null,
name: user.userID ? user.userID.name : null,
email: user.userID ? user.userID.email : null,
comment: user.userID ? user.userID.comment : null,
2021-07-09 15:44:52 -06:00
isPrimary: primaryUser.index === i,
isRevoked: false
},
claims: []
2020-12-05 15:13:44 -07:00
}
2021-04-19 03:06:29 -06:00
2021-07-09 15:44:52 -06:00
if ('selfCertifications' in user && user.selfCertifications.length > 0) {
const selfCertification = user.selfCertifications[0]
2021-07-09 15:44:52 -06:00
const notations = selfCertification.rawNotations
usersOutput[i].claims = notations
.filter(
({ name, humanReadable }) =>
2021-11-06 11:38:22 -06:00
humanReadable && (name === 'proof@ariadne.id' || name === 'proof@metacode.biz')
2021-07-09 15:44:52 -06:00
)
.map(
({ value }) =>
2021-11-17 07:54:48 -07:00
new Claim(new TextDecoder().decode(value), fingerprint)
2021-07-09 15:44:52 -06:00
)
2020-11-16 18:13:56 -07:00
2021-07-09 15:44:52 -06:00
usersOutput[i].userData.isRevoked = selfCertification.revoked
}
2020-12-05 15:13:44 -07:00
})
2021-07-09 15:44:52 -06:00
return {
fingerprint: fingerprint,
users: usersOutput,
primaryUserIndex: primaryUser.index,
key: {
data: publicKey,
fetchMethod: null,
uri: null
}
}
2021-04-22 08:00:37 -06:00
}