|
@@ -1,26 +1,51 @@
|
|
|
+import { omit } from '@peertube/peertube-core-utils'
|
|
|
import { sha256 } from '@peertube/peertube-node-utils'
|
|
|
import { createSign, createVerify } from 'crypto'
|
|
|
import cloneDeep from 'lodash-es/cloneDeep.js'
|
|
|
import { MActor } from '../types/models/index.js'
|
|
|
+import { getAllContext } from './activity-pub-utils.js'
|
|
|
+import { jsonld } from './custom-jsonld-signature.js'
|
|
|
+import { isArray } from './custom-validators/misc.js'
|
|
|
import { logger } from './logger.js'
|
|
|
import { assertIsInWorkerThread } from './threads.js'
|
|
|
-import { jsonld } from './custom-jsonld-signature.js'
|
|
|
|
|
|
-export function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> {
|
|
|
- if (signedDocument.signature.type === 'RsaSignature2017') {
|
|
|
- return isJsonLDRSA2017Verified(fromActor, signedDocument)
|
|
|
+type ExpressRequest = { body: any }
|
|
|
+
|
|
|
+export function compactJSONLDAndCheckSignature (fromActor: MActor, req: ExpressRequest): Promise<boolean> {
|
|
|
+ if (req.body.signature.type === 'RsaSignature2017') {
|
|
|
+ return compactJSONLDAndCheckRSA2017Signature(fromActor, req)
|
|
|
}
|
|
|
|
|
|
- logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument)
|
|
|
+ logger.warn('Unknown JSON LD signature %s.', req.body.signature.type, req.body)
|
|
|
|
|
|
return Promise.resolve(false)
|
|
|
}
|
|
|
|
|
|
// Backward compatibility with "other" implementations
|
|
|
-export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) {
|
|
|
+export async function compactJSONLDAndCheckRSA2017Signature (fromActor: MActor, req: ExpressRequest) {
|
|
|
+ const compacted = await jsonldCompact(omit(req.body, [ 'signature' ]))
|
|
|
+
|
|
|
+ fixCompacted(req.body, compacted)
|
|
|
+
|
|
|
+ req.body = { ...compacted, signature: req.body.signature }
|
|
|
+
|
|
|
+ if (compacted['@include']) {
|
|
|
+ logger.warn('JSON-LD @include is not supported')
|
|
|
+ return false
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: compat with < 6.1, remove in 7.0
|
|
|
+ let safe = true
|
|
|
+ if (
|
|
|
+ (compacted.type === 'Create' && (compacted?.object?.type === 'WatchAction' || compacted?.object?.type === 'CacheFile')) ||
|
|
|
+ (compacted.type === 'Undo' && compacted?.object?.type === 'Create' && compacted?.object?.object.type === 'CacheFile')
|
|
|
+ ) {
|
|
|
+ safe = false
|
|
|
+ }
|
|
|
+
|
|
|
const [ documentHash, optionsHash ] = await Promise.all([
|
|
|
- createDocWithoutSignatureHash(signedDocument),
|
|
|
- createSignatureHash(signedDocument.signature)
|
|
|
+ hashObject(compacted, safe),
|
|
|
+ createSignatureHash(req.body.signature, safe)
|
|
|
])
|
|
|
|
|
|
const toVerify = optionsHash + documentHash
|
|
@@ -28,7 +53,39 @@ export async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument
|
|
|
const verify = createVerify('RSA-SHA256')
|
|
|
verify.update(toVerify, 'utf8')
|
|
|
|
|
|
- return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64')
|
|
|
+ return verify.verify(fromActor.publicKey, req.body.signature.signatureValue, 'base64')
|
|
|
+}
|
|
|
+
|
|
|
+function fixCompacted (original: any, compacted: any) {
|
|
|
+ if (!original || !compacted) return
|
|
|
+
|
|
|
+ for (const [ k, v ] of Object.entries(original)) {
|
|
|
+ if (k === '@context' || k === 'signature') continue
|
|
|
+ if (v === undefined || v === null) continue
|
|
|
+
|
|
|
+ const cv = compacted[k]
|
|
|
+ if (cv === undefined || cv === null) continue
|
|
|
+
|
|
|
+ if (typeof v === 'string') {
|
|
|
+ if (v === 'https://www.w3.org/ns/activitystreams#Public' && cv === 'as:Public') {
|
|
|
+ compacted[k] = v
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isArray(v) && !isArray(cv)) {
|
|
|
+ compacted[k] = [ cv ]
|
|
|
+
|
|
|
+ for (let i = 0; i < v.length; i++) {
|
|
|
+ if (v[i] === 'https://www.w3.org/ns/activitystreams#Public' && cv[i] === 'as:Public') {
|
|
|
+ compacted[k][i] = v[i]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (typeof v === 'object') {
|
|
|
+ fixCompacted(original[k], compacted[k])
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
export async function signJsonLDObject <T> (options: {
|
|
@@ -66,35 +123,40 @@ export async function signJsonLDObject <T> (options: {
|
|
|
// Private
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
-async function hashObject (obj: any): Promise<any> {
|
|
|
- const res = await (jsonld as any).promises.normalize(obj, {
|
|
|
- safe: false,
|
|
|
+async function hashObject (obj: any, safe: boolean): Promise<any> {
|
|
|
+ const res = await jsonldNormalize(obj, safe)
|
|
|
+
|
|
|
+ return sha256(res)
|
|
|
+}
|
|
|
+
|
|
|
+function jsonldCompact (obj: any) {
|
|
|
+ return (jsonld as any).promises.compact(obj, getAllContext())
|
|
|
+}
|
|
|
+
|
|
|
+function jsonldNormalize (obj: any, safe: boolean) {
|
|
|
+ return (jsonld as any).promises.normalize(obj, {
|
|
|
+ safe,
|
|
|
algorithm: 'URDNA2015',
|
|
|
format: 'application/n-quads'
|
|
|
})
|
|
|
-
|
|
|
- return sha256(res)
|
|
|
}
|
|
|
|
|
|
-function createSignatureHash (signature: any) {
|
|
|
- const signatureCopy = cloneDeep(signature)
|
|
|
- Object.assign(signatureCopy, {
|
|
|
+// ---------------------------------------------------------------------------
|
|
|
+
|
|
|
+function createSignatureHash (signature: any, safe = true) {
|
|
|
+ return hashObject({
|
|
|
'@context': [
|
|
|
'https://w3id.org/security/v1',
|
|
|
{ RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' }
|
|
|
- ]
|
|
|
- })
|
|
|
-
|
|
|
- delete signatureCopy.type
|
|
|
- delete signatureCopy.id
|
|
|
- delete signatureCopy.signatureValue
|
|
|
+ ],
|
|
|
|
|
|
- return hashObject(signatureCopy)
|
|
|
+ ...omit(signature, [ 'type', 'id', 'signatureValue' ])
|
|
|
+ }, safe)
|
|
|
}
|
|
|
|
|
|
function createDocWithoutSignatureHash (doc: any) {
|
|
|
const docWithoutSignature = cloneDeep(doc)
|
|
|
delete docWithoutSignature.signature
|
|
|
|
|
|
- return hashObject(docWithoutSignature)
|
|
|
+ return hashObject(docWithoutSignature, true)
|
|
|
}
|