aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/helpers
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2017-11-09 17:51:58 +0100
committerChocobozzz <florian.bigard@gmail.com>2017-11-27 19:40:51 +0100
commite4f97babf701481b55cc10fb3448feab5f97c867 (patch)
treeaf37402a594dc5ff09f71ecb0687e8cfe4cdb471 /server/helpers
parent343ad675f2a26c15b86150a9a3552e619d5d44f4 (diff)
downloadPeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.tar.gz
PeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.tar.zst
PeerTube-e4f97babf701481b55cc10fb3448feab5f97c867.zip
Begin activitypub
Diffstat (limited to 'server/helpers')
-rw-r--r--server/helpers/activitypub.ts123
-rw-r--r--server/helpers/core-utils.ts26
-rw-r--r--server/helpers/custom-validators/activitypub/account.ts123
-rw-r--r--server/helpers/custom-validators/activitypub/index.ts4
-rw-r--r--server/helpers/custom-validators/activitypub/misc.ts17
-rw-r--r--server/helpers/custom-validators/activitypub/signature.ts22
-rw-r--r--server/helpers/custom-validators/activitypub/videos.ts (renamed from server/helpers/custom-validators/remote/videos.ts)0
-rw-r--r--server/helpers/custom-validators/index.ts2
-rw-r--r--server/helpers/custom-validators/remote/index.ts1
-rw-r--r--server/helpers/ffmpeg-utils.ts1
-rw-r--r--server/helpers/index.ts2
-rw-r--r--server/helpers/peertube-crypto.ts158
-rw-r--r--server/helpers/requests.ts78
-rw-r--r--server/helpers/webfinger.ts44
14 files changed, 443 insertions, 158 deletions
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts
new file mode 100644
index 000000000..ecb509b66
--- /dev/null
+++ b/server/helpers/activitypub.ts
@@ -0,0 +1,123 @@
1import * as url from 'url'
2
3import { database as db } from '../initializers'
4import { logger } from './logger'
5import { doRequest } from './requests'
6import { isRemoteAccountValid } from './custom-validators'
7import { ActivityPubActor } from '../../shared/models/activitypub/activitypub-actor'
8import { ResultList } from '../../shared/models/result-list.model'
9
10async function fetchRemoteAccountAndCreatePod (accountUrl: string) {
11 const options = {
12 uri: accountUrl,
13 method: 'GET'
14 }
15
16 let requestResult
17 try {
18 requestResult = await doRequest(options)
19 } catch (err) {
20 logger.warning('Cannot fetch remote account %s.', accountUrl, err)
21 return undefined
22 }
23
24 const accountJSON: ActivityPubActor = requestResult.body
25 if (isRemoteAccountValid(accountJSON) === false) return undefined
26
27 const followersCount = await fetchAccountCount(accountJSON.followers)
28 const followingCount = await fetchAccountCount(accountJSON.following)
29
30 const account = db.Account.build({
31 uuid: accountJSON.uuid,
32 name: accountJSON.preferredUsername,
33 url: accountJSON.url,
34 publicKey: accountJSON.publicKey.publicKeyPem,
35 privateKey: null,
36 followersCount: followersCount,
37 followingCount: followingCount,
38 inboxUrl: accountJSON.inbox,
39 outboxUrl: accountJSON.outbox,
40 sharedInboxUrl: accountJSON.endpoints.sharedInbox,
41 followersUrl: accountJSON.followers,
42 followingUrl: accountJSON.following
43 })
44
45 const accountHost = url.parse(account.url).host
46 const podOptions = {
47 where: {
48 host: accountHost
49 },
50 defaults: {
51 host: accountHost
52 }
53 }
54 const pod = await db.Pod.findOrCreate(podOptions)
55
56 return { account, pod }
57}
58
59function activityPubContextify (data: object) {
60 return Object.assign(data,{
61 '@context': [
62 'https://www.w3.org/ns/activitystreams',
63 'https://w3id.org/security/v1',
64 {
65 'Hashtag': 'as:Hashtag',
66 'uuid': 'http://schema.org/identifier',
67 'category': 'http://schema.org/category',
68 'licence': 'http://schema.org/license',
69 'nsfw': 'as:sensitive',
70 'language': 'http://schema.org/inLanguage',
71 'views': 'http://schema.org/Number',
72 'size': 'http://schema.org/Number'
73 }
74 ]
75 })
76}
77
78function activityPubCollectionPagination (url: string, page: number, result: ResultList<any>) {
79 const baseUrl = url.split('?').shift
80
81 const obj = {
82 id: baseUrl,
83 type: 'Collection',
84 totalItems: result.total,
85 first: {
86 id: baseUrl + '?page=' + page,
87 type: 'CollectionPage',
88 totalItems: result.total,
89 next: baseUrl + '?page=' + (page + 1),
90 partOf: baseUrl,
91 items: result.data
92 }
93 }
94
95 return activityPubContextify(obj)
96}
97
98// ---------------------------------------------------------------------------
99
100export {
101 fetchRemoteAccountAndCreatePod,
102 activityPubContextify,
103 activityPubCollectionPagination
104}
105
106// ---------------------------------------------------------------------------
107
108async function fetchAccountCount (url: string) {
109 const options = {
110 uri: url,
111 method: 'GET'
112 }
113
114 let requestResult
115 try {
116 requestResult = await doRequest(options)
117 } catch (err) {
118 logger.warning('Cannot fetch remote account count %s.', url, err)
119 return undefined
120 }
121
122 return requestResult.totalItems ? requestResult.totalItems : 0
123}
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts
index 3dae78144..d8748e1d7 100644
--- a/server/helpers/core-utils.ts
+++ b/server/helpers/core-utils.ts
@@ -19,8 +19,10 @@ import * as mkdirp from 'mkdirp'
19import * as bcrypt from 'bcrypt' 19import * as bcrypt from 'bcrypt'
20import * as createTorrent from 'create-torrent' 20import * as createTorrent from 'create-torrent'
21import * as rimraf from 'rimraf' 21import * as rimraf from 'rimraf'
22import * as openssl from 'openssl-wrapper' 22import * as pem from 'pem'
23import * as Promise from 'bluebird' 23import * as jsonld from 'jsonld'
24import * as jsig from 'jsonld-signatures'
25jsig.use('jsonld', jsonld)
24 26
25function isTestInstance () { 27function isTestInstance () {
26 return process.env.NODE_ENV === 'test' 28 return process.env.NODE_ENV === 'test'
@@ -54,6 +56,12 @@ function escapeHTML (stringParam) {
54 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s]) 56 return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s])
55} 57}
56 58
59function pageToStartAndCount (page: number, itemsPerPage: number) {
60 const start = (page - 1) * itemsPerPage
61
62 return { start, count: itemsPerPage }
63}
64
57function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { 65function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
58 return function promisified (): Promise<A> { 66 return function promisified (): Promise<A> {
59 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { 67 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
@@ -104,13 +112,16 @@ const readdirPromise = promisify1<string, string[]>(readdir)
104const mkdirpPromise = promisify1<string, string>(mkdirp) 112const mkdirpPromise = promisify1<string, string>(mkdirp)
105const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) 113const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes)
106const accessPromise = promisify1WithVoid<string | Buffer>(access) 114const accessPromise = promisify1WithVoid<string | Buffer>(access)
107const opensslExecPromise = promisify2WithVoid<string, any>(openssl.exec) 115const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey)
116const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey)
108const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) 117const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare)
109const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) 118const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt)
110const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash) 119const bcryptHashPromise = promisify2<any, string | number, string>(bcrypt.hash)
111const createTorrentPromise = promisify2<string, any, any>(createTorrent) 120const createTorrentPromise = promisify2<string, any, any>(createTorrent)
112const rimrafPromise = promisify1WithVoid<string>(rimraf) 121const rimrafPromise = promisify1WithVoid<string>(rimraf)
113const statPromise = promisify1<string, Stats>(stat) 122const statPromise = promisify1<string, Stats>(stat)
123const jsonldSignPromise = promisify2<object, { privateKeyPem: string, creator: string }, object>(jsig.sign)
124const jsonldVerifyPromise = promisify2<object, object, object>(jsig.verify)
114 125
115// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
116 127
@@ -118,9 +129,11 @@ export {
118 isTestInstance, 129 isTestInstance,
119 root, 130 root,
120 escapeHTML, 131 escapeHTML,
132 pageToStartAndCount,
121 133
122 promisify0, 134 promisify0,
123 promisify1, 135 promisify1,
136
124 readdirPromise, 137 readdirPromise,
125 readFilePromise, 138 readFilePromise,
126 readFileBufferPromise, 139 readFileBufferPromise,
@@ -130,11 +143,14 @@ export {
130 mkdirpPromise, 143 mkdirpPromise,
131 pseudoRandomBytesPromise, 144 pseudoRandomBytesPromise,
132 accessPromise, 145 accessPromise,
133 opensslExecPromise, 146 createPrivateKey,
147 getPublicKey,
134 bcryptComparePromise, 148 bcryptComparePromise,
135 bcryptGenSaltPromise, 149 bcryptGenSaltPromise,
136 bcryptHashPromise, 150 bcryptHashPromise,
137 createTorrentPromise, 151 createTorrentPromise,
138 rimrafPromise, 152 rimrafPromise,
139 statPromise 153 statPromise,
154 jsonldSignPromise,
155 jsonldVerifyPromise
140} 156}
diff --git a/server/helpers/custom-validators/activitypub/account.ts b/server/helpers/custom-validators/activitypub/account.ts
new file mode 100644
index 000000000..8a7d1b7fe
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/account.ts
@@ -0,0 +1,123 @@
1import * as validator from 'validator'
2
3import { exists, isUUIDValid } from '../misc'
4import { isActivityPubUrlValid } from './misc'
5import { isUserUsernameValid } from '../users'
6
7function isAccountEndpointsObjectValid (endpointObject: any) {
8 return isAccountSharedInboxValid(endpointObject.sharedInbox)
9}
10
11function isAccountSharedInboxValid (sharedInbox: string) {
12 return isActivityPubUrlValid(sharedInbox)
13}
14
15function isAccountPublicKeyObjectValid (publicKeyObject: any) {
16 return isAccountPublicKeyIdValid(publicKeyObject.id) &&
17 isAccountPublicKeyOwnerValid(publicKeyObject.owner) &&
18 isAccountPublicKeyValid(publicKeyObject.publicKeyPem)
19}
20
21function isAccountPublicKeyIdValid (id: string) {
22 return isActivityPubUrlValid(id)
23}
24
25function isAccountTypeValid (type: string) {
26 return type === 'Person' || type === 'Application'
27}
28
29function isAccountPublicKeyOwnerValid (owner: string) {
30 return isActivityPubUrlValid(owner)
31}
32
33function isAccountPublicKeyValid (publicKey: string) {
34 return exists(publicKey) &&
35 typeof publicKey === 'string' &&
36 publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
37 publicKey.endsWith('-----END PUBLIC KEY-----')
38}
39
40function isAccountIdValid (id: string) {
41 return isActivityPubUrlValid(id)
42}
43
44function isAccountFollowingValid (id: string) {
45 return isActivityPubUrlValid(id)
46}
47
48function isAccountFollowersValid (id: string) {
49 return isActivityPubUrlValid(id)
50}
51
52function isAccountInboxValid (inbox: string) {
53 return isActivityPubUrlValid(inbox)
54}
55
56function isAccountOutboxValid (outbox: string) {
57 return isActivityPubUrlValid(outbox)
58}
59
60function isAccountNameValid (name: string) {
61 return isUserUsernameValid(name)
62}
63
64function isAccountPreferredUsernameValid (preferredUsername: string) {
65 return isAccountNameValid(preferredUsername)
66}
67
68function isAccountUrlValid (url: string) {
69 return isActivityPubUrlValid(url)
70}
71
72function isAccountPrivateKeyValid (privateKey: string) {
73 return exists(privateKey) &&
74 typeof privateKey === 'string' &&
75 privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
76 privateKey.endsWith('-----END RSA PRIVATE KEY-----')
77}
78
79function isRemoteAccountValid (remoteAccount: any) {
80 return isAccountIdValid(remoteAccount.id) &&
81 isUUIDValid(remoteAccount.uuid) &&
82 isAccountTypeValid(remoteAccount.type) &&
83 isAccountFollowingValid(remoteAccount.following) &&
84 isAccountFollowersValid(remoteAccount.followers) &&
85 isAccountInboxValid(remoteAccount.inbox) &&
86 isAccountOutboxValid(remoteAccount.outbox) &&
87 isAccountPreferredUsernameValid(remoteAccount.preferredUsername) &&
88 isAccountUrlValid(remoteAccount.url) &&
89 isAccountPublicKeyObjectValid(remoteAccount.publicKey) &&
90 isAccountEndpointsObjectValid(remoteAccount.endpoint)
91}
92
93function isAccountFollowingCountValid (value: string) {
94 return exists(value) && validator.isInt('' + value, { min: 0 })
95}
96
97function isAccountFollowersCountValid (value: string) {
98 return exists(value) && validator.isInt('' + value, { min: 0 })
99}
100
101// ---------------------------------------------------------------------------
102
103export {
104 isAccountEndpointsObjectValid,
105 isAccountSharedInboxValid,
106 isAccountPublicKeyObjectValid,
107 isAccountPublicKeyIdValid,
108 isAccountTypeValid,
109 isAccountPublicKeyOwnerValid,
110 isAccountPublicKeyValid,
111 isAccountIdValid,
112 isAccountFollowingValid,
113 isAccountFollowersValid,
114 isAccountInboxValid,
115 isAccountOutboxValid,
116 isAccountPreferredUsernameValid,
117 isAccountUrlValid,
118 isAccountPrivateKeyValid,
119 isRemoteAccountValid,
120 isAccountFollowingCountValid,
121 isAccountFollowersCountValid,
122 isAccountNameValid
123}
diff --git a/server/helpers/custom-validators/activitypub/index.ts b/server/helpers/custom-validators/activitypub/index.ts
new file mode 100644
index 000000000..800f0ddf3
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/index.ts
@@ -0,0 +1,4 @@
1export * from './account'
2export * from './signature'
3export * from './misc'
4export * from './videos'
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts
new file mode 100644
index 000000000..806d33483
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/misc.ts
@@ -0,0 +1,17 @@
1import { exists } from '../misc'
2
3function isActivityPubUrlValid (url: string) {
4 const isURLOptions = {
5 require_host: true,
6 require_tld: true,
7 require_protocol: true,
8 require_valid_protocol: true,
9 protocols: [ 'http', 'https' ]
10 }
11
12 return exists(url) && validator.isURL(url, isURLOptions)
13}
14
15export {
16 isActivityPubUrlValid
17}
diff --git a/server/helpers/custom-validators/activitypub/signature.ts b/server/helpers/custom-validators/activitypub/signature.ts
new file mode 100644
index 000000000..683ed2b1c
--- /dev/null
+++ b/server/helpers/custom-validators/activitypub/signature.ts
@@ -0,0 +1,22 @@
1import { exists } from '../misc'
2import { isActivityPubUrlValid } from './misc'
3
4function isSignatureTypeValid (signatureType: string) {
5 return exists(signatureType) && signatureType === 'GraphSignature2012'
6}
7
8function isSignatureCreatorValid (signatureCreator: string) {
9 return exists(signatureCreator) && isActivityPubUrlValid(signatureCreator)
10}
11
12function isSignatureValueValid (signatureValue: string) {
13 return exists(signatureValue) && signatureValue.length > 0
14}
15
16// ---------------------------------------------------------------------------
17
18export {
19 isSignatureTypeValid,
20 isSignatureCreatorValid,
21 isSignatureValueValid
22}
diff --git a/server/helpers/custom-validators/remote/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts
index e0ffba679..e0ffba679 100644
--- a/server/helpers/custom-validators/remote/videos.ts
+++ b/server/helpers/custom-validators/activitypub/videos.ts
diff --git a/server/helpers/custom-validators/index.ts b/server/helpers/custom-validators/index.ts
index c79982660..869b08870 100644
--- a/server/helpers/custom-validators/index.ts
+++ b/server/helpers/custom-validators/index.ts
@@ -1,4 +1,4 @@
1export * from './remote' 1export * from './activitypub'
2export * from './misc' 2export * from './misc'
3export * from './pods' 3export * from './pods'
4export * from './pods' 4export * from './pods'
diff --git a/server/helpers/custom-validators/remote/index.ts b/server/helpers/custom-validators/remote/index.ts
deleted file mode 100644
index e29a9b767..000000000
--- a/server/helpers/custom-validators/remote/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './videos'
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts
index f18b6bd9a..c07dddefe 100644
--- a/server/helpers/ffmpeg-utils.ts
+++ b/server/helpers/ffmpeg-utils.ts
@@ -1,4 +1,3 @@
1import * as Promise from 'bluebird'
2import * as ffmpeg from 'fluent-ffmpeg' 1import * as ffmpeg from 'fluent-ffmpeg'
3 2
4import { CONFIG } from '../initializers' 3import { CONFIG } from '../initializers'
diff --git a/server/helpers/index.ts b/server/helpers/index.ts
index 846bd796f..2c7ac3954 100644
--- a/server/helpers/index.ts
+++ b/server/helpers/index.ts
@@ -1,3 +1,4 @@
1export * from './activitypub'
1export * from './core-utils' 2export * from './core-utils'
2export * from './logger' 3export * from './logger'
3export * from './custom-validators' 4export * from './custom-validators'
@@ -6,3 +7,4 @@ export * from './database-utils'
6export * from './peertube-crypto' 7export * from './peertube-crypto'
7export * from './requests' 8export * from './requests'
8export * from './utils' 9export * from './utils'
10export * from './webfinger'
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts
index 10a226af4..6d50e446f 100644
--- a/server/helpers/peertube-crypto.ts
+++ b/server/helpers/peertube-crypto.ts
@@ -1,77 +1,68 @@
1import * as crypto from 'crypto' 1import * as jsig from 'jsonld-signatures'
2import { join } from 'path'
3 2
4import { 3import {
5 SIGNATURE_ALGORITHM, 4 PRIVATE_RSA_KEY_SIZE,
6 SIGNATURE_ENCODING, 5 BCRYPT_SALT_SIZE
7 PRIVATE_CERT_NAME,
8 CONFIG,
9 BCRYPT_SALT_SIZE,
10 PUBLIC_CERT_NAME
11} from '../initializers' 6} from '../initializers'
12import { 7import {
13 readFilePromise,
14 bcryptComparePromise, 8 bcryptComparePromise,
15 bcryptGenSaltPromise, 9 bcryptGenSaltPromise,
16 bcryptHashPromise, 10 bcryptHashPromise,
17 accessPromise, 11 createPrivateKey,
18 opensslExecPromise 12 getPublicKey,
13 jsonldSignPromise,
14 jsonldVerifyPromise
19} from './core-utils' 15} from './core-utils'
20import { logger } from './logger' 16import { logger } from './logger'
17import { AccountInstance } from '../models/account/account-interface'
21 18
22function checkSignature (publicKey: string, data: string, hexSignature: string) { 19async function createPrivateAndPublicKeys () {
23 const verify = crypto.createVerify(SIGNATURE_ALGORITHM) 20 logger.info('Generating a RSA key...')
24
25 let dataString
26 if (typeof data === 'string') {
27 dataString = data
28 } else {
29 try {
30 dataString = JSON.stringify(data)
31 } catch (err) {
32 logger.error('Cannot check signature.', err)
33 return false
34 }
35 }
36 21
37 verify.update(dataString, 'utf8') 22 const { key } = await createPrivateKey(PRIVATE_RSA_KEY_SIZE)
23 const { publicKey } = await getPublicKey(key)
38 24
39 const isValid = verify.verify(publicKey, hexSignature, SIGNATURE_ENCODING) 25 return { privateKey: key, publicKey }
40 return isValid
41} 26}
42 27
43async function sign (data: string | Object) { 28function isSignatureVerified (fromAccount: AccountInstance, signedDocument: object) {
44 const sign = crypto.createSign(SIGNATURE_ALGORITHM) 29 const publicKeyObject = {
45 30 '@context': jsig.SECURITY_CONTEXT_URL,
46 let dataString: string 31 '@id': fromAccount.url,
47 if (typeof data === 'string') { 32 '@type': 'CryptographicKey',
48 dataString = data 33 owner: fromAccount.url,
49 } else { 34 publicKeyPem: fromAccount.publicKey
50 try {
51 dataString = JSON.stringify(data)
52 } catch (err) {
53 logger.error('Cannot sign data.', err)
54 return ''
55 }
56 } 35 }
57 36
58 sign.update(dataString, 'utf8') 37 const publicKeyOwnerObject = {
38 '@context': jsig.SECURITY_CONTEXT_URL,
39 '@id': fromAccount.url,
40 publicKey: [ publicKeyObject ]
41 }
59 42
60 const myKey = await getMyPrivateCert() 43 const options = {
61 return sign.sign(myKey, SIGNATURE_ENCODING) 44 publicKey: publicKeyObject,
62} 45 publicKeyOwner: publicKeyOwnerObject
46 }
63 47
64function comparePassword (plainPassword: string, hashPassword: string) { 48 return jsonldVerifyPromise(signedDocument, options)
65 return bcryptComparePromise(plainPassword, hashPassword) 49 .catch(err => {
50 logger.error('Cannot check signature.', err)
51 return false
52 })
66} 53}
67 54
68async function createCertsIfNotExist () { 55function signObject (byAccount: AccountInstance, data: any) {
69 const exist = await certsExist() 56 const options = {
70 if (exist === true) { 57 privateKeyPem: byAccount.privateKey,
71 return 58 creator: byAccount.url
72 } 59 }
73 60
74 return createCerts() 61 return jsonldSignPromise(data, options)
62}
63
64function comparePassword (plainPassword: string, hashPassword: string) {
65 return bcryptComparePromise(plainPassword, hashPassword)
75} 66}
76 67
77async function cryptPassword (password: string) { 68async function cryptPassword (password: string) {
@@ -80,69 +71,12 @@ async function cryptPassword (password: string) {
80 return bcryptHashPromise(password, salt) 71 return bcryptHashPromise(password, salt)
81} 72}
82 73
83function getMyPrivateCert () {
84 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
85 return readFilePromise(certPath, 'utf8')
86}
87
88function getMyPublicCert () {
89 const certPath = join(CONFIG.STORAGE.CERT_DIR, PUBLIC_CERT_NAME)
90 return readFilePromise(certPath, 'utf8')
91}
92
93// --------------------------------------------------------------------------- 74// ---------------------------------------------------------------------------
94 75
95export { 76export {
96 checkSignature, 77 isSignatureVerified,
97 comparePassword, 78 comparePassword,
98 createCertsIfNotExist, 79 createPrivateAndPublicKeys,
99 cryptPassword, 80 cryptPassword,
100 getMyPrivateCert, 81 signObject
101 getMyPublicCert,
102 sign
103}
104
105// ---------------------------------------------------------------------------
106
107async function certsExist () {
108 const certPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
109
110 // If there is an error the certificates do not exist
111 try {
112 await accessPromise(certPath)
113
114 return true
115 } catch {
116 return false
117 }
118}
119
120async function createCerts () {
121 const exist = await certsExist()
122 if (exist === true) {
123 const errorMessage = 'Certs already exist.'
124 logger.warning(errorMessage)
125 throw new Error(errorMessage)
126 }
127
128 logger.info('Generating a RSA key...')
129
130 const privateCertPath = join(CONFIG.STORAGE.CERT_DIR, PRIVATE_CERT_NAME)
131 const genRsaOptions = {
132 'out': privateCertPath,
133 '2048': false
134 }
135
136 await opensslExecPromise('genrsa', genRsaOptions)
137 logger.info('RSA key generated.')
138 logger.info('Managing public key...')
139
140 const publicCertPath = join(CONFIG.STORAGE.CERT_DIR, 'peertube.pub')
141 const rsaOptions = {
142 'in': privateCertPath,
143 'pubout': true,
144 'out': publicCertPath
145 }
146
147 await opensslExecPromise('rsa', rsaOptions)
148} 82}
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts
index af1f401de..8c4c983f7 100644
--- a/server/helpers/requests.ts
+++ b/server/helpers/requests.ts
@@ -9,7 +9,13 @@ import {
9} from '../initializers' 9} from '../initializers'
10import { PodInstance } from '../models' 10import { PodInstance } from '../models'
11import { PodSignature } from '../../shared' 11import { PodSignature } from '../../shared'
12import { sign } from './peertube-crypto' 12import { signObject } from './peertube-crypto'
13
14function doRequest (requestOptions: request.CoreOptions & request.UriOptions) {
15 return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => {
16 request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body }))
17 })
18}
13 19
14type MakeRetryRequestParams = { 20type MakeRetryRequestParams = {
15 url: string, 21 url: string,
@@ -31,61 +37,57 @@ function makeRetryRequest (params: MakeRetryRequestParams) {
31} 37}
32 38
33type MakeSecureRequestParams = { 39type MakeSecureRequestParams = {
34 method: 'GET' | 'POST'
35 toPod: PodInstance 40 toPod: PodInstance
36 path: string 41 path: string
37 data?: Object 42 data?: Object
38} 43}
39function makeSecureRequest (params: MakeSecureRequestParams) { 44function makeSecureRequest (params: MakeSecureRequestParams) {
40 return new Promise<{ response: request.RequestResponse, body: any }>((res, rej) => { 45 const requestParams: {
41 const requestParams: { 46 method: 'POST',
42 url: string, 47 uri: string,
43 json: { 48 json: {
44 signature: PodSignature, 49 signature: PodSignature,
45 data: any 50 data: any
46 }
47 } = {
48 url: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
49 json: {
50 signature: null,
51 data: null
52 }
53 } 51 }
54 52 } = {
55 if (params.method !== 'POST') { 53 method: 'POST',
56 return rej(new Error('Cannot make a secure request with a non POST method.')) 54 uri: REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path,
55 json: {
56 signature: null,
57 data: null
57 } 58 }
59 }
58 60
59 const host = CONFIG.WEBSERVER.HOST 61 const host = CONFIG.WEBSERVER.HOST
60 62
61 let dataToSign 63 let dataToSign
62 if (params.data) { 64 if (params.data) {
63 dataToSign = params.data 65 dataToSign = params.data
64 } else { 66 } else {
65 // We do not have data to sign so we just take our host 67 // We do not have data to sign so we just take our host
66 // It is not ideal but the connection should be in HTTPS 68 // It is not ideal but the connection should be in HTTPS
67 dataToSign = host 69 dataToSign = host
68 } 70 }
69 71
70 sign(dataToSign).then(signature => { 72 sign(dataToSign).then(signature => {
71 requestParams.json.signature = { 73 requestParams.json.signature = {
72 host, // Which host we pretend to be 74 host, // Which host we pretend to be
73 signature 75 signature
74 } 76 }
75 77
76 // If there are data information 78 // If there are data information
77 if (params.data) { 79 if (params.data) {
78 requestParams.json.data = params.data 80 requestParams.json.data = params.data
79 } 81 }
80 82
81 request.post(requestParams, (err, response, body) => err ? rej(err) : res({ response, body })) 83 return doRequest(requestParams)
82 })
83 }) 84 })
84} 85}
85 86
86// --------------------------------------------------------------------------- 87// ---------------------------------------------------------------------------
87 88
88export { 89export {
90 doRequest,
89 makeRetryRequest, 91 makeRetryRequest,
90 makeSecureRequest 92 makeSecureRequest
91} 93}
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts
new file mode 100644
index 000000000..9586fa562
--- /dev/null
+++ b/server/helpers/webfinger.ts
@@ -0,0 +1,44 @@
1import * as WebFinger from 'webfinger.js'
2
3import { isTestInstance } from './core-utils'
4import { isActivityPubUrlValid } from './custom-validators'
5import { WebFingerData } from '../../shared'
6import { fetchRemoteAccountAndCreatePod } from './activitypub'
7
8const webfinger = new WebFinger({
9 webfist_fallback: false,
10 tls_only: isTestInstance(),
11 uri_fallback: false,
12 request_timeout: 3000
13})
14
15async function getAccountFromWebfinger (url: string) {
16 const webfingerData: WebFingerData = await webfingerLookup(url)
17
18 if (Array.isArray(webfingerData.links) === false) return undefined
19
20 const selfLink = webfingerData.links.find(l => l.rel === 'self')
21 if (selfLink === undefined || isActivityPubUrlValid(selfLink.href) === false) return undefined
22
23 const { account } = await fetchRemoteAccountAndCreatePod(selfLink.href)
24
25 return account
26}
27
28// ---------------------------------------------------------------------------
29
30export {
31 getAccountFromWebfinger
32}
33
34// ---------------------------------------------------------------------------
35
36function webfingerLookup (url: string) {
37 return new Promise<WebFingerData>((res, rej) => {
38 webfinger.lookup('nick@silverbucket.net', (err, p) => {
39 if (err) return rej(err)
40
41 return p
42 })
43 })
44}