1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505: 506: 507: 508: 509: 510: 511: 512: 513: 514: 515: 516: 517: 518: 519: 520: 521: 522: 523: 524: 525: 526: 527: 528: 529: 530: 531: 532: 533: 534: 535: 536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546: 547: 548: 549: 550: 551: 552: 553: 554: 555: 556: 557: 558: 559: 560: 561: 562: 563: 564: 565: 566: 567: 568: 569: 570: 571: 572: 573: 574: 575: 576: 577: 578: 579: 580: 581: 582: 583: 584: 585: 586: 587: 588: 589: 590: 591: 592: 593: 594: 595: 596: 597: 598: 599: 600: 601: 602: 603: 604: 605: 606: 607: 608: 609: 610: 611: 612: 613: 614: 615: 616: 617: 618: 619: 620: 621: 622: 623: 624: 625: 626: 627: 628: 629: 630: 631: 632: 633: 634: 635: 636: 637: 638: 639: 640: 641: 642: 643: 644: 645: 646: 647: 648: 649: 650: 651: 652: 653: 654: 655: 656: 657: 658: 659: 660: 661: 662: 663: 664: 665: 666: 667: 668: 669: 670: 671: 672: 673: 674: 675: 676: 677: 678: 679: 680: 681: 682: 683: 684: 685: 686: 687: 688: 689: 690: 691: 692: 693: 694: 695: 696: 697: 698: 699: 700: 701: 702: 703: 704: 705: 706: 707: 708: 709: 710: 711: 712: 713: 714: 715: 716: 717: 718: 719: 720: 721: 722: 723: 724: 725: 726: 727: 728: 729: 730: 731: 732: 733: 734: 735: 736: 737: 738: 739: 740: 741: 742: 743: 744: 745: 746: 747: 748: 749: 750: 751: 752: 753: 754: 755: 756: 757: 758: 759: 760: 761: 762: 763: 764: 765: 766: 767: 768: 769: 770: 771: 772: 773: 774: 775: 776: 777: 778: 779: 780: 781: 782: 783: 784: 785: 786: 787: 788: 789: 790: 791: 792: 793: 794: 795: 796: 797: 798: 799: 800: 801: 802: 803: 804: 805: 806: 807: 808: 809: 810: 811: 812: 813: 814: 815: 816: 817: 818: 819: 820: 821: 822: 823: 824: 825: 826: 827: 828: 829: 830: 831: 832: 833: 834: 835: 836: 837: 838: 839: 840: 841: 842: 843: 844: 845: 846: 847: 848: 849: 850: 851: 852: 853: 854: 855: 856: 857: 858: 859: 860: 861: 862: 863: 864: 865: 866: 867: 868: 869: 870: 871: 872: 873: 874: 875: 876: 877: 878: 879: 880: 881: 882: 883: 884: 885: 886: 887: 888: 889: 890: 891: 892: 893: 894: 895: 896: 897: 898: 899: 900: 901: 902: 903: 904: 905: 906: 907: 908: 909: 910: 911: 912: 913: 914: 915: 916: 917: 918: 919: 920: 921: 922: 923: 924: 925: 926: 927: 928: 929: 930: 931: 932: 933: 934: 935: 936: 937: 938: 939: 940: 941: 942: 943: 944: 945: 946: 947: 948: 949: 950: 951: 952: 953: 954: 955: 956: 957: 958: 959: 960: 961: 962: 963: 964: 965: 966: 967: 968: 969: 970: 971: 972: 973: 974: 975: 976: 977: 978: 979: 980: 981: 982: 983: 984: 985: 986: 987: 988: 989: 990: 991: 992: 993: 994: 995: 996: 997: 998: 999: 1000: 1001: 1002: 1003: 1004: 1005: 1006: 1007: 1008: 1009: 1010: 1011: 1012: 1013: 1014: 1015: 1016: 1017: 1018: 1019: 1020: 1021: 1022: 1023: 1024: 1025: 1026: 1027: 1028: 1029: 1030: 1031: 1032: 1033: 1034: 1035: 1036: 1037: 1038: 1039: 1040: 1041: 1042: 1043: 1044: 1045: 1046: 1047: 1048: 1049: 1050: 1051: 1052: 1053: 1054: 1055: 1056: 1057: 1058: 1059: 1060: 1061: 1062: 1063: 1064: 1065: 1066: 1067: 1068: 1069: 1070: 1071: 1072: 1073: 1074: 1075: 1076: 1077: 1078: 1079: 1080: 1081: 1082: 1083: 1084: 1085: 1086: 1087: 1088: 1089: 1090: 1091: 1092: 1093: 1094: 1095: 1096: 1097: 1098: 1099: 1100: 1101: 1102: 1103: 1104: 1105: 1106: 1107: 1108: 1109: 1110: 1111: 1112: 1113: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 1121: 1122: 1123: 1124: 1125: 1126: 1127: 1128: 1129: 1130: 1131: 1132: 1133: 1134: 1135: 1136: 1137: 1138: 1139: 1140: 1141: 1142: 1143: 1144: 1145: 1146: 1147: 1148: 1149: 1150: 1151: 1152: 1153: 1154: 1155: 1156: 1157: 1158: 1159: 1160: 1161: 1162: 1163: 1164: 1165: 1166: 1167: 1168: 1169: 1170: 1171: 1172: 1173: 1174: 1175: 1176: 1177: 1178: 1179: 1180: 1181: 1182: 1183: 1184: 1185: 1186: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 1199: 1200: 1201: 1202: 1203: 1204: 1205: 1206: 1207: 1208: 1209: 1210: 1211: 1212: 1213: 1214: 1215: 1216: 1217: 1218: 1219: 1220: 1221: 1222: 1223: 1224: 1225: 1226: 1227: 1228: 1229: 1230: 1231: 1232: 1233: 1234: 1235: 1236: 1237: 1238: 1239: 1240: 1241: 1242: 1243: 1244: 1245: 1246: 1247: 1248: 1249: 1250: 1251: 1252: 1253: 1254: 1255: 1256: 1257: 1258: 1259: 1260: 1261: 1262: 1263: 1264: 1265: 1266: 1267: 1268: 1269: 1270: 1271: 1272: 1273: 1274: 1275: 1276: 1277: 1278: 1279: 1280: 1281: 1282: 1283: 1284: 1285: 1286: 1287: 1288: 1289: 1290: 1291: 1292: 1293: 1294: 1295: 1296: 1297: 1298: 1299: 1300: 1301: 1302: 1303: 1304: 1305: 1306: 1307: 1308: 1309: 1310: 1311: 1312: 1313: 1314: 1315: 1316: 1317: 1318: 1319: 1320: 1321: 1322: 1323: 1324: 1325: 1326: 1327: 1328: 1329: 1330: 1331: 1332: 1333: 1334: 1335: 1336: 1337: 1338: 1339: 1340: 1341: 1342: 1343: 1344: 1345: 1346: 1347: 1348: 1349: 1350: 1351: 1352: 1353: 1354: 1355: 1356: 1357: 1358: 1359: 1360: 1361: 1362: 1363: 1364: 1365: 1366: 1367: 1368: 1369: 1370: 1371: 1372: 1373: 1374: 1375: 1376: 1377: 1378: 1379: 1380: 1381: 1382: 1383: 1384: 1385: 1386: 1387: 1388: 1389: 1390: 1391: 1392: 1393: 1394: 1395: 1396: 1397: 1398: 1399: 1400: 1401: 1402: 1403: 1404: 1405: 1406: 1407: 1408: 1409: 1410: 1411: 1412: 1413: 1414: 1415: 1416: 1417: 1418: 1419: 1420: 1421: 1422: 1423: 1424: 1425: 1426: 1427: 1428: 1429: 1430: 1431: 1432: 1433: 1434: 1435: 1436: 1437: 1438: 1439: 1440: 1441: 1442: 1443: 1444: 1445: 1446: 1447: 1448: 1449: 1450: 1451: 1452: 1453: 1454: 1455: 1456: 1457: 1458: 1459: 1460: 1461: 1462: 1463: 1464: 1465: 1466: 1467: 1468: 1469: 1470: 1471: 1472: 1473: 1474: 1475: 1476: 1477: 1478: 1479: 1480: 1481: 1482: 1483: 1484: 1485: 1486: 1487: 1488: 1489: 1490: 1491: 1492: 1493: 1494: 1495: 1496: 1497: 1498: 1499: 1500: 1501: 1502: 1503: 1504: 1505: 1506: 1507: 1508: 1509: 1510: 1511: 1512: 1513: 1514: 1515: 1516: 1517: 1518: 1519: 1520: 1521: 1522: 1523: 1524: 1525: 1526: 1527: 1528: 1529: 1530: 1531: 1532: 1533: 1534: 1535: 1536: 1537: 1538: 1539: 1540: 1541: 1542: 1543: 1544: 1545: 1546: 1547: 1548: 1549: 1550: 1551: 1552: 1553: 1554: 1555: 1556: 1557: 1558: 1559: 1560: 1561: 1562: 1563: 1564: 1565: 1566: 1567: 1568: 1569: 1570: 1571: 1572: 1573: 1574: 1575: 1576: 1577: 1578: 1579: 1580: 1581: 1582: 1583: 1584: 1585: 1586: 1587: 1588: 1589: 1590: 1591: 1592: 1593: 1594: 1595: 1596: 1597: 1598: 1599: 1600: 1601: 1602: 1603: 1604: 1605: 1606: 1607: 1608: 1609: 1610: 1611: 1612: 1613: 1614: 1615: 1616: 1617: 1618: 1619: 1620: 1621: 1622: 1623: 1624: 1625: 1626: 1627: 1628: 1629: 1630: 1631: 1632: 1633: 1634: 1635: 1636: 1637: 1638: 1639: 1640: 1641: 1642: 1643: 1644: 1645: 1646: 1647: 1648: 1649: 1650: 1651: 1652: 1653: 1654: 1655: 1656: 1657: 1658: 1659: 1660: 1661: 1662: 1663: 1664: 1665: 1666: 1667: 1668: 1669: 1670: 1671: 1672: 1673: 1674: 1675: 1676: 1677: 1678: 1679: 1680: 1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688: 1689: 1690: 1691: 1692: 1693: 1694: 1695: 1696: 1697: 1698: 1699: 1700: 1701: 1702: 1703: 1704: 1705: 1706: 1707: 1708: 1709: 1710: 1711: 1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720: 1721: 1722: 1723: 1724: 1725: 1726: 1727: 1728: 1729: 1730: 1731: 1732: 1733: 1734: 1735: 1736: 1737: 1738: 1739: 1740: 1741: 1742: 1743: 1744: 1745: 1746: 1747: 1748: 1749: 1750: 1751: 1752: 1753: 1754: 1755: 1756: 1757: 1758: 1759: 1760: 1761: 1762: 1763: 1764: 1765: 1766: 1767: 1768: 1769: 1770: 1771: 1772: 1773: 1774: 1775: 1776: 1777: 1778: 1779: 1780: 1781: 1782: 1783: 1784: 1785: 1786: 1787: 1788: 1789: 1790: 1791: 1792: 1793: 1794: 1795: 1796: 1797: 1798: 1799: 1800: 1801: 1802: 1803: 1804: 1805: 1806: 1807: 1808: 1809: 1810: 1811: 1812: 1813: 1814: 1815: 1816: 1817: 1818: 1819: 1820: 1821: 1822: 1823: 1824: 1825: 1826: 1827: 1828: 1829: 1830: 1831: 1832: 1833: 1834: 1835: 1836: 1837: 1838: 1839: 1840: 1841: 1842: 1843: 1844: 1845: 1846: 1847: 1848: 1849: 1850: 1851: 1852: 1853: 1854: 1855: 1856: 1857: 1858: 1859: 1860: 1861: 1862: 1863: 1864: 1865: 1866: 1867: 1868: 1869: 1870: 1871: 1872: 1873: 1874: 1875: 1876: 1877: 1878: 1879: 1880: 1881: 1882: 1883: 1884: 1885: 1886: 1887: 1888: 1889: 1890: 1891: 1892: 1893: 1894: 1895: 1896: 1897: 1898: 1899: 1900: 1901: 1902: 1903: 1904: 1905: 1906: 1907: 1908: 1909: 1910: 1911: 1912: 1913: 1914: 1915: 1916: 1917: 1918: 1919: 1920: 1921: 1922: 1923: 1924: 1925: 1926: 1927: 1928: 1929: 1930: 1931: 1932: 1933: 1934: 1935: 1936: 1937: 1938: 1939: 1940: 1941: 1942: 1943: 1944: 1945: 1946: 1947: 1948: 1949: 1950: 1951: 1952: 1953: 1954: 1955: 1956: 1957: 1958: 1959: 1960: 1961: 1962: 1963: 1964: 1965: 1966: 1967: 1968: 1969: 1970: 1971: 1972: 1973: 1974: 1975: 1976: 1977: 1978: 1979: 1980: 1981: 1982: 1983: 1984: 1985: 1986: 1987: 1988: 1989: 1990: 1991: 1992: 1993: 1994: 1995: 1996: 1997: 1998: 1999: 2000: 2001: 2002: 2003: 2004: 2005: 2006: 2007: 2008: 2009: 2010: 2011: 2012: 2013: 2014: 2015: 2016: 2017: 2018: 2019: 2020: 2021: 2022: 2023: 2024: 2025: 2026: 2027: 2028: 2029: 2030: 2031: 2032: 2033: 2034: 2035: 2036: 2037: 2038: 2039: 2040: 2041: 2042: 2043: 2044: 2045: 2046: 2047: 2048: 2049: 2050: 2051: 2052: 2053: 2054: 2055: 2056: 2057: 2058: 2059: 2060: 2061: 2062: 2063: 2064: 2065: 2066: 2067: 2068: 2069: 2070: 2071: 2072: 2073: 2074: 2075: 2076: 2077: 2078: 2079: 2080: 2081: 2082: 2083: 2084: 2085: 2086: 2087: 2088: 2089: 2090: 2091: 2092: 2093: 2094: 2095: 2096: 2097: 2098: 2099: 2100: 2101: 2102: 2103: 2104: 2105: 2106: 2107: 2108: 2109: 2110: 2111: 2112: 2113: 2114: 2115: 2116: 2117: 2118: 2119: 2120: 2121: 2122: 2123: 2124: 2125: 2126: 2127: 2128: 2129: 2130: 2131: 2132: 2133: 2134: 2135: 2136: 2137: 2138: 2139: 2140: 2141: 2142: 2143: 2144: 2145: 2146: 2147: 2148: 2149: 2150: 2151: 2152: 2153: 2154: 2155: 2156: 2157: 2158: 2159: 2160: 2161: 2162: 2163: 2164: 2165: 2166: 2167: 2168: 2169: 2170: 2171: 2172: 2173: 2174: 2175: 2176: 2177: 2178: 2179: 2180: 2181: 2182: 2183: 2184: 2185: 2186: 2187: 2188: 2189: 2190: 2191: 2192: 2193: 2194: 2195: 2196: 2197: 2198: 2199: 2200: 2201: 2202: 2203: 2204: 2205: 2206: 2207: 2208: 2209: 2210: 2211: 2212: 2213: 2214: 2215: 2216: 2217: 2218: 2219: 2220: 2221: 2222: 2223: 2224: 2225: 2226: 2227: 2228: 2229: 2230: 2231: 2232: 2233: 2234: 2235: 2236: 2237: 2238: 2239: 2240: 2241: 2242: 2243: 2244: 2245: 2246: 2247: 2248: 2249: 2250: 2251: 2252: 2253: 2254: 2255: 2256: 2257: 2258: 2259: 2260: 2261: 2262: 2263: 2264: 2265: 2266: 2267: 2268: 2269: 2270: 2271: 2272: 2273: 2274: 2275: 2276: 2277: 2278: 2279: 2280: 2281: 2282: 2283: 2284: 2285: 2286: 2287: 2288: 2289: 2290: 2291: 2292: 2293: 2294: 2295: 2296: 2297: 2298: 2299: 2300: 2301: 2302: 2303: 2304: 2305: 2306: 2307: 2308: 2309: 2310: 2311: 2312: 2313: 2314: 2315: 2316: 2317: 2318: 2319: 2320: 2321: 2322: 2323: 2324: 2325: 2326: 2327: 2328: 2329: 2330: 2331: 2332: 2333: 2334: 2335: 2336: 2337: 2338: 2339: 2340: 2341: 2342: 2343: 2344: 2345: 2346: 2347: 2348: 2349: 2350: 2351: 2352: 2353: 2354: 2355: 2356: 2357: 2358: 2359: 2360: 2361: 2362: 2363: 2364: 2365: 2366: 2367: 2368: 2369: 2370: 2371: 2372: 2373: 2374: 2375: 2376: 2377: 2378: 2379: 2380: 2381: 2382: 2383: 2384: 2385: 2386: 2387: 2388: 2389: 2390: 2391: 2392: 2393: 2394: 2395: 2396: 2397: 2398: 2399: 2400: 2401: 2402: 2403: 2404: 2405: 2406: 2407: 2408: 2409: 2410: 2411: 2412: 2413: 2414: 2415: 2416: 2417: 2418: 2419: 2420: 2421: 2422: 2423: 2424: 2425: 2426: 2427: 2428: 2429: 2430: 2431: 2432: 2433: 2434: 2435: 2436: 2437: 2438: 2439: 2440: 2441: 2442: 2443: 2444: 2445: 2446: 2447: 2448: 2449: 2450: 2451: 2452: 2453: 2454: 2455: 2456: 2457: 2458: 2459: 2460: 2461: 2462: 2463: 2464: 2465: 2466: 2467: 2468: 2469: 2470: 2471: 2472: 2473: 2474: 2475: 2476: 2477: 2478: 2479: 2480: 2481: 2482: 2483: 2484: 2485: 2486: 2487: 2488: 2489: 2490: 2491: 2492: 2493: 2494: 2495: 2496: 2497: 2498: 2499: 2500: 2501: 2502: 2503: 2504: 2505: 2506: 2507: 2508: 2509: 2510: 2511: 2512: 2513: 2514: 2515: 2516: 2517: 2518: 2519: 2520: 2521: 2522: 2523: 2524: 2525: 2526: 2527: 2528: 2529: 2530: 2531: 2532: 2533: 2534: 2535: 2536: 2537: 2538: 2539: 2540: 2541: 2542: 2543: 2544: 2545: 2546: 2547: 2548: 2549: 2550: 2551: 2552: 2553: 2554: 2555: 2556: 2557: 2558: 2559: 2560: 2561: 2562: 2563: 2564: 2565: 2566: 2567: 2568: 2569: 2570: 2571: 2572: 2573: 2574: 2575: 2576: 2577: 2578: 2579: 2580: 2581: 2582: 2583: 2584: 2585: 2586: 2587: 2588: 2589: 2590: 2591: 2592: 2593: 2594: 2595: 2596: 2597: 2598: 2599: 2600: 2601: 2602: 2603: 2604: 2605: 2606: 2607: 2608: 2609: 2610: 2611: 2612: 2613: 2614: 2615: 2616: 2617: 2618: 2619:
<?php
/**
* XMLDSigToken.php
*
* Copyright © 2017 - Thierry Thiers <webcoder31@gmail.com>
*
* This software is governed by the CeCILL-C license under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at the following URL:
*
* http://www.cecill.info
*
* As a counterpart to the access to the source code and rights to copy, modify
* and redistribute granted by the license, users are provided only with a
* limited warranty and the software's author, the holder of the economic
* rights, and the successive licensors have only limited liability.
*
* In this respect, the user's attention is drawn to the risks associated with
* loading, using, modifying and/or developing or reproducing the software by
* the user in light of its specific status of free software, that may mean that
* it is complicated to manipulate, and that also therefore means that it is
* reserved for developers and experienced professionals having in-depth
* computer knowledge. Users are therefore encouraged to load and test the
* software's suitability as regards their requirements in conditions enabling
* the security of their systems and/or data to be ensured and, more generally,
* to use and operate it in the same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*
* @author Thierry Thiers <webcoder31@gmail.com>
* @copyright 2017 - Thierry Thiers <webcoder31@gmail.com>
* @license http://www.cecill.info CeCILL-C License
* @version 1.0.0
*/
// Namespace.
namespace webcoder31\ezxmldsig;
// Use.
use DateTime;
use DateTimeZone;
use DOMDocument;
use DOMElement;
use DOMNode;
use DOMXPath;
use SimpleXMLElement;
use Exception;
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecEnc;
use RobRichards\XMLSecLibs\XMLSecurityKey;
/**
* Build or analyze an XML token.
*
* An XML token is an enveloping XML Digital Signature (cf. RFC 3275)
* containing signed and timestamped user data. These data may also be
* encrypted. In this case we talk about Secure XML token.
*
* **This class uses the following default algorithms to operate:**
*
* - Algorithm used to canonalize data before signing is:
* http://www.w3.org/TR/2001/REC-xml-c14n-20010315.
*
* - Algorithm used to generate the signature is:
* http://www.w3.org/2000/09/xmldsig#rsa-sha1.
*
* - Algorithm used to compute the hash of token data is:
* http://www.w3.org/2000/09/xmldsig#sha1.
*
* - Algorithm used to encode data's values is: Base64.
*
* **An XML token looks like this:**
*
* <code>
* <?xml version="1.0"?>
* <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
* <ds:SignedInfo>
* <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
* <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
* <ds:Reference URI="#Token">
* <ds:Transforms>
* <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
* </ds:Transforms>
* <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
* <ds:DigestValue>---- TOKEN HASH ----</ds:DigestValue>
* </ds:Reference>
* </ds:SignedInfo>
* <ds:SignatureValue>---- SIGNATURE VALUE ----</ds:SignatureValue>
* <ds:KeyInfo>
* <ds:X509Data>
* <ds:X509Certificate>---- X.509 CERTIFICATE ----</ds:X509Certificate>
* </ds:X509Data>
* </ds:KeyInfo>
* <ds:Object Id="Token">
* <Token>
* <TokenTimestamp>2016-10-24T08:33:14Z</TokenTimestamp>
* <TokenData>
* <data1 Algorithm="base64">---- BASE64 ENCODED DATA ----</data1>
* <data2 Algorithm="base64">---- BASE64 ENCODED DATA ----</data2>
* <data3>
* <data31 Algorithm="base64">---- BASE64 ENCODED DATA ----</data31>
* <data32 Algorithm="base64">---- BASE64 ENCODED DATA ----</data32>
* </data3>
* <data4 Algorithm="base64">---- BASE64 ENCODED DATA ----</data4>
* </TokenData>
* </Token>
* </ds:Object>
* </ds:Signature>
* </code>
*
* **The signing process consists in the following actions:**
*
* - Load data from an associative array.
*
* - Encode data values using Base64.
*
* - Build a node from the encoded array of data (cf. node `<TokenData />`).
*
* - Compute an UTC timestamp and store it in a node `<TokenTimestamp />`.
*
* - Aggregate the nodes `<TokenTimestamp />` and `<TokenData />` inside a node
* `<Token />`.
*
* - Load the base DOM document representing an XML digital signature:
*
* <code>
* <ds:Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
* <ds:SignedInfo>
* <ds:SignatureMethod />
* </ds:SignedInfo>
* </ds:Signature>
* </code>
*
* - Create a node `<ds:Object Id="Token" />` and append it to the node
* `<ds:Signature />`.
*
* - Add the node `<Token />` to the node `<ds:Object Id="Token" />`.
*
* - NOTE : If data encryption is requested, this process will take place here
* (this will be described later).
*
* - Build a node `<ds:Reference URI="#Token" />` and append it to the node
* `<ds:SignedInfo />`. This consists in:
*
* - Canonicalizing the content of the node `<ds:Object Id="Token" />`.
*
* - Computing the SHA1 hash of the canonicalized node and storing it in the
* node `<ds:DigestValue />`.
*
* - Includes, in the the XML token, the X.509 certificate associated to the
* private key that will be used to perform the signing (see the next steps
* below). This cerficate will be used to perform the signature validation
* process, avoiding the need, for the receiver of the XML Digital Signature
* to own it on its side.
*
* - Canonicalize the content of the node `<ds:SignedInfo />`.
*
* - Compute the signature of the canonicalized node using the private key
* dedicated to perform signing.
*
* - Store the result in the node `<ds:SignatureValue />`.
*
* - Save the DOM document in XML format.
*
* **The signature validation process consist in the following actions:**
*
* - Load the XML Digital Singature in a DOM document.
*
* - Compute the SHA1 hash of the content of the node
* `<ds:Object Id="Token" />`.
*
* - Compare the computed hash with the one stored in the node
* `<ds:DigestValue />`. If the hash are different, this means that the
* content of the node `<ds:Object Id="Token" />` has been altered.
*
* - Canonicalize the content of the node `<ds:SignedInfo />`.
*
* - Compute the signature of the node `<ds:SignedInfo />` using the X.509
* certificate included in the XML token.
*
* - Compare the computed signature with the one stored in the node
* `<ds:SignatureValue>`. If the signatures are different, this means that
* content the node `<ds:SignedInfo />` has not been signed using the private
* key associated to the X.509 certificate included in the XML Digital
* Signature.
*
* - NOTE: If data decryption is required, this process will take place here
* (this will be described later).
*
* - Extract the timestamp stored in the content of the node
* `<ds:Object Id="Token" />` and memorise it.
*
* - Extract the data stored in the content of the node
* `<ds:Object Id="Token" />`.
*
* - Decode data values using Base64.
*
* - Buid an associative array containing the decoded data.
*
* **Token data encryption / decryption:**
*
* The token data may also be encrypted before signing. In this case the class
* will use the following default algorithms to perform encryption and
* decryption of data:
*
* - Algorithm used to encrypt / decrypt data is:
* http://www.w3.org/2001/04/xmlenc#aes128-cbc (symetric ciphering).
*
* - Algorithm used to encrypt / decrypt the session key is:
* http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p (asymetric ciphering).
*
* **A secure (crypted) XML token looks like this:**
*
* <code>
* <?xml version="1.0"?>
* <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
* <ds:SignedInfo>
* <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
* <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
* <ds:Reference URI="#Token">
* <ds:Transforms>
* <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
* </ds:Transforms>
* <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
* <ds:DigestValue>---- TOKEN HASH ----</ds:DigestValue>
* </ds:Reference>
* </ds:SignedInfo>
* <ds:SignatureValue>---- SIGNATURE VALUE ----</ds:SignatureValue>
* <ds:KeyInfo>
* <ds:X509Data>
* <ds:X509Certificate>---- X.509 CERTIFICATE ----</ds:X509Certificate>
* </ds:X509Data>
* </ds:KeyInfo>
* <ds:Object Id="Token">
* <xenc:EncryptedData xmlns:xenc="http://www.w3.org/2001/04/xmlenc#" Type="http://www.w3.org/2001/04/xmlenc#Content">
* <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes128-cbc"/>
* <ds:KeyInfo xmlns:dsig="http://www.w3.org/2000/09/xmldsig#">
* <xenc:EncryptedKey>
* <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p"/>
* <xenc:CipherData>
* <xenc:CipherValue>---- CIPHERED SESSION KEY ----</xenc:CipherValue>
* </xenc:CipherData>
* </xenc:EncryptedKey>
* </ds:KeyInfo>
* <xenc:CipherData>
* <xenc:CipherValue>---- ENCRYPTED DATA ----</xenc:CipherValue>
* </xenc:CipherData>
* </xenc:EncryptedData>
* </ds:Object>
* </ds:Signature>
* </code>
*
* **The encryption process is the following:**
*
* - Randomly generate a session key.
*
* - Cipher the session key using the X.509 certificate dedicated to the
* encryption process, in order to transmit it safely in the XML Digital
* Signature.
*
* - Store the ciphered session key in the node `<xenc:CipherValue />` which is
* located inside the node `<xenc:EncryptedKey />`.
*
* - Encrypt the content of the node `<ds:Object Id="Token" />` using the
* non-ciphered session key and store the result in the last node
* `<xenc:CipherValue />`.
*
* **The decryption process is the following:**
*
* - Retrieve the node `<xenc:EncryptedData />`.
*
* - Decipher the ciphered session key stored in the node `<xenc:CipherValue />`
* (the one which is located inside the node `<xenc:EncryptedKey />`) using
* the private key associated to the X.509 certificate that has been used to
* perform the encryption.
*
* - Decrypt the content of the last node `<xenc:CipherValue />` using the
* deciphered session key.
*/
class XMLDSigToken
{
/* CONSTANTS */
/** @const string The default XMLDSIG namespace prefix used in the XML digital
signature. */
const XMLDSIG_NS_PREFIX = 'ds';
/** @const string The name of the token node. */
const TOKEN_NAME = 'Token';
/** @const string The name of the token data node. */
const TOKEN_DATA_NAME = 'TokenData';
/** @const string The name of the token timestamp node. */
const TOKEN_TIMESTAMP_NAME = 'TokenTimestamp';
/** @const string The format of the token timestamp (YYYY-MM-DDThh:mm:ssZ).
NOTE: The timestamp should always be expressed in UTC. */
const TOKEN_TIMESTAMP_FORMAT = 'Y-m-d\TH:i:s\Z';
/** @const int The default time to live (in seconds) used to check token
peremption. */
const TOKEN_DEFAULT_TTL = 60;
/** @const int The synchronization offset (in seconds) allowed for verifying
if token is out of date. */
const DESYNC_TOLERANCE = 30;
const C14N = XMLSecurityDSig::C14N;
const C14N_COMMENTS = XMLSecurityDSig::C14N_COMMENTS;
const EXC_C14N = XMLSecurityDSig::EXC_C14N;
const EXC_C14N_COMMENTS = XMLSecurityDSig::EXC_C14N_COMMENTS;
const SHA1 = XMLSecurityDSig::SHA1;
const SHA256 = XMLSecurityDSig::SHA256;
const SHA384 = XMLSecurityDSig::SHA384;
const SHA512 = XMLSecurityDSig::SHA512;
const RIPEMD160 = XMLSecurityDSig::RIPEMD160;
const TRIPLEDES_CBC = XMLSecurityKey::TRIPLEDES_CBC;
const AES128_CBC = XMLSecurityKey::AES128_CBC;
const AES192_CBC = XMLSecurityKey::AES192_CBC;
const AES256_CBC = XMLSecurityKey::AES256_CBC;
const RSA_1_5 = XMLSecurityKey::RSA_1_5;
const RSA_OAEP_MGF1P = XMLSecurityKey::RSA_OAEP_MGF1P;
const DSA_SHA1 = XMLSecurityKey::DSA_SHA1;
const RSA_SHA1 = XMLSecurityKey::RSA_SHA1;
const RSA_SHA256 = XMLSecurityKey::RSA_SHA256;
const RSA_SHA384 = XMLSecurityKey::RSA_SHA384;
const RSA_SHA512 = XMLSecurityKey::RSA_SHA512;
const HMAC_SHA1 = XMLSecurityKey::HMAC_SHA1;
/* PROPERTIES */
/** @var string The XML namespace (xmlns) prefix to use in the XML digital
signature tags. */
private $xmldsigNsPrefix = self::XMLDSIG_NS_PREFIX;
/** @var string The algorithm that should be used to canonicalize the node
"ds:Signature/ds:SignedInfo" of the XML digital signature before signing
it. */
private $canonicalizationAlgorithm = self::C14N;
/** @var string The algorithm that should be used to sign the computed hash
of the token. */
private $signatureAlgorithm = self::RSA_SHA1;
/** @var string The algorithm that should be used to compute the hash of the
token. */
private $digestAlgorithm = self::SHA1;
/** @var string The algorithm that should be used to canonicalize the token
before computing its hash. NOTE: In our case, it is the same algorithm as
the one used to canonicalize the node "ds:Signature/ds:signedInfo"
(see : $canonicalizationAlgorithm property). */
private $transformAlgorithm = self::C14N;
/** @var boolean Whether to check the algorithms actually used (read from
the XML digital signature) to sign the token or not. */
private $checkSigningAlgorithms = true;
/** @var string The algorithm that should be used to generate the session
key which will be used to encrypt the token. */
private $sessionKeyCipheringAlgorithm = self::AES128_CBC;
/** @var string The algorithm that should be used to encrypt the token with
the generated session key. */
private $cryptAlgorithm = self::RSA_OAEP_MGF1P;
/** @var boolean Whether to check the algorithms actually used (read from
the XML digital signature) to encrypt the token or not. */
private $checkCryptingAlgorithms = true;
/** @var DOMXPath The DOMXPath object used to navigate through the XML
digital signature enveloping the token. */
private $xpath = null;
/** @var string The content of the private key used to sign the token
data. */
private $signKey = null;
/** @var string The password to access the private key used to sign the
token data. */
private $signKeyPassword = null;
/** @var string The content of the X.509 certificate associated to the
private key (cf. $signKey) used to sign the token. This certificate is
included in the XML token, in order to verify that the XML
Digital Signature is valid. */
private $signCert = null;
/** @var string The content of the private key used to decrypt the token. */
private $cryptKey = null;
/** @var string The password to access the private key used to decrypt the
token. */
private $cryptKeyPassword = null;
/** @var string The content of the X.509 certificate associated to the
private key used to encrypt the session key used to encrypt the token. */
private $cryptCert = null;
/** @var string The XML digital signature enveloping the token. */
private $xml = null;
/** @var array The token data (a flat or multidimensional associative
array). */
private $data = null;
/** @var boolean Flag that indicates whether the token data should be base64
encoded when building the XML token or not. */
private $base64EncodeData = true;
/** @var string The token timestamp (format : 'yyyy-MM-ddThh:mm:ssZ'). */
private $timestamp = null;
/** @var X509Cert The X.509 certificate included in the XML Digital
Signature. */
private $x509Certificate = null;
/** @var boolean Flag that indicates whether the token is encrypted or
not. */
private $isDataEncrypted = null;
/** @var boolean Flag that indicates whether the token hash is valid or
not. */
private $isDigestValueOk = null;
/** @var boolean Flag that indicates whether the token hash signature is
valid or not. */
private $isSignatureValueOk = null;
/** @var string Error encountered during the analysis of
the XML digital signature envoloping the token. */
private $error = null;
/** @var array List of the anomalies encountered during the analysis
of the XML digital signature envoloping the token. */
private $anomalies = array();
/**
* Defines a custom output for `print_r()` and `var_dump()` functions,
* in order to hide cryptographic material used by the object for
* security reasons.
*/
public function __debugInfo()
{
return [
'isValid' => $this->isSignatureValid(),
'isDigestValueOk' => $this->isDigestValueOk,
'isSignatureValueOk' => $this->isSignatureValueOk,
'isDataEncrypted' => $this->isDataEncrypted,
'timestamp' => $this->timestamp,
'data' => $this->data,
'error' => $this->error,
'anomalies' => $this->anomalies
];
}
/**
* Instantiate an XMLDSigToken object.
*
* This constructor is `protected`. To get an instance of an XMLDSigToken
* object use one of the following `static` functions according to what the
* object is intended for:
*
* - `createXMLToken()`
*
* - `createSecureXMLToken()`
*
* - `analyzeXMLToken()`
*
* - `analyzeSecureXMLToken()`
*
* An XMLDSigToken object allows creating an XML token and also analyzing an
* existing XML token. The type of the `$xmlOrData` parameter determine how
* the object behave.
*
* **XML token creation:**
*
* If `$xmlOrData` is an array (a flat or multidimensional associative
* array), the object will create an enveloping XML digital signature
* containing an XML token that holds the provided data and a timestamp
* generated automatically. The envoloped XML token will be encrypted if an
* X.509 certificate is provided for that. In this case, the `$signKeyPath`
* and `$signCertPath` parameters are required. If the `$cryptCertPath`
* parameter is also provided, the XML token will be encrypted. If the
* `$cryptCertPath` parameter is also provided, the $cryptKeyPath parameter
* must be provided too, in order to verify that the created XML Digital
* Signature is well formed. If the X.509 certificate used for encryption
* (cf. `$cryptCertPath` parameter) is protected by a password, this
* password may be passed using the `$cryptKeyPassword` parameter.
*
* **XML token analysis:**
*
* If `$xmlOrData` is a string, the object will treat it as an enveloping
* XML digital signature containing a token:
*
* - It will check that the signature is valid.
*
* - It will decrypt the token if this one is encrypted.
*
* - It will extract timestamp and data from the token.
*
* In this case, only the `$xmlOrData` parameters is required. If the XML
* token is encrypted, the `$cryptKeyPath` parameter must also be provided.
* If the private key used for encryption (cf. `$cryptKeyPath` parameter) is
* protected by a password this password may be passed using the
* `$cryptKeyPassword` parameter.
*
* **Configuring the object:**
*
* The `$options` parameter of this function allows to override the default
* configuration of the object passing it the desired options via an
* associative array.
*
* Example:
*
* <code>
* $options = [
* 'base64Encode' => false,
* 'xmldsigNsPrefix' => '',
* 'checkSigningAlgorithms' => false
* ];
* </code>
*
* Available options are the following:
*
* - **`base64Encode`** [boolean] Whether to base64 encode token data or
* not.
* <br/>Default value: **`TRUE`**.
*
* - **`xmldsigNsPrefix`** [string] The XML namespace (xmlns) prefix to use
* in the XML digital signature tags. Set it to empty string to avoid
* prefix usage.
* <br/>Default value: **`'ds'`**.
*
* - **`checkSigningAlgorithms`** [boolean] Whether to check that the
* algorithms used to sign the token are the expected ones or not.
* <br/>Default value: **`TRUE`**.
*
* - **`checkCryptingAlgorithms`** [boolean] Whether to check that the
* algorithms used to crypt the token are the expected ones or not.
* <br/>Default value: **`TRUE`**.
*
* - **`canonicalizationAlgorithm`** [string] The algorithm used to
* canonicalize the XMLdigital signature and to canonicalize token data
* before computing its hash.
* <br/>Possible values:
*
* - **`C14N`** (Default value)
* <br/>cf. http://www.w3.org/TR/2001/REC-xml-c14n-20010315
*
* - `C14N_COMMENTS`
* <br/>cf. http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments
*
* - `EXC_C14N`
* <br/>cf. http://www.w3.org/2001/10/xml-exc-c14n#
*
* - `EXC_C14N_COMMENTS`
* <br/>cf. http://www.w3.org/2001/10/xml-exc-c14n#WithComments
*
* - **`signatureAlgorithm`** [string] The asymmetric algorithm used to sign
* the hash of token data.
* <br/>Possible values:
*
* - `RSA_1_5`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#rsa-1_5
*
* - `RSA_OAEP_MGF1P`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p
*
* - `DSA_SHA1` (Does not work)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#dsa-sha1)
*
* - **`RSA_SHA1`** (Default value)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#rsa-sha1
*
* - `RSA_SHA256`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
*
* - `RSA_SHA384`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
*
* - `RSA_SHA512`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
*
* - `HMAC_SHA1` (Does not work)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#hmac-sha1
*
* - **`digestAlgorithm`** [string] The algorithm used to compute the hash
* of token data.
* <br/>Possible values:
*
* - **`SHA1`** (Default value)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#sha1
*
* - `SHA256`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#sha256
*
* - `SHA384`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#sha384
*
* - `SHA512`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#sha512
*
* - `RIPEMD160`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#ripemd160
*
* - **`sessionKeyCipheringAlgorithm`** [string] The symmetric algorithm
* used to cipher the session key which will be used to encrypt token
* date.
* <br/>Possible values:
*
* - `TRIPLEDES_CBC`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#tripledes-cbc
*
* - **`AES128_CBC`** (Default value)
* <br/>cf. http://www.w3.org/2001/04/xmlenc#aes128-cbc
*
* - `AES192_CBC`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#aes192-cbc
*
* - `AES256_CBC`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#aes256-cbc
*
* - **`cryptAlgorithm`** [string] The asymmetric algorithm used to encrypt
* token data with the ciphered session key.
* <br/>Possible values:
*
* - `RSA_1_5`
* <br/>cf. http://www.w3.org/2001/04/xmlenc#rsa-1_5
*
* - **`RSA_OAEP_MGF1P`** (Default value)
* <br/>cf. http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p
*
* - `DSA_SHA1` (Does not work)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#dsa-sha1
*
* - `RSA_SHA1` (Does not work)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#rsa-sha1
*
* - `RSA_SHA256`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
*
* - `RSA_SHA384`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha384
*
* - `RSA_SHA512`
* <br/>cf. http://www.w3.org/2001/04/xmldsig-more#rsa-sha512
*
* - `HMAC_SHA1` (Does not work)
* <br/>cf. http://www.w3.org/2000/09/xmldsig#hmac-sha1
*
* @param mixed $xmlOrData An XML token (string) or token data (associative
* array).
*
* @param string $signKeyPath The path to the PEM private key file that will
* be used to sign the token.
*
* @param string $signKeyPassword The password, if needed, to access the
* private key that will be used to sign the token. Use NULL if no
* password needed.
*
* @param string $signCertPath The path to the PEM X.509 certificate file
* that will be included in the XML digital signature, in order to
* verify the signature.
*
* @param string $cryptKeyPath The path to the PEM private key file that
* will be used to decrypt the session key which was used to encrypt the
* token.
*
* @param string $cryptKeyPassword The password to access the private key
* that will be used to decrypt the session key which was used to
* encrypt the token. Use NULL if no password needed.
*
* @param string $cryptCertPath The path to the PEM X.509 certificate file
* that will be used to encrypt the session key which will be used to
* encrypt the token.
*
* @param array $options Configuration options.
*
* @throws Exception
*
* @see createXMLToken(), createSecureXMLToken(), analyzeXMLToken(),
* analyzeSecureXMLToken()
*/
protected function __construct(
$xmlOrData,
$signKeyPath = null,
$signKeyPassword = null,
$signCertPath = null,
$cryptKeyPath = null,
$cryptKeyPassword = null,
$cryptCertPath = null,
$options = []
)
{
// Load configuration options.
$base64Encode = (isset($options['base64Encode']) && is_bool($options['base64Encode'])) ? $options['base64Encode'] : true;
$xmldsigNsPrefix = (isset($options['xmldsigNsPrefix']) && is_string($options['xmldsigNsPrefix'])) ? $options['xmldsigNsPrefix'] : null;
$checkSigningAlgorithms = (isset($options['checkSigningAlgorithms']) && is_bool($options['checkSigningAlgorithms'])) ? $options['checkSigningAlgorithms'] : true;
$checkCryptingAlgorithms = (isset($options['checkCryptingAlgorithms']) && is_bool($options['checkCryptingAlgorithms'])) ? $options['checkCryptingAlgorithms'] : true;
$canonicalizationAlgorithm = isset($options['canonicalizationAlgorithm']) ? $options['canonicalizationAlgorithm'] : null;
$signatureAlgorithm = isset($options['signatureAlgorithm']) ? $options['signatureAlgorithm'] : null;
$digestAlgorithm = isset($options['digestAlgorithm']) ? $options['digestAlgorithm'] : null;
$sessionKeyCipheringAlgorithm = isset($options['sessionKeyCipheringAlgorithm']) ? $options['sessionKeyCipheringAlgorithm'] : null;
$cryptAlgorithm = isset($options['cryptAlgorithm']) ? $options['cryptAlgorithm'] : null;
// If provided, set and store the canonicalization algorithm.
if (!is_null($canonicalizationAlgorithm))
{
if (self::_isValidCanonicalizationAlgorithm($canonicalizationAlgorithm))
{
$this->canonicalizationAlgorithm = $canonicalizationAlgorithm;
$this->transformAlgorithm = $canonicalizationAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'canonicalizationAlgorithm'! Unknwon canonicalization algorithm.");
}
}
// If provided, set and store the signature algorithm.
if (!is_null($signatureAlgorithm))
{
if (self::_isValidAsymmetricCipheringAlgorithm($signatureAlgorithm))
{
$this->signatureAlgorithm = $signatureAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'signatureAlgorithm'! Unknwon signature algorithm.");
}
}
// If provided, set and store the digest algorithm.
if (!is_null($digestAlgorithm))
{
if (self::_isValidHashAlgorithm($digestAlgorithm))
{
$this->digestAlgorithm = $digestAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'digestAlgorithm'! Unknwon digest algorithm.");
}
}
// If requested, disable check of signature algorithms.
if (false === $checkSigningAlgorithms)
{
$this->checkSigningAlgorithms = false;
}
// Check what have to be done according to the type of $xmlOrData
// parameter.
if (is_array($xmlOrData))
{
// $xmlOrData is an array, so we have to create an XML token.
// Check that $data is a non-empty array.
if (empty($xmlOrData))
{
throw new Exception("Invalid parameter 'data'! Token data should not be an empty array.");
}
// Check that $data is an associative array.
if (count(array_filter(array_keys($xmlOrData), 'is_string')) == 0)
{
throw new Exception("Invalid parameter 'data'! Token data should be an associative array.");
}
// If requested, disable base64 encoding of token data.
if (false === $base64Encode)
{
$this->base64EncodeData = false;
}
// Check that required private key and X.509 certificate
// are provided, in order to sign the token.
if (is_null($signKeyPath))
{
throw new Exception("Missing parameter 'signKeyPath'! A private key is required to sign token.");
}
// Check that required private key and X.509 certificate
// are provided, in order to sign the token.
if (is_null($signCertPath))
{
throw new Exception("Missing parameter 'signCertPath'! An X.509 certificate is required to sign token.");
}
// Get and store the private key that will be used to sign the token
// data.
$signKey = @file_get_contents($signKeyPath);
if (false === $signKey)
{
throw new Exception("Invalid parameter 'signKeyPath'! Cannot read private key to sign token.");
}
$this->signKey = $signKey;
// If provided, store the password to access the private key that
// will be used to sign the token data.
if (!is_null($signKeyPassword))
{
if (!is_string($signKeyPassword))
{
throw new Exception("Invalid parameter 'signKeyPassword'! Parameter must be a string.");
}
$this->signKeyPassword = $signKeyPassword;
}
// Get and store the X.509 certificate that will be included in the
// XML token.
$signCert = @file_get_contents($signCertPath);
if (false === $signCert)
{
throw new Exception("Invalid parameter 'signCertPath'! Cannot read X.509 certificate to sign token.");
}
$this->signCert = $signCert;
// If provided, store the prefix namespace to use in the XML digital
// signature tags.
if (!is_null($xmldsigNsPrefix))
{
if (!is_string($xmldsigNsPrefix))
{
throw new Exception("Invalid parameter 'xmldsigNsPrefix'! Parameter must be a string.");
}
$this->xmldsigNsPrefix = $xmldsigNsPrefix;
}
// If provided, get and store X.509 certificate that will be used
// to encrypt the session key which will be used to encrypt the
// token data.
if (!is_null($cryptCertPath))
{
$cryptCert = @file_get_contents($cryptCertPath);
if (false === $cryptCert)
{
throw new Exception("Invalid parameter 'cryptCertPath'! Cannot read X.509 certificate to encrypt token.");
}
$this->cryptCert = $cryptCert;
// Ensures the private key associated to the X.509 certificate
// that willbe used to perform encryption is also provided, in
// order to verify that the created XML token is well formed.
if (is_null($cryptKeyPath)) {
throw new Exception("Missing parameter 'cryptKeyPath'! A private key must be provided to decrypt token.");
}
// Get and store the private key that will be used to decrypt
// the session key which was used to encrypt the token data.
// NOTE : This key will be used to verify that the generated
// XML token is well formed.
$cryptKey = @file_get_contents($cryptKeyPath);
if (false === $cryptKey)
{
throw new Exception("Invalid parameter 'cryptKeyPath'! Cannot read private key to decrypt token.");
}
$this->cryptKey = $cryptKey;
// If provided, store the password to access the private key
// that will be used used to decrypt the session key.
if (!is_null($cryptKeyPassword))
{
if (!is_string($cryptKeyPassword))
{
throw new Exception("Invalid parameter 'cryptKeyPassword'! Parameter must be a string.");
}
$this->cryptKeyPassword = $cryptKeyPassword;
}
// If provided, set and store the session key algorithm.
if (!is_null($sessionKeyCipheringAlgorithm))
{
if (self::_isValidHashAlgorithm($sessionKeyCipheringAlgorithm))
{
$this->sessionKeyCipheringAlgorithm = $sessionKeyCipheringAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'sessionKeyCipheringAlgorithm'! Unknwon session key ciphering algorithm.");
}
}
// If provided, set and store the crypt algorithm.
if (!is_null($cryptAlgorithm))
{
if (self::_isValidAsymmetricCipheringAlgorithm($cryptAlgorithm))
{
$this->cryptAlgorithm = $cryptAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'cryptAlgorithm'! Unknwon crypt algorithm.");
}
}
// If requested, disable check of used crypt algorithms.
if (false === $checkCryptingAlgorithms)
{
$this->checkCryptingAlgorithms = false;
}
}
// Create an XML token with the provided data.
try
{
// NOTE: This function will call the _readXML() function which
// will verify that the created token is correct and which will
// terminate setting object preperties.
$this->_writeXML($xmlOrData);
}
catch (Exception $e)
{
// ERROR : XML token analysis failed on fatal error.
$this->error = $e->getMessage();
}
}
elseif (is_string($xmlOrData))
{
// $xmlOrData is a string, so we have to analyze the XML token.
// If provided, get and store the private key that will be used
// to decrypt the session key which was used to encrypt the token.
if (!is_null($cryptKeyPath))
{
$cryptKey = @file_get_contents($cryptKeyPath);
if (false === $cryptKey)
{
throw new Exception("Invalid parameter 'cryptKeyPath'! Cannot read private key to decrypt token.");
}
$this->cryptKey = $cryptKey;
// If provided, store the password to access the private key
// that will be used used to decrypt the session key.
if (!is_null($cryptKeyPassword))
{
if (!is_string($cryptKeyPassword))
{
throw new Exception("Invalid parameter 'cryptKeyPassword'! Parameter must be a string.");
}
$this->cryptKeyPassword = $cryptKeyPassword;
}
// If provided, set and store the session key algorithm.
if (!is_null($sessionKeyCipheringAlgorithm))
{
if (self::_isValidHashAlgorithm($sessionKeyCipheringAlgorithm))
{
$this->sessionKeyCipheringAlgorithm = $sessionKeyCipheringAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'sessionKeyCipheringAlgorithm'! Unknwon session key ciphering algorithm.");
}
}
// If provided, set and store the crypt algorithm.
if (!is_null($cryptAlgorithm))
{
if (self::_isValidAsymmetricCipheringAlgorithm($cryptAlgorithm))
{
$this->cryptAlgorithm = $cryptAlgorithm;
}
else
{
throw new Exception("Invalid parameter 'cryptAlgorithm'! Unknwon crypt algorithm.");
}
}
// If requested, disable check of used crypt algorithms.
if (false === $checkCryptingAlgorithms)
{
$this->checkCryptingAlgorithms = false;
}
}
// Parse the provided XML token to ensure all is correct and to
// terminate initializing object properties.
try
{
$this->_readXML($xmlOrData);
}
catch (Exception $e)
{
// ERROR : XML token analysis failed on fatal error.
$this->error = $e->getMessage();
}
}
else
{
throw new Exception("Invalid parameter 'xmlOrData'! Parameter must be either an array or a string!");
}
}
/**
* Creates an XML token for the given user data.
*
* The created XML token can be retrieved using the fucnction `getXML()`.
*
* @param array $data Token data (an associative array that may be
* multi-dimensional).
*
* @param string $signKeyPath The path to the PEM private key file that will
* be used to sign the token.
*
* @param string $signCertPath The path to the PEM X.509 certificate file
* that will be included in the XML digital signature, in order to
* verify the signature.
*
* @param string $signKeyPassword The password to access the private key
* that will be used to sign the token. Use NULL if no password needed.
*
* @param array $options Configuration options
* (see `__construct()` for details).
*
* @return XMLDSigToken An XMLDSigToken object or NULL if the object
* creation failed.
*
* @throws Exception
*
* @see __construct()
*/
public static function createXMLToken(
$data,
$signKeyPath,
$signCertPath,
$signKeyPassword = null,
$options = []
)
{
return new XMLDSigToken(
$data,
$signKeyPath,
$signKeyPassword,
$signCertPath,
$options
);
}
/**
* Creates a secure (crypted) XML token for the given user data.
*
* The created XML token can be retrieved using the function `getXML()`.
*
* @param array $data Token data (an associative array that may be
* multi-dimensional).
*
* @param string $signKeyPath The path to the PEM private key file that will
* be used to sign the token.
*
* @param string $signCertPath The path to the PEM X.509 certificate file
* that will be included in the XML digital signature, in order to
* verify the signature.
*
* @param string $cryptKeyPath The path to the PEM private key file that
* will be used to decrypt the session key which was used to encrypt the
* token.
*
* @param string $cryptCertPath The path to the PEM X.509 certificate file
* that will be used to encrypt the session key which will be used to
* encrypt the token.
*
* @param string $signKeyPassword The password to access the private key
* that will be used to sign the token. Use NULL if no password needed.
*
* @param string $cryptKeyPassword The password to access the private key
* that will be used to decrypt the session key which was used to
* encrypt the token. Use NULL if no password needed.
*
* @param array $options Configuration options
* (see `__construct()` for details).
*
* @return XMLDSigToken An XMLDSigToken object or NULL if the object
* creation failed.
*
* @throws Exception
*
* @see __construct()
*/
public static function createSecureXMLToken(
$data,
$signKeyPath,
$signCertPath,
$cryptKeyPath,
$cryptCertPath,
$signKeyPassword = null,
$cryptKeyPassword = null,
$options = []
)
{
return new XMLDSigToken(
$data,
$signKeyPath,
$signKeyPassword,
$signCertPath,
$cryptKeyPath,
$cryptKeyPassword,
$cryptCertPath,
$options
);
}
/**
* Parse an XML token.
*
* @param string $xml An XML token.
*
* @param array $options Configuration options
* (see `__construct()` for details).
*
* @return XMLDSigToken An XMLDSigToken object or NULL if the object
* creation failed.
*
* @throws Exception
*
* @see __construct()
*/
public static function analyzeXMLToken(
$xml,
$options = []
)
{
return new XMLDSigToken(
$xml,
$signKeyPath = null,
$signKeyPassword = null,
$signCertPath = null,
$cryptKeyPath = null,
$cryptKeyPassword = null,
$cryptCertPath = null,
$options
);
}
/**
* Parse an XML token whose data are crypted.
*
* NOTE: Uncrypted token can also be parsed using this function.
*
* @param string $xml An XML token.
*
* @param string $cryptKeyPath The path to the PEM private key file that
* will be used to decrypt the session key which was used to encrypt
* token data.
*
* @param string $cryptKeyPassword The password to access the private key
* that will be used to decrypt the session key which was used to
* encrypt the token. Use NULL if no password needed.
*
* @param array $options Configuration options
* (see `__construct()` for details).
*
* @return XMLDSigToken An XMLDSigToken object or NULL if the object
* creation failed.
*
* @throws Exception
*
* @see __construct()
*/
public static function analyzeSecureXMLToken(
$xml,
$cryptKeyPath,
$cryptKeyPassword = null,
$options = []
)
{
return new XMLDSigToken(
$xml,
$signKeyPath = null,
$signKeyPassword = null,
$signCertPath = null,
$cryptKeyPath,
$cryptKeyPassword,
$cryptCertPath = null,
$options
);
}
/**
* Write the XML Digital Signature representing the XML token for the
* provided user data.
*
* The XML token can then be obtained usind the `getXML()` function.
*
* @param array $data A flat or multidimensional associative array
* containing the token data.
*
* @throws Exception
*/
private function _writeXML($data)
{
// Whether to base64 encode data or not.
$base64Encode = $this->base64EncodeData;
// Create an XML document to hold the token data.
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
// Do not format the XML digital signature when it will be saved as an
// XML string.
$xmlDoc->formatOutput = false;
// Create the root node "Token" that holds the whole content of the
// token.
// NOTE: This node is explicitly created without namespace if no
// namespace is used for XML digital signature tags.
if ($this->xmldsigNsPrefix === '')
{
$tokenElement = $xmlDoc->createElementNS('', self::TOKEN_NAME);
}
else
{
$tokenElement = $xmlDoc->createElement(self::TOKEN_NAME);
}
$tokenNode = $xmlDoc->appendChild($tokenElement);
// Create a node "timestamp" that holds the token timestamp in the
// format "YYYY-MM-DDThh:mm:ssZ", using UTC timezone, and append it to
// the node "Token".
$dt = new DateTime();
$dt->setTimeZone(new DateTimeZone('UTC'));
$tokenTimestampElement = $xmlDoc->createElement('TokenTimestamp', $dt->format(self::TOKEN_TIMESTAMP_FORMAT));
$tokenTimestampNode = $tokenNode->appendChild($tokenTimestampElement);
/**
* Inline function to build a node tree, whose root node will be named
* $nodeName, from the array $data and appends it to the given
* $parentNode node. The value of the leaves are base64 encoded by this
* function.
*
* NOTE : This function is called recursively if $data is a
* multi-dimensional associative array.
*
* @param array $data A flat or multi-dimensional associative array that
* holds the data to convert in a node tree.
*
* @param string $nodeName The name of the node that will hold the node
* tree.
*
* @param DOMNode $parentNode An existing DOM node to which to append
* the node tree.
*/
$dataToNodeTree = function($data, $nodeName, $parentNode) use (&$dataToNodeTree, $base64Encode)
{
if(is_array($data))
{
// Appends node.
$element = $parentNode->ownerDocument->createElement($nodeName);
$dataNode = $parentNode->appendChild($element);
// Append child nodes/leaves recursively.
array_walk($data, $dataToNodeTree, $dataNode);
}
else
{
// Appends leaf.
$element = $parentNode->ownerDocument->createElement($nodeName, $base64Encode ? base64_encode($data) : $data);
if ($base64Encode)
{
$attribute = $parentNode->ownerDocument->createAttribute('Algorithm');
$attribute->value = 'base64';
$element->appendChild($attribute);
}
$dataNode = $parentNode->appendChild($element);
}
};
// Create a node "TokenData" that holds a node tree representing the
// token data encoded in base64 and append it to the node "Token".
$dataToNodeTree($data, self::TOKEN_DATA_NAME, $tokenNode);
// Create an XMLSecurityDSig object.
// This object will hold the XML Digital Signature we are building.
$XMLDSig = new XMLSecurityDSig($this->xmldsigNsPrefix);
$XMLDSig->setCanonicalMethod($this->canonicalizationAlgorithm);
// Add a node "ds:Object" to the XML Digital Signature and attach our
// node "Token" to it.
$objectNode = $XMLDSig->addObject($xmlDoc->documentElement);
$objectNode->setAttribute('Id', self::TOKEN_NAME);
// Encrypt our node "Token" if requested (if an X.509 certificate is
// provided to perform encryption).
if (!is_null($this->cryptCert))
{
// Randomly generate a session key using AES-128 algorithm.
// This key will be used to cipher our node "Token" (the content of
// the node "ds:Object").
$sessionKey = new XMLSecurityKey($this->sessionKeyCipheringAlgorithm);
$sessionKey->generateSessionKey();
// Create an asymetric key from the X.509 certificate using RSA-OAEP
// algorithm. This key will be used to encrypt the session key.
$asymKey = new XMLSecurityKey($this->cryptAlgorithm, array('type' => 'public'));
$asymKey->loadKey($this->cryptCert, false, true);
if (empty($asymKey->key))
{
// ERROR: Failed loading certificate to encrypt session key!
throw new Exception("Failed loading certificate to encrypt session key!");
}
// Encrypt the session key using the asymetric key.
$objXMLSecEnc = new XMLSecEnc();
$objXMLSecEnc->encryptKey($asymKey, $sessionKey);
// Encrypt the content (cf. XMLSecEnc::Content) of the node
// "ds:Object" that holds our node "Token" using the encrypted
// session key.
$objXMLSecEnc->setNode($objectNode);
$objXMLSecEnc->type = XMLSecEnc::Content;
$encryptedObjectNode = $objXMLSecEnc->encryptNode($sessionKey);
}
// Add a reference to the node "ds:Object" which is contained in the
// XML Digital Signature itself (case of an enveloping signature).
// This consists in :
// - Adding a node "ds:Reference" to the node "ds:SignedInfo".
// - Computing a hash, using the digest algorithm, from the
// canonicalized XML sting corresponding to the node "ds:Object".
// - Adding this hash in a child node named "ds:DigestValue".
//
// NOTE: Only the first child node of the node "ds:Object" is taken in
// account to generate the hash which will then be signed.
$XMLDSig->addReference($objectNode, $this->digestAlgorithm, NULL, array('overwrite' => false, 'force_uri' => true));
// Create the private key object that will be used to sign the token
// applying the signature algorithm. If a password is provided, use it
// to access the private key.
$privateKey = new XMLSecurityKey($this->signatureAlgorithm, array('type' => 'private'));
if (!is_null($this->signKeyPassword))
{
$privateKey->passphrase = $this->signKeyPassword;
}
$privateKey->loadKey($this->signKey, false, false);
if (empty($privateKey->key))
{
// ERROR: Failed loading private key to sign token data!
throw new Exception("Failed loading private key to sign token data!");
}
// Add the X.509 certificate associated to the private key to the XML
// Digital Signature. So, the recipient of the XML token will not need
// to own it, in order to verify the signature. It will simply get it
// from the XML token. This consists in adding a node "ds:KeyInfo" that
// holds the content of the X.509 certificate.
$XMLDSig->add509Cert($this->signCert);
// Sign the token data using the private security key associated to the
// X.509 certificate.
// This consists in :
// - Computing the signature of the node "ds:SignedInfo".
// - Adding a node named "ds:SignatureValue" that contains the
// computed signature.
$XMLDSig->sign($privateKey);
// Check that the created XML token is well formed (if it's valid and if
// timestamp and data can be retrived from it) and, by the way,
// terminate to initialize the properties of our XMLDSigToken object.
$this->_readXML($XMLDSig->sigNode->ownerDocument->saveXML());
}
/**
* Read the XML Digital Signature representing an XML token.
*
* This function will check that the provided XML Digital Signature is
* valid, will decrypt the token data it contains if this one is encrypted
* and then will extract timestamp and data.
*
* The data can then be obtained using the `getData()` function. Neverless,
* you have to ensure that you can trust it using functions provided for
* that (i.e. `isSignatureValid()`, `isOutOfDate()`, `isValidIssuer()`,
* `isValidCA()` and so on).
*
* NOTE: This function is called by the `_writeXML()` function to ensure the
* XML token it built is correct and because this function set most of the
* properties of the XMLDSigToken object that are issued from the analysis
* of the XML token.
*
* @param string $xml The XML Digital Signature representing an XML token.
*
* @throws Exception
*/
private function _readXML($xml)
{
// Store the provided XML Digital Signature.
$this->xml = $xml;
// Check that the provided string is a valid XML document.
$isValidXml = true;
$prevState = libxml_use_internal_errors(true); // Told PHP to not output warnings on errors.
try
{
new SimpleXMLElement($this->xml);
}
catch (Exception $e)
{
$isValidXml = false;
}
if (count(libxml_get_errors()) > 0)
{
// There has been XML errors.
$isValidXml = false;
}
libxml_clear_errors(); // Clean up libxml errors.
libxml_use_internal_errors($prevState); // Restore libxml to its previous state.
if (!$isValidXml)
{
throw new Exception("XML Digital Signature is not a valid XML document!");
}
// Prepair a DOM document where to load the XML Digital Signature.
$xmlDoc = new DOMDocument('1.0', 'UTF-8');
// Load the enveloping XML Digital Signature into the DOM document
// preserving its formatting.
$xmlDoc->preserveWhiteSpaces = true;
$xmlDoc->loadXML($xml);
// Instantiate and store a DOMXpath object to navigate through the DOM
// document that holds the XML Digital Signature.
$this->xpath = new DOMXPath($xmlDoc);
$this->xpath->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
$this->xpath->registerNamespace('xenc', XMLSecEnc::XMLENCNS);
// Verify that the XML Digital Signature is well formed.
$queries = array(
"//ds:Signature",
"//ds:Signature/ds:SignedInfo",
"//ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod",
"//ds:Signature/ds:SignedInfo/ds:SignatureMethod",
"//ds:Signature/ds:SignedInfo/ds:Reference",
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms",
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform",
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod",
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue",
"//ds:Signature/ds:SignatureValue",
"//ds:Signature/ds:KeyInfo",
"//ds:Signature/ds:KeyInfo/ds:X509Data",
"//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
"//ds:Signature/ds:Object"
);
foreach ($queries as $query)
{
if ($this->xpath->query($query)->length !== 1)
{
// ERROR: XML Digital Signature is malformed!
throw new Exception("XML digital signature is malformed!");
}
}
$queries = array(
// "//ds:Signature/@xmlns" => XMLSecurityDSig::XMLDSIGNS,
"//ds:Signature/ds:SignedInfo/ds:Reference/@URI" => '#' . self::TOKEN_NAME,
"//ds:Signature/ds:Object/@Id" => self::TOKEN_NAME
);
foreach ($queries as $query => $value)
{
if ($this->xpath->evaluate("string(" . $query . ")") !== $value)
{
// ERROR: XML digital signature is malformed!
throw new Exception("XML digital signature is malformed!");
}
}
// Verify the algorithms used to sign the token.
$queries = array(
"//ds:Signature/ds:SignedInfo/ds:CanonicalizationMethod/@Algorithm"
=> array(
'name' => 'Canonicalization',
'validateFunction' => '_isValidCanonicalizationAlgorithm',
'expectedValue' => $this->canonicalizationAlgorithm
),
"//ds:Signature/ds:SignedInfo/ds:SignatureMethod/@Algorithm"
=> array(
'name' => 'Signature',
'validateFunction' => '_isValidAsymmetricCipheringAlgorithm',
'expectedValue' => $this->signatureAlgorithm
),
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:Transforms/ds:Transform/@Algorithm"
=> array(
'name' => 'Transform',
'validateFunction' => '_isValidCanonicalizationAlgorithm',
'expectedValue' => $this->transformAlgorithm
),
"//ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestMethod/@Algorithm"
=> array(
'name' => 'Digest',
'validateFunction' => '_isValidHashAlgorithm',
'expectedValue' => $this->digestAlgorithm
)
);
foreach ($queries as $query => $algorithm)
{
$usedAlgorithm = $this->xpath->evaluate("string(" . $query . ")");
if (empty($usedAlgorithm))
{
// ERROR: Algorithm is missing!
throw new Exception($algorithm['name'] . " algorithm is missing!");
}
if (!call_user_func(self::class . '::' . $algorithm['validateFunction'], $usedAlgorithm))
{
// ERROR: Algorithm is invalid!
throw new Exception($algorithm['name'] . " algorithm is invalid!");
}
if ($usedAlgorithm !== $algorithm['expectedValue'] && $this->checkSigningAlgorithms)
{
// ANOMALY: Unauthorized algorithm!
$this->anomalies[] = "Unauthorized " . $algorithm['name'] . " algorithm! Expected: " . $algorithm['expectedValue'] . ", Used: " . $usedAlgorithm . ".";
}
}
// Check whether the token is encrypted or not.
if ($this->xpath->query("//ds:Signature/ds:Object/xenc:EncryptedData")->length === 1)
{
// Verify that the encrypted data is well formed.
$queries = array(
"//ds:Signature/ds:Object/xenc:EncryptedData/xenc:EncryptionMethod",
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo",
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey",
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:EncryptionMethod",
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData",
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue",
"//ds:Signature/ds:Object/xenc:EncryptedData/xenc:CipherData",
"//ds:Signature/ds:Object/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue"
);
foreach ($queries as $query)
{
if ($this->xpath->query($query)->length !== 1)
{
// ERROR: Encrypted data is malformed!
throw new Exception("Encrypted data is malformed!");
}
}
$queries = array(
"//ds:Signature/ds:Object/xenc:EncryptedData/@Type" => XMLSecEnc::Content
);
foreach ($queries as $query => $value)
{
if ($this->xpath->evaluate("string(" . $query . ")") !== $value)
{
// ERROR: Encrypted data is malformed!
throw new Exception("Encrypted data is malformed!");
}
}
// Verify the algorithms used to encrypt the token.
$queries = array(
"//ds:Signature/ds:Object/xenc:EncryptedData/xenc:EncryptionMethod/@Algorithm"
=> array(
'name' => 'Session Key Ciphering',
'validateFunction' => '_isValidSymmetricCipheringAlgorithm',
'expectedValue' => $this->sessionKeyCipheringAlgorithm
),
"//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:EncryptionMethod/@Algorithm"
=> array(
'name' => 'Crypt',
'validateFunction' => '_isValidAsymmetricCipheringAlgorithm',
'expectedValue' => $this->cryptAlgorithm
)
);
foreach ($queries as $query => $algorithm)
{
$usedAlgorithm = $this->xpath->evaluate("string(" . $query . ")");
if (empty($usedAlgorithm))
{
// ERROR: Algorithm is missing!
throw new Exception($algorithm['name'] . " algorithm is missing!");
}
if (!call_user_func(self::class . '::' . $algorithm['validateFunction'], $usedAlgorithm))
{
// ERROR: Algorithm is invalid!
throw new Exception($algorithm['name'] . " algorithm is invalid!");
}
if ($usedAlgorithm !== $algorithm['expectedValue'] && $this->checkCryptingAlgorithms)
{
// ANOMALY: Unauthorized algorithm!
$this->anomalies[] = "Unauthorized " . $algorithm['name'] . " algorithm! Expected: " . $algorithm['expectedValue'] . ", Used: " . $usedAlgorithm . ".";
}
}
}
// Create an XMLSecurityDSig object.
$XMLDSig = new XMLSecurityDSig();
// Locate the node "ds:Signature".
$nodeSignature = $XMLDSig->locateSignature($xmlDoc);
if (is_null($nodeSignature))
{
// ERROR: Cannot locate signature!
throw new Exception("Cannot locate signature!");
}
// Build an XMLSecurityKey object from the X.509 certificate which is
// included in the XML Digital Signature.
$objKeyX509 = $XMLDSig->locateKey();
if (!$objKeyX509)
{
// ERROR: Cannot locate X.509 certificate!
throw new Exception("Cannot locate X.509 certificate!");
}
// Configure the $objKeyX509 XMLSecurityKey object with its related
// informations which should be stored in the node
// "ds:Signature/ds:KeyInfo".
if (is_null(XMLSecEnc::staticLocateKeyInfo($objKeyX509, $nodeSignature)))
{
// ERROR: Cannot locate key information for X.509 certificate!
throw new Exception("Cannot locate key information for X.509 certificate!");
}
// Create a new X509Cert object for the X.509 certificate found in the
// XML Digital Signature.
$this->x509Certificate = new X509Cert($objKeyX509->getX509Certificate());
// Verify that the hash computed from the node "ds:Signature/ds:Object"
// is the same as the one stored in the node
// "ds:Signature/ds:SignedInfo/ds:Reference/ds:DigestValue".
try
{
$XMLDSig->validateReference();
$this->isDigestValueOk = true;
}
catch (Exception $e)
{
// ANOMALY: Token hash verification failed!
$this->isDigestValueOk = false;
$this->anomalies[] = "Token hash verification failed!";
}
// Canonicalize the node "ds:Signature/ds:SignedInfo".
$XMLDSig->canonicalizeSignedInfo();
// Verifies that the signature held in the node
// "ds:Signature/ds:SignatureValue" is correct for the node
// "ds:Signature/ds:SignedInfo" using the key we've just built from the
// X.509 certificate found in the XML Digital Signature.
if ($XMLDSig->verify($objKeyX509))
{
// Token signature is not valid!
$this->isSignatureValueOk = true;
}
else
{
// ANOMALY: Token hash signature verification failed!
$this->isSignatureValueOk = false;
$this->anomalies[] = "Token hash signature verification failed!";
}
// Create an XMLSecEnc object.
$objXMLSecEnc = new XMLSecEnc();
// Check if the content of the node "ds:Object" is ciphered.
$encryptedDataNode = $objXMLSecEnc->locateEncryptedData($xmlDoc);
if (is_null($encryptedDataNode))
{
// The node "ds:Object" is not ciphered.
// Set encryption flag to FALSE.
$this->isDataEncrypted = false;
}
else
{
// The node "ds:Object" is ciphered. We will decrypt it now.
// Set encryption flag to TRUE.
$this->isDataEncrypted = true;
// Check that we have a private key to perform decryption.
if (is_null($this->cryptKey))
{
// ERROR: No private key provided for decryption.
throw new Exception("Token is encrypted but no private key is provided for decryption!");
}
// Passes the node containing the ciphered token to the XMLSecEnc
// object.
$objXMLSecEnc->setNode($encryptedDataNode);
// Indicates to the XMLSecEnc object what is encrypted (the node
// element or only its content) by reading the attribute "Type" of
// the node containing the ciphered data (the child node of the
// node "ds:Object").
$objXMLSecEnc->type = $encryptedDataNode->getAttribute("Type");
// Get an XMLSecurityKey object already configured with the
// algorithm which was used to encrypt the session key.
$objKeyCrypt = $objXMLSecEnc->locateKey();
if (is_null($objKeyCrypt))
{
// ERROR: The encrypted session key has been found but not the
// algorithm.
throw new Exception("Cannot locate session key ciphering algorithm!");
}
// Retrieve encrypted session key and decipher it using the private
// key. We will use it later to decrypt the token data.
$objKeyInfoCrypt = $objXMLSecEnc->locateKeyInfo($objKeyCrypt);
if (is_null($objKeyInfoCrypt))
{
// ERROR: Session key cannot be found.
throw new Exception("Cannot locate session key!");
}
else
{
if (!$objKeyInfoCrypt->isEncrypted)
{
// ERROR: Session key found but it is not encrypted.
throw new Exception("Session key is not encrypted!");
}
else
{
// Loads the private key to be be used for deciphering the
// crypted session key. If a password is provided, use it to
// access the private key.
if (!is_null($this->cryptKeyPassword))
{
$objKeyInfoCrypt->passphrase = $this->cryptKeyPassword;
}
$objKeyInfoCrypt->loadKey($this->cryptKey, false);
if (empty($objKeyInfoCrypt->key))
{
// ERROR: Failed loading private key to decrypt session
// key!
throw new Exception("Failed loading private key to decrypt session key!");
}
// Prepare the decryption key object that will be used to
// decipher the token data by passing the deciphered session
// key to it.
$objXMLSecCrypt = $objKeyInfoCrypt->encryptedCtx;
try
{
$decrypedtKey = $objXMLSecCrypt->decryptKey($objKeyInfoCrypt);
}
catch (Exception $e)
{
// ERROR: Session key decryption failed.
throw new Exception("Session key decryption failed! Reason: " . $e->getMessage() . ".");
}
$objKeyCrypt->loadKey($decrypedtKey);
if (empty($objKeyCrypt->key))
{
// ERROR: Failed loading decrypted session key to
// decrypt token data!
throw new Exception("Failed loading decrypted session key to decrypt token data!");
}
}
}
// Decrypt the ciphered token data using the decryption key we've
// just prepared.
try
{
$decryptedDataNode = $objXMLSecEnc->decryptNode($objKeyCrypt, true);
}
catch (Exception $e)
{
// ERROR: Token data decryption failed.
throw new Exception("Token data decryption failed! Reason: " . $e->getMessage() . ".");
}
}
// Verify that the token data is well formed.
$queries = array(
"//ds:Signature/ds:Object/" . self::TOKEN_NAME,
"//ds:Signature/ds:Object/" . self::TOKEN_NAME . "/" . self::TOKEN_TIMESTAMP_NAME,
"//ds:Signature/ds:Object/" . self::TOKEN_NAME . "/" . self::TOKEN_DATA_NAME,
);
foreach ($queries as $query)
{
if ($this->xpath->query($query)->length !== 1)
{
// ERROR: Malformed token!
throw new Exception("Malformed token!");
}
}
// Get the node "ds:Signature/ds:Object/Token/TokenTimestamp".
$query = "//ds:Signature/ds:Object/" . self::TOKEN_NAME . "/" . self::TOKEN_TIMESTAMP_NAME;
$nodeTokenTimestamp = $this->xpath->query($query)->item(0);
// Check that token timestamp node is a DOM element that contains solely
// a text node.
if (XML_ELEMENT_NODE === $nodeTokenTimestamp->nodeType
&& $nodeTokenTimestamp->hasChildNodes()
&& 1 === $nodeTokenTimestamp->childNodes->length
&& XML_TEXT_NODE === $nodeTokenTimestamp->childNodes->item(0)->nodeType)
{
// Compute the UTC UNIX timestamp from the token timestamp string.
$timezone = new DateTimeZone('UTC');
$UTCDate = DateTime::createFromFormat(self::TOKEN_TIMESTAMP_FORMAT, $nodeTokenTimestamp->nodeValue, $timezone);
if (false !== $UTCDate)
{
// Store token timestamp.
$this->timestamp = $nodeTokenTimestamp->nodeValue;
}
else
{
// ANOMALY: Token timestamp format is incorrect!
$this->anomalies[] = "Token timestamp format is invalid!";
}
}
else
{
// ANOMALY: Malformed token timestamp.
$this->anomalies[] = "Token timestamp is malformed!";
}
// Get the node "ds:Signature/ds:Object/Token/TokenData".
$query = "//ds:Signature/ds:Object/" . self::TOKEN_NAME . "/" . self::TOKEN_DATA_NAME;
$nodeTokenData = $this->xpath->query($query)->item(0);
/**
* Inline function to build a flat or multi-dimensional associative
* array from a node tree whose leaves values are base64 encoded. The
* values of the leaves are base64 decoded by this function.
*
* NOTE : This function is called recursively if the node tree is more
* than one level deep.
*
* @param string $rootNode The node that holds the node tree.
*
* @param array $data The array to fill up with decoded data.
*/
$nodeTreeToData = function($rootNode, &$data) use (&$nodeTreeToData)
{
if (!$rootNode->hasChildNodes())
{
// ANOMALY: Empty token data.
$this->anomalies[] = "Token data is empty!";
}
else
{
foreach ($rootNode->childNodes as $node)
{
if ((XML_TEXT_NODE === $node->nodeType && 1 !== $node->parentNode->childNodes->length))
{
// ANOMALY: Malformed token data.
$this->anomalies[] = "Token data is malformed!";
}
if (XML_ELEMENT_NODE === $node->nodeType && $node->hasChildNodes())
{
if (1 === $node->childNodes->length && XML_TEXT_NODE === $node->childNodes->item(0)->nodeType)
{
$decode = ("base64" === $node->getAttribute('Algorithm')) ? true : false;
$data[$node->nodeName] = $decode ? base64_decode($node->childNodes->item(0)->nodeValue) : $node->childNodes->item(0)->nodeValue;
}
else
{
$data[$node->nodeName] = array();
// Recursive call.
$nodeTreeToData($node, $data[$node->nodeName]);
}
}
}
}
};
// Check that token data if well formed and extract data.
$tokenData = array();
$nodeTreeToData($nodeTokenData, $tokenData);
// Store token data.
$this->data = $tokenData;
// If any anomaly has been detected during analysis, set an error
// message.
if (!empty($this->anomalies))
{
$nb = count($this->anomalies);
throw new Exception($nb . ($nb === 1 ? " anomaly" : " anomalies") . " detected: " . implode(" ; ", $this->anomalies));
}
}
/**
* Indicates whether XML token signature is valid or not.
*
* This means that:
*
* - No error occured during the analysis of the XML digital signature.
*
* - No anomaly detected during the analysis of the XML digital signature.
*
* - The token hash has been verified and is valid.
*
* - The token hash signature has been verified and is valid.
*
* NOTE: This function only guarantee the integrity of the token regarding
* the X.509 certificate included in the XML token. It does not verify
* whether this certificate is valid or not.
*
* @return boolean TRUE if XML token is valid, FALSE otherwise.
*/
public function isSignatureValid()
{
return true === $this->isDigestValueOk && true === $this->isSignatureValueOk && is_null($this->error);
}
/**
* Indicates whether the XML token is out of date or not.
*
* NOTE: If token timestamp is over the current date/time, we assume token
* is peremted too.
*
* @param int $ttl The time to live (in seconds) allowed for the token.
*
* @return boolean TRUE if token is out of date, FALSE otherwise.
*/
public function isOutOfDate($ttl = self::TOKEN_DEFAULT_TTL)
{
if (is_null($this->timestamp))
{
// Timestamp is missing! So we assume token is peremted.
return true;
}
// We will compare date using UTC timezone.
$timezone = new DateTimeZone('UTC');
// Compute the UTC UNIX timestamp from the token timestamp string.
$tokenUTCDate = DateTime::createFromFormat(self::TOKEN_TIMESTAMP_FORMAT, $this->timestamp, $timezone);
if (false === $tokenUTCDate)
{
// Token timestamp format is incorrect! So we assume token is
// peremted.
return true;
}
$tokenUTCTimestamp = $tokenUTCDate->getTimestamp();
// Compute the UTC UNIX timestamp for the current date/time.
$curUTCDate = new DateTime('now', $timezone);
$curUTCTimestamp = $curUTCDate->getTimestamp();
// Return timestamps comparison result.
return $tokenUTCTimestamp > ($curUTCTimestamp + self::DESYNC_TOLERANCE) || ($tokenUTCTimestamp + $ttl) < $curUTCTimestamp;
}
/**
* Indicates whether token data is encrypted or not.
*
* NOTE: If not evaluated (because an error occured before), this function
* return NULL.
*
* @return boolean TRUE if token data is encrypted, FALSE otherwise.
*/
public function isDataEncrypted()
{
return $this->isDataEncrypted;
}
/**
* Indicates whether the digest of token data (a hash) has been verified and
* is valid or not.
*
* NOTE: If not evaluated (because an error occured before), this function
* return NULL.
*
* @return boolean|null TRUE if token hash has been verified and is valid,
* FALSE otherwise, NULL if cannot be evaluated.
*/
public function isDigestValueOk()
{
return $this->isDigestValueOk;
}
/**
* Indicates whether signature of the digest of the token data has been
* verified and is valid or not.
*
* NOTE: If not evaluated (because an error occured before), this function
* return NULL.
*
* @return boolean|null TRUE if token hash signature has been verified and
* is valid, FALSE otherwise, NULL if cannot be evaluated.
*/
public function isSignatureValueOk()
{
return $this->isSignatureValueOk;
}
/**
* Get the XML token.
*
* @return string|null The XML token, NULL if not available.
*/
public function getXML()
{
return $this->xml;
}
/**
* Get the user data contained in the XML token.
*
* @return array|null The associative array containing token data, NULL if
* not available.
*/
public function getData()
{
return $this->data;
}
/**
* Get the timestamp of the XML token.
*
* @return string|null The token timestamp, NULL if not available.
*/
public function getTimestamp()
{
return $this->timestamp;
}
/**
* Get the message of the error that occured while parsing the XML token.
*
* @return string|null The error message. If no error, NULL is returned.
*/
public function getError()
{
return $this->error;
}
/**
* Get the list of anomalies that occured while parsing the XML token.
*
* @return array|null The list of anomalies if any, NULL otherwise.
*/
public function getAnomalies()
{
return $this->anomalies;
}
/**
* Get the X.509 certificate that is included in the XML token (PEM format).
*
* @return string|null The content of the X.509 certificate, NULL if not
* available.
*/
public function getCertificate()
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getPem();
}
/**
* Get the issuer information of the X.509 certificate that is included in
* the XML token.
*
* @return array|null The issuer information of the X.509 certificate, NULL
* if not available.
*/
public function getCertIssuer()
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getIssuer();
}
/**
* Get the subject information of the X.509 certificate that is included in
* the XML token.
*
* @return array|null The subject information of the X.509 certificate, NULL
* if not available.
*/
public function getCertSubject()
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getSubject();
}
/**
* Get the Distinguished Name of the X.509 certificate that is included in
* the XML token.
*
* @return string|null The Distinguished Name of the X.509 certificate, NULL
* if not available.
*/
public function getCertDN()
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getDN();
}
/**
* Get the UTC date from which the X.509 certificate that is included in the
* XML token is valid.
*
* @param string The format of the returned date (Default:
* XMLDSigToken::TOKEN_TIMESTAMP_FORMAT).
*
* @return string|null|false The date from which the X.509 certificate is
* valid, NULL if not available, FALSE if $dateFormat is invalid.
*/
public function getCertValidFrom($dateFormat = self::TOKEN_TIMESTAMP_FORMAT)
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getValidFrom($dateFormat);
}
/**
* Get the UTC date to which the X.509 certificate that is included in the
* XML token is valid.
*
* @param string The format of the returned date (Default:
* XMLDSigToken::TOKEN_TIMESTAMP_FORMAT).
*
* @return string|null|false The date to which the X.509 certificate, NULL
* if not available, FALSE if $dateFormat is invalid.
*/
public function getCertValidTo($dateFormat = self::TOKEN_TIMESTAMP_FORMAT)
{
if (is_null($this->x509Certificate))
{
return null;
}
return $this->x509Certificate->getValidTo($dateFormat);
}
/**
* Indicates whether the X.509 certificate included in the XML token is out
* of date or not.
*
* @return boolean TRUE if X.509 certificate is out of date, FALSE
* otherwise.
*/
public function isCertOutOfDate()
{
if (is_null($this->x509Certificate))
{
return true;
}
return $this->x509Certificate->isOutOfDate();
}
/**
* Check that the issuer information of the X.509 certificate that is
* included in the XML token matches the expected one.
*
* The expected issuer information must me passed as an array to the
* function:
*
* <code>
* $ExpectedIssuerInfo = [
* 'C' => 'DK',
* 'ST' => 'Jylland',
* 'O' => 'Lothbrok Ltd',
* 'OU' => 'Jarl Dept',
* 'CN' => 'www.Lothbrok.dk',
* 'emailAddress' => 'ragnar@Lothbrok.dk'
* ];
* </code>
*
* NOTE: Character case of data is significant.
*
* @param array $expectedIssuerInfo The issuer information that the X.509
* certificate should match.
*
* @return boolean TRUE if the X.509 certificate has been signed with the
* provided CA certificate, FALSE otherwise.
*
* @throws Exception
*/
public function isValidCertIssuer($expectedIssuerInfo)
{
// Check if the expected issuer information is an array.
if (!is_array($expectedIssuerInfo))
{
throw new Exception("Invalid parameter 'issuerInfo'! Issuer information should be an array.");
}
// Verify that the information of the X.509 certificate matches the
// expected one.
$certIssuerInfo = $this->getCertIssuer();
if (is_null($certIssuerInfo))
{
return false;
}
foreach ($expectedIssuerInfo as $key => $value)
{
if (!isset($certIssuerInfo[$key]) || $certIssuerInfo[$key] !== $value)
{
// Issuer information of the X.509 certificate does not match
// the expected one.
return false;
}
}
// Issuer is the expected one.
return true;
}
/**
* Check that the X.509 certificate included in the XML token comes from the
* expected CA.
*
* Note that more than one CA certificate can give a positive result, some
* certificates re-issue signing certificates after having only changed the
* expiration dates.
*
* Note that it also works with self-signed certificates. In this case,
* passes the X.509 certificate that is supposed been included in the XML
* token to the function.
*
* @param string $caCertPath The PEM certificate (public key) that is
* supposed been used by CA to sign the X.509 certificate included in
* the XML token.
*
* @return boolean TRUE if the X.509 certificate has been signed by the
* expected CA, FALSE otherwise.
*
* @throws Exception
*/
public function isValidCertCA($caCertPath)
{
// Get the CA public key that is supposed be used to sign the X.509
// certificate.
$caCert = @file_get_contents($caCertPath);
if (false === $caCert)
{
throw new Exception("Cannot read CA certificate to perform signer check! File: " . $caCertPath);
}
return $this->x509Certificate->isValidCA($caCert);
}
/**
* Check if an algorithm is valid to perform XML canonicalization
* operations.
*
* @param string $algorithm The algorithm reference (URL).
*
* @return boolean TRUE if valid, FALSE otherwise.
*/
static private function _isValidCanonicalizationAlgorithm($algorithm)
{
switch ($algorithm)
{
case (self::C14N):
case (self::C14N_COMMENTS):
case (self::EXC_C14N):
case (self::EXC_C14N_COMMENTS):
return(true);
default:
return(false);
}
}
/**
* Check if an algorithm is valid to perform hash operations.
*
* @param string $algorithm The algorithm reference (URL).
*
* @return boolean TRUE if valid, FALSE otherwise.
*/
static private function _isValidHashAlgorithm($algorithm)
{
switch ($algorithm)
{
case (self::SHA1):
case (self::SHA256):
case (self::SHA384):
case (self::SHA512):
case (self::RIPEMD160):
return(true);
default:
return(false);
}
}
/**
* Check if an algorithm is valid to perform asymmetric ciphering
* operations.
*
* @param string $algorithm The algorithm reference (URL).
*
* @return boolean TRUE if valid, FALSE otherwise.
*/
static private function _isValidAsymmetricCipheringAlgorithm($algorithm)
{
switch ($algorithm)
{
case (self::RSA_1_5):
case (self::RSA_OAEP_MGF1P):
// case (self::DSA_SHA1): // Does not work.
case (self::RSA_SHA1): // Does not work as signature algorithm.
case (self::RSA_SHA256):
case (self::RSA_SHA384):
case (self::RSA_SHA512):
// case (self::HMAC_SHA1): // Does not work.
return(true);
default:
return(false);
}
}
/**
* Check if an algorithm is valid to perform symmetric ciphering operations.
*
* @param string $algorithm The algorithm reference (URL).
*
* @return boolean TRUE if valid, FALSE otherwise.
*/
static private function _isValidSymmetricCipheringAlgorithm($algorithm)
{
switch ($algorithm)
{
case (self::TRIPLEDES_CBC):
case (self::AES128_CBC):
case (self::AES192_CBC):
case (self::AES256_CBC):
return(true);
default:
return(false);
}
}
/**
* Utility function for modifying a node of an XML content.
*
* @param string $xml XML content, passed by reference.
*
* @param string $nodePath The xpath query to select the node to modify.
* This should be a full path that identifies a single node.
*
* @param string $nodeAttribute In case you want to modify an attribute of
* the specified node, provide its name here, otherwise set this
* parameter to NULL.
*
* @param string $newValue The value that will replace original the value of
* the node or of the attribute of the node if $nodeAttribute parameter
* is provided.
*
* @param boolean $delete If TRUE delete the specified node or attribute.
*
* @return string|false The value of the node or attribute before
* modification or deletion, FALSE otherwise.
*/
static public function alterXML(&$xml, $nodePath, $nodeAttribute, $newValue, $delete = false)
{
// Assume modification will fail.
$oldValue = false;
// Load the XML content into a DOM document.
$xmlDoc = new DOMDocument();
$xmlDoc->formatOutput = false;
$xmlDoc->loadXML($xml);
// Instantiate a DOMXpath object to navigate in the XML document.
$xpath = new DOMXPath($xmlDoc);
$xpath->registerNamespace('ds', XMLSecurityDSig::XMLDSIGNS);
$xpath->registerNamespace('xenc', XMLSecEnc::XMLENCNS);
// Locate the node.
$nodeList = $xpath->query($nodePath);
if (1 <= $nodeList->length)
{
// Node found.
$node = $nodeList->item(0);
if (is_null($nodeAttribute))
{
if ($delete)
{
// Delete the node.
$oldValue = $node->nodeValue;
$node->parentNode->removeChild($node);
}
else
{
// Replace node value with the given one.
$oldValue = $node->nodeValue;
$node->nodeValue = $newValue;
}
}
else
{
if ($node->hasAttribute($nodeAttribute))
{
if ($delete)
{
// Delete attribute.
$oldValue = $node->getAttribute($nodeAttribute);
$node->removeAttribute($nodeAttribute);
}
else
{
// Replace attribute value with the given one.
$oldValue = $node->getAttribute($nodeAttribute);
$node->removeAttribute($nodeAttribute);
$node->setAttribute($nodeAttribute, $newValue);
}
}
}
}
// Update the XML content with its modified version.
if ($oldValue)
{
$xml = $xmlDoc->saveXML();
}
// Return the old value of the modified or deleted element (a node or
// an attribute) or false on failure.
return $oldValue;
}
/**
* Return the XML token in a pretty readable format.
*
* This function is intented for display purpose only. Use the `getXML()`
* function to obtain the raw XML token you want to work with.
*
* @return string The pretty formatted XMLDSigToken signature.
*/
public function getPrettyXML()
{
$xml = $this->xml;
$formatted = '';
$pad = 0;
$indent_size = 2;
$matches = array();
// Inline function that split the value of a node
// into multiple lines of 64 characters long.
$splitNodeValue = function(&$xml, $prefix, $xpathQuery)
{
$xpathQuery = str_replace('ds:', $prefix, $xpathQuery);
$nodeValue = XMLDSigToken::alterXML($xml, $xpathQuery, null, 'placeholer', false);
$nodeValue = PHP_EOL . implode(str_split($nodeValue, 64), PHP_EOL) . PHP_EOL;
XMLDSigToken::alterXML($xml, $xpathQuery, null, $nodeValue, false);
// Remove XML encoded carriage return introduced when spliting node
// value.
$xml = str_replace('
', '', $xml);
};
// Split node values that are too long.
$prefix = $this->xmldsigNsPrefix ? $this->xmldsigNsPrefix . ':' : '';
$splitNodeValue($xml, $prefix, '//ds:Signature/ds:SignatureValue');
$splitNodeValue($xml, $prefix, '//ds:Signature/ds:KeyInfo/ds:X509Data/ds:X509Certificate');
$splitNodeValue($xml, $prefix, '//ds:Signature/ds:Object/xenc:EncryptedData/ds:KeyInfo/xenc:EncryptedKey/xenc:CipherData/xenc:CipherValue');
$splitNodeValue($xml, $prefix, '//ds:Signature/ds:Object/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue');
// Tokenise the XML string.
$xml = preg_replace('/(>)[\s]*(<)(\/*)/', "$1\n$2$3", $xml);
// Get the first token.
$tok = strtok($xml, "\n");
// Scan each token and adjust indent based on opening/closing tags.
while ($tok !== false)
{
// Test for the various tag states
if (preg_match('/.+<\/\w[^>]*>$/', $tok, $matches))
{
// Opening and closing tags on same line : no change.
$indent = 0;
}
elseif (preg_match('/^<\/\w/', $tok, $matches))
{
// Closing tag : outdent now.
$pad = $pad - $indent_size;
}
elseif (preg_match('/^<\w[^>]*[^\/]>.*$/', $tok, $matches))
{
// Opening tag : don't pad this one, only subsequent tags.
$indent = $indent_size;
}
else
{
// No indentation needed.
$indent = 0;
}
// Pad the line with the required number of leading spaces.
$prettyLine = str_pad($tok, strlen($tok) + $pad, ' ', STR_PAD_LEFT);
$formatted .= $prettyLine . "\n";
// Get the next token
$tok = strtok("\n");
// Update the pad size for subsequent token.
$pad += $indent;
}
// Return formatted XML string.
return $formatted;
}
/**
* Utility function that returns information on the XML token in HTML
* format.
*/
public function getHTMLDump()
{
// Inline function to output var_dump() into a string.
$dumpToString = function($var)
{
ob_start();
var_dump($var);
return preg_replace('@<small>.+</small>.*\n@', '', ob_get_clean(), 1);
};
$results = array();
$results['isSignatureValid()'] = $this->isSignatureValid() ? 'TRUE' : 'FALSE';
$results['isDataEncrypted()'] = $this->isDataEncrypted() ? 'TRUE' : 'FALSE';
$results['getTimestamp()'] = $this->getTimestamp();
$results['getData()'] = $dumpToString($this->getData());
$results['getError()'] = is_null($this->getError()) ? 'NULL' : $this->getError();
$results['getAnomalies()'] = $dumpToString($this->getAnomalies());
$results['getCertDN()'] = $this->getCertDN();
$results['getCertSubject()'] = $dumpToString($this->getCertSubject());
$results['getCertIssuer()'] = $dumpToString($this->getCertIssuer());
$results['getCertValidFrom()'] = $this->getCertValidFrom();
$results['getCertValidTo()'] = $this->getCertValidTo();
$results['isCertOutOfDate()'] = $this->isCertOutOfDate() ? 'TRUE' : 'FALSE';
$html = '<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>';
$html .= '<table cellpadding="5">';
$html .= '<tr><td colspan="2"><pre class="xdebug-var-dump"><b><span style="color: red">XML Digital Signature</span></b></pre></td></tr>';
$html .= '<tr><td colspan="2"><pre class=\"prettyprint\">' . htmlentities($this->getPrettyXML()) . '</pre></td></tr>';
$html .= '<tr><td width="1%"><pre class="xdebug-var-dump"><b><span style="color: red">METHOD CALLS</span></b></pre></td>';
$html .= '<td valign="top" width="99%"><pre class="xdebug-var-dump"><b><span style="color: red">RESULTS</span></b></pre></td></tr>';
foreach ($results as $key => $value) {
$html .= '<tr><td valign="top"><pre class="xdebug-var-dump"><b>' . $key . '</b></pre></td>';
$html .= '<td valign="top"><pre class="xdebug-var-dump">' . $value . '</pre></td></tr>';
}
$html .= '</table>';
return $html;
}
}