aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorcel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2018-11-30 14:57:12 -1000
committercel <cel@f/6sQ6d2CMxRUhLpspgGIulDxDCwYD7DzFzPNr7u5AU=.ed25519>2018-11-30 14:58:13 -1000
commita61c80c4140564b1a4daf19d9086c05a8f276c20 (patch)
tree858bb36795bd5699d99639f637bd3492e3fe1fa6 /lib
parent328b6ffd8dc1a0cb66111a0ae8b346c5c2aad8f5 (diff)
downloadpatchfoo-a61c80c4140564b1a4daf19d9086c05a8f276c20.tar.gz
patchfoo-a61c80c4140564b1a4daf19d9086c05a8f276c20.zip
Show git signatures
- Add /git/signature page to verify signatures using gpg - Show link to signature pages for tag and commit objects
Diffstat (limited to 'lib')
-rw-r--r--lib/app.js62
-rw-r--r--lib/serve.js74
2 files changed, 136 insertions, 0 deletions
diff --git a/lib/app.js b/lib/app.js
index b4da954..6689a0e 100644
--- a/lib/app.js
+++ b/lib/app.js
@@ -19,6 +19,9 @@ var toPull = require('stream-to-pull-stream')
var BoxStream = require('pull-box-stream')
var crypto = require('crypto')
var SsbNpmRegistry = require('ssb-npm-registry')
+var os = require('os')
+var path = require('path')
+var fs = require('fs')
var zeros = new Buffer(24); zeros.fill(0)
@@ -1141,3 +1144,62 @@ App.prototype.sbotStatus = function (cb) {
}
if (typeof status === 'object' && status !== null) return cb(null, status)
}
+
+function writeAll(fd, buf, cb) {
+ var offset = 0
+ var remaining = buf.length
+ fs.write(fd, buf, function onWrite(err, bytesWritten) {
+ if (err) return cb(err)
+ offset += bytesWritten
+ remaining -= bytesWritten
+ if (remaining > 0) fs.write(fd, buf, offset, remaining, null, onWrite)
+ else cb()
+ })
+}
+
+App.prototype.verifyGitObjectSignature = function (obj, cb) {
+ var self = this
+ var tmpPath = path.join(os.tmpdir(), '.git_vtag_tmp' + Math.random().toString('36'))
+ // use a temp file to work around https://github.com/nodejs/node/issues/13542
+ function closeEnd(err) {
+ fs.close(fd, function (err1) {
+ fs.unlink(tmpPath, function (err2) {
+ cb(err2 || err1 || err)
+ })
+ })
+ }
+ fs.open(tmpPath, 'w+', function (err, fd) {
+ if (err) return cb(err)
+ self.git.extractSignature(obj, function (err, parts) {
+ if (err) return closeEnd(err)
+ writeAll(fd, parts.signature, function (err) {
+ if (err) return closeEnd(err)
+ try { next(fd, parts) }
+ catch(e) { closeEnd(e) }
+ })
+ })
+ })
+ function next(fd, parts) {
+ var readSig = fs.createReadStream(null, {fd: fd, start: 0})
+ var done = multicb({pluck: 1, spread: true})
+ var gpg = proc.spawn('gpg', ['--status-fd=1', '--keyid-format=long',
+ '--verify', '/dev/fd/3', '-'], {
+ stdio: ['pipe', 'pipe', 'pipe', readSig]
+ }).on('close', done().bind(null, null))
+ .on('error', console.error.bind(console, 'gpg'))
+ gpg.stdin.end(parts.payload)
+ pull(toPull.source(gpg.stdout), u.pullConcat(done()))
+ pull(toPull.source(gpg.stderr), u.pullConcat(done()))
+ done(function (err, code, status, output) {
+ if (err) return closeEnd(err)
+ fs.unlink(tmpPath, function (err) {
+ if (err) return cb(err)
+ cb(null, {
+ goodsig: status.includes('\n[GNUPG:] GOODSIG '),
+ status: status.toString(),
+ output: output.toString()
+ })
+ })
+ })
+ }
+}
diff --git a/lib/serve.js b/lib/serve.js
index a28f758..fc8aa7b 100644
--- a/lib/serve.js
+++ b/lib/serve.js
@@ -1870,6 +1870,7 @@ Serve.prototype.git = function (url) {
case 'blob': return this.gitBlob(m[2])
case 'raw': return this.gitRaw(m[2])
case 'diff': return this.gitDiff(m[2])
+ case 'signature': return this.gitSignature(m[2])
case 'line-comment': return this.gitLineComment(m[2])
default: return this.respond(404, 'Not found')
}
@@ -1985,6 +1986,67 @@ Serve.prototype.gitObject = function (rev) {
})
}
+Serve.prototype.gitSignature = function (id) {
+ var self = this
+ if (!/[0-9a-f]{24}/.test(id)) {
+ return pull(
+ ph('div.error', 'not a git object id'),
+ self.wrapPage('git'),
+ self.respondSink(400)
+ )
+ }
+ if (!u.isRef(self.query.msg)) return pull(
+ ph('div.error', 'missing message id'),
+ self.wrapPage('git signature for ' + id),
+ self.respondSink(400)
+ )
+
+ self.app.git.openObject({
+ obj: id,
+ msg: self.query.msg,
+ type: self.query.type,
+ }, function (err, obj) {
+ if (err) return handleError(err)
+ var msgDate = new Date(obj.msg.value.timestamp)
+ self.app.verifyGitObjectSignature(obj, function (err, verification) {
+ if (err) return handleError(err)
+ var objPath = '/git/object/' + id + '?msg=' + encodeURIComponent(obj.msg.key)
+ pull(
+ ph('section', [
+ ph('h3', [
+ ph('a', {href: self.app.render.toUrl(objPath)}, id), ': ',
+ ph('a', {href: ''}, 'signature')
+ ]),
+ ph('div', [
+ self.phIdLink(obj.msg.value.author), ' pushed ',
+ ph('a', {
+ href: self.app.render.toUrl(obj.msg.key),
+ title: msgDate.toLocaleString(),
+ }, htime(msgDate))
+ ]),
+ ph('pre', u.escapeHTML(verification.output))
+ /*
+ verification.goodsig ? 'good' : 'bad',
+ ph('pre', u.escapeHTML(verification.status))
+ */
+ ]),
+ self.wrapPage('git signature for ' + id),
+ self.respondSink(200)
+ )
+ })
+ })
+
+ function handleError(err) {
+ if (err && err.name === 'BlobNotFoundError')
+ return self.askWantBlobs(err.links)
+ if (err) return pull(
+ pull.once(u.renderError(err).outerHTML),
+ self.wrapPage('git signature for ' + id),
+ self.respondSink(400)
+ )
+ }
+}
+
Serve.prototype.gitCommit = function (rev) {
var self = this
if (!/[0-9a-f]{24}/.test(rev)) {
@@ -2073,6 +2135,12 @@ Serve.prototype.gitCommit = function (rev) {
pull.once(commit.tree),
self.gitObjectLinks(obj.msg.key, 'tree')
)]) : '',
+ commit.gpgsig ? ph('div', [
+ ph('a', {href: self.app.render.toUrl(
+ '/git/signature/' + rev + '?msg=' + encodeURIComponent(self.query.msg)
+ )}, 'signature'),
+ commit.signatureVersion ? [' from ', ph('code', u.escapeHTML(commit.signatureVersion))] : ''
+ ]) : '',
h('blockquote',
self.app.render.gitCommitBody(commit.body)).outerHTML,
ph('h4', 'files'),
@@ -2203,6 +2271,12 @@ Serve.prototype.gitTag = function (rev) {
self.gitObjectLinks(obj.msg.key, tag.type)
), ' ',
ph('code', u.escapeHTML(tag.tag)),
+ tag.gpgsig ? ph('div', [
+ ph('a', {href: self.app.render.toUrl(
+ '/git/signature/' + rev + '?msg=' + encodeURIComponent(self.query.msg)
+ )}, 'signature'),
+ tag.signatureVersion ? [' from ', ph('code', u.escapeHTML(tag.signatureVersion))] : ''
+ ]) : '',
h('pre', self.app.render.linkify(tag.body)).outerHTML,
]
]),