From 9db48d627dfbce19c0091ac058f8070e5125086b Mon Sep 17 00:00:00 2001 From: Yarmo Mackenbach Date: Thu, 7 Jan 2021 16:15:50 +0100 Subject: [PATCH] Add signature verification --- src/index.js | 2 ++ src/signatures.js | 80 +++++++++++++++++++++++++++++++++++++++++ test/signatures.test.js | 56 +++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 src/signatures.js create mode 100644 test/signatures.test.js diff --git a/src/index.js b/src/index.js index 7b310d3..26fdbbb 100644 --- a/src/index.js +++ b/src/index.js @@ -15,10 +15,12 @@ limitations under the License. */ const claims = require('./claims') const keys = require('./keys') +const signatures = require('./signatures') const serviceproviders = require('./serviceproviders') const utils = require('./utils') exports.claims = claims exports.keys = keys +exports.signatures = signatures exports.serviceproviders = serviceproviders exports.utils = utils diff --git a/src/signatures.js b/src/signatures.js new file mode 100644 index 0000000..932e740 --- /dev/null +++ b/src/signatures.js @@ -0,0 +1,80 @@ +/* +Copyright 2020 Yarmo Mackenbach + +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. +*/ +const openpgp = require('openpgp') +const mergeOptions = require('merge-options') +const claims = require('./claims') +const keys = require('./keys') + +const verify = (signature, opts) => { + return new Promise(async (resolve, reject) => { + let errors = [], sigData + try { + sigData = await openpgp.cleartext.readArmored(signature) + } catch (error) { + errors.push('invalid_signature') + reject({ errors: errors }) + } + + const text = sigData.getText() + let sigKeys = [] + let sigClaims = [] + text.split('\n').forEach((line, i) => { + const match = line.match(/^(.*)\=(.*)$/i) + if (!match) { + return + } + switch (match[1].toLowerCase()) { + case 'key': + sigKeys.push(match[2]) + break + + case 'proof': + sigClaims.push(match[2]) + break + + default: + break + } + }) + + if (sigKeys.length === 0) { + errors.push('no_linked_keys') + reject({ errors: errors }) + } + + const keyData = await keys.fetch.uri(sigKeys[0]) + const fingerprint = keyData.keyPacket.getFingerprint() + + try { + const sigVerification = await sigData.verify([keyData]) + await sigVerification[0].verified + } catch (e) { + errors.push('invalid_signature_verification') + reject({ errors: errors }) + } + + const claimVerifications = await claims.verify(sigClaims, fingerprint, opts) + + resolve({ + errors: errors, + publicKey: keyData, + fingerprint: fingerprint, + claims: claimVerifications + }) + }) +} + +exports.verify = verify \ No newline at end of file diff --git a/test/signatures.test.js b/test/signatures.test.js new file mode 100644 index 0000000..99bff16 --- /dev/null +++ b/test/signatures.test.js @@ -0,0 +1,56 @@ +/* +Copyright 2020 Yarmo Mackenbach + +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. +*/ +const chai = require('chai') +const expect = chai.expect + +const doipjs = require('../src') + +const sigProfile = `-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +Hey there! Here's a signature profile with doip-related proofs. + +openpgp4fpr:3637202523e7c1309ab79e99ef2dc5827b445f4b +key=hkp:test@doip.rocks +proof=dns:doip.rocks +-----BEGIN PGP SIGNATURE----- + +iQHEBAEBCgAuFiEENjcgJSPnwTCat56Z7y3FgntEX0sFAl/3JBMQHHRlc3RAZG9p +cC5yb2NrcwAKCRDvLcWCe0RfS7XvC/wN9F/0ef/w1yXJqApgSNfc8WJxKS232g7L +prb3EMhNI9JV13yfZObb664WahkrMOiiIeN2vyofpU1h80cucQwmTcsBav/TX7HI +aBtXYtC6XvAhNUsctfA7C/uTSL3+St8G6ahbP7RLmal0r8vfIRgLMco1LtNpQM1v +gjkjNpceKkl10cJgx7UiT1RWIIvisnEGNgK31XaN8oRwAMSySjl2n4fRjDRlJPVd +cK+WvS4GJS24jRqGqZASTusPVRAOxtY+uEwX0HepUicgaHdFSFZ4iHByyrKEMi9L +sS5Z7/ZvHXgmS1BUV9++vtChi6zaFwMJZnkMci3C0xwoQ3MECNN2OrPExFFcqk/z +CgC81QrXNjGMZrBmSzPDgsibGe5G1VlQ73h1VhMjdcBZ1EjN0trEm3Ka8TDhJysS +cXbjvHSGniZ7M3S9S8knAfIquPvTp7+L7wWgSSB5VObPp1r+96n87hyFZUp7PCvl +3XkJV2l34fePSR73Ka7jmX86ARn4+HM= +=ADl+ +-----END PGP SIGNATURE-----` + +describe('signatures.verify', () => { + it('should be a function (2 arguments)', () => { + expect(doipjs.signatures.verify).to.be.a('function') + expect(doipjs.signatures.verify).to.have.length(2) + }) + it('should verify the demonstration signature', async () => { + const verification = await doipjs.signatures.verify(sigProfile) + expect(verification.errors).to.be.length(0) + expect(verification.fingerprint).to.be.equal('3637202523e7c1309ab79e99ef2dc5827b445f4b') + expect(verification.claims).to.be.length(1) + expect(verification.claims[0].isVerified).to.be.true + }) +})