Skip to content

Commit 5679b27

Browse files
authored
replace node-webid (#1569)
1 parent fc2542b commit 5679b27

File tree

9 files changed

+278
-838
lines changed

9 files changed

+278
-838
lines changed

lib/api/authn/webid-tls.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const webid = require('webid/tls')
1+
const webid = require('../../webid/tls')
22
const debug = require('../../debug').authentication
33

44
function initialize (app, argv) {

lib/models/authenticator.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
const debug = require('./../debug').authentication
44
const validUrl = require('valid-url')
5-
const webid = require('webid/tls')
5+
const webid = require('../webid/tls')
66
const provider = require('@solid/oidc-auth-manager/src/preferred-provider')
77
const { domainMatches } = require('@solid/oidc-auth-manager/src/oidc-manager')
88

lib/models/webid-tls-certificate.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use strict'
22
/* eslint-disable node/no-deprecated-api */
33

4-
const webidTls = require('webid')('tls')
4+
const webidTls = require('../webid')('tls')
55
const forge = require('node-forge')
66
const utils = require('../utils')
77

lib/webid/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
module.exports = webid
2+
3+
const tls = require('./tls')
4+
5+
function webid (type) {
6+
type = type || 'tls'
7+
8+
if (type === 'tls') {
9+
return tls
10+
}
11+
12+
throw new Error('No other WebID supported')
13+
}

lib/webid/lib/get.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module.exports = get
2+
3+
const request = require('request')
4+
const url = require('url')
5+
6+
function get (webid, callback) {
7+
const uri = url.URL(webid)
8+
const options = {
9+
url: uri,
10+
method: 'GET',
11+
headers: {
12+
Accept: 'text/turtle, application/ld+json'
13+
}
14+
}
15+
16+
request(options, function (err, res, body) {
17+
if (err) {
18+
return callback(new Error('Failed to fetch profile from ' + uri.href + ': ' + err))
19+
}
20+
21+
if (res.statusCode !== 200) {
22+
return callback(new Error('Failed to retrieve WebID from ' + uri.href + ': HTTP ' + res.statusCode))
23+
}
24+
25+
callback(null, body, res.headers['content-type'])
26+
})
27+
}

lib/webid/lib/parse.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
module.exports = parse
2+
3+
const $rdf = require('rdflib')
4+
5+
function parse (profile, graph, uri, mimeType, callback) {
6+
try {
7+
$rdf.parse(profile, graph, uri, mimeType)
8+
return callback(null, graph)
9+
} catch (e) {
10+
return callback(new Error('Could not load/parse profile data: ' + e))
11+
}
12+
}

lib/webid/tls/index.js

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
exports.verify = verify
2+
exports.generate = generate
3+
exports.verifyKey = verifyKey
4+
5+
const $rdf = require('rdflib')
6+
const get = require('../lib/get')
7+
const parse = require('../lib/parse')
8+
const forge = require('node-forge')
9+
const url = require('url')
10+
const crypto = require('crypto')
11+
const certificate = new crypto.Certificate()
12+
const pki = forge.pki
13+
const Graph = $rdf.graph
14+
const SPARQL_QUERY = 'PREFIX cert: <http://www.w3.org/ns/auth/cert#> SELECT ?webid ?m ?e WHERE { ?webid cert:key ?key . ?key cert:modulus ?m . ?key cert:exponent ?e . }'
15+
16+
function verify (certificate, callback) {
17+
if (!certificate) {
18+
return callback(new Error('No certificate given'))
19+
}
20+
21+
// Collect URIs in certificate
22+
const uris = getUris(certificate)
23+
24+
// No uris
25+
if (uris.length === 0) {
26+
return callback(new Error('Empty Subject Alternative Name field in certificate'))
27+
}
28+
29+
// Get first URI
30+
const uri = uris.shift()
31+
get(uri, function (err, body, contentType) {
32+
if (err) {
33+
return callback(err)
34+
}
35+
36+
// Verify Key
37+
verifyKey(certificate, uri, body, contentType, function (err, success) {
38+
return callback(err, uri)
39+
})
40+
})
41+
}
42+
43+
function getUris (certificate) {
44+
const uris = []
45+
46+
if (certificate && certificate.subjectaltname) {
47+
certificate
48+
.subjectaltname
49+
.replace(/URI:([^, ]+)/g, function (match, uri) {
50+
return uris.push(uri)
51+
})
52+
}
53+
return uris
54+
}
55+
56+
function verifyKey (certificate, uri, profile, contentType, callback) {
57+
const graph = new Graph()
58+
let found = false
59+
60+
if (!certificate.modulus) {
61+
return callback(new Error('Missing modulus value in client certificate'))
62+
}
63+
64+
if (!certificate.exponent) {
65+
return callback(new Error('Missing exponent value in client certificate'))
66+
}
67+
68+
if (!contentType) {
69+
return callback(new Error('No value specified for the Content-Type header'))
70+
}
71+
72+
const mimeType = contentType.replace(/;.*/, '')
73+
parse(profile, graph, uri, mimeType, function (err) {
74+
if (err) {
75+
return callback(err)
76+
}
77+
const certExponent = parseInt(certificate.exponent, 16).toString()
78+
const query = $rdf.SPARQLToQuery(SPARQL_QUERY, undefined, graph)
79+
graph.query(
80+
query,
81+
function (result) {
82+
if (found) {
83+
return
84+
}
85+
const modulus = result['?m'].value
86+
const exponent = result['?e'].value
87+
88+
if (modulus != null &&
89+
exponent != null &&
90+
(modulus.toLowerCase() === certificate.modulus.toLowerCase()) &&
91+
exponent === certExponent) {
92+
found = true
93+
}
94+
},
95+
undefined, // testing
96+
function () {
97+
if (!found) {
98+
return callback(new Error('Certificate public key not found in the user\'s profile'))
99+
}
100+
return callback(null, true)
101+
}
102+
)
103+
})
104+
}
105+
106+
function generate (options, callback) {
107+
if (!options.agent) {
108+
return callback(new Error('No agent uri found'))
109+
}
110+
if (!options.spkac) {
111+
return callback(new Error('No public key found'), null)
112+
}
113+
if (!certificate.verifySpkac(Buffer.from(options.spkac))) {
114+
return callback(new Error('Invalid SPKAC'))
115+
}
116+
options.duration = options.duration || 10
117+
118+
// Generate a new certificate
119+
const cert = pki.createCertificate()
120+
cert.serialNumber = (Date.now()).toString(16)
121+
122+
// Get fields from SPKAC to populate new cert
123+
const publicKey = certificate.exportPublicKey(options.spkac).toString()
124+
cert.publicKey = pki.publicKeyFromPem(publicKey)
125+
126+
// Validity of 10 years
127+
cert.validity.notBefore = new Date()
128+
cert.validity.notAfter = new Date()
129+
cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + options.duration)
130+
131+
// `.` is default with the OpenSSL command line tool
132+
const commonName = options.commonName || url.URL(options.agent).hostname
133+
const attrsSubject = [{
134+
name: 'commonName',
135+
value: commonName
136+
}, {
137+
name: 'organizationName',
138+
value: options.organizationName || 'WebID'
139+
}]
140+
141+
const attrsIssuer = [{
142+
name: 'commonName',
143+
value: commonName
144+
}, {
145+
name: 'organizationName',
146+
value: options.organizationName || 'WebID'
147+
}]
148+
149+
if (options.issuer) {
150+
if (options.issuer.commonName) {
151+
attrsIssuer[0].value = options.issuer.commonName
152+
}
153+
if (options.issuer.organizationName) {
154+
attrsIssuer[1].value = options.issuer.organizationName
155+
}
156+
}
157+
158+
// Set same fields for certificate and issuer
159+
cert.setSubject(attrsSubject)
160+
cert.setIssuer(attrsIssuer)
161+
162+
// Set the cert extensions
163+
cert.setExtensions([
164+
{
165+
name: 'basicConstraints',
166+
cA: false,
167+
critical: true
168+
}, {
169+
name: 'subjectAltName',
170+
altNames: [{
171+
type: 6, // URI
172+
value: options.agent
173+
}]
174+
}, {
175+
name: 'subjectKeyIdentifier'
176+
}
177+
])
178+
179+
// Generate a new keypair to sign the certificate
180+
// TODO this make is not really "self-signed"
181+
const keys = pki.rsa.generateKeyPair(1024)
182+
cert.sign(keys.privateKey, forge.md.sha256.create())
183+
184+
return callback(null, cert)
185+
}

0 commit comments

Comments
 (0)