const isObject = require('../help/is_object') let validateCrit = require('../help/validate_crit') const { JWSInvalid } = require('../errors') validateCrit = validateCrit.bind(undefined, JWSInvalid) const compactSerializer = (payload, [recipient]) => { return `${recipient.protected}.${payload}.${recipient.signature}` } compactSerializer.validate = (jws, { 0: { unprotectedHeader, protectedHeader }, length }) => { if (length !== 1 || unprotectedHeader) { throw new JWSInvalid('JWS Compact Serialization doesn\'t support multiple recipients or JWS unprotected headers') } validateCrit(protectedHeader, unprotectedHeader, protectedHeader ? protectedHeader.crit : undefined) } const flattenedSerializer = (payload, [recipient]) => { const { header, signature, protected: prot } = recipient return { payload, ...prot ? { protected: prot } : undefined, ...header ? { header } : undefined, signature } } flattenedSerializer.validate = (jws, { 0: { unprotectedHeader, protectedHeader }, length }) => { if (length !== 1) { throw new JWSInvalid('Flattened JWS JSON Serialization doesn\'t support multiple recipients') } validateCrit(protectedHeader, unprotectedHeader, protectedHeader ? protectedHeader.crit : undefined) } const generalSerializer = (payload, recipients) => { return { payload, signatures: recipients.map(({ header, signature, protected: prot }) => { return { ...prot ? { protected: prot } : undefined, ...header ? { header } : undefined, signature } }) } } generalSerializer.validate = (jws, recipients) => { let validateB64 = false recipients.forEach(({ protectedHeader, unprotectedHeader }) => { if (protectedHeader && !validateB64 && 'b64' in protectedHeader) { validateB64 = true } validateCrit(protectedHeader, unprotectedHeader, protectedHeader ? protectedHeader.crit : undefined) }) if (validateB64) { const values = recipients.map(({ protectedHeader }) => protectedHeader && protectedHeader.b64) if (!values.every((actual, i, [expected]) => actual === expected)) { throw new JWSInvalid('the "b64" Header Parameter value MUST be the same for all recipients') } } } const isJSON = (input) => { return isObject(input) && (typeof input.payload === 'string' || Buffer.isBuffer(input.payload)) } const isValidRecipient = (recipient) => { return isObject(recipient) && typeof recipient.signature === 'string' && (recipient.header === undefined || isObject(recipient.header)) && (recipient.protected === undefined || typeof recipient.protected === 'string') } const isMultiRecipient = (input) => { if (Array.isArray(input.signatures) && input.signatures.every(isValidRecipient)) { return true } return false } const detect = (input) => { if (typeof input === 'string' && input.split('.').length === 3) { return 'compact' } if (isJSON(input)) { if (isMultiRecipient(input)) { return 'general' } if (isValidRecipient(input)) { return 'flattened' } } throw new JWSInvalid('JWS malformed or invalid serialization') } module.exports = { compact: compactSerializer, flattened: flattenedSerializer, general: generalSerializer, detect }