Comparing version 2.4.0-rc4 to 2.4.0-rc5
@@ -55,3 +55,2 @@ "use strict"; | ||
var lodash_1 = require("lodash"); | ||
var xmlTag = urn_1.tags.xmlTag; | ||
var binding = urn_1.wording.binding; | ||
@@ -58,0 +57,0 @@ /** |
@@ -106,27 +106,2 @@ "use strict"; | ||
}; | ||
/** | ||
* @desc Verify time stamp | ||
* @param {date} notBefore | ||
* @param {date} notOnOrAfter | ||
* @return {boolean} | ||
*/ | ||
Entity.prototype.verifyTime = function (notBefore, notOnOrAfter) { | ||
var now = new Date(); | ||
if (lodash_1.isUndefined(notBefore) && lodash_1.isUndefined(notOnOrAfter)) { | ||
return true; // throw exception todo | ||
} | ||
if (!lodash_1.isUndefined(notBefore) && lodash_1.isUndefined(notOnOrAfter)) { | ||
var notBeforeLocal = new Date(notBefore.toUTCString()); | ||
return +notBeforeLocal <= +now; | ||
} | ||
if (lodash_1.isUndefined(notBefore) && !lodash_1.isUndefined(notOnOrAfter)) { | ||
var notOnOrAfterLocal = new Date(notOnOrAfter.toUTCString()); | ||
return now < notOnOrAfterLocal; | ||
} | ||
else { | ||
var notBeforeLocal = new Date(notBefore.toUTCString()); | ||
var notOnOrAfterLocal = new Date(notOnOrAfter.toUTCString()); | ||
return +notBeforeLocal <= +now && now < notOnOrAfterLocal; | ||
} | ||
}; | ||
/** @desc Generates the logout request for developers to design their own method | ||
@@ -133,0 +108,0 @@ * @param {ServiceProvider} sp object of service provider |
@@ -39,2 +39,3 @@ "use strict"; | ||
var utility_1 = require("./utility"); | ||
var validator_1 = require("./validator"); | ||
var libsaml_1 = require("./libsaml"); | ||
@@ -123,3 +124,3 @@ var extractor_1 = require("./extractor"); | ||
return __awaiter(this, void 0, void 0, function () { | ||
var request, from, self, parserType, _a, checkSignature, body, direction, encodedRequest, samlContent, verificationOptions, decryptRequired, extractorFields, _b, verified, verifiedAssertionNode, result, _c, verified, verifiedAssertionNode, parseResult; | ||
var request, from, self, parserType, _a, checkSignature, body, direction, encodedRequest, samlContent, verificationOptions, decryptRequired, extractorFields, _b, verified, verifiedAssertionNode, result, _c, verified, verifiedAssertionNode, parseResult, targetEntityMetadata, issuer, extractedProperties; | ||
return __generator(this, function (_d) { | ||
@@ -139,2 +140,8 @@ switch (_d.label) { | ||
extractorFields = []; | ||
if (!(parserType === 'SAMLResponse')) return [3 /*break*/, 2]; | ||
return [4 /*yield*/, libsaml_1.default.isValidXml(samlContent)]; | ||
case 1: | ||
_d.sent(); | ||
_d.label = 2; | ||
case 2: | ||
if (parserType !== 'SAMLResponse') { | ||
@@ -154,14 +161,8 @@ extractorFields = getDefaultExtractorFields(parserType, null); | ||
} | ||
if (!(parserType === 'SAMLResponse' && decryptRequired)) return [3 /*break*/, 2]; | ||
if (!(parserType === 'SAMLResponse' && decryptRequired)) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, libsaml_1.default.decryptAssertion(self, samlContent)]; | ||
case 1: | ||
case 3: | ||
result = _d.sent(); | ||
samlContent = result[0]; | ||
extractorFields = getDefaultExtractorFields(parserType, result[1]); | ||
_d.label = 2; | ||
case 2: | ||
if (!(parserType === 'SAMLResponse')) return [3 /*break*/, 4]; | ||
return [4 /*yield*/, libsaml_1.default.isValidXml(samlContent)]; | ||
case 3: | ||
_d.sent(); | ||
_d.label = 4; | ||
@@ -184,5 +185,23 @@ case 4: | ||
}; | ||
// TODO: basic validator (issuer, timer) | ||
// const targetEntityMetadata = from.entityMeta; | ||
// const issuer = targetEntityMetadata.getEntityID(); | ||
targetEntityMetadata = from.entityMeta; | ||
issuer = targetEntityMetadata.getEntityID(); | ||
extractedProperties = parseResult.extract; | ||
// unmatched issuer | ||
if ((parserType === 'LogoutResponse' || parserType === 'SAMLResponse') | ||
&& extractedProperties | ||
&& extractedProperties.issuer !== issuer) { | ||
return [2 /*return*/, Promise.reject('ERR_UNMATCH_ISSUER')]; | ||
} | ||
// invalid session time | ||
if (parserType === 'SAMLResponse' | ||
&& !validator_1.verifyTime(undefined, extractedProperties.sessionIndex.sessionNotOnOrAfter)) { | ||
return [2 /*return*/, Promise.reject('ERR_EXPIRED_SESSION')]; | ||
} | ||
// invalid time | ||
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf | ||
if (parserType === 'SAMLResponse' | ||
&& extractedProperties.conditions | ||
&& !validator_1.verifyTime(extractedProperties.conditions.notBefore, extractedProperties.conditions.notOnOrAfter)) { | ||
return [2 /*return*/, Promise.reject('ERR_SUBJECT_UNCONFIRMED')]; | ||
} | ||
return [2 /*return*/, Promise.resolve(parseResult)]; | ||
@@ -189,0 +208,0 @@ } |
@@ -54,3 +54,4 @@ "use strict"; | ||
var fs = require("fs"); | ||
var Validator = require("xsd-schema-validator"); | ||
var Validator = require("@passify/xsd-schema-validator"); | ||
var extractor_1 = require("./extractor"); | ||
var signatureAlgorithms = urn_1.algorithms.signature; | ||
@@ -282,17 +283,2 @@ var digestAlgorithms = urn_1.algorithms.digest; | ||
} | ||
// response must be signed, either entire document or assertion | ||
// default we will take the assertion section under root | ||
if (messageSignatureNode.length === 1) { | ||
var node = xpath_1.select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc); | ||
if (node.length === 1) { | ||
assertionNode = node[0].toString(); | ||
} | ||
// remove message signature | ||
doc.removeChild(messageSignatureNode[0]); | ||
} | ||
if (assertionSignatureNode.length === 1) { | ||
assertionNode = assertionSignatureNode[0].parentNode.toString(); | ||
// remove assertion signature | ||
doc.removeChild(assertionSignatureNode[0]); | ||
} | ||
// guarantee to have a signature in saml response | ||
@@ -304,3 +290,4 @@ if (selection.length === 0) { | ||
var verified = true; | ||
selection.forEach(function (s) { | ||
// need to refactor later on | ||
selection.forEach(function (signatureNode) { | ||
var selectedCert = ''; | ||
@@ -321,3 +308,3 @@ sig.signatureAlgorithm = opts.signatureAlgorithm; | ||
metadataCert = metadataCert.map(utility_1.default.normalizeCerString); | ||
var x509Certificate = xpath_1.select(".//*[local-name(.)='X509Certificate']", s)[0].firstChild.data; | ||
var x509Certificate = xpath_1.select(".//*[local-name(.)='X509Certificate']", signatureNode)[0].firstChild.data; | ||
x509Certificate = utility_1.default.normalizeCerString(x509Certificate); | ||
@@ -335,5 +322,49 @@ if (lodash_1.includes(metadataCert, x509Certificate)) { | ||
} | ||
sig.loadSignature(s); | ||
sig.loadSignature(signatureNode); | ||
doc.removeChild(signatureNode); | ||
verified = verified && sig.checkSignature(doc.toString()); | ||
// immediately throw error when any one of the signature is failed to get verified | ||
if (!verified) { | ||
throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE'); | ||
} | ||
}); | ||
// response must be signed, either entire document or assertion | ||
// default we will take the assertion section under root | ||
if (messageSignatureNode.length === 1) { | ||
var node = xpath_1.select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc); | ||
if (node.length === 1) { | ||
assertionNode = node[0].toString(); | ||
} | ||
} | ||
if (assertionSignatureNode.length === 1) { | ||
var verifiedAssertionInfo = extractor_1.extract(assertionSignatureNode[0].toString(), [{ | ||
key: 'refURI', | ||
localPath: ['Signature', 'SignedInfo', 'Reference'], | ||
attributes: ['URI'] | ||
}]); | ||
// get the assertion supposed to be the one should be verified | ||
var desiredAssertionInfo = extractor_1.extract(doc.toString(), [{ | ||
key: 'id', | ||
localPath: ['~Response', 'Assertion'], | ||
attributes: ['ID'] | ||
}]); | ||
// 5.4.2 References | ||
// SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of | ||
// the assertion or protocol message being signed. The assertion’s or protocol message's root element may | ||
// or may not be the root element of the actual XML document containing the signed assertion or protocol | ||
// message (e.g., it might be contained within a SOAP envelope). | ||
// Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID | ||
// attribute value of the root element of the assertion or protocol message being signed. For example, if the | ||
// ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo". | ||
if (verifiedAssertionInfo.refURI !== "#" + desiredAssertionInfo.id) { | ||
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK'); | ||
} | ||
var verifiedDoc = extractor_1.extract(doc.toString(), [{ | ||
key: 'assertion', | ||
localPath: ['~Response', 'Assertion'], | ||
attributes: [], | ||
context: true | ||
}]); | ||
assertionNode = verifiedDoc.assertion.toString(); | ||
} | ||
return [verified, assertionNode]; | ||
@@ -340,0 +371,0 @@ }, |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
/** | ||
* @file utility.ts | ||
* @author tngan | ||
* @desc Library for some common functions (e.g. de/inflation, en/decoding) | ||
*/ | ||
var node_forge_1 = require("node-forge"); | ||
@@ -4,0 +9,0 @@ var deflate_js_1 = require("deflate-js"); |
{ | ||
"name": "samlify", | ||
"version": "2.4.0-rc4", | ||
"version": "2.4.0-rc5", | ||
"description": "High-level API for Single Sign On (SAML 2.0)", | ||
@@ -36,2 +36,3 @@ "main": "build/index.js", | ||
"@passify/xml-encryption": "^0.11.1", | ||
"@passify/xsd-schema-validator": "^0.7.1", | ||
"@types/camelcase": "^0.0.30", | ||
@@ -53,4 +54,3 @@ "@types/lodash": "^4.14.112", | ||
"xmldom": "^0.1.19", | ||
"xpath": "0.0.24", | ||
"xsd-schema-validator": "^0.6.0" | ||
"xpath": "0.0.24" | ||
}, | ||
@@ -63,2 +63,2 @@ "devDependencies": { | ||
} | ||
} | ||
} |
@@ -7,3 +7,3 @@ /** | ||
import { wording, tags, namespace, StatusCode } from './urn'; | ||
import { wording, namespace, StatusCode } from './urn'; | ||
import { BindingContext } from './entity'; | ||
@@ -14,3 +14,2 @@ import libsaml from './libsaml'; | ||
const xmlTag = tags.xmlTag; | ||
const binding = wording.binding; | ||
@@ -17,0 +16,0 @@ |
@@ -8,3 +8,3 @@ /** | ||
import libsaml from './libsaml'; | ||
import Entity, { BindingContext } from './entity'; | ||
import { BindingContext } from './entity'; | ||
import { IdentityProvider as Idp } from './entity-idp'; | ||
@@ -141,3 +141,3 @@ import { ServiceProvider as Sp } from './entity-sp'; | ||
SessionIndex: user.sessionIndex, | ||
} | ||
}; | ||
if (initSetting.logoutRequestTemplate) { | ||
@@ -144,0 +144,0 @@ const info = customTagReplacement(initSetting.logoutRequestTemplate, requiredTags); |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
import Entity, { ESamlHttpRequest, ParseResult } from './entity'; | ||
import Entity, { ESamlHttpRequest } from './entity'; | ||
import { | ||
@@ -9,0 +9,0 @@ ServiceProviderConstructor as ServiceProvider, |
@@ -133,27 +133,2 @@ /** | ||
} | ||
/** | ||
* @desc Verify time stamp | ||
* @param {date} notBefore | ||
* @param {date} notOnOrAfter | ||
* @return {boolean} | ||
*/ | ||
verifyTime(notBefore?: Date, notOnOrAfter?: Date): boolean { | ||
const now = new Date(); | ||
if (isUndefined(notBefore) && isUndefined(notOnOrAfter)) { | ||
return true; // throw exception todo | ||
} | ||
if (!isUndefined(notBefore) && isUndefined(notOnOrAfter)) { | ||
const notBeforeLocal = new Date(notBefore.toUTCString()); | ||
return +notBeforeLocal <= +now; | ||
} | ||
if (isUndefined(notBefore) && !isUndefined(notOnOrAfter)) { | ||
const notOnOrAfterLocal = new Date(notOnOrAfter.toUTCString()); | ||
return now < notOnOrAfterLocal; | ||
} else { | ||
const notBeforeLocal = new Date(notBefore.toUTCString()); | ||
const notOnOrAfterLocal = new Date(notOnOrAfter.toUTCString()); | ||
return +notBeforeLocal <= +now && now < notOnOrAfterLocal; | ||
} | ||
} | ||
/** @desc Generates the logout request for developers to design their own method | ||
@@ -160,0 +135,0 @@ * @param {ServiceProvider} sp object of service provider |
import { inflateString, base64Decode } from './utility'; | ||
import { verifyTime } from './validator'; | ||
import libsaml from './libsaml'; | ||
import { DOMParser as dom } from 'xmldom'; | ||
import { | ||
@@ -54,3 +54,3 @@ extract, | ||
const { SigAlg: sigAlg, Signature: signature } = query; | ||
const targetEntityMetadata = from.entityMeta; | ||
@@ -95,3 +95,3 @@ | ||
const decodeSigAlg = decodeURIComponent(sigAlg); | ||
const verified = libsaml.verifyMessageSignature(targetEntityMetadata, octetString, base64Signature, sigAlg); | ||
@@ -113,3 +113,3 @@ | ||
const { | ||
const { | ||
request, | ||
@@ -138,2 +138,7 @@ from, | ||
// validate the xml first | ||
if (parserType === 'SAMLResponse') { | ||
await libsaml.isValidXml(samlContent); | ||
} | ||
if (parserType !== 'SAMLResponse') { | ||
@@ -150,4 +155,4 @@ extractorFields = getDefaultExtractorFields(parserType, null); | ||
if (!verified) { | ||
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE'); | ||
} | ||
return Promise.reject('ERR_FAIL_TO_VERIFY_ETS_SIGNATURE'); | ||
} | ||
if (!decryptRequired) { | ||
@@ -164,6 +169,2 @@ extractorFields = getDefaultExtractorFields(parserType, verifiedAssertionNode); | ||
if (parserType === 'SAMLResponse') { | ||
await libsaml.isValidXml(samlContent); | ||
} | ||
// verify the signatures (the repsonse is signed then encrypted, then decrypt first then verify) | ||
@@ -178,3 +179,3 @@ if ( | ||
} else { | ||
return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE'); | ||
return Promise.reject('ERR_FAIL_TO_VERIFY_STE_SIGNATURE'); | ||
} | ||
@@ -188,6 +189,40 @@ } | ||
// TODO: basic validator (issuer, timer) | ||
// const targetEntityMetadata = from.entityMeta; | ||
// const issuer = targetEntityMetadata.getEntityID(); | ||
// validation part | ||
const targetEntityMetadata = from.entityMeta; | ||
const issuer = targetEntityMetadata.getEntityID(); | ||
const extractedProperties = parseResult.extract; | ||
// unmatched issuer | ||
if ( | ||
(parserType === 'LogoutResponse' || parserType === 'SAMLResponse') | ||
&& extractedProperties | ||
&& extractedProperties.issuer !== issuer | ||
) { | ||
return Promise.reject('ERR_UNMATCH_ISSUER'); | ||
} | ||
// invalid session time | ||
if ( | ||
parserType === 'SAMLResponse' | ||
&& !verifyTime( | ||
undefined, | ||
extractedProperties.sessionIndex.sessionNotOnOrAfter | ||
) | ||
) { | ||
return Promise.reject('ERR_EXPIRED_SESSION'); | ||
} | ||
// invalid time | ||
// 2.4.1.2 https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf | ||
if ( | ||
parserType === 'SAMLResponse' | ||
&& extractedProperties.conditions | ||
&& !verifyTime( | ||
extractedProperties.conditions.notBefore, | ||
extractedProperties.conditions.notOnOrAfter | ||
) | ||
) { | ||
return Promise.reject('ERR_SUBJECT_UNCONFIRMED'); | ||
} | ||
return Promise.resolve(parseResult); | ||
@@ -194,0 +229,0 @@ } |
@@ -19,3 +19,4 @@ /** | ||
import * as fs from 'fs'; | ||
import * as Validator from 'xsd-schema-validator'; | ||
import * as Validator from '@passify/xsd-schema-validator'; | ||
import { extract } from './extractor'; | ||
@@ -347,19 +348,3 @@ const signatureAlgorithms = algorithms.signature; | ||
} | ||
// response must be signed, either entire document or assertion | ||
// default we will take the assertion section under root | ||
if (messageSignatureNode.length === 1) { | ||
const node = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc); | ||
if (node.length === 1) { | ||
assertionNode = node[0].toString(); | ||
} | ||
// remove message signature | ||
doc.removeChild(messageSignatureNode[0]); | ||
} | ||
if (assertionSignatureNode.length === 1) { | ||
assertionNode = assertionSignatureNode[0].parentNode.toString(); | ||
// remove assertion signature | ||
doc.removeChild(assertionSignatureNode[0]); | ||
} | ||
// guarantee to have a signature in saml response | ||
@@ -369,6 +354,7 @@ if (selection.length === 0) { | ||
} | ||
const sig = new SignedXml(); | ||
let verified = true; | ||
selection.forEach(s => { | ||
// need to refactor later on | ||
selection.forEach(signatureNode => { | ||
let selectedCert = ''; | ||
@@ -387,3 +373,3 @@ sig.signatureAlgorithm = opts.signatureAlgorithm; | ||
metadataCert = metadataCert.map(utility.normalizeCerString); | ||
let x509Certificate = select(".//*[local-name(.)='X509Certificate']", s)[0].firstChild.data; | ||
let x509Certificate = select(".//*[local-name(.)='X509Certificate']", signatureNode)[0].firstChild.data; | ||
x509Certificate = utility.normalizeCerString(x509Certificate); | ||
@@ -400,6 +386,56 @@ if (includes(metadataCert, x509Certificate)) { | ||
} | ||
sig.loadSignature(s); | ||
sig.loadSignature(signatureNode); | ||
doc.removeChild(signatureNode); | ||
verified = verified && sig.checkSignature(doc.toString()); | ||
// immediately throw error when any one of the signature is failed to get verified | ||
if (!verified) { | ||
throw new Error('ERR_FAILED_TO_VERIFY_SIGNATURE'); | ||
} | ||
}); | ||
// response must be signed, either entire document or assertion | ||
// default we will take the assertion section under root | ||
if (messageSignatureNode.length === 1) { | ||
const node = select("/*[contains(local-name(), 'Response') or contains(local-name(), 'Request')]/*[local-name(.)='Assertion']", doc); | ||
if (node.length === 1) { | ||
assertionNode = node[0].toString(); | ||
} | ||
} | ||
if (assertionSignatureNode.length === 1) { | ||
const verifiedAssertionInfo = extract(assertionSignatureNode[0].toString(), [{ | ||
key: 'refURI', | ||
localPath: ['Signature', 'SignedInfo', 'Reference'], | ||
attributes: ['URI'] | ||
}]); | ||
// get the assertion supposed to be the one should be verified | ||
const desiredAssertionInfo = extract(doc.toString(), [{ | ||
key: 'id', | ||
localPath: ['~Response', 'Assertion'], | ||
attributes: ['ID'] | ||
}]); | ||
// 5.4.2 References | ||
// SAML assertions and protocol messages MUST supply a value for the ID attribute on the root element of | ||
// the assertion or protocol message being signed. The assertion’s or protocol message's root element may | ||
// or may not be the root element of the actual XML document containing the signed assertion or protocol | ||
// message (e.g., it might be contained within a SOAP envelope). | ||
// Signatures MUST contain a single <ds:Reference> containing a same-document reference to the ID | ||
// attribute value of the root element of the assertion or protocol message being signed. For example, if the | ||
// ID attribute value is "foo", then the URI attribute in the <ds:Reference> element MUST be "#foo". | ||
if (verifiedAssertionInfo.refURI !== `#${desiredAssertionInfo.id}`) { | ||
throw new Error('ERR_POTENTIAL_WRAPPING_ATTACK'); | ||
} | ||
const verifiedDoc = extract(doc.toString(), [{ | ||
key: 'assertion', | ||
localPath: ['~Response', 'Assertion'], | ||
attributes: [], | ||
context: true | ||
}]); | ||
assertionNode = verifiedDoc.assertion.toString(); | ||
} | ||
return [verified, assertionNode]; | ||
@@ -406,0 +442,0 @@ }, |
@@ -6,7 +6,5 @@ /** | ||
*/ | ||
import * as fs from 'fs'; | ||
import { pki, util, asn1 } from 'node-forge'; | ||
import { inflate, deflate } from 'deflate-js'; | ||
import { isString } from 'lodash'; | ||
import { DOMParser } from 'xmldom'; | ||
@@ -13,0 +11,0 @@ const BASE64_STR = 'base64'; |
@@ -57,9 +57,2 @@ /// <reference types="node" /> | ||
verifyFields(field: string | string[], metaField: string): boolean; | ||
/** | ||
* @desc Verify time stamp | ||
* @param {date} notBefore | ||
* @param {date} notOnOrAfter | ||
* @return {boolean} | ||
*/ | ||
verifyTime(notBefore?: Date, notOnOrAfter?: Date): boolean; | ||
/** @desc Generates the logout request for developers to design their own method | ||
@@ -66,0 +59,0 @@ * @param {ServiceProvider} sp object of service provider |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
5875896
121
7017
+ Added@passify/xsd-schema-validator@0.7.1(transitive)
- Removedxsd-schema-validator@^0.6.0
- Removedxsd-schema-validator@0.6.0(transitive)