Overview

Namespaces

  • webcoder31
    • ezxmldsig

Classes

  • webcoder31\ezxmldsig\X509Cert
  • webcoder31\ezxmldsig\XMLDSigToken
  • Overview
  • Namespace
  • Class
   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('&#xD;', '', $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;
    }
}
API documentation generated by ApiGen